collabora-online/browser/src/control/Toolbar.js

1148 lines
36 KiB
JavaScript

/* -*- js-indent-level: 8; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Toolbar handler
*/
/* global app $ window sanitizeUrl brandProductName brandProductURL _ w2ui */
L.Map.include({
// a mapping of uno commands to more readable toolbar items
unoToolbarCommands: [
'.uno:StyleApply',
'.uno:CharFontName'
],
_modalDialogOptions: {
overlayClose:true,
opacity: 80,
overlayCss: {
backgroundColor : '#000'
},
containerCss: {
overflow : 'hidden',
backgroundColor : '#fff',
padding : '20px',
border : '2px solid #000'
}
},
onFontSelect: function(e) {
var font = e.target.value;
this.applyFont(font);
this.focus();
},
_getCurrentFontName: function() {
return this['stateChangeHandler'].getItemValue('.uno:CharFontName');
},
createFontSelector: function(nodeSelector) {
var that = this;
var fontcombobox = $(nodeSelector);
if (!fontcombobox.hasClass('select2')) {
fontcombobox.select2({
placeholder: _('Font')
});
}
var createSelector = function() {
var commandValues = that.getToolbarCommandValues('.uno:CharFontName');
var data = []; // reset data in order to avoid that the font select box is populated with styles, too.
// Old browsers like IE11 et al don't like Object.keys with
// empty arguments
if (typeof commandValues === 'object') {
data = data.concat(Object.keys(commandValues));
}
fontcombobox.empty();
for (var i = 0; i < data.length; ++i) {
if (!data[i]) continue;
var option = document.createElement('option');
option.text = data[i];
option.value = data[i];
fontcombobox.append(option);
}
fontcombobox.on('select2:select', that.onFontSelect.bind(that));
fontcombobox.val(that._getCurrentFontName()).trigger('change');
};
createSelector();
var onCommandStateChanged = function(e) {
var commandName = e.commandName;
if (commandName !== '.uno:CharFontName')
return;
var state = e.state;
var found = false;
fontcombobox.children('option').each(function () {
var value = this.value;
if (value.toLowerCase() === state.toLowerCase()) {
found = true;
return;
}
});
if (!found && state) {
fontcombobox
.append($('<option></option>')
.text(state));
}
fontcombobox.val(state).trigger('change');
};
var onFontListChanged = function(e) {
if (e.commandName === '.uno:CharFontName')
createSelector();
};
this.off('commandstatechanged', onCommandStateChanged);
this.on('commandstatechanged', onCommandStateChanged);
this.off('updatetoolbarcommandvalues', onFontListChanged);
this.on('updatetoolbarcommandvalues', onFontListChanged);
},
onFontSizeSelect: function(e) {
this.applyFontSize(e.target.value);
this.focus();
},
createFontSizeSelector: function(nodeSelector) {
var data = [6, 7, 8, 9, 10, 10.5, 11, 12, 13, 14, 15, 16, 18, 20,
22, 24, 26, 28, 32, 36, 40, 44, 48, 54, 60, 66, 72, 80, 88, 96];
var fontsizecombobox = $(nodeSelector);
if (!fontsizecombobox.hasClass('select2')) {
fontsizecombobox.select2({
dropdownAutoWidth: true,
width: 'auto',
placeholder: _('Font Size'),
//Allow manually entered font size.
createTag: function(query) {
return {
id: query.term,
text: query.term,
tag: true
};
},
tags: true,
sorter: function(data) { return data.sort(function(a, b) {
return parseFloat(a.text) - parseFloat(b.text);
});}
});
}
fontsizecombobox.empty();
for (var i = 0; i < data.length; ++i) {
var option = document.createElement('option');
option.text = data[i];
option.value = data[i];
fontsizecombobox.append(option);
}
fontsizecombobox.off('select2:select', this.onFontSizeSelect.bind(this)).on('select2:select', this.onFontSizeSelect.bind(this));
var onCommandStateChanged = function(e) {
var commandName = e.commandName;
if (commandName !== '.uno:FontHeight')
return;
var state = e.state;
var found = false;
if (state === '0') {
state = '';
}
fontsizecombobox.children('option').each(function (i, e) {
if ($(e).text() === state) {
found = true;
}
});
if (!found) {
// we need to add the size
fontsizecombobox
.append($('<option>')
.text(state).val(state));
}
fontsizecombobox.val(state).trigger('change');
};
this.off('commandstatechanged', onCommandStateChanged);
this.on('commandstatechanged', onCommandStateChanged);
},
applyFont: function (fontName) {
if (!fontName)
return;
if (this.isEditMode()) {
var msg = 'uno .uno:CharFontName {' +
'"CharFontName.FamilyName": ' +
'{"type": "string", "value": "' + fontName + '"}}';
app.socket.sendMessage(msg);
}
},
applyFontSize: function (fontSize) {
if (this.isEditMode()) {
var msg = 'uno .uno:FontHeight {' +
'"FontHeight.Height": ' +
'{"type": "float", "value": "' + fontSize + '"}}';
app.socket.sendMessage(msg);
}
},
getToolbarCommandValues: function (command) {
if (this._docLayer) {
return this._docLayer._toolbarCommandValues[command];
}
return undefined;
},
downloadAs: function (name, format, options, id) {
if (this._fatal) {
return;
}
id = id || 'export'; // not any special download, simple export
if ((id === 'print' && this['wopi'].DisablePrint) ||
(id === 'export' && this['wopi'].DisableExport)) {
this.hideBusy();
return;
}
if (format === undefined || format === null) {
format = '';
}
if (options === undefined || options === null) {
options = '';
}
// printing: don't export form fields, irrelevant, and can be buggy
// comments are irrelevant, too
if (id === 'print' && format === 'pdf' && options === '')
options = '{\"ExportFormFields\":{\"type\":\"boolean\",\"value\":\"false\"},' +
'\"ExportNotes\":{\"type\":\"boolean\",\"value\":\"false\"}}';
// download: don't export comments into PDF by default
if (id == 'export' && format === 'pdf' && options === '')
options = '{\"ExportNotes\":{\"type\":\"boolean\",\"value\":\"false\"}}';
if (!window.ThisIsAMobileApp)
this.showBusy(_('Downloading...'), false);
app.socket.sendMessage('downloadas ' +
'name=' + encodeURIComponent(name) + ' ' +
'id=' + id + ' ' +
'format=' + format + ' ' +
'options=' + options);
},
print: function (options) {
if (window.ThisIsTheiOSApp || window.ThisIsTheAndroidApp) {
window.postMobileMessage('PRINT');
} else {
this.showBusy(_('Downloading...'), false);
this.downloadAs('print.pdf', 'pdf', options, 'print');
}
},
saveAs: function (url, format, options) {
if (url === undefined || url == null) {
return;
}
if (format === undefined || format === null) {
format = '';
}
if (options === undefined || options === null) {
options = '';
}
this.showBusy(_('Saving...'), false);
app.socket.sendMessage('saveas ' +
'url=wopi:' + encodeURIComponent(url) + ' ' +
'format=' + format + ' ' +
'options=' + options);
},
exportAs: function (url) {
if (url === undefined || url == null) {
return;
}
app.socket.sendMessage('exportas url=wopi:' + encodeURIComponent(url));
},
renameFile: function (filename) {
if (!filename) {
return;
}
this.showBusy(_('Renaming...'), false);
app.socket.sendMessage('renamefile filename=' + encodeURIComponent(filename));
},
applyStyle: function (style, familyName) {
if (!style || !familyName) {
this.fire('error', {cmd: 'setStyle', kind: 'incorrectparam'});
return;
}
if (this.isEditMode()) {
var msg = 'uno .uno:StyleApply {' +
'"Style":{"type":"string", "value": "' + style + '"},' +
'"FamilyName":{"type":"string", "value":"' + familyName + '"}' +
'}';
app.socket.sendMessage(msg);
}
},
applyLayout: function (layout) {
if (!layout) {
this.fire('error', {cmd: 'setLayout', kind: 'incorrectparam'});
return;
}
if (this.isEditMode()) {
var msg = 'uno .uno:AssignLayout {' +
'"WhatPage":{"type":"unsigned short", "value": "' + this.getCurrentPartNumber() + '"},' +
'"WhatLayout":{"type":"unsigned short", "value": "' + layout + '"}' +
'}';
app.socket.sendMessage(msg);
}
},
save: function(dontTerminateEdit, dontSaveIfUnmodified, extendedData) {
var msg = 'save' +
' dontTerminateEdit=' + (dontTerminateEdit ? 1 : 0) +
' dontSaveIfUnmodified=' + (dontSaveIfUnmodified ? 1 : 0);
if (extendedData !== undefined) {
msg += ' extendedData=' + extendedData;
}
app.socket.sendMessage(msg);
},
messageNeedsToBeRedirected: function(command) {
if (command === '.uno:EditHyperlink') {
var that = this;
setTimeout(function () { that.showHyperlinkDialog(); }, 500);
return true;
}
else {
return false;
}
},
sendUnoCommand: function (command, json, force) {
if (command.indexOf('.uno:') < 0)
console.error('Trying to send uno command without prefix: "' + command + '"');
if ((command.startsWith('.uno:Sidebar') && !command.startsWith('.uno:SidebarShow')) ||
command.startsWith('.uno:SlideChangeWindow') || command.startsWith('.uno:CustomAnimation') ||
command.startsWith('.uno:MasterSlidesPanel') || command.startsWith('.uno:ModifyPage') ||
command.startsWith('.uno:Navigator')) {
// sidebar control is present only in desktop/tablet case
if (this.sidebar) {
if (this.sidebar.isVisible()) {
this.sidebar.setupTargetDeck(command);
} else {
// we don't know which deck was active last, show first then switch if needed
app.socket.sendMessage('uno .uno:SidebarShow');
this.sidebar.setupTargetDeck(command);
return;
}
}
}
var isAllowedInReadOnly = false;
var allowedCommands = ['.uno:Save', '.uno:WordCountDialog',
'.uno:Signature', '.uno:ShowResolvedAnnotations',
'.uno:ToolbarMode?Mode:string=notebookbar_online.ui', '.uno:ToolbarMode?Mode:string=Default',
'.uno:ExportToEPUB', '.uno:ExportToPDF', '.uno:ExportDirectToPDF', '.uno:MoveKeepInsertMode'];
if (app.isCommentEditingAllowed()) {
allowedCommands.push('.uno:InsertAnnotation','.uno:DeleteCommentThread', '.uno:DeleteAnnotation', '.uno:DeleteNote',
'.uno:DeleteComment', '.uno:ReplyComment', '.uno:ReplyToAnnotation', '.uno:ResolveComment',
'.uno:ResolveCommentThread', '.uno:ResolveComment', '.uno:EditAnnotation', '.uno:ExportToEPUB', '.uno:ExportToPDF',
'.uno:ExportDirectToPDF');
}
for (var i in allowedCommands) {
if (allowedCommands[i] === command) {
isAllowedInReadOnly = true;
break;
}
}
if (command.startsWith('.uno:SpellOnline')) {
var map = this;
var val = map['stateChangeHandler'].getItemValue('.uno:SpellOnline');
// proceed if the toggle button is pressed
if (val && (json === undefined || json === null)) {
// because it is toggle, state has to be the opposite
var state = !(val === 'true');
if (window.isLocalStorageAllowed)
window.localStorage.setItem('SpellOnline', state);
}
}
if (this.uiManager.isUIBlocked())
return;
if ((this.dialog.hasOpenedDialog() || (this.jsdialog && this.jsdialog.hasDialogOpened()))
&& !command.startsWith('.uno:ToolbarMode') && !force) {
console.debug('Cannot execute: ' + command + ' when dialog is opened.');
this.dialog.blinkOpenDialog();
} else if (this.isEditMode() || isAllowedInReadOnly) {
if (!this.messageNeedsToBeRedirected(command))
app.socket.sendMessage('uno ' + command + (json ? ' ' + JSON.stringify(json) : ''));
}
},
toggleCommandState: function (unoState) {
if (this.isEditMode()) {
if (!unoState.startsWith('.uno:')) {
unoState = '.uno:' + unoState;
}
this.sendUnoCommand(unoState);
}
},
insertFile: function (file) {
this.fire('insertfile', {file: file});
},
insertURL: function (url) {
this.fire('inserturl', {url: url});
},
selectBackground: function (file) {
this.fire('selectbackground', {file: file});
},
onHelpOpen: function(id, map, productName) {
var i;
// Display keyboard shortcut or online help
if (id === 'keyboard-shortcuts-content') {
document.getElementById('online-help-content').style.display='none';
// Display help according to document opened
if (map.getDocType() === 'text') {
document.getElementById('text-shortcuts').style.display='block';
}
else if (map.getDocType() === 'spreadsheet') {
document.getElementById('spreadsheet-shortcuts').style.display='block';
}
else if (map.getDocType() === 'presentation') {
document.getElementById('presentation-shortcuts').style.display='block';
}
else if (map.getDocType() === 'drawing') {
document.getElementById('drawing-shortcuts').style.display='block';
}
} else /* id === 'online-help' */ {
document.getElementById('keyboard-shortcuts-content').style.display='none';
if (window.socketProxy) {
var helpdiv = document.getElementById('online-help-content');
var imgList = helpdiv.querySelectorAll('img');
for (var p = 0; p < imgList.length; p++) {
var imgSrc = imgList[p].src;
imgSrc = imgSrc.substring(imgSrc.indexOf('/images'));
imgList[p].src = window.makeWsUrl('/browser/dist'+ imgSrc);
}
}
// Display help according to document opened
if (map.getDocType() === 'text') {
var x = document.getElementsByClassName('text');
for (i = 0; i < x.length; i++) {
x[i].style.display = 'block';
}
}
else if (map.getDocType() === 'spreadsheet') {
x = document.getElementsByClassName('spreadsheet');
for (i = 0; i < x.length; i++) {
x[i].style.display = 'block';
}
}
else if (map.getDocType() === 'presentation' || map.getDocType() === 'drawing') {
x = document.getElementsByClassName('presentation');
for (i = 0; i < x.length; i++) {
x[i].style.display = 'block';
}
}
}
var contentElement = document.getElementById(id);
// Let's translate
var max;
var translatableContent = contentElement.querySelectorAll('h1');
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
translatableContent = contentElement.querySelectorAll('h2');
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
translatableContent = contentElement.querySelectorAll('h3');
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
translatableContent = contentElement.querySelectorAll('h4');
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
translatableContent = contentElement.querySelectorAll('td');
for (i = 0, max = translatableContent.length; i < max; i++) {
var orig = translatableContent[i].innerHTML;
var trans = translatableContent[i].innerHTML.toLocaleString();
// Try harder to get translation of keyboard shortcuts (html2po trims starting <kbd> and ending </kbd>)
if (orig === trans && orig.indexOf('kbd') != -1) {
var trimmedOrig = orig.replace(/^(<kbd>)/,'').replace(/(<\/kbd>$)/,'');
var trimmedTrans = trimmedOrig.toLocaleString();
if (trimmedOrig !== trimmedTrans) {
trans = '<kbd>' + trimmedTrans + '</kbd>';
}
}
translatableContent[i].innerHTML = trans;
}
translatableContent = contentElement.querySelectorAll('p');
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
translatableContent = contentElement.querySelectorAll('button'); // TOC
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].innerHTML = translatableContent[i].innerHTML.toLocaleString();
}
//translatable screenshots
var supportedLanguage = ['de', 'fr', 'it', 'es', 'pt-BR'];
var currentLanguage = String.locale;
if (supportedLanguage.indexOf(currentLanguage) >= 0) {
translatableContent = $(contentElement.querySelectorAll('.screenshot img'));
for (i = 0, max = translatableContent.length; i < max; i++) {
translatableContent[i].src = translatableContent[i].src.replace('/en/', '/'+currentLanguage+'/');
}
}
// Substitute %productName in Online Help and replace special Mac key names
if (id === 'online-help-content') {
var productNameContent = contentElement.querySelectorAll('span.productname');
for (i = 0, max = productNameContent.length; i < max; i++) {
productNameContent[i].innerHTML = productNameContent[i].innerHTML.replace(/%productName/g, productName);
}
document.getElementById('online-help-content').innerHTML = L.Util.replaceCtrlAltInMac(document.getElementById('online-help-content').innerHTML);
}
if (id === 'keyboard-shortcuts-content') {
document.getElementById('keyboard-shortcuts-content').innerHTML = L.Util.replaceCtrlAltInMac(document.getElementById('keyboard-shortcuts-content').innerHTML);
}
var searchInput = document.getElementById('online-help-search-input');
searchInput.setAttribute('placeholder',_('Search'));
searchInput.focus(); // auto focus on user input field
var helpContentParent = document.getElementsByClassName('ui-dialog-content')[0];
var startFilter = false;
var isAnyMatchingContent = false;
searchInput.addEventListener('input', function () {
// Hide all elements within the #online-help-content on first key stroke/at start of filter content
if (!startFilter || !isAnyMatchingContent) {
helpContentParent.setAttribute('style', 'background-color: var(--color-background-dark) !important');
// Hide all <p> tags within .text, .spreadsheet, or .presentation sections
document.querySelectorAll('#online-help-content > *:not(a), .link-section p, .product-header').forEach(function (element) {
// Check if the element has class text, spreadsheet, or presentation
if (!element.classList.contains('text') && !element.classList.contains('spreadsheet') && !element.classList.contains('presentation')) {
this.hide(element);
}
}.bind(this));
startFilter = true;
}
var searchTerm = searchInput.value.trim();
// Reset highlighting and visibility if search term is empty
if (searchTerm === '') {
this.resetFilterResults();
startFilter = false;
}
else {
this.filterResults(searchTerm, isAnyMatchingContent, id);
}
}.bind(this));
},
filterResults: function (searchTerm, isAnyMatchingContent, id) {
var mainDiv = document.getElementById(id);
// Combine query parameters to select main sections
var mainSectionsQuery = '.section:not(div.text .section, div.spreadsheet .section, div.presentation .section)';
var docType = this.getDocType() === 'drawing' ? 'presentation' : this.getDocType();
mainSectionsQuery += ', div.' + docType + ' .section';
// Select nain sections elements within the mainDiv
var mainSections = mainDiv.querySelectorAll(mainSectionsQuery);
isAnyMatchingContent = false;
// Loop through each main section
mainSections.forEach(function (mainSection) {
// check header text matches or not
var headerText = mainSection.querySelector('.section-header').textContent.toLowerCase();
var containsTermInHeader = headerText.includes(searchTerm.toLowerCase());
// check main section text matches or not
var sectionText = mainSection.textContent.toLowerCase();
var containsTerm = sectionText.includes(searchTerm.toLowerCase());
//sub-section text matches or not
var subSections = mainSection.querySelectorAll('.sub-section');
var subSectionContainsTerm = false;
// if text matching with the main header then display full main section
if (containsTermInHeader) {
// first need to reset display of subsection
subSections.forEach(function(subSection) {
mainSection.style.backgroundColor = '';
mainSection.style.paddingInline = '';
mainSection.style.borderRadius = '';
this.show(subSection);
}.bind(this));
mainSection.style.backgroundColor = 'var(--color-background-lighter)';
mainSection.style.paddingInline = '12px';
mainSection.style.borderRadius = 'var(--border-radius-large)';
this.show(mainSection);
}
else {
// else Loop through each sub-section and display subsections with matching text has search term
subSections.forEach(function (subSection) {
// Highlight matching sub-sections
if (subSection.textContent.toLowerCase().includes(searchTerm.toLowerCase())) {
subSection.style.color = 'var(--color-text-darker)';
this.show(subSection);
mainSection.style.backgroundColor = 'var(--color-background-lighter)';
mainSection.style.paddingInline = '12px';
mainSection.style.borderRadius = 'var(--border-radius-large)';
// make sure main section of matched subsection is visible
this.show(mainSection);
subSectionContainsTerm = true;
} else {
subSection.style.color = ''; // Remove previous highlighting
this.hide(subSection);
}
}.bind(this));
}
if (!subSectionContainsTerm && !containsTerm) {
this.hide(mainSection);
}
else {
isAnyMatchingContent = true;
}
}.bind(this));
if (!isAnyMatchingContent) {
this.resetFilterResults();
$('#online-help-search-input').addClass('search-not-found');
setTimeout(function () {
$('#online-help-search-input').removeClass('search-not-found');
}, 800);
}
},
resetFilterResults: function () {
var helpContentParent = document.getElementsByClassName('ui-dialog-content')[0];
helpContentParent.style.backgroundColor='';
// Select main sections and make it visible
var mainSections = document.querySelectorAll('.section');
mainSections.forEach(function(mainSection) {
mainSection.style.backgroundColor = '';
this.show(mainSection);
var subSections = mainSection.querySelectorAll('.sub-section');
subSections.forEach(function(subSection) {
this.show(subSection);
}.bind(this));
}.bind(this));
// select all event scroll elements, main-header elements, product header elements and make visible to user if search term is empty
document.querySelectorAll('.m-v-0, .product-header, .help-dialog-header').forEach(function(element) {
this.show(element);
element.style.backgroundColor = '';
}.bind(this));
},
show: function(element) {
element.classList.remove('hide');
element.classList.add('show');
},
hide: function(element) {
element.classList.remove('show');
element.classList.add('hide');
},
_doOpenHelpFile: function(data, id, map) {
var productName;
if (window.ThisIsAMobileApp) {
productName = window.MobileAppName;
} else {
productName = (typeof brandProductName !== 'undefined') ? brandProductName : 'Collabora Online Development Edition (unbranded)';
}
map.uiManager.showYesNoButton(id + '-box', productName, '', _('OK'), null, null, null, true);
var box = document.getElementById(id + '-box');
var innerDiv = L.DomUtil.create('div', '', null);
box.insertBefore(innerDiv, box.firstChild);
innerDiv.innerHTML = data;
this.onHelpOpen(id, map, productName);
},
showHelp: function(id) {
var map = this;
if (window.ThisIsAMobileApp) {
map._doOpenHelpFile(window.HelpFile, id, map);
return;
}
var helpLocation = 'cool-help.html';
if (window.socketProxy)
helpLocation = window.makeWsUrl('/browser/dist/' + helpLocation);
$.get(helpLocation, function(data) {
map._doOpenHelpFile(data, id, map);
});
},
aboutDialogKeyHandler: function(event) {
if (event.key === 'd') {
this._debug.toggle();
} else if (event.key === 'l') {
// L toggges the Online logging level between the default (whatever
// is set in coolwsd.xml or on the coolwsd command line) and the
// most verbose a client is allowed to set (which also can be set in
// coolwsd.xml or on the coolwsd command line).
//
// In a typical developer "make run" setup, the default is "trace"
// so there is nothing more verbose. But presumably it is different
// in production setups.
app.socket.threadLocalLoggingLevelToggle = !app.socket.threadLocalLoggingLevelToggle;
var newLogLevel = (app.socket.threadLocalLoggingLevelToggle ? 'verbose' : 'default');
app.socket.sendMessage('loggingleveloverride ' + newLogLevel);
var logLevelInformation;
if (newLogLevel === 'default')
logLevelInformation = 'default (from coolwsd.xml)';
else if (newLogLevel === 'verbose')
logLevelInformation = 'most verbose (from coolwsd.xml)';
else if (newLogLevel === 'terse')
logLevelInformation = 'least verbose (from coolwsd.xml)';
else
logLevelInformation = newLogLevel;
console.debug('Log level: ' + logLevelInformation);
}
},
aboutDialogClickHandler: function(event) {
if (event.detail === 3) {
this._debug.toggle();
}
},
showLOAboutDialog: function() {
// Just as a test to exercise the Async Trace Event functionality, uncomment this
// line and the asyncTraceEvent.finish() below.
// var asyncTraceEvent = app.socket.createAsyncTraceEvent('cool-showLOAboutDialog');
var aboutDialogId = 'about-dialog';
// Move the div sitting in 'body' as content and make it visible
var content = document.getElementById(aboutDialogId).cloneNode(true);
content.style.display = 'block';
// fill product-name and product-string
var productName;
if (window.ThisIsAMobileApp) {
productName = window.MobileAppName;
} else {
productName = (typeof brandProductName !== 'undefined') ? brandProductName : 'Collabora Online Development Edition (unbranded)';
}
var productURL = (typeof brandProductURL !== 'undefined') ? brandProductURL : 'https://collaboraonline.github.io/';
content.querySelector('#product-name').innerText = productName;
content.classList.add('product-' + productName.split(/[ ()]+/).join('-').toLowerCase());
var productString = _('This version of %productName is powered by');
var productNameWithURL;
if (!window.ThisIsAMobileApp)
productNameWithURL = '<a href="' + sanitizeUrl(productURL) +
'" target="_blank">' + productName + '</a>';
else
productNameWithURL = productName;
if (content.querySelector('#product-string'))
content.querySelector('#product-string').innerText = productString.replace('%productName', productNameWithURL);
if (window.socketProxy)
content.querySelector('#slow-proxy').innerText = _('"Slow Proxy"');
var map = this;
if (window.indirectSocket)
content.querySelector('#routeToken').innerText = 'RouteToken: ' + window.routeToken;
map.uiManager.showYesNoButton(aboutDialogId + '-box', productName, '', _('OK'), null, null, null, true);
var box = document.getElementById(aboutDialogId + '-box');
var innerDiv = L.DomUtil.create('div', '', null);
box.insertBefore(innerDiv, box.firstChild);
innerDiv.innerHTML = content.outerHTML;
var form = document.getElementById('about-dialog-box');
form.addEventListener('click', this.aboutDialogClickHandler.bind(this));
form.addEventListener('keyup', this.aboutDialogKeyHandler.bind(this));
form.querySelector('#coolwsd-version').querySelector('a').focus();
var copyversion = L.DomUtil.create('button', 'ui-pushbutton jsdialog', null);
copyversion.setAttribute('id', 'modal-dialog-about-dialog-box-copybutton');
copyversion.setAttribute('title', _('Copy all version information in English'));
var img = L.DomUtil.create('img', null, null);
L.LOUtil.setImage(img, 'lc_copy.svg', map);
copyversion.innerHTML = '<img src="' + img.src +'" width="18px" height="18px">';
copyversion.addEventListener('click', this.copyVersionInfoToClipboard.bind(this));
map.uiManager.enableTooltip(copyversion);
var aboutok = document.getElementById('modal-dialog-about-dialog-box-yesbutton');
if (aboutok) {
aboutok.before(copyversion);
}
},
getVersionInfoFromClass: function(className) {
var versionElement = document.getElementById(className);
var versionInfo = versionElement.innerText;
var gitHashIndex = versionInfo.indexOf('git hash');
if (gitHashIndex > -1) {
versionInfo = versionInfo.slice(0, gitHashIndex) + '(' + versionInfo.slice(gitHashIndex) + ')';
}
return versionInfo;
},
copyVersionInfoToClipboard: function() {
var text = 'COOLWSD version: ' + this.getVersionInfoFromClass('coolwsd-version') + '\n';
text += 'LOKit version: ' + this.getVersionInfoFromClass('lokit-version') + '\n';
text += 'Served by: ' + document.getElementById('os-info').innerText + '\n';
text += 'Server ID: ' + document.getElementById('coolwsd-id').innerText + '\n';
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text)
.then(function() {
window.console.log('Text copied to clipboard');
this.contentHasBeenCopiedShowSnackbar();
}.bind(this))
.catch(function(error) {
window.console.error('Error copying text to clipboard:', error);
});
} else {
var textArea = document.createElement('textarea');
textArea.style.position = 'absolute';
textArea.style.opacity = 0;
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
window.console.log('Text copied to clipboard');
this.contentHasBeenCopiedShowSnackbar();
} catch (error) {
window.console.error('Error copying text to clipboard:', error);
} finally {
document.body.removeChild(textArea);
}
}
},
contentHasBeenCopiedShowSnackbar: function() {
var timeout = 1000;
this.uiManager.showSnackbar('Version information has been copied', null, null, timeout);
var copybutton = document.querySelector('#modal-dialog-about-dialog-box-copybutton > img');
L.LOUtil.setImage(copybutton, 'lc_clipboard-check.svg', this);
setTimeout(function () {
L.LOUtil.setImage(copybutton, 'lc_copy.svg', this);
}.bind(this), timeout);
},
extractContent: function(html) {
var parser = new DOMParser;
return parser.parseFromString(html, 'text/html').documentElement.getElementsByTagName('body')[0].textContent;
},
makeURLFromStr: function(str) {
if (!(str.toLowerCase().startsWith('http://') || str.toLowerCase().startsWith('https://'))) {
str = 'http://' + str;
}
return str;
},
_createAndRunHyperlinkDialog: function(defaultText, defaultLink) {
var map = this;
var id = 'hyperlink';
var title = _('Insert hyperlink');
var dialogId = 'modal-dialog-' + id;
var json = map.uiManager._modalDialogJSON(id, title, true, [
{
id: 'hyperlink-text-box-label',
type: 'fixedtext',
text: _('Text'),
labelFor: 'hyperlink-text-box'
},
{
id: 'hyperlink-text-box',
type: 'multilineedit',
text: defaultText,
labelledBy: 'hyperlink-text-box-label'
},
{
id: 'hyperlink-link-box-label',
type: 'fixedtext',
text: _('Link'),
labelFor: 'hyperlink-link-box'
},
{
id: 'hyperlink-link-box',
type: 'edit',
text: defaultLink,
labelledBy: 'hyperlink-link-box-label'
},
{
type: 'buttonbox',
enabled: true,
children: [
{
id: 'response-cancel',
type: 'pushbutton',
text: _('Cancel'),
},
{
id: 'response-ok',
type: 'pushbutton',
text: _('OK'),
'has_default': true,
}
],
vertical: false,
layoutstyle: 'end'
},
], 'hyperlink-link-box');
map.uiManager.showModal(json, [
{id: 'response-ok', func: function() {
var text = document.getElementById('hyperlink-text-box');
var link = document.getElementById('hyperlink-link-box');
if (link.value != '') {
if (!text.value || text.value === '')
text.value = link.value;
var command = {
'Hyperlink.Text': {
type: 'string',
value: text.value
},
'Hyperlink.URL': {
type: 'string',
value: map.makeURLFromStr(link.value)
}
};
map.sendUnoCommand('.uno:SetHyperlink', command, true);
}
map.uiManager.closeModal(dialogId);
}}
]);
},
getTextForLink: function() {
var map = this;
var text = '';
if (this.hyperlinkUnderCursor && this.hyperlinkUnderCursor.text) {
text = this.hyperlinkUnderCursor.text;
} else if (this._clip && this._clip._selectionType == 'text') {
if (map['stateChangeHandler'].getItemValue('.uno:Copy') === 'enabled') {
text = this.extractContent(this._clip._selectionContent);
}
} else if (this._docLayer._selectedTextContent) {
text = this.extractContent(this._docLayer._selectedTextContent);
}
return text;
},
showHyperlinkDialog: function() {
if (this.getDocType() === 'spreadsheet') {
// show native core dialog
// in case we try to edit email EditHyperlink doesn't work
this.sendUnoCommand('.uno:HyperlinkDialog');
return;
}
var text = this.getTextForLink();
var link = '';
if (this.hyperlinkUnderCursor && this.hyperlinkUnderCursor.link)
link = this.hyperlinkUnderCursor.link;
this._createAndRunHyperlinkDialog(text ? text.trim() : '', link);
},
cancelSearch: function() {
var toolbar = window.mode.isMobile() ? w2ui['searchbar'] : null;
var statusBar = this.statusBar;
var searchInput = L.DomUtil.get('search-input');
this.resetSelection();
if (toolbar) {
toolbar.hide('cancelsearch');
toolbar.disable('searchprev');
toolbar.disable('searchnext');
} else if (statusBar) {
statusBar.showItem('cancelsearch', false);
statusBar.enableItem('searchprev', false);
statusBar.enableItem('searchnext', false);
}
searchInput.value = '';
if (window.mode.isMobile()) {
searchInput.focus();
// odd, but on mobile we need to invoke it twice
toolbar.hide('cancelsearch');
}
this._onGotFocus();
},
preventKeyboardPopup: function (id) {
// In the iOS app we don't want clicking on the toolbar to pop up the keyboard.
if (!window.ThisIsTheiOSApp && id !== 'zoomin' && id !== 'zoomout' && id !== 'mobile_wizard' && id !== 'insertion_mobile_wizard') {
this.focus(this.canAcceptKeyboardInput()); // Maintain same keyboard state.
}
},
// used in onClick method of w2ui toolbar
executeUnoAction: function (item) {
if (item.unosheet && this.getDocType() === 'spreadsheet') {
this.toggleCommandState(item.unosheet);
}
else {
this.toggleCommandState(window.getUNOCommand(item.uno));
}
},
openRevisionHistory: function () {
var map = this;
// if we are being loaded inside an iframe, ask
// our host to show revision history mode
map.fire('postMessage', {msgId: 'rev-history', args: {Deprecated: true}});
map.fire('postMessage', {msgId: 'UI_FileVersions'});
},
openShare: function () {
var map = this;
map.fire('postMessage', {msgId: 'UI_Share'});
},
openSaveAs: function (format) {
var map = this;
map.fire('postMessage', {msgId: 'UI_SaveAs', args: {format: format}});
},
onFormulaBarFocus: function() {
var mobileTopBar = w2ui['actionbar'];
var jsdialogFormulabar = this.formulabar;
var target = jsdialogFormulabar;
if (window.mode.isMobile() === true) {
mobileTopBar.hide('undo');
mobileTopBar.hide('redo');
target = mobileTopBar;
} else {
jsdialogFormulabar.hide('startformula');
jsdialogFormulabar.hide('AutoSumMenu');
}
target.show('cancelformula');
target.show('acceptformula');
},
onFormulaBarBlur: function() {
// The timeout is needed because we want 'click' event on 'cancel',
// 'accept' button to act before we hide these buttons because
// once hidden, click event won't be processed.
// TODO: Some better way to do it ?
var map = this;
setTimeout(function() {
if ($('.leaflet-cursor').is(':visible'))
return;
var mobileTopBar = window.mode.isMobile() ? w2ui['actionbar'] : null;
var jsdialogFormulabar = map.formulabar;
var target = window.mode.isMobile() ? mobileTopBar : jsdialogFormulabar;
target.hide('cancelformula');
target.hide('acceptformula');
if (mobileTopBar) {
mobileTopBar.show('undo');
mobileTopBar.show('redo');
}
$('#AutoSumMenu-button').css('margin-inline', '0');
$('#AutoSumMenu .unoarrow').css('margin', '0');
jsdialogFormulabar.show('startformula');
jsdialogFormulabar.show('AutoSumMenu');
// clear reference marks
map._docLayer._clearReferences();
}, 250);
map.formulabar.blurField();
$('#addressInput').blur();
},
formulabarBlur: function() {
if (!this.uiManager.isAnyDialogOpen())
this.focus();
},
formulabarFocus: function() {
this.formulabar.focusField();
},
formulabarSetDirty: function() {
if (this.formulabar)
this.formulabar.dirty = true;
},
setAccessibilityState: function(enable) {
if (this._accessibilityState === enable)
return;
this._accessibilityState = enable;
app.socket.sendMessage('a11ystate ' + enable);
this.removeLayer(this._textInput);
this._textInput = enable ? L.a11yTextInput() : L.textInput();
this.addLayer(this._textInput);
if (enable) {
this._textInput._requestFocusedParagraph();
}
this._textInput.showCursor();
},
});