// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors // // SPDX-License-Identifier: AGPL-3.0-or-later define([ 'jquery', '/api/config', '/api/broadcast', '/common/common-util.js', '/common/common-hash.js', '/common/common-language.js', '/common/common-interface.js', '/common/common-constants.js', '/common/common-feedback.js', '/common/hyperscript.js', '/common/clipboard.js', '/customize/messages.js', '/customize/application_config.js', '/customize/pages.js', '/components/nthen/index.js', '/common/inner/invitation.js', '/common/visible.js', '/common/pad-types.js', '/common/common-icons.js', ], function ($, Config, Broadcast, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible, PadTypes, Icons) { var UIElements = {}; var urlArgs = Config.requireConf.urlArgs; UIElements.getSvgLogo = function () { var svg = (function(){/* */}).toString().slice(14,-3); return svg; }; UIElements.prettySize = function (bytes) { var unit = Util.magnitudeOfBytes(bytes); if (unit === 'GB') { return Messages._getKey('formattedGB', [ Util.bytesToGigabytes(bytes)]); } else if (unit === 'MB') { return Messages._getKey('formattedMB', [ Util.bytesToMegabytes(bytes)]); } else { return Messages._getKey('formattedKB', [ Util.bytesToKilobytes(bytes)]); } }; UIElements.updateTags = function (common, hrefs) { var existing, tags; var allTags = {}; if (!hrefs || typeof (hrefs) === "string") { hrefs = [hrefs]; } NThen(function(waitFor) { common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) { if (err || res.error) { return void console.error(err || res.error); } existing = Object.keys(res.tags).sort(); })); }).nThen(function (waitFor) { hrefs.forEach(function (href) { common.getPadAttribute('tags', waitFor(function (err, res) { if (err) { if (err === 'NO_ENTRY') { UI.alert(Messages.tags_noentry); } waitFor.abort(); return void console.error(err); } allTags[href] = res || []; if (tags) { // Intersect with tags from previous pads tags = (res || []).filter(function (tag) { return tags.indexOf(tag) !== -1; }); } else { tags = res || []; } }), href); }); }).nThen(function () { UI.dialog.tagPrompt(tags, existing, function (newTags) { if (!Array.isArray(newTags)) { return; } var added = []; var removed = []; newTags.forEach(function (tag) { if (tags.indexOf(tag) === -1) { added.push(tag); } }); tags.forEach(function (tag) { if (newTags.indexOf(tag) === -1) { removed.push(tag); } }); var update = function (oldTags) { Array.prototype.push.apply(oldTags, added); removed.forEach(function (tag) { var idx = oldTags.indexOf(tag); oldTags.splice(idx, 1); }); }; hrefs.forEach(function (href) { var oldTags = allTags[href] || []; update(oldTags); common.setPadAttribute('tags', Util.deduplicateString(oldTags), null, href); }); }); }); }; var dcAlert; UIElements.disconnectAlert = function () { if (dcAlert && $(dcAlert.element).length) { return; } dcAlert = UI.alert(Messages.common_connectionLost, undefined, true); }; UIElements.reconnectAlert = function () { if (!dcAlert) { return; } if (!dcAlert.delete) { dcAlert = undefined; return; } dcAlert.delete(); dcAlert = undefined; }; var importContent = UIElements.importContent = function (type, f, cfg) { return function (_file) { var todo = function (file) { var reader = new FileReader(); var parsed = file && file.name && /.+\.([^.]+)$/.exec(file.name); var ext = parsed && parsed[1]; reader.onload = function (e) { f(e.target.result, file, ext); }; if (cfg && cfg.binary && cfg.binary.indexOf(ext) !== -1) { reader.readAsArrayBuffer(file, type); } else { reader.readAsText(file, type); } }; if (_file) { return void todo(_file); } var $files = $('', {type:"file"}); if (cfg && cfg.accept) { $files.attr('accept', cfg.accept); } $files.click(); $files.on('change', function (e) { var file = e.target.files[0]; todo(file); }); }; }; UIElements.getUserGrid = function (label, config, onSelect) { var common = config.common; var users = config.data; if (!users) { return; } var icons = Object.keys(users).map(function (key, i) { var data = users[key]; var name = UI.getDisplayName(data.displayName || data.name); var avatar = h('span.cp-usergrid-avatar.cp-avatar', { 'aria-hidden': true, }); common.displayAvatar($(avatar), data.avatar, name, Util.noop, data.uid); var removeBtn, el; if (config.remove) { removeBtn = h('span',[Icons.get('close')]); $(removeBtn).attr('tabindex', '0').attr('role', 'button'); $(removeBtn).on('click keydown', function(event) { if (event.type === 'click' || (event.type === 'keydown' && event.key === 'Enter')) { event.preventDefault(); config.remove(el); } }); } el = h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), { 'data-ed': data.edPublic, 'data-teamid': data.teamId, 'data-curve': data.curvePublic || '', 'data-name': name.toLowerCase(), 'data-order': i, 'tabindex': config.noSelect ? '-1' : '0', style: 'order:'+i+';' },[ avatar, h('span.cp-usergrid-user-name', name), data.notRemovable ? undefined : removeBtn ]); return el; }).filter(function (x) { return x; }); var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : ''; var classes = noOthers + (config.large?'.large':'') + (config.list?'.list':'') + (config.radio?'.radio':''); var inputFilter = h('input', { placeholder: Messages.share_filterFriend }); var div = h('div.cp-usergrid-container' + classes, [ label ? h('label', label) : undefined, h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [ inputFilter ]), ]); var $div = $(div); // Hide friends when they are filtered using the text input var redraw = function () { var name = $(inputFilter).val().trim().replace(/"/g, '').toLowerCase(); $div.find('.cp-usergrid-user').show(); if (name) { $div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide(); } }; $(inputFilter).on('input keydown keyup change', redraw); if (config.evOnFilter) { config.evOnFilter.reg(redraw); } $(div).append(h('div.cp-usergrid-grid', icons)); if (!config.noSelect) { $div.on('click', '.cp-usergrid-user', function () { var sel = $(this).hasClass('cp-selected'); if (config.radio) { $div.find('.cp-usergrid-user.cp-selected').removeClass('cp-selected'); } if (!sel) { $(this).addClass('cp-selected'); } else if (!config.radio) { $(this).removeClass('cp-selected'); } onSelect(!sel ? this : undefined); }); $div.on('keydown', '.cp-usergrid-user', function (e) { if (e.which === 13) { e.preventDefault(); e.stopPropagation(); $(this).trigger('click'); } }); } return { icons: icons, div: div }; }; UIElements.getUserTeamPicker = (common, config, onSelected) => { const { msg, friendsData, teamsData } = config; let teams; const filter = h('input', { placeholder: Messages.share_filterFriend }); const evOnFilter = Util.mkEvent(); const contacts = UIElements.getUserGrid(Messages.contacts, { common: common, data: friendsData, noFilter: false, radio: true, large: true, evOnFilter }, el => { onSelected(el); $(teams.div).find('.cp-selected').removeClass('cp-selected'); }); teams = UIElements.getUserGrid(Messages.teams, { common: common, data: teamsData, noFilter: false, radio: true, large: true, evOnFilter }, el => { onSelected(el); $(contacts.div).find('.cp-selected').removeClass('cp-selected'); }); const $contactFilter = $(contacts.div).find('.cp-usergrid-filter input').hide(); const $teamFilter = $(teams.div).find('.cp-usergrid-filter input').hide(); $(filter).on('input', () => { const val = filter.value; $teamFilter.val(val); $contactFilter.val(val); evOnFilter.fire(); }); const content = h('div.cp-userteam-picker', [ h('div', msg), h('div.cp-userteam-filter', filter), contacts.div, teams.div ]); return content; }; UIElements.noContactsMessage = function (common) { var metadataMgr = common.getMetadataMgr(); var data = metadataMgr.getUserData(); var origin = metadataMgr.getPrivateData().origin; if (common.isLoggedIn()) { return { content: h('p', Messages.share_noContactsLoggedIn), buttons: [{ className: 'secondary', name: Messages.share_copyProfileLink, iconClass: 'copy', onClick: function () { var profile = data.profile ? (origin + '/profile/#' + data.profile) : ''; Clipboard.copy(profile, (err) => { if (!err) { UI.log(Messages.shareSuccess); } }); }, keys: [13] }] }; } else { return { content: h('p', Messages.share_noContactsNotLoggedIn), buttons: [{ className: 'secondary', name: Messages.login_register, onClick: function () { common.setLoginRedirect('register'); } }, { className: 'secondary', name: Messages.login_login, onClick: function () { common.setLoginRedirect('login'); } }] }; } }; UIElements.createInviteTeamModal = function (config) { var common = config.common; var hasFriends = Object.keys(config.friends || {}).length !== 0; var privateData = common.getMetadataMgr().getPrivateData(); var team = privateData.teams[config.teamId]; if (!team) { return void UI.warn(Messages.error); } var origin = privateData.origin; var module = config.module || common.makeUniversal('team'); // Invite contacts var $div; var refreshButton = function () { if (!$div) { return; } var $modal = $div.closest('.alertify'); var $nav = $modal.find('nav'); var $btn = $nav.find('button.primary'); var selected = $div.find('.cp-usergrid-user.cp-selected').length; if (selected) { $btn.prop('disabled', ''); } else { $btn.prop('disabled', 'disabled'); } }; var getContacts = function () { var list = UIElements.getUserGrid(Messages.team_pickFriends, { common: common, data: config.friends, large: true }, refreshButton); var div = h('div.contains-nav'); var $div = $(div); $div.append(list.div); var contactsButtons = [{ className: 'primary', iconClass: 'send', name: Messages.team_inviteModalButton, onClick: function () { var $sel = $div.find('.cp-usergrid-user.cp-selected'); var sel = $sel.toArray(); if (!sel.length) { return; } sel.forEach(function (el) { var curve = $(el).attr('data-curve'); module.execCommand('INVITE_TO_TEAM', { teamId: config.teamId, user: config.friends[curve] }, function (obj) { if (obj && obj.error) { console.error(obj.error); return UI.warn(Messages.error); } }); }); }, keys: [13] }]; return { content: div, buttons: contactsButtons }; }; var friendsObject = hasFriends ? getContacts() : UIElements.noContactsMessage(common); var friendsList = friendsObject.content; var contactsButtons = friendsObject.buttons; contactsButtons.unshift({ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }); var contactsContent = h('div.cp-share-modal', [ friendsList ]); var frameContacts = UI.dialog.customModal(contactsContent, { buttons: contactsButtons, }); var linkName, linkPassword, linkMessage, linkError; var linkForm, linkSpin, linkResult, linkUses, linkRole; var linkWarning; // Invite from link var dismissButton = h('span', Icons.get('close')); var roleViewer = UI.createRadio('cp-team-role', 'cp-team-role-viewer', Messages.team_viewers, true, { input: { value: 'VIEWER' }, }); var roleMember = UI.createRadio('cp-team-role', 'cp-team-role-member', Messages.team_members, false, { input: { value: 'MEMBER' }, }); var linkContent = h('div.cp-share-modal', [ h('p', Messages.team_inviteLinkTitle ), linkError = h('div.alert.alert-danger.cp-teams-invite-alert', {style : 'display: none;'}), linkForm = h('div.cp-teams-invite-form', [ // autofill: 'off' was insufficient // adding these two fake inputs confuses firefox and prevents unwanted form autofill h('input', { type: 'text', style: 'display: none'}), h('input', { type: 'password', style: 'display: none'}), linkName = h('input', { placeholder: Messages.team_inviteLinkTempName }), h('br'), h('div.cp-teams-invite-block', [ h('span', Messages.team_inviteLinkSetPassword), h('a.cp-teams-help', { href: Pages.localizeDocsLink('https://docs.cryptpad.org/en/user_guide/security.html#passwords-for-documents-and-folders'), target: "_blank", 'data-tippy-placement': "right" }, Icons.get('circle-question')) ]), linkPassword = UI.passwordInput({ id: 'cp-teams-invite-password', placeholder: Messages.login_password }), h('div.cp-teams-invite-block', h('span', Messages.team_inviteLinkNote) ), linkMessage = h('textarea.cp-teams-invite-message', { placeholder: Messages.team_inviteLinkNoteMsg, rows: 3 }), linkRole = h('div.cp-teams-invite-block.cp-teams-invite-role', h('span', Messages.team_inviteRole), roleViewer, roleMember ), h('div.cp-teams-invite-block.cp-teams-invite-uses', linkUses = h('input', { type: 'number', min: 0, max: 999, value: 1 }), h('span', Messages.team_inviteUses) ), ]), linkSpin = h('div.cp-teams-invite-spinner', { style: 'display: none;' }, [ Icons.get('loading'), h('span', Messages.team_inviteLinkLoading) ]), linkResult = h('div', { style: 'display: none;' }, h('textarea', { readonly: 'readonly' })), linkWarning = h('div.cp-teams-invite-alert.alert.alert-warning.dismissable', { style: "display: none;" }, [ h('span.cp-inline-alert-text', Messages.team_inviteLinkWarning), dismissButton ]) ]); $(linkUses).on('change keyup', function(e) { if (e.target.value === '') { e.target.value = 0; } }); $(linkMessage).keydown(function (e) { if (e.which === 13) { e.stopPropagation(); } }); var localStore = window.cryptpadStore; localStore.get('hide-alert-teamInvite', function (val) { if (val === '1') { return; } $(linkWarning).css('display', 'flex'); $(dismissButton).on('click', function () { localStore.put('hide-alert-teamInvite', '1'); $(linkWarning).remove(); }); }); var $linkContent = $(linkContent); var href; var process = function () { var $nav = $linkContent.closest('.alertify').find('nav'); $(linkError).text('').hide(); var name = $(linkName).val(); var uses = Number($(linkUses).val()); if (isNaN(uses) || !uses) { uses = -1; } var role = $(linkRole).find("input[name='cp-team-role']:checked").val() || 'VIEWER'; var pw = $(linkPassword).find('input').val(); var msg = $(linkMessage).val(); var hash = Hash.createRandomHash('invite', pw); var hashData = Hash.parseTypeHash('invite', hash); href = origin + '/teams/#' + hash; if (!name || !name.trim()) { $(linkError).text(Messages.team_inviteLinkErrorName).show(); return true; } var seeds = InviteInner.deriveSeeds(hashData.key); var salt = InviteInner.deriveSalt(pw, AppConfig.loginSalt); var bytes64; NThen(function (waitFor) { $(linkForm).hide(); $(linkSpin).show(); $nav.find('button.cp-teams-invite-create').hide(); $nav.find('button.cp-teams-invite-copy').show(); setTimeout(waitFor(), 150); }).nThen(function (waitFor) { InviteInner.deriveBytes(seeds.scrypt, salt, waitFor(function (_bytes) { bytes64 = _bytes; })); }).nThen(function (waitFor) { module.execCommand('CREATE_INVITE_LINK', { name: name, password: pw, message: msg, bytes64: bytes64, hash: hash, teamId: config.teamId, seeds: seeds, role: role, uses: uses }, waitFor(function (obj) { if (obj && obj.error) { waitFor.abort(); $(linkSpin).hide(); $(linkForm).show(); $nav.find('button.cp-teams-invite-create').show(); $nav.find('button.cp-teams-invite-copy').hide(); return void $(linkError).text(Messages.team_inviteLinkError).show(); } // Display result here $(linkSpin).hide(); $(linkResult).show().find('textarea').text(href); $nav.find('button.cp-teams-invite-copy').prop('disabled', ''); })); }); return true; }; var linkButtons = [{ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }, { className: 'primary cp-teams-invite-create', name: Messages.team_inviteLinkCreate, iconClass: 'link', onClick: function () { return process(); }, keys: [] }, { className: 'primary cp-teams-invite-copy', name: Messages.team_inviteLinkCopy, iconClass: 'copy', onClick: function () { if (!href) { return; } Clipboard.copy(href, (err) => { if (!err) { UI.log(Messages.shareSuccess); } }); }, keys: [] }]; var frameLink = UI.dialog.customModal(linkContent, { buttons: linkButtons, }); $(frameLink).find('.cp-teams-invite-copy').prop('disabled', 'disabled').hide(); // Create modal var tabs = [{ title: Messages.share_contactCategory, icon: "contacts-book", content: frameContacts, active: hasFriends }, { title: Messages.share_linkCategory, icon: "link", content: frameLink, active: !hasFriends }]; var modal = UI.dialog.tabs(tabs); UI.openCustomModal(modal); }; UIElements.openDirectlyConfirmation = function (common, cb) { cb = cb || Util.noop; UI.confirm(h('p', Messages.ui_openDirectly), yes => { if (!yes) { return void cb(yes); } common.openDirectly(); cb(yes); }); }; UIElements.getEntryFromButton = function ($button) { if (!$button || !$button.length) { return; } let $icon = $button.children('svg,i,[data-lucide]').first(); if (!$icon.length) $icon = $button.find('svg,i,[data-lucide]').first(); let attributes = {}; let btnClass = $button.attr('class'); let btnId = $button.attr('id'); let btnTitle = $button.attr('title'); let btnVisibility = $button.attr('style'); if (btnClass) { attributes['class'] = btnClass; } if (btnId) { attributes['id'] = btnId; } if (btnTitle && !attributes.title) { attributes['title'] = btnTitle; } if (btnVisibility) { attributes['style'] = btnVisibility; } return UIElements.createDropdownEntry({ tag: 'a', attributes: attributes, content: [ $icon.length ? $icon.clone()[0] : null, h('span', $button.text()) ], action: function () { $button.click(); return true; } }); }; UIElements.createButton = function (common, type, rightside, data, callback) { var AppConfig = common.getAppConfig(); var button; var sframeChan = common.getSframeChannel(); var appType = (common.getMetadataMgr().getMetadata().type || 'pad').toUpperCase(); data = data || {}; if (!callback && data.callback) { callback = data.callback; } let makeButton = function(iconClasses, buttonClasses, title, text) { const ariaLabel = title || text || ''; return $(h('button', { class: buttonClasses, title: title, 'aria-label': ariaLabel }, [ iconClasses ? Icons.get(iconClasses) : null, text ? h('span', { class: 'cp-toolbar-drawer-element' }, text) : null ])); }; switch (type) { case 'export': button = makeButton('export', 'cp-toolbar-icon-export', Messages.exportButtonTitle, Messages.exportButton); button .click(common.prepareFeedback(type)) .click(UI.clearTooltipsDelay); if (callback) { button.click(callback); } break; case 'import': button = makeButton('import', 'cp-toolbar-icon-import', Messages.importButtonTitle, Messages.importButton); var importer = importContent((data && data.binary) ? 'application/octet-stream' : 'text/plain', callback, { accept: data ? data.accept : undefined, binary: data ? data.binary : undefined }); var handler = data.first? function () { data.first(function () { importer(); // Make sure we don't pass arguments to importer }); }: importer; //importContent; button .click(common.prepareFeedback(type)) .click(function () { handler(); UI.clearTooltipsDelay(); }); //} break; case 'upload': button = makeButton('upload', 'btn btn-primary new', Messages.uploadButtonTitle, Messages.uploadButton); if (!data.FM) { return; } var $input = $('', { 'type': 'file', 'style': 'display: none;', 'multiple': 'multiple' }).on('change', function (e) { var files = Util.slice(e.target.files); files.forEach(function (file) { var ev = { target: data.target }; if (data.filter && !data.filter(file)) { return; } if (data.transformer) { data.transformer(file, function (newFile) { data.FM.handleFile(newFile, ev); }); return; } data.FM.handleFile(file, ev); }); if (callback) { callback(); } }); if (data.accept) { $input.attr('accept', data.accept); } button.click(function () { $input.click(); UI.clearTooltipsDelay(); }); break; case 'copy': button = makeButton('copy', 'cp-toolbar-icon-copy', '', Messages.makeACopy); button .click(common.prepareFeedback(type)) .click(function () { sframeChan.query('EV_MAKE_A_COPY'); UI.clearTooltipsDelay(); }); break; case 'importtemplate': if (!AppConfig.enableTemplates) { return; } if (!common.isLoggedIn()) { return; } button = makeButton('import-template', 'cp-toolbar-icon-import-template', '', Messages.template_import); button .click(common.prepareFeedback(type)) .click(function () { if (callback) { return void callback(); } UIElements.openTemplatePicker(common, true); UI.clearTooltipsDelay(); }); break; case 'template': if (!AppConfig.enableTemplates) { return; } if (!common.isLoggedIn()) { return; } button = makeButton('file-template', 'cp-toolbar-icon-template', '', Messages.saveTemplateButton); if (data.rt || data.callback) { button .click(function () { var title = data.getTitle() || document.title; var todo = function (val) { if (typeof(val) !== "string") { return; } if (data.callback) { return void data.callback(val); } var toSave = data.rt.getUserDoc(); if (val.trim()) { val = val.trim(); title = val; try { var parsed = JSON.parse(toSave); var meta; if (Array.isArray(parsed) && typeof(parsed[3]) === "object") { meta = parsed[3].metadata; // pad } else if (parsed.info) { meta = parsed.info; // poll } else { meta = parsed.metadata; } if (typeof(meta) === "object") { meta.title = val; meta.defaultTitle = val; delete meta.users; } toSave = JSON.stringify(parsed); } catch(e) { console.error("Parse error while setting the title", e); } } sframeChan.query('Q_SAVE_AS_TEMPLATE', { toSave: toSave, title: title }, function () { UI.alert(Messages.templateSaved); Feedback.send('TEMPLATE_CREATED'); }); }; UI.prompt(Messages.saveTemplatePrompt, title, todo); UI.clearTooltipsDelay(); }); } break; case 'forget': button = makeButton('trash-full', 'cp-toolbar-icon-forget', '', Messages.fc_delete); callback = typeof callback === "function" ? callback : function () {}; button .click(common.prepareFeedback(type)) .click(function() { common.isPadStored(function (err, data) { if (!data) { return void UI.alert(Messages.autostore_notAvailable); } sframeChan.query('Q_IS_ONLY_IN_SHARED_FOLDER', null, function (err, res) { if (err || res.error) { return void console.log(err || res.error); } var msg = Messages.forgetPrompt; if (res) { UI.alert(Messages.sharedFolders_forget); return; } else if (!common.isLoggedIn()) { msg = Messages.fm_removePermanentlyDialog; } UI.confirm(msg, function (yes) { if (!yes) { return; } sframeChan.query('Q_MOVE_TO_TRASH', null, function (err, obj) { err = err || (obj && obj.error); if (err) { callback(err); return void UI.warn(Messages.fm_forbidden); } var msg; if (common.isLoggedIn()) { msg = Pages.setHTML(h('div'), Messages.movedToTrash); $(msg).find('a').attr('href', '/drive/'); common.fixLinks(msg); } else { msg = h('div', Messages.deleted); } UI.alert(msg); callback(); return; }); }); }); }); }); break; case 'present': button = $(h('button', { //title: Messages.presentButtonTitle, // TODO display if the label text is collapsed }, [ Icons.get('play'), h('span.cp-toolbar-name', Messages.share_linkPresent) ])).click(common.prepareFeedback(type)); break; case 'preview': button = $(h('button', { //title: Messages.previewButtonTitle, // TODO display if the label text is collapsed }, [ Icons.get('preview'), h('span.cp-toolbar-name', Messages.toolbar_preview) ])).click(common.prepareFeedback(type)); break; case 'print': button = makeButton('print', 'cp-toolbar-icon-print', Messages.printButtonTitle2, Messages.printText); break; case 'history': button = makeButton('history', 'cp-toolbar-icon-history', Messages.historyButton, Messages.historyText, Messages.historyButton); if (data.histConfig) { button.click(common.prepareFeedback(type)).on('click', function () { common.getHistory(data.histConfig); UI.clearTooltipsDelay(); }); } if (!AppConfig.enableHistory) { button.css('display', 'none'); } break; case 'mediatag': button = $(h('button.cp-toolbar-mediatag', { //title: Messages.filePickerButton, // TODO display if the label text is collapsed }, [ Icons.get('toolbar-insert'), h('span.cp-toolbar-name', Messages.toolbar_insert) ])).click(common.prepareFeedback(type)); break; case 'savetodrive': button = $(h('button.cp-toolbar-savetodrive', { title: Messages.canvas_saveToDrive, }, [ Icons.get('file-image'), h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_savetodrive) ])).click(common.prepareFeedback(type)); if (callback) { button.click(callback); } break; case 'storeindrive': button = $(h('button.cp-toolbar-storeindrive', { style: 'display:none;' }, [ Icons.get('drive'), h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_storeInDrive) ])).click(common.prepareFeedback(type)).click(function () { $('.cp-toolbar-storeindrive').hide(); common.getSframeChannel().query("Q_AUTOSTORE_STORE", { forceOwnDrive: true, }, function (err, obj) { var error = err || (obj && obj.error); if (error) { $(button).show(); if (error === 'E_OVER_LIMIT') { return void UI.warn(Messages.pinLimitReached); } return void UI.warn(Messages.autostore_error); } $(document).trigger('cpPadStored'); UI.log(Messages.autostore_saved); }); }); break; case 'hashtag': button = makeButton('tag', 'cp-toolbar-icon-hashtag', Messages.tags_title, Messages.fc_hashtag); button.click(common.prepareFeedback(type)) .click(function () { common.isPadStored(function (err, data) { if (!data) { return void UI.alert(Messages.autostore_notAvailable); } UIElements.updateTags(common, null); }); UI.clearTooltipsDelay(); }); break; case 'toggle': button = $(h('button.cp-toolbar-tools', { //title: data.title || '', // TODO display if the label text is collapsed 'aria-label': data.text || Messages.toolbar_tools, // Fallback 'aria-pressed': false }, [ Icons.get('apps-settings'), h('span.cp-toolbar-name', data.text || Messages.toolbar_tools) ])).click(common.prepareFeedback(type)); /* window.setTimeout(function () { button.attr('title', data.title); }); var updateIcon = function (isVisible) { button.removeClass('fa-caret-down').removeClass('fa-caret-up'); if (!isVisible) { button.addClass('fa-caret-down'); } else { button.addClass('fa-caret-up'); } }; */ button.click(function (e) { data.element.toggle(); var isVisible = data.element.is(':visible'); button.attr('aria-pressed', isVisible ? 'true' : 'false'); if (callback) { callback(isVisible); } if (isVisible) { button.addClass('cp-toolbar-button-active'); if (e.originalEvent) { Feedback.send('TOGGLE_SHOW_' + appType); } } else { button.removeClass('cp-toolbar-button-active'); if (e.originalEvent) { Feedback.send('TOGGLE_HIDE_' + appType); } } //updateIcon(isVisible); }); //updateIcon(data.element.is(':visible')); break; case 'properties': button = makeButton('properties', 'cp-toolbar-icon-properties', Messages.propertiesButtonTitle, Messages.propertiesButton); button .click(common.prepareFeedback(type)) .click(function () { var isTop; try { isTop = common.getMetadataMgr().getPrivateData().isTop; } catch (err) { console.error(err); } if (!isTop) { return void UIElements.openDirectlyConfirmation(common); } sframeChan.event('EV_PROPERTIES_OPEN'); UI.clearTooltipsDelay(); }); break; case 'save': // OnlyOffice save button = makeButton('save', '', Messages.settings_save, Messages.settings_save); button .click(function() { common.prepareFeedback(type); UI.clearTooltipsDelay(); }); if (callback) { button.click(callback); } break; case 'newpad': button = makeButton('add', 'cp-toolbar-icon-newpad', Messages.newButtonTitle, Messages.newButton); button .click(common.prepareFeedback(type)) .click(function () { common.createNewPadModal(); UI.clearTooltipsDelay(); }); break; case 'snapshots': button = makeButton('snapshot', 'cp-toolbar-icon-snapshots', Messages.snapshots_button,Messages.snapshots_button); button .click(common.prepareFeedback(type)) .click(function () { if (typeof(data.load) !== "function" || typeof(data.make) !== "function") { return; } UIElements.openSnapshotsModal(common, data.load, data.make, data.remove); UI.clearTooltipsDelay(); }); break; default: var drawerCls = data.drawer === false ? '' : '.cp-toolbar-drawer-element'; var icon = data.icon || "circle-question"; button = $(h('button', { title: data.tippy || '' //title: data.title || '', }, [ Icons.get(icon), h('span.cp-toolbar-name'+drawerCls, data.text) ])); var feedbackHandler = common.prepareFeedback(data.name || 'DEFAULT'); Util.onClickEnter(button, function () { feedbackHandler(); if (typeof(callback) !== 'function') { return; } callback(); }); if (data.style) { button.attr('style', data.style); } if (data.id) { button.attr('id', data.id); } if (data.hiddenReadOnly) { button.addClass('cp-hidden-if-readonly'); } if (data.name) { button.addClass('cp-toolbar-icon-'+data.name); } } if (rightside) { button.addClass('cp-toolbar-rightside-button'); } return button; }; var createMdToolbar = function (common, editor, cfg) { cfg = cfg || {}; var $toolbar = $('
', { 'class': 'cp-markdown-toolbar' }); var clean = function (str) { return str.replace(/^(\n)+/, '').replace(/(\n)+$/, ''); }; var actions = { 'bold': { // Msg.mdToolbar_bold expr: '**{0}**', icon: 'bold' }, 'italic': { // Msg.mdToolbar_italic expr: '_{0}_', icon: 'italic' }, 'strikethrough': { // Msg.mdToolbar_strikethrough expr: '~~{0}~~', icon: 'strikethrough' }, 'heading': { // Msg.mdToolbar_heading apply: function (str) { return '\n'+clean(str).split('\n').map(function (line) { return '# '+line; }).join('\n')+'\n'; }, icon: 'heading' }, 'link': { // Msg.mdToolbar_link expr: '[{0}](http://)', icon: 'link' }, 'quote': { // Msg.mdToolbar_quote apply: function (str) { return '\n\n'+str.split('\n').map(function (line) { return '> '+line; }).join('\n')+'\n\n'; }, icon: 'quote' }, 'nlist': { // Msg.mdToolbar_nlist apply: function (str) { return '\n'+clean(str).split('\n').map(function (line) { return '1. '+line; }).join('\n')+'\n'; }, icon: 'list-ol' }, 'list': { // Msg.mdToolbar_list apply: function (str) { return '\n'+clean(str).split('\n').map(function (line) { return '* '+line; }).join('\n')+'\n'; }, icon: 'list' }, 'check': { // Msg.mdToolbar_check apply: function (str) { return '\n' + clean(str).split('\n').map(function (line) { return '* [ ] ' + line; }).join('\n') + '\n'; }, icon: 'list-todo' }, 'code': { // Msg.mdToolbar_code apply: function (str) { if (str.indexOf('\n') !== -1) { return '\n```\n' + clean(str) + '\n```\n'; } return '`' + str + '`'; }, icon: 'code' }, 'toc': { // Msg.mdToolbar_toc expr: '[TOC]', icon: 'toc' } }; if (typeof(cfg.embed) === "function") { actions.embed = { // Messages.mdToolbar_embed icon: 'embed', action: function () { var _cfg = { types: ['file', 'link'], where: ['root'] }; common.openFilePicker(_cfg, function (data) { // Embed links if (data.static) { var a = h('a', { href: data.href }, data.name); cfg.embed(a, data); return; } // Embed files if (data.type !== 'file') { console.log("Unexpected data type picked " + data.type); return; } if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; } common.setPadAttribute('atime', +new Date(), null, data.href); var privateDat = common.getMetadataMgr().getPrivateData(); var origin = privateDat.fileHost || privateDat.origin; var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src; cfg.embed(h('media-tag', { src: src, 'data-crypto-key': 'cryptpad:' + data.key, }), data); }); } }; } var onClick = function () { var type = $(this).attr('data-type'); var texts = editor.getSelections(); if (actions[type].action) { return actions[type].action(); } var newTexts = texts.map(function (str) { str = str || Messages.mdToolbar_defaultText; if (actions[type].apply) { return actions[type].apply(str); } return actions[type].expr.replace('{0}', str); }); editor.replaceSelections(newTexts, 'around'); editor.focus(); }; for (var k in actions) { let $b = $('