From da7411dab687b84eb3707dcc5dc7a25bf854cacf Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 18:46:29 +0200 Subject: [PATCH 01/39] Hide CKE insert image button in pad --- www/pad/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/pad/inner.js b/www/pad/inner.js index 2a6840dca..f05bfca57 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -985,6 +985,7 @@ define([ var $ckeToolbar = $('#cke_1_top').find('.cke_toolbox_main'); $mainContainer.prepend($ckeToolbar.addClass('cke_reset_all')); $contentContainer.append(h('div#cp-app-pad-comments')); + $ckeToolbar.find('.cke_button__image_icon').parent().hide(); }).nThen(waitFor()); }).nThen(function (/*waitFor*/) { From 8e08f1b3e5907028df135d56e0a19fe317878585 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 10:46:34 +0200 Subject: [PATCH 02/39] Don't add author data for anonymous users --- www/pad/comments.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 233bb3a13..3606e1d29 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -66,8 +66,13 @@ define([ return uid || authorUid(existing); }; + // Return the author ID and add/update the data for registered users + // Return the username for unregistered users var updateAuthorData = function (Env) { var userData = Env.metadataMgr.getUserData(); + if (!Env.common.isLoggedIn()) { + return userData.name; + } var myAuthorId = getAuthorId(Env, userData.curvePublic); var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {}; data.name = userData.name; @@ -184,7 +189,9 @@ define([ var content = []; obj.m.forEach(function (msg, i) { - var author = (Env.comments.authors || {})[msg.u] || {}; + var author = typeof(msg.u) === "number" ? + ((Env.comments.authors || {})[msg.u] || {}) : + { name: msg.u }; var name = Util.fixHTML(author.name || Messages.anonymous); var date = new Date(msg.t); var avatar = h('span.cp-avatar'); @@ -257,9 +264,9 @@ define([ }).join('\n'); // Push the reply - var myId = updateAuthorData(Env); + var user = updateAuthorData(Env); obj.m.push({ - u: myId, + u: user, // id (number) or name (string) t: +new Date(), m: val, v: value @@ -477,10 +484,10 @@ define([ // Don't override existing data if (Env.comments.data[uid]) { return; } - var myId = updateAuthorData(Env); + var user = updateAuthorData(Env); Env.comments.data[uid] = { m: [{ - u: myId, + u: user, // Id or name t: +new Date(), m: val, v: canonicalize(Env.editor.getSelection().getSelectedText()) From e71a6fb1e27f7273f2ec8b021cfa67213f9bb628 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 11:39:06 +0200 Subject: [PATCH 03/39] Update user data in comments in realtime --- www/pad/comments.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 3606e1d29..bdb65b95f 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -68,17 +68,21 @@ define([ // Return the author ID and add/update the data for registered users // Return the username for unregistered users - var updateAuthorData = function (Env) { + var updateAuthorData = function (Env, onChange) { var userData = Env.metadataMgr.getUserData(); if (!Env.common.isLoggedIn()) { return userData.name; } var myAuthorId = getAuthorId(Env, userData.curvePublic); var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {}; + var old = Sortify(data); data.name = userData.name; data.avatar = userData.avatar; data.profile = userData.profile; data.curvePublic = userData.curvePublic; + if (typeof(onChange) === "function" && Sortify(data) !== old) { + onChange(); + } return myAuthorId; }; @@ -154,7 +158,7 @@ define([ var redrawComments = function (Env) { // Don't redraw if there were no change - var str = Sortify((Env.comments || {}).data || {}); + var str = Sortify(Env.comments || {}); if (str === Env.oldComments) { return; } Env.oldComments = str; @@ -341,10 +345,22 @@ define([ } if (Env.ready === 0) { Env.ready = true; + updateAuthorData(Env, function () { + changed = true; + }); + // On ready, if our user data have changed or if we've added the initial structure + // of the comments, push the changes if (changed) { updateMetadata(Env); Env.framework.localChange(); } + } else if (Env.ready) { + // Everytime there is a metadata change, check if our user data have changed + // and puhs the update sif necessary + updateAuthorData(Env, function () { + updateMetadata(Env); + Env.framework.localChange(); + }); } redrawComments(Env); }; From 52f2ad68d833afe8d3bf628c1201a58cc00a3bfa Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 13:01:42 +0200 Subject: [PATCH 04/39] Move large mode icon to cke toolbar --- www/pad/inner.js | 56 ++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/www/pad/inner.js b/www/pad/inner.js index f05bfca57..c037f8d0d 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -530,6 +530,15 @@ define([ mkHelpMenu(framework); + framework._.sfCommon.getAttribute(['pad', 'width'], function (err, data) { + var active = data || typeof(data) === "undefined"; + if (active) { + $contentContainer.addClass('cke_body_width'); + } else { + editor.execCommand('pagemode'); + } + }); + framework.onEditableChange(function (unlocked) { if (!framework.isReadOnly()) { $inner.attr('contenteditable', '' + Boolean(unlocked)); @@ -717,29 +726,6 @@ define([ } }); - framework._.sfCommon.getAttribute(['pad', 'width'], function (err, data) { - var active = data || typeof(data) === "undefined"; - if (active) { - $contentContainer.addClass('cke_body_width'); - } - var $width = framework._.sfCommon.createButton('', true, { - icon: 'fa-arrows-h', - text: active ? Messages.pad_useFullWidth : Messages.pad_usePageWidth, - name: "pad-width", - },function () { - if (active) { - $contentContainer.removeClass('cke_body_width'); - } else { - $contentContainer.addClass('cke_body_width'); - } - active = !active; - var key = active ? Messages.pad_useFullWidth : Messages.pad_usePageWidth; - $width.find('.cp-toolbar-drawer-element').text(key); - framework._.sfCommon.setAttribute(['pad', 'width'], active); - }); - framework._.toolbar.$drawer.append($width); - }); - framework._.sfCommon.isPadStored(function (err, val) { if (!val) { return; } var b64images = $inner.find('img[src^="data:image"]:not(.cke_reset)'); @@ -972,6 +958,30 @@ define([ module.ckeditor = editor = Ckeditor.replace('editor1', { customConfig: '/customize/ckeditor-config.js', }); + + editor.addCommand('pagemode', { + exec: function () { + if (!framework) { return; } + var $contentContainer = $('#cke_1_contents'); + var $button = $('.cke_button__pagemode'); + var isLarge = $button.hasClass('cke_button_on'); + if (isLarge) { + $button.addClass('cke_button_off').removeClass('cke_button_on'); + $contentContainer.addClass('cke_body_width'); + } else { + $button.addClass('cke_button_on').removeClass('cke_button_off'); + $contentContainer.removeClass('cke_body_width'); + } + framework._.sfCommon.setAttribute(['pad', 'width'], isLarge); + } + }); + editor.ui.addButton('PageMode', { + label: Messages.pad_useFullWidth, + command: 'pagemode', + icon: '/pad/icons/arrows-h.png', + toolbar: 'document,60' + }); + editor.on('instanceReady', waitFor()); }).nThen(function () { editor.plugins.mediatag.import = function ($mt) { From 473577a9f17f1eb11acc4dfaf202b613d125c748 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 14:38:29 +0200 Subject: [PATCH 05/39] Send a notification when replying to a comment --- www/common/notifications.js | 26 +++++++++++++ www/common/outer/mailbox-handlers.js | 55 ++++++++++++++++++++++++++++ www/pad/comments.js | 41 +++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/www/common/notifications.js b/www/common/notifications.js index 8546f2787..8db8db284 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -315,6 +315,32 @@ define([ } }; + Messages.comments_notification = 'Replies to your comment "{0}" in {1}'; // XXX + handlers['COMMENT_REPLY'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Display the notification + //var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var comment = Util.fixHTML(msg.content.comment).slice(0,20).trim(); + if (msg.content.comment.length > 20) { + comment += '...'; + } + var title = Util.fixHTML(msg.content.title); + var href = msg.content.href; + + content.getFormatText = function () { + return Messages._getKey('comments_notification', [comment, title]); + }; + content.handler = function () { + common.openURL(href); + defaultDismiss(common, data)(); + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + // NOTE: don't forget to fixHTML everything returned by "getFormatText" diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index fec467fc5..cc90861c1 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -527,6 +527,61 @@ define([ }; + // Hide duplicates when receiving a SHARE_PAD notification: + // Keep only one notification per channel: the stronger and more recent one + var comments = {}; + handlers['COMMENT_REPLY'] = function (ctx, box, data, cb) { + var msg = data.msg; + var hash = data.hash; + var content = msg.content; + // content.channel + + //if (isMuted(ctx, data)) { return void cb(true); } + + var channel = content.channel; + if (!channel) { return void cb(true); } + var res = ctx.store.manager.findChannel(channel); + if (!res.length) { return void cb(true); } + + var title, href; + // Check if the pad is in our drive + if (!res.some(function (obj) { + if (!obj.data) { return; } + href = obj.data.href; + title = obj.data.filename || obj.data.title; + return true; + })) { return void cb(true); } + + // If we don't have the edit url, ignore this notification + if (!href) { return void cb(true); } + + // Add the title + content.href = href; + content.title = title; + + // Remove duplicates + var old = comments[channel]; + var toRemove = old ? old.data : undefined; + + // Update the data + comments[channel] = { + data: { + type: box.type, + hash: hash + } + }; + + cb(false, toRemove); + }; + removeHandlers['COMMENT_REPLY'] = function (ctx, box, data, hash) { + var content = data.content; + var channel = content.channel; + var old = comments[channel]; + if (old && old.data && old.data.hash === hash) { + delete comments[channel]; + } + }; + return { add: function (ctx, box, data, cb) { diff --git a/www/pad/comments.js b/www/pad/comments.js index bdb65b95f..d881ba2fd 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -80,6 +80,7 @@ define([ data.avatar = userData.avatar; data.profile = userData.profile; data.curvePublic = userData.curvePublic; + data.notifications = userData.notifications; if (typeof(onChange) === "function" && Sortify(data) !== old) { onChange(); } @@ -92,6 +93,43 @@ define([ Env.metadataMgr.updateMetadata(md); }; + var sendReplyNotification = function (Env, uid) { + if (!Env.comments || !Env.comments.data || !Env.comments.authors) { return; } + if (!Env.common.isLoggedIn()) { return; } + var thread = Env.comments.data[uid]; + if (!thread || !Array.isArray(thread.m)) { return; } + var userData = Env.metadataMgr.getUserData(); + var privateData = Env.metadataMgr.getPrivateData(); + var others = {}; + // Get all the other registered users with a mailbox + thread.m.forEach(function (obj) { + var u = obj.u; + if (typeof(u) !== "number") { return; } + var author = Env.comments.authors[u]; + if (!author || others[u] || !author.notifications || !author.curvePublic) { return; } + if (author.curvePublic === userData.curvePublic) { return; } // don't send to yourself + others[u] = { + curvePublic: author.curvePublic, + comment: obj.m, + content: obj.v, + notifications: author.notifications + }; + }); + // Send the notification + Object.keys(others).forEach(function (id) { + var data = others[id]; + Env.common.mailbox.sendTo("COMMENT_REPLY", { + channel: privateData.channel, + comment: data.comment, + content: data.content + }, { + channel: data.notifications, + curvePublic: data.curvePublic + }); + }); + + }; + Messages.comments_submit = "Submit"; // XXX Messages.comments_reply = "Reply"; // XXX Messages.comments_resolve = "Resolve"; // XXX @@ -276,6 +314,9 @@ define([ v: value }); + // Notify other users + sendReplyNotification(Env, key); + // Send to chainpad updateMetadata(Env); Env.framework.localChange(); From 3d22a5e95a0998e8fc7c47d95227d917f0f07762 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 14:38:44 +0200 Subject: [PATCH 06/39] Add missing icon --- www/pad/icons/arrows-h.png | Bin 0 -> 17842 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 www/pad/icons/arrows-h.png diff --git a/www/pad/icons/arrows-h.png b/www/pad/icons/arrows-h.png new file mode 100644 index 0000000000000000000000000000000000000000..a3002cf4dfaf114dee81a2d29050ee2529fb0f4d GIT binary patch literal 17842 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 Date: Mon, 27 Apr 2020 15:09:29 +0200 Subject: [PATCH 07/39] Ability to disable comments notifications --- www/common/outer/mailbox-handlers.js | 5 +++-- www/settings/inner.js | 33 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index cc90861c1..0998a4511 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -534,9 +534,10 @@ define([ var msg = data.msg; var hash = data.hash; var content = msg.content; - // content.channel - //if (isMuted(ctx, data)) { return void cb(true); } + if (Util.find(ctx.store.proxy, ['settings', 'pad', 'disableNotif'])) { + return void cb(true); + } var channel = content.channel; if (!channel) { return void cb(true); } diff --git a/www/settings/inner.js b/www/settings/inner.js index 5541277ed..9353b2c64 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -78,6 +78,7 @@ define([ 'pad': [ 'cp-settings-pad-width', 'cp-settings-pad-spellcheck', + 'cp-settings-pad-notif', ], 'code': [ 'cp-settings-code-indent-unit', @@ -1275,6 +1276,38 @@ define([ return $div; }; + // XXX Messages.settings_padNotifTitle + // XXX Messages.settings_padNotifHint + // XXX Messages.settings_padNotifCheckbox ("disable comments notifications") + Messages.settings_padNotifTitle = "Comments notifications"; // XXX + Messages.settings_padNotifHint = "Block notifications when someone replies to one of your comments"; // XXX + Messages.settings_padNotifCheckbox = "Disable comment notifications"; + makeBlock('pad-notif', function (cb) { + var $cbox = $(UI.createCheckbox('cp-settings-pad-notif', + Messages.settings_padNotifCheckbox, + false, { label: {class: 'noTitle'} })); + + var spinner = UI.makeSpinner($cbox); + + // Checkbox: "Enable safe links" + var $checkbox = $cbox.find('input').on('change', function () { + spinner.spin(); + var val = $checkbox.is(':checked'); + common.setAttribute(['pad', 'disableNotif'], val, function () { + spinner.done(); + }); + }); + + common.getAttribute(['pad', 'disableNotif'], function (e, val) { + if (e) { return void console.error(e); } + if (val === true) { + $checkbox.attr('checked', 'checked'); + } + }); + + cb($cbox); + }, true); + // Code settings create['code-indent-unit'] = function () { From 73654a4e65c273641fe046b273d8b0ba5133e457 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 15:13:51 +0200 Subject: [PATCH 08/39] Fix 'show/hide cke toolbar' button --- www/pad/inner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/pad/inner.js b/www/pad/inner.js index c037f8d0d..c77b99713 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -397,7 +397,7 @@ define([ var addToolbarHideBtn = function (framework, $bar) { // Expand / collapse the toolbar var cfg = { - element: $bar.find('.cke_toolbox_main') + element: $bar }; var onClick = function (visible) { framework._.sfCommon.setAttribute(['pad', 'showToolbar'], visible); @@ -672,7 +672,7 @@ define([ }); if (!framework.isReadOnly()) { - addToolbarHideBtn(framework, $contentContainer); + addToolbarHideBtn(framework, $('.cke_toolbox_main')); } else { $('.cke_toolbox_main').hide(); } From 2608b0f66e92a7b4f3dd530e0d6fda6bb80c3819 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Apr 2020 16:47:03 +0200 Subject: [PATCH 09/39] Comment out inactive code --- www/common/sframe-app-framework.js | 2 ++ www/pad/comments.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 24e7d0abd..5cccc6cba 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -289,9 +289,11 @@ define([ throw new Error("Content must be an object or array, type is " + typeof(content)); } + /* if (padChange && hasChanged(content)) { //cpNfInner.metadataMgr.addAuthor(); } + */ oldContent = content; if (Array.isArray(content)) { diff --git a/www/pad/comments.js b/www/pad/comments.js index d881ba2fd..c777a4dc4 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -397,7 +397,7 @@ define([ } } else if (Env.ready) { // Everytime there is a metadata change, check if our user data have changed - // and puhs the update sif necessary + // and push the updates if necessary updateAuthorData(Env, function () { updateMetadata(Env); Env.framework.localChange(); From 7309ae1b23c4df383251f1e96b152cbd83e84778 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 15:54:12 +0200 Subject: [PATCH 10/39] Add mentions --- customize.dist/src/less2/include/buttons.less | 15 +- .../src/less2/include/comments.less | 3 + .../src/less2/include/dropdown.less | 19 ++ .../src/less2/include/framework.less | 2 + www/common/common-ui-elements.js | 254 ++++++++++++++++++ www/common/notifications.js | 22 ++ www/common/outer/mailbox-handlers.js | 51 ++++ www/common/sframe-common.js | 1 + www/pad/comments.js | 124 ++++++++- 9 files changed, 480 insertions(+), 11 deletions(-) diff --git a/customize.dist/src/less2/include/buttons.less b/customize.dist/src/less2/include/buttons.less index 7936a5ee6..d769f8e07 100644 --- a/customize.dist/src/less2/include/buttons.less +++ b/customize.dist/src/less2/include/buttons.less @@ -9,7 +9,7 @@ @alertify-input-bg: @colortheme_modal-input; @alertify-input-fg: @colortheme_modal-input-fg; - input:not(.form-control), textarea { + input:not(.form-control), textarea, div.cp-textarea { // background-color: @alertify-input-fg; color: @cryptpad_text_col; border: 1px solid @alertify-input-bg; @@ -44,13 +44,24 @@ } } - textarea { + textarea, div.cp-textarea { padding: 8px; &[readonly] { overflow: hidden; resize: none; } } + div.cp-textarea { + height: 60px; + width: 100%; + background-color: white; + cursor: text; + outline: none; + white-space: pre-wrap; + overflow-y: auto; + word-break: break-word; + resize: vertical; + } div.cp-button-confirm { display: inline-block; diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index be72e71f6..b6b0a21dc 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -38,6 +38,9 @@ } } + #cp-comments-label { + display: none; + } .cp-comment-container { outline: none; diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 271603216..0cabeabbf 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -1,11 +1,30 @@ @import (reference) "./colortheme-all.less"; @import (reference) "./tools.less"; +@import (reference) "./avatar.less"; /* The container
- needed to position the dropdown content */ .dropdown_main () { --LessLoader_require: LessLoader_currentFile(); } & { + .cp-autocomplete-value { + display: flex; + align-items: center; + .cp-avatar { + .avatar_main(30px); + padding: 1px; + } + & > span:last-child { + flex: 1; + height: 32px; + line-height: 32px; + padding: 0 10px; + } + span { + margin: 0px !important; + border: none !important; + } + } .cp-dropdown-container { @dropdown_font: @colortheme_app-font-size @colortheme_font; position: relative; diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less index 0eefababb..0d34361ab 100644 --- a/customize.dist/src/less2/include/framework.less +++ b/customize.dist/src/less2/include/framework.less @@ -15,6 +15,7 @@ @import (reference) "./messenger.less"; @import (reference) "./cursor.less"; @import (reference) "./usergrid.less"; +@import (reference) "./mentions.less"; @import (reference) "./modals-ui-elements.less"; .framework_main(@bg-color, @warn-color, @color) { @@ -44,6 +45,7 @@ .messenger_main(); .cursor_main(); .usergrid_main(); + .mentions_main(); .creation_main( @bg-color: @bg-color, @color: @color diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a5c1396c2..63e110c0b 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3666,5 +3666,259 @@ define([ UI.proposal(div, todo); }; + var insertTextAtCursor = function (text) { + var selection = window.getSelection(); + var range = selection.getRangeAt(0); + range.deleteContents(); + var node = document.createTextNode(text); + range.insertNode(node); + + for (var position = 0; position != text.length; position++) { + selection.modify("move", "right", "character"); + }; + } + + var getSource = {}; + getSource['contacts'] = function (common, sources) { + var priv = common.getMetadataMgr().getPrivateData(); + Object.keys(priv.friends || {}).forEach(function (key) { + if (key === 'me') { return; } + var f = priv.friends[key]; + if (!f.curvePublic || sources[f.curvePublic]) { return; } + sources[f.curvePublic] = { + avatar: f.avatar, + name: f.displayName, + curvePublic: f.curvePublic, + profile: f.profile, + notifications: f.notifications + }; + }); + }; + UIElements.addMentions = function (common, options) { + if (!options.$input) { return; } + var $t = options.$input; + + var getValue = function () { return $t.val(); }; + var setValue = function (val) { $t.val(val); }; + + var div = false; + if (options.contenteditable) { + div = true; + getValue = function () { return $t.html(); }; + setValue = function () {}; // Not used, we insert data at the node level + $t.on('paste', function (e) { + try { + insertTextAtCursor(e.originalEvent.clipboardData.getData('text')); + e.preventDefault(); + } catch (e) { console.error(e); } + }); + + // Fix backspace with "contenteditable false" children + $t.on('keydown', function (e) { + if (e.which !== 8 && e.which !== 46) { return; } // Backspace or del + var sel = document.getSelection(); + if (sel.anchorNode.nodeType !== Node.TEXT_NODE) { return; } // text nodes only + + // Only fix node located after mentions + var n = sel.anchorNode; + var prev = n && n.previousSibling; + // Check if our caret is just after a mention + if (!prev || !prev.classList || !prev.classList.contains('cp-mentions')) { return; } + + // Del: if we're at offset 0, make sure we won't delete the text node + if (e.which === 46) { + if (!sel.anchorOffset && sel.anchorNode.length === 1) { + sel.anchorNode.nodeValue = " "; + e.preventDefault(); + } + return; + } + + // Backspace + // If we're not at offset 0, make sure we won't delete the text node + if (e.which === 8 && sel.anchorOffset) { + if (sel.anchorNode.length === 1) { + sel.anchorNode.nodeValue = " "; + e.preventDefault(); + } + return; + } + // If we're at offset 0, We're just after a mention: delete it + prev.parentElement.removeChild(prev); + e.preventDefault(); + }); + } + + // Add the sources + // NOTE: Sources must have a "name". They can have an "avatar". + var sources = options.sources || {}; + if (!getSource[options.type]) { return; } + getSource[options.type](common, sources); + + + // Sort autocomplete result by label + var sort = function (a, b) { + var _a = a.label.toLowerCase(); + var _b = b.label.toLowerCase(); + if (_a.label < _b.label) { return -1; } + if (_b.label < _a.label) { return 1; } + return 0; + }; + + // Get the text between the last @ before the cursor and the cursor + var extractLast = function (term, offset) { + var offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; + var startOffset = term.slice(0,offset).lastIndexOf('@'); + return term.slice(startOffset+1, offset); + }; + // Insert the autocomplete value in the input field + var insertValue = function (value, offset, content) { + var offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; + var content = content || getValue(); + var startOffset = content.slice(0,offset).lastIndexOf('@'); + var length = offset - startOffset; + if (length <= 0) { return; } + var result = content.slice(0,startOffset) + value + content.slice(offset); + if (content) { + return { + result: result, + startOffset: startOffset + }; + } + setValue(result); + }; + // Set the value to receive from the autocomplete + var toInsert = function (data, key) { + var name = data.name.replace(/[^a-zA-Z0-9]+/g, "-"); + return "[@"+name+"|"+key+"]"; + }; + + // Fix the functions when suing a contenteditable div + if (div) { + var _extractLast = extractLast; + // Use getSelection to get the cursor position in contenteditable + extractLast = function () { + var val = getValue(); + var sel = document.getSelection(); + if (sel.anchorNode.nodeType !== Node.TEXT_NODE) { return; } + return _extractLast(sel.anchorNode.nodeValue, sel.anchorOffset); + }; + var _insertValue = insertValue; + insertValue = function (value) { + // Get the selected node + var sel = document.getSelection(); + if (sel.anchorNode.nodeType !== Node.TEXT_NODE) { return; } + var node = sel.anchorNode; + + // Remove the "term" + var insert =_insertValue("", sel.anchorOffset, node.nodeValue); + if (insert) { + node.nodeValue = insert.result; + } + var breakAt = insert ? insert.startOffset : sel.anchorOffset; + + var el; + if (typeof(value) === "string") { el = document.createTextNode(value); } + else { el = value; } + + node.parentNode.insertBefore(el, node.splitText(breakAt)); + var next = el.nextSibling; + if (!next) { + next = document.createTextNode(" "); + el.parentNode.appendChild(next); + } else if (next.nodeType === Node.TEXT_NODE && !next.nodeValue) { + next.nodeValue = " "; + } + var range = document.createRange(); + range.setStart(next, 0); + range.setEnd(next, 0); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }; + + // Inserting contacts into contenteditable: use mention UI + if (options.type === "contacts") { + toInsert = function (data, key) { + var avatar = h('span.cp-avatar', { + contenteditable: false + }); + common.displayAvatar($(avatar), data.avatar, data.name); + return h('span.cp-mentions', { + 'data-curve': data.curvePublic, + 'data-notifications': data.notifications, + 'data-profile': data.profile, + 'data-name': Util.fixHTML(data.name), + 'data-avatar': data.avatar || "", + }, [ + avatar, + h('span.cp-mentions-name', { + contenteditable: false + }, data.name) + ]); + }; + } + } + + + // don't navigate away from the field on tab when selecting an item + $t.on("keydown", function(e) { + // Tab or enter + if ((e.which === 13 || e.which === 9)) { + try { + var visible = $t.autocomplete("instance").menu.activeMenu.is(':visible'); + if (visible) { + e.preventDefault(); + e.stopPropagation(); + } + } catch (e) {} + } + }).autocomplete({ + minLength: 0, + source: function(data, cb) { + var term = data.term; + var results = []; + if (term.indexOf("@") >= 0) { + term = extractLast(data.term) || ''; + results = Object.keys(sources).filter(function (key) { + var data = sources[key]; + return data.name.toLowerCase().indexOf(term.toLowerCase()) !== -1; + }).map(function (key) { + var data = sources[key]; + return { + label: data.name, + value: key + }; + }); + results.sort(sort); + } + cb(results); + }, + focus: function() { + // prevent value inserted on focus + return false; + }, + select: function(event, ui) { + // add the selected item + var key = ui.item.value; + var data = sources[key]; + var value = toInsert(data, key); + insertValue(value); + return false; + } + }).autocomplete( "instance" )._renderItem = function( ul, item ) { + var key = item.value; + var obj = sources[key]; + if (!obj) { return; } + var avatar = h('span.cp-avatar'); + common.displayAvatar($(avatar), obj.avatar, obj.name); + var li = h('li.cp-autocomplete-value', [ + avatar, + h('span', obj.name) + ]); + return $(li).appendTo(ul); + }; + }; + return UIElements; }); diff --git a/www/common/notifications.js b/www/common/notifications.js index 8db8db284..7fa1bc6ad 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -341,6 +341,28 @@ define([ } }; + Messages.mentions_notification = '{0} has mentionned you in {1}'; // XXX + handlers['MENTION'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Display the notification + var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var title = Util.fixHTML(msg.content.title); + var href = msg.content.href; + + content.getFormatText = function () { + return Messages._getKey('mentions_notification', [name, title]); + }; + content.handler = function () { + common.openURL(href); + defaultDismiss(common, data)(); + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + // NOTE: don't forget to fixHTML everything returned by "getFormatText" diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 0998a4511..ea9af5778 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -583,6 +583,57 @@ define([ } }; + // Hide duplicates when receiving a SHARE_PAD notification: + // Keep only one notification per channel: the stronger and more recent one + var mentions = {}; + handlers['MENTION'] = function (ctx, box, data, cb) { + var msg = data.msg; + var hash = data.hash; + var content = msg.content; + + if (isMuted(ctx, data)) { return void cb(true); } + + var channel = content.channel; + if (!channel) { return void cb(true); } + var res = ctx.store.manager.findChannel(channel); + if (!res.length) { return void cb(true); } + + var title, href; + // Check if the pad is in our drive + if (!res.some(function (obj) { + if (!obj.data) { return; } + href = obj.data.href || obj.data.roHref; // XXX send the href when we mention? + title = obj.data.filename || obj.data.title; + return true; + })) { return void cb(true); } + + // Add the title + content.href = href; + content.title = title; + + // Remove duplicates + var old = mentions[channel]; + var toRemove = old ? old.data : undefined; + + // Update the data + mentions[channel] = { + data: { + type: box.type, + hash: hash + } + }; + + cb(false, toRemove); + }; + removeHandlers['MENTION'] = function (ctx, box, data, hash) { + var content = data.content; + var channel = content.channel; + var old = mentions[channel]; + if (old && old.data && old.data.hash === hash) { + delete mentions[channel]; + } + }; + return { add: function (ctx, box, data, cb) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 0eb35b824..5d4a630c1 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -103,6 +103,7 @@ define([ funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); + funcs.addMentions = callWithCommon(UIElements.addMentions); funcs.importMediaTagMenu = callWithCommon(MT.importMediaTagMenu); funcs.getMediaTagPreview = callWithCommon(MT.getMediaTagPreview); diff --git a/www/pad/comments.js b/www/pad/comments.js index c777a4dc4..dc2bffead 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -130,17 +130,35 @@ define([ }; + var cleanMentions = function ($el, full) { + $el.html(''); + var el = $el[0]; + var allowed = full ? ['data-profile', 'data-name', 'data-avatar', 'class'] + : ['class']; + // Remove unnecessary/unsafe attributes + for (var i=el.attributes.length-1; i>0; i--) { + var name = el.attributes[i] && el.attributes[i].name; + if (allowed.indexOf(name) === -1) { + $el.removeAttr(name); + } + } + }; + Messages.comments_submit = "Submit"; // XXX Messages.comments_reply = "Reply"; // XXX Messages.comments_resolve = "Resolve"; // XXX - var getCommentForm = function (Env, reply, _cb) { var cb = Util.once(_cb); var userData = Env.metadataMgr.getUserData(); var name = Util.fixHTML(userData.name || Messages.anonymous); var avatar = h('span.cp-avatar'); - var textarea = h('textarea', { - tabindex: 1 + var textarea = h('div.cp-textarea', { + tabindex: 1, + role: 'textbox', + 'aria-multiline': true, + 'aria-labelledby': 'cp-comments-label', + 'aria-required': true, + contenteditable: true, }); Env.common.displayAvatar($(avatar), userData.avatar, name); @@ -157,25 +175,78 @@ define([ Messages.comments_submit ]); + // List of allowed attributes in mentions $(submit).click(function (e) { e.stopPropagation(); - cb(textarea.value); + var clone = textarea.cloneNode(true); + var notify = {}; + var $clone = $(clone); + $clone.find('span.cp-mentions').each(function (i, el) { + var $el = $(el); + var curve = $el.attr('data-curve'); + var notif = $el.attr('data-notifications'); + cleanMentions($el, true); + if (!curve || !notif) { return; } + notify[curve] = notif; + }); + $clone.find('> *:not(.cp-mentions)').remove(); + var content = clone.innerHTML.trim(); + if (!content) { return; } + + // Send notification + var privateData = Env.metadataMgr.getPrivateData(); + Object.keys(notify).forEach(function (curve) { + Env.common.mailbox.sendTo("MENTION", { + channel: privateData.channel, + }, { + channel: notify[curve], + curvePublic: curve + }); + }); + + // Push the content + cb(content); }); $(cancel).click(function (e) { e.stopPropagation(); cb(); }); - $(textarea).keydown(function (e) { + var $text = $(textarea).keydown(function (e) { + e.stopPropagation(); if (e.which === 27) { $(cancel).click(); } if (e.which === 13 && !e.shiftKey) { + // Submit form on Enter is the autocompelte menu is not visible + try { + var visible = $text.autocomplete("instance").menu.activeMenu.is(':visible'); + if (visible) { return; } + } catch (e) {} $(submit).click(); e.preventDefault(); } + }).click(function (e) { + e.stopPropagation(); }); + + if (Env.common.isLoggedIn()) { + var authors = {}; + Object.keys((Env.comments && Env.comments.authors) || {}).forEach(function (id) { + var obj = Util.clone(Env.comments.authors[id]); + authors[obj.curvePublic] = obj; + }); + Env.common.addMentions({ + $input: $text, + contenteditable: true, + type: 'contacts', + sources: authors + }); + } + + + setTimeout(function () { $(textarea).focus(); }); @@ -206,6 +277,9 @@ define([ Env.$container.html(''); + var label = h('label#cp-comments-label', Messages.comments_comment); + Env.$container.append(label); + var show = false; if ($oldInput && !$oldInput.attr('data-uid')) { @@ -245,6 +319,40 @@ define([ }); } + // Build sanitized html with mentions + var m = h('div.cp-comment-content'); + m.innerHTML = msg.m; + var $m = $(m); + $m.find('> *:not(span.cp-mentions)').remove(); + $m.find('span.cp-mentions').each(function (i, el) { + var $el = $(el); + var name = $el.attr('data-name'); + var avatarUrl = $el.attr('data-avatar'); + var profile = $el.attr('data-profile'); + if (!name && !avatar && !profile) { + $el.remove(); + return; + } + cleanMentions($el); + var avatar = h('span.cp-avatar'); + Env.common.displayAvatar($(avatar), avatarUrl, name); + $el.append([ + avatar, + h('span.cp-mentions-name', name) + ]); + if (profile) { + $el.attr('tabindex', 1); + $el.addClass('cp-mentions-clickable').click(function (e) { + e.preventDefault(); + e.stopPropagation(); + Env.common.openURL(Hash.hashToHref(profile, 'profile')); + }).focus(function (e) { + e.stopPropagation(); + }); + } + }); + + // Add the comment content.push(h('div.cp-comment'+(i === 0 ? '' : '.cp-comment-reply'), [ h('div.cp-comment-header', [ avatar, @@ -253,9 +361,7 @@ define([ h('span.cp-comment-time', date.toLocaleString()) ]) ]), - h('div.cp-comment-content', [ - msg.m - ]) + m ])); }); @@ -291,9 +397,9 @@ define([ e.stopPropagation(); $actions.hide(); var form = getCommentForm(Env, key, function (val) { - $(form).remove(); $(form).closest('.cp-comment-container') .find('.cp-comment-actions').css('display', ''); + $(form).remove(); if (!val) { return; } var obj = Env.comments.data[key]; From fd348dc4e24ab2657901de657724869b1a7ca920 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 15:56:57 +0200 Subject: [PATCH 11/39] Add missing less file --- .../src/less2/include/mentions.less | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 customize.dist/src/less2/include/mentions.less diff --git a/customize.dist/src/less2/include/mentions.less b/customize.dist/src/less2/include/mentions.less new file mode 100644 index 000000000..424b8b710 --- /dev/null +++ b/customize.dist/src/less2/include/mentions.less @@ -0,0 +1,31 @@ +@import (reference) "./tools.less"; +@import (reference) "./avatar.less"; + +.mentions_main() { + --LessLoader_require: LessLoader_currentFile(); +} + +& { + .cp-mentions { + .avatar_main(20px); + .tools_unselectable(); + display: inline-flex; + align-items: center; + vertical-align: bottom; + background-color: #eee; + + span.cp-mentions-name { + max-width: 150px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + &.cp-mentions-clickable { + outline: none; + cursor: pointer; + &:hover { + background-color: #ddd; + } + } + } +} From 60555f65a7b8f5662472a3f57eaa1a31c7d93ebe Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 16:40:40 +0200 Subject: [PATCH 12/39] Little fixes and improvements to comments --- .../src/less2/include/comments.less | 5 ++-- www/common/notifications.js | 25 +++++++++++-------- www/common/outer/mailbox-handlers.js | 13 +++++----- www/pad/comments.js | 2 ++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index b6b0a21dc..82647e2b4 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -24,8 +24,9 @@ .avatar_main(40px); display: flex; align-items: flex-start; - textarea { + div.cp-textarea { flex: 1; + min-height: 50px; height: 50px; padding: 2px 8px; } @@ -82,7 +83,7 @@ background-color: white; padding: 10px 5px 5px; white-space: pre-wrap; - word-break: break-all; + word-break: break-word; } .cp-comment-actions { display: none; diff --git a/www/common/notifications.js b/www/common/notifications.js index 7fa1bc6ad..771a84c3c 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -316,6 +316,7 @@ define([ }; Messages.comments_notification = 'Replies to your comment "{0}" in {1}'; // XXX + Messages.unknownPad = "Unknown pad"; // XXX handlers['COMMENT_REPLY'] = function (common, data) { var content = data.content; var msg = content.msg; @@ -326,16 +327,18 @@ define([ if (msg.content.comment.length > 20) { comment += '...'; } - var title = Util.fixHTML(msg.content.title); + var title = Util.fixHTML(msg.content.title || Messages.unknownPad); var href = msg.content.href; content.getFormatText = function () { return Messages._getKey('comments_notification', [comment, title]); }; - content.handler = function () { - common.openURL(href); - defaultDismiss(common, data)(); - }; + if (href) { + content.handler = function () { + common.openURL(href); + defaultDismiss(common, data)(); + }; + } if (!content.archived) { content.dismissHandler = defaultDismiss(common, data); } @@ -348,16 +351,18 @@ define([ // Display the notification var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; - var title = Util.fixHTML(msg.content.title); + var title = Util.fixHTML(msg.content.title || Messages.unknownPad); var href = msg.content.href; content.getFormatText = function () { return Messages._getKey('mentions_notification', [name, title]); }; - content.handler = function () { - common.openURL(href); - defaultDismiss(common, data)(); - }; + if (href) { + content.handler = function () { + common.openURL(href); + defaultDismiss(common, data)(); + }; + } if (!content.archived) { content.dismissHandler = defaultDismiss(common, data); } diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index ea9af5778..841dadaf3 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -546,12 +546,12 @@ define([ var title, href; // Check if the pad is in our drive - if (!res.some(function (obj) { + res.some(function (obj) { if (!obj.data) { return; } - href = obj.data.href; + href = obj.data.href || obj.data.roHref; title = obj.data.filename || obj.data.title; return true; - })) { return void cb(true); } + }); // If we don't have the edit url, ignore this notification if (!href) { return void cb(true); } @@ -596,16 +596,17 @@ define([ var channel = content.channel; if (!channel) { return void cb(true); } var res = ctx.store.manager.findChannel(channel); + console.log(res); if (!res.length) { return void cb(true); } var title, href; // Check if the pad is in our drive - if (!res.some(function (obj) { + res.some(function (obj) { if (!obj.data) { return; } - href = obj.data.href || obj.data.roHref; // XXX send the href when we mention? + href = obj.data.href || obj.data.roHref; title = obj.data.filename || obj.data.title; return true; - })) { return void cb(true); } + }); // Add the title content.href = href; diff --git a/www/pad/comments.js b/www/pad/comments.js index dc2bffead..0133a47c4 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -195,7 +195,9 @@ define([ // Send notification var privateData = Env.metadataMgr.getPrivateData(); + var userData = Env.metadataMgr.getUserData(); Object.keys(notify).forEach(function (curve) { + if (curve === userData.curvePublic) { return; } Env.common.mailbox.sendTo("MENTION", { channel: privateData.channel, }, { From a05015ad3cfc7e57b94e5383687c480964e4e816 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 16:43:44 +0200 Subject: [PATCH 13/39] Display mention notification if you don't have access to the pad --- www/common/outer/mailbox-handlers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 841dadaf3..194d375d2 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -542,7 +542,6 @@ define([ var channel = content.channel; if (!channel) { return void cb(true); } var res = ctx.store.manager.findChannel(channel); - if (!res.length) { return void cb(true); } var title, href; // Check if the pad is in our drive @@ -596,8 +595,6 @@ define([ var channel = content.channel; if (!channel) { return void cb(true); } var res = ctx.store.manager.findChannel(channel); - console.log(res); - if (!res.length) { return void cb(true); } var title, href; // Check if the pad is in our drive From 4e7f4482b561ffa43c305ef0c21de017fd9fd63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Tue, 28 Apr 2020 16:27:11 +0100 Subject: [PATCH 14/39] add padding to comment header --- customize.dist/src/less2/include/comments.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index be72e71f6..721f308cb 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -63,6 +63,8 @@ align-items: center; display: flex; background-color: white; + padding: 5px; + box-sizing: content-box; .avatar_main(40px); .cp-comment-metadata { flex: 1; From 9467839d6ca21a27a1b523941b908c06337007cc Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 18:11:46 +0200 Subject: [PATCH 15/39] Fix size of the autocomplete dropdown for mentions --- customize.dist/src/less2/include/comments.less | 3 +++ www/common/common-ui-elements.js | 10 ++++++++++ www/pad/comments.js | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 82647e2b4..50164a05c 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -22,6 +22,9 @@ } .cp-comment-form-input { .avatar_main(40px); + .cp-avatar { + border: 1px solid transparent; + } display: flex; align-items: flex-start; div.cp-textarea { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 63e110c0b..cb325af77 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3893,6 +3893,16 @@ define([ results.sort(sort); } cb(results); + // Set max-height to the autocomplete dropdown + try { + var max = window.innerHeight; + var pos = $t[0].getBoundingClientRect(); + var menu = $t.autocomplete("instance").menu.activeMenu; + menu.css({ + 'overflow-y': 'auto', + 'max-height': (max-pos.bottom)+'px' + }); + } catch (e) {} }, focus: function() { // prevent value inserted on focus diff --git a/www/pad/comments.js b/www/pad/comments.js index 0133a47c4..894cea838 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -239,6 +239,11 @@ define([ var obj = Util.clone(Env.comments.authors[id]); authors[obj.curvePublic] = obj; }); + for (var i = 0; i< 30; i++) { + authors['test'+i] = { + name: "Name"+i + }; + } Env.common.addMentions({ $input: $text, contenteditable: true, @@ -429,7 +434,13 @@ define([ updateMetadata(Env); Env.framework.localChange(); }); + $div.append(form); + + // Make sure the submit button is visible: scroll by the height of the form + setTimeout(function () { + Env.$container.scrollTop(Env.$container.scrollTop() + 55); + }); }); UI.confirmButton(resolve, { From 4d6d9637ffe1408eca76a4ef6cc493c44e8bc82d Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Apr 2020 18:16:21 +0200 Subject: [PATCH 16/39] lint compliance --- www/common/common-ui-elements.js | 25 ++++++++++++------------- www/common/sframe-app-framework.js | 4 +++- www/pad/comments.js | 9 ++------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index cb325af77..8ce295fcf 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3673,10 +3673,10 @@ define([ var node = document.createTextNode(text); range.insertNode(node); - for (var position = 0; position != text.length; position++) { + for (var position = 0; position !== text.length; position++) { selection.modify("move", "right", "character"); - }; - } + } + }; var getSource = {}; getSource['contacts'] = function (common, sources) { @@ -3710,7 +3710,7 @@ define([ try { insertTextAtCursor(e.originalEvent.clipboardData.getData('text')); e.preventDefault(); - } catch (e) { console.error(e); } + } catch (err) { console.error(err); } }); // Fix backspace with "contenteditable false" children @@ -3767,14 +3767,14 @@ define([ // Get the text between the last @ before the cursor and the cursor var extractLast = function (term, offset) { - var offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; + offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; var startOffset = term.slice(0,offset).lastIndexOf('@'); return term.slice(startOffset+1, offset); }; // Insert the autocomplete value in the input field var insertValue = function (value, offset, content) { - var offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; - var content = content || getValue(); + offset = typeof(offset) !== "undefined" ? offset : $t[0].selectionStart; + content = content || getValue(); var startOffset = content.slice(0,offset).lastIndexOf('@'); var length = offset - startOffset; if (length <= 0) { return; } @@ -3798,7 +3798,6 @@ define([ var _extractLast = extractLast; // Use getSelection to get the cursor position in contenteditable extractLast = function () { - var val = getValue(); var sel = document.getSelection(); if (sel.anchorNode.nodeType !== Node.TEXT_NODE) { return; } return _extractLast(sel.anchorNode.nodeValue, sel.anchorOffset); @@ -3832,14 +3831,14 @@ define([ var range = document.createRange(); range.setStart(next, 0); range.setEnd(next, 0); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); + var newSel = window.getSelection(); + newSel.removeAllRanges(); + newSel.addRange(range); }; // Inserting contacts into contenteditable: use mention UI if (options.type === "contacts") { - toInsert = function (data, key) { + toInsert = function (data) { var avatar = h('span.cp-avatar', { contenteditable: false }); @@ -3871,7 +3870,7 @@ define([ e.preventDefault(); e.stopPropagation(); } - } catch (e) {} + } catch (err) { console.error(err); } } }).autocomplete({ minLength: 0, diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 5cccc6cba..7fc6e6692 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -265,6 +265,7 @@ define([ if (!bool && update) { onRemote(); } }; + /* var hasChanged = function (content) { try { var oldValue = JSON.parse(cpNfInner.chainpad.getUserDoc()); @@ -276,8 +277,9 @@ define([ } catch (e) {} return false; }; + */ - onLocal = function (padChange) { + onLocal = function (/*padChange*/) { if (state !== STATE.READY) { return; } if (readOnly) { return; } diff --git a/www/pad/comments.js b/www/pad/comments.js index 894cea838..f34749d5f 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -224,7 +224,7 @@ define([ try { var visible = $text.autocomplete("instance").menu.activeMenu.is(':visible'); if (visible) { return; } - } catch (e) {} + } catch (err) {} $(submit).click(); e.preventDefault(); } @@ -239,11 +239,6 @@ define([ var obj = Util.clone(Env.comments.authors[id]); authors[obj.curvePublic] = obj; }); - for (var i = 0; i< 30; i++) { - authors['test'+i] = { - name: "Name"+i - }; - } Env.common.addMentions({ $input: $text, contenteditable: true, @@ -336,7 +331,7 @@ define([ var name = $el.attr('data-name'); var avatarUrl = $el.attr('data-avatar'); var profile = $el.attr('data-profile'); - if (!name && !avatar && !profile) { + if (!name && !avatarUrl && !profile) { $el.remove(); return; } From 5c64e1fa4ed8bbee2a2d193b9a9d1c405b8d6ae6 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 29 Apr 2020 15:24:25 +0200 Subject: [PATCH 17/39] Make comment input grow with new lines --- customize.dist/src/less2/include/comments.less | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 50164a05c..1abb1f3e4 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -29,9 +29,10 @@ align-items: flex-start; div.cp-textarea { flex: 1; - min-height: 50px; - height: 50px; - padding: 2px 8px; + min-height: 52px; // 22px per line + 8 (padding+border) + height: unset !important; + max-height: 140px; // 6 lines + padding: 3px 5px; } margin-bottom: 5px; } From 1e4af92c13a6578eade740de65d388964a6c1c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 29 Apr 2020 14:27:16 +0100 Subject: [PATCH 18/39] margin adjustments --- customize.dist/src/less2/include/comments.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index f0e265e0c..f9b6b36e6 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -11,7 +11,7 @@ .buttons_main(); .cp-comment-reply { - margin-left: 40px; + margin-left: 30px; } @@ -86,7 +86,7 @@ } .cp-comment-content { background-color: white; - padding: 10px 5px 5px; + padding: 0px 5px 5px 5px; white-space: pre-wrap; word-break: break-word; } From 0d78ae86dcbb63c57d541bea51cc4d7614f1d74e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 29 Apr 2020 16:45:42 +0200 Subject: [PATCH 19/39] Ability to edit a comment --- .../src/less2/include/comments.less | 25 +++ www/common/common-ui-elements.js | 2 +- www/pad/comments.js | 157 ++++++++++++++---- 3 files changed, 154 insertions(+), 30 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 1abb1f3e4..5d5a0a7ab 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -7,12 +7,18 @@ .comments_main() { @data-color: #888; overflow-y: auto; + color: @cryptpad_text_col; .buttons_main(); .cp-comment-reply { margin-left: 40px; } + .cp-comment-deleted { + background: white; + font-size: 14px; + padding: 5px; + } .cp-comment-form { @@ -71,6 +77,7 @@ align-items: center; display: flex; background-color: white; + position: relative; .avatar_main(40px); .cp-comment-metadata { flex: 1; @@ -82,6 +89,18 @@ color: @data-color; } } + .cp-comment-edit { + position: absolute; + right: 0; + top: 0; + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + &:hover { + color: lighten(@cryptpad_text_col, 10%); + } + } } .cp-comment-content { background-color: white; @@ -89,6 +108,12 @@ white-space: pre-wrap; word-break: break-word; } + .cp-comment-edited { + background-color: white; + font-size: 13px; + color: @data-color; + padding: 0 5px; + } .cp-comment-actions { display: none; text-align: right; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8ce295fcf..068c60c31 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3870,7 +3870,7 @@ define([ e.preventDefault(); e.stopPropagation(); } - } catch (err) { console.error(err); } + } catch (err) { console.error(err, $t); } } }).autocomplete({ minLength: 0, diff --git a/www/pad/comments.js b/www/pad/comments.js index f34749d5f..0cad4cd58 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -25,9 +25,11 @@ define([ u: id, m: "str", // comment t: +new Date, - v: "str" // value of the commented content + v: "str", // value of the commented content + e: undefined/1, // edited + d: undefined/1, // deleted }], - (deleted: undefined/true,) + d: undefined/1, } } } @@ -130,11 +132,10 @@ define([ }; - var cleanMentions = function ($el, full) { + var cleanMentions = function ($el) { $el.html(''); var el = $el[0]; - var allowed = full ? ['data-profile', 'data-name', 'data-avatar', 'class'] - : ['class']; + var allowed = ['data-profile', 'data-name', 'data-avatar', 'class']; // Remove unnecessary/unsafe attributes for (var i=el.attributes.length-1; i>0; i--) { var name = el.attributes[i] && el.attributes[i].name; @@ -144,10 +145,12 @@ define([ } }; + Messages.comments_deleted = "Comment deleted by its author"; // XXX + Messages.comments_edited = "Edited"; // XXX Messages.comments_submit = "Submit"; // XXX Messages.comments_reply = "Reply"; // XXX Messages.comments_resolve = "Resolve"; // XXX - var getCommentForm = function (Env, reply, _cb) { + var getCommentForm = function (Env, reply, _cb, editContent) { var cb = Util.once(_cb); var userData = Env.metadataMgr.getUserData(); var name = Util.fixHTML(userData.name || Messages.anonymous); @@ -218,6 +221,7 @@ define([ e.stopPropagation(); if (e.which === 27) { $(cancel).click(); + e.stopImmediatePropagation(); } if (e.which === 13 && !e.shiftKey) { // Submit form on Enter is the autocompelte menu is not visible @@ -226,6 +230,7 @@ define([ if (visible) { return; } } catch (err) {} $(submit).click(); + e.stopImmediatePropagation(); e.preventDefault(); } }).click(function (e) { @@ -247,6 +252,22 @@ define([ }); } + var deleteButton; + // Edit? start with the old content + // Add a space to make sure we won't end with a mention and a bad cursor + if (editContent) { + textarea.innerHTML = editContent + " "; + deleteButton = h('button.btn.btn-danger', { + tabindex: 1 + }, [ + h('i.fa.fa-times'), + Messages.kanban_delete + ]); + $(deleteButton).click(function (e) { + e.stopPropagation(); + cb(); + }); + } setTimeout(function () { @@ -262,6 +283,7 @@ define([ ]), h('div.cp-comment-form-actions', [ cancel, + deleteButton, submit ]) ]); @@ -270,43 +292,60 @@ define([ var redrawComments = function (Env) { // Don't redraw if there were no change var str = Sortify(Env.comments || {}); - if (str === Env.oldComments) { return; } Env.oldComments = str; + // Store existing input form in memory var $oldInput = Env.$container.find('.cp-comment-form').detach(); if ($oldInput.length !== 1) { $oldInput = undefined; } + // Remove everything Env.$container.html(''); + // "show" tells us if we need to display the "comments" column or not + var show = false; + + // Add invisible label for accessibility tools var label = h('label#cp-comments-label', Messages.comments_comment); Env.$container.append(label); - var show = false; - + // If we were adding a new comment, redraw our form if ($oldInput && !$oldInput.attr('data-uid')) { show = true; Env.$container.append($oldInput); } - var order = Env.$inner.find('comment').map(function (i, el) { + var userData = Env.metadataMgr.getUserData(); + + // Get all the comment threads in their order in the pad + var threads = Env.$inner.find('comment').map(function (i, el) { return el.getAttribute('data-uid'); }).toArray(); - var done = []; - - order.forEach(function (key) { - // Avoir duplicates - if (done.indexOf(key) !== -1) { return; } - done.push(key); + // Draw all comment threads + Util.deduplicateString(threads).forEach(function (key) { + // Get thread data var obj = Env.comments.data[key]; - if (!obj || obj.deleted || !Array.isArray(obj.m) || !obj.m.length) { + if (!obj || obj.d || !Array.isArray(obj.m) || !obj.m.length) { return; } + + // If at least one thread is visible, display the "comments" column show = true; var content = []; - obj.m.forEach(function (msg, i) { + var $div; + var $actions; + + // Draw all messages for this thread + (obj.m || []).forEach(function (msg, i) { + var replyCls = i === 0 ? '' : '.cp-comment-reply'; + if (msg.d) { + + content.push(h('div.cp-comment.cp-comment-deleted'+replyCls, + Messages.comments_deleted)); + return; + } var author = typeof(msg.u) === "number" ? ((Env.comments.authors || {})[msg.u] || {}) : { name: msg.u }; @@ -354,16 +393,71 @@ define([ } }); + // edited state + var edited; + if (msg.e) { + edited = h('div.cp-comment-edited', Messages.comments_edited); + } + + var container; + + // Add edit button when applicable (last message of the thread, written by ourselves) + var edit; + if (i === (obj.m.length -1) && author.curvePublic === userData.curvePublic) { + edit = h('span.cp-comment-edit', { + title: Messages.clickToEdit + }, h('i.fa.fa-pencil')); + $(edit).click(function (e) { + e.stopPropagation(); + Env.$container.find('.cp-comment-form').remove(); + if ($actions) { $actions.hide(); } + var form = getCommentForm(Env, key, function (val) { + // Show the "reply" and "resolve" buttons again + $(form).closest('.cp-comment-container') + .find('.cp-comment-actions').css('display', ''); + $(form).remove(); + + var obj = Env.comments.data[key]; + if (!obj || !Array.isArray(obj.m)) { return; } + var msg = obj.m[i]; + if (!msg) { return; } + // i is our index + if (!val) { + msg.d = 1; + if (container) { + $(container).addClass('cp-comment-deleted') + .html(Messages.comments_deleted); + } + if (obj.m.length === 1) { + delete Env.comments.data[key]; + } + } else { + msg.e = 1; + msg.m = val; + } + + // Send to chainpad + updateMetadata(Env); + Env.framework.localChange(); + }, m.innerHTML); + + if (!$div) { return; } + $div.append(form); + }); + } + // Add the comment - content.push(h('div.cp-comment'+(i === 0 ? '' : '.cp-comment-reply'), [ + content.push(container = h('div.cp-comment'+replyCls, [ h('div.cp-comment-header', [ avatar, h('span.cp-comment-metadata', [ h('span.cp-comment-author', name), h('span.cp-comment-time', date.toLocaleString()) - ]) + ]), + edit ]), - m + m, + edited ])); }); @@ -386,19 +480,20 @@ define([ reply, resolve ])); - var $actions = $(actions); + $actions = $(actions); var div; Env.$container.append(div = h('div.cp-comment-container', { 'data-uid': key, tabindex: 1 }, content)); - var $div = $(div); + $div = $(div); $(reply).click(function (e) { e.stopPropagation(); $actions.hide(); var form = getCommentForm(Env, key, function (val) { + // Show the "reply" and "resolve" buttons again $(form).closest('.cp-comment-container') .find('.cp-comment-actions').css('display', ''); $(form).remove(); @@ -434,7 +529,11 @@ define([ // Make sure the submit button is visible: scroll by the height of the form setTimeout(function () { - Env.$container.scrollTop(Env.$container.scrollTop() + 55); + var yContainer = Env.$container[0].getBoundingClientRect().bottom; + var yActions = form.getBoundingClientRect().bottom; + if (yActions > yContainer) { + Env.$container.scrollTop(Env.$container.scrollTop() + 55); + } }); }); @@ -531,7 +630,7 @@ define([ // If there is no comment stored in the metadata, abort var comments = Object.keys(Env.comments.data || {}).filter(function (id) { - return !Env.comments.data[id].deleted; + return !Env.comments.data[id].d; }); var changed = false; @@ -556,8 +655,8 @@ define([ } // If this comment was deleted, we're probably using "undo" to restore it: // remove the "deleted" state and continue - if (obj.deleted) { - delete obj.deleted; + if (obj.d) { + delete obj.d; changed = true; } return id; @@ -575,7 +674,7 @@ define([ // comment has been deleted var data = Env.comments.data[uid]; if (!data) { return; } - data.deleted = true; + data.d = 1; //delete Env.comments.data[uid]; changed = true; }); @@ -711,7 +810,7 @@ define([ // Clear data var data = (Env.comments && Env.comments.data) || {}; Object.keys(data).forEach(function (uid) { - if (data[uid].deleted) { delete data[uid]; } + if (data[uid].d) { delete data[uid]; } }); // Commit From abf083252c9eb8cee83222edc94d632774b80021 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 29 Apr 2020 16:54:02 +0200 Subject: [PATCH 20/39] Fix multiline comments --- www/pad/comments.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/pad/comments.js b/www/pad/comments.js index 0cad4cd58..9a4641253 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -192,6 +192,7 @@ define([ if (!curve || !notif) { return; } notify[curve] = notif; }); + $clone.find('br').replaceWith("\n"); $clone.find('> *:not(.cp-mentions)').remove(); var content = clone.innerHTML.trim(); if (!content) { return; } From 6db6bc86db709365bd927acf1785a809f280e4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 29 Apr 2020 15:56:16 +0100 Subject: [PATCH 21/39] margin adjustments --- customize.dist/src/less2/include/comments.less | 3 ++- www/pad/app-pad.less | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index fe8be2d73..fef4fc6d6 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -17,6 +17,7 @@ .cp-comment-form { &:not(:last-child) { + padding: 5px; margin-bottom: 10px; } } @@ -63,7 +64,7 @@ } .cp-comment { &:not(:first-child) { - margin-top: 5px; + // margin-top: 5px; } } .cp-comment-header { diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 1be1d61ce..c829d4347 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -86,7 +86,8 @@ body.cp-app-pad { order: 3; width: 300px; //background-color: white; - margin: 30px; + margin: 0px 20px; + padding: 10px 0px; // XXX not applied at the bottom .comments_main(); } &.cke_body_width { From 7fa9fd454ea094e2fd89f1fb03f98b2865164826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 29 Apr 2020 16:41:54 +0100 Subject: [PATCH 22/39] fix margins --- customize.dist/src/less2/include/comments.less | 8 +++++++- www/pad/app-pad.less | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index c2d86b1d3..66f0021ba 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -67,10 +67,16 @@ margin-top: 5px; } padding: 5px; + &:nth-child(2) { + margin-top: 10px; + }; + &:last-child { + margin-bottom: 10px; + } } .cp-comment { &:not(:first-child) { - // margin-top: 5px; + margin-top: 5px; } } .cp-comment-header { diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index c829d4347..56b2b727a 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -87,7 +87,6 @@ body.cp-app-pad { width: 300px; //background-color: white; margin: 0px 20px; - padding: 10px 0px; // XXX not applied at the bottom .comments_main(); } &.cke_body_width { From 003ad074a5dc57da2e0cedc91ac0f2c04f33917e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 29 Apr 2020 16:53:36 +0100 Subject: [PATCH 23/39] adjust colors --- www/pad/comment.js | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 0f54ff8ac..412a762be 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -1,28 +1,28 @@ -(function () { +(function() { var CKEDITOR = window.CKEDITOR; - function isUnstylable (el) { + function isUnstylable(el) { if (el.hasClass('cke_widget_mediatag')) { return false; } - var b = el.getAttribute( 'contentEditable' ) === 'false' || - el.getAttribute( 'data-nostyle' ); + var b = el.getAttribute('contentEditable') === 'false' || + el.getAttribute('data-nostyle'); return b; } - var color1 = 'rgba(252, 165, 3, 0.8)'; - var color2 = 'rgba(252, 231, 3, 0.8)'; + var color1 = 'rgba(249, 230, 65, 1.0)'; + var color2 = 'rgba(252, 181, 0, 1.0)'; CKEDITOR.plugins.add('comments', { - onLoad: function () { - CKEDITOR.addCss('comment { background-color: '+color1+'; }' + - '@keyframes color { 0% { background-color: '+color2+'; } 50% { background-color: '+color1+'; } 100% { background-color: '+color2+'; } }' + - 'comment.active { animation-name: color; animation-duration: 1s; animation-iteration-count: 2; background-color: '+color2+'; outline: none;}' + - 'comment media-tag { border: 2px solid '+color1+' !important; }' + - 'comment.active media-tag { border: 2px solid '+color2+' !important; }' + + onLoad: function() { + CKEDITOR.addCss('comment { background-color: ' + color1 + '; }' + + '@keyframes color { 0% { background-color: ' + color2 + '; } 50% { background-color: ' + color1 + '; } 100% { background-color: ' + color2 + '; } }' + + 'comment.active { animation-name: color; animation-duration: 1s; animation-iteration-count: 2; background-color: ' + color2 + '; outline: none;}' + + 'comment media-tag { border: 2px solid ' + color1 + ' !important; }' + + 'comment.active media-tag { border: 2px solid ' + color2 + ' !important; }' + 'comment * { background-color: transparent !important; }'); }, - init: function (editor) { + init: function(editor) { var Messages = CKEDITOR._commentsTranslations; var styleDef = { @@ -30,14 +30,14 @@ attributes: { 'data-uid': '#(uid)', }, - overrides: [ { + overrides: [{ element: 'comment' - } ], + }], childRule: isUnstylable }; var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); - var isApplicable = editor.plugins.comments.isApplicable = function (path, sel) { + var isApplicable = editor.plugins.comments.isApplicable = function(path, sel) { path = path || editor.elementPath(); sel = sel || editor.getSelection(); var applicable = removeStyle.checkApplicable(path, editor); @@ -49,12 +49,12 @@ // Register the command. editor.addCommand('comment', { - exec: function (editor) { + exec: function(editor) { if (editor.readOnly) { return; } editor.focus(); var uid = CKEDITOR.tools.getUniqueId(); - editor.plugins.comments.addComment(uid, function () { + editor.plugins.comments.addComment(uid, function() { // Make an undo spnashot editor.fire('saveSnapshot'); // Make sure comments won't overlap @@ -65,16 +65,16 @@ editor.applyStyle(s); // Save the undo snapshot after all changes are affected. - setTimeout( function() { + setTimeout(function() { editor.fire('saveSnapshot'); - }, 0 ); + }, 0); }); } }); // Uncomment provided element - editor.plugins.comments.uncomment = function (id, els) { + editor.plugins.comments.uncomment = function(id, els) { if (editor.readOnly) { return; } editor.fire('saveSnapshot'); @@ -86,7 +86,7 @@ }, }); style.alwaysRemoveElement = true; - els.forEach(function (el) { + els.forEach(function(el) { // Create range for this element el.removeAttribute('class'); var node = new CKEDITOR.dom.node(el); @@ -101,22 +101,22 @@ } }); - setTimeout( function() { + setTimeout(function() { editor.fire('saveSnapshot'); - }, 0 ); + }, 0); }; // Uncomment from context menu, disabled for now... editor.addCommand('uncomment', { - exec: function (editor, data) { + exec: function(editor, data) { if (editor.readOnly) { return; } editor.fire('saveSnapshot'); - if (!data || !data.id) { + if (!data ||  !data.id) { editor.focus(); editor.removeStyle(removeStyle); - setTimeout( function() { + setTimeout(function() { editor.fire('saveSnapshot'); - }, 0 ); + }, 0); return; } } @@ -127,7 +127,7 @@ editor.ui.addButton('Comment', { label: Messages.comment, command: 'comment', - icon : '/pad/icons/comment.png', + icon: '/pad/icons/comment.png', toolbar: 'insert,10' }); } @@ -136,7 +136,7 @@ editor.addMenuGroup('comments'); editor.addMenuItem('comment', { label: Messages.comment, - icon : '/pad/icons/comment.png', + icon: '/pad/icons/comment.png', command: 'comment', group: 'comments' }); @@ -159,7 +159,7 @@ }; }); */ - editor.contextMenu.addListener(function (element, sel, path) { + editor.contextMenu.addListener(function(element, sel, path) { var applicable = isApplicable(path, sel); if (!applicable) { return; } return { @@ -170,4 +170,4 @@ } }); -})(); +})(); \ No newline at end of file From b37b8c4b31a4ad55e4c3385bf52b695039649761 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 30 Apr 2020 12:18:23 +0200 Subject: [PATCH 24/39] Fix race condition between click events in comments --- customize.dist/src/less2/include/comments.less | 2 ++ www/pad/comments.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 66f0021ba..f6d034967 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -99,6 +99,8 @@ } } .cp-comment-edit { + cursor: pointer; + outline: none; position: absolute; right: 0; top: 0; diff --git a/www/pad/comments.js b/www/pad/comments.js index 9a4641253..ff0ec4878 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -266,7 +266,7 @@ define([ ]); $(deleteButton).click(function (e) { e.stopPropagation(); - cb(); + cb(false); }); } @@ -406,9 +406,12 @@ define([ var edit; if (i === (obj.m.length -1) && author.curvePublic === userData.curvePublic) { edit = h('span.cp-comment-edit', { + tabindex:1, title: Messages.clickToEdit }, h('i.fa.fa-pencil')); $(edit).click(function (e) { + Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); + $div.addClass('cp-comment-active'); e.stopPropagation(); Env.$container.find('.cp-comment-form').remove(); if ($actions) { $actions.hide(); } @@ -418,12 +421,14 @@ define([ .find('.cp-comment-actions').css('display', ''); $(form).remove(); + if (typeof(val) === "undefined") { return; } + var obj = Env.comments.data[key]; if (!obj || !Array.isArray(obj.m)) { return; } var msg = obj.m[i]; if (!msg) { return; } // i is our index - if (!val) { + if (val === false) { msg.d = 1; if (container) { $(container).addClass('cp-comment-deleted') @@ -563,7 +568,7 @@ define([ if (!visible) { $last[0].scrollIntoView(); } }; - $div.on('click focus', function () { + $div.on('click focus', function (e) { if ($div.hasClass('cp-comment-active')) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); $div.addClass('cp-comment-active'); @@ -832,12 +837,14 @@ define([ } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); Env.$inner.find('comment.active').removeClass('active'); + Env.$container.find('.cp-comment-form').remove(); }); // Unselect comment when clicking on another part of the doc Env.$inner.on('click', function (e) { if ($(e.target).closest('comment').length) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); Env.$inner.find('comment.active').removeClass('active'); + Env.$container.find('.cp-comment-form').remove(); }); Env.$inner.on('click', 'comment', function (e) { var $comment = $(e.target); From 49d037a085910c44f00b873c2838f99a1c171f8f Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 30 Apr 2020 12:25:22 +0200 Subject: [PATCH 25/39] Fix resolve button UI issues in comments --- customize.dist/src/less2/include/buttons.less | 2 +- www/pad/comments.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/buttons.less b/customize.dist/src/less2/include/buttons.less index d769f8e07..87fe9b050 100644 --- a/customize.dist/src/less2/include/buttons.less +++ b/customize.dist/src/less2/include/buttons.less @@ -66,7 +66,7 @@ div.cp-button-confirm { display: inline-block; button { - margin: 0; + margin: 0 !important; } .cp-button-timer { height: 3px; diff --git a/www/pad/comments.js b/www/pad/comments.js index ff0ec4878..a2154c69c 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -544,7 +544,7 @@ define([ }); UI.confirmButton(resolve, { - classes: 'btn-danger-alt' + classes: 'btn-danger' }, function () { // Delete the comment delete Env.comments.data[key]; From 7ba88751eb879a4b0f6daf9a3c76214717debfcc Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 30 Apr 2020 14:11:50 +0200 Subject: [PATCH 26/39] Fix addComment button --- www/pad/comment.js | 2 +- www/pad/comments.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 412a762be..b79be44f1 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -170,4 +170,4 @@ } }); -})(); \ No newline at end of file +})(); diff --git a/www/pad/comments.js b/www/pad/comments.js index a2154c69c..482591ca2 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -723,7 +723,8 @@ define([ node: node, button: button }; - $(button).click(function () { + $(button).click(function (e) { + e.stopPropagation(); Env.editor.execCommand('comment'); Env.bubble = undefined; }); @@ -745,6 +746,8 @@ define([ return; } + // Remove active class on other comments + Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); Env.$container.find('.cp-comment-form').remove(); var form = getCommentForm(Env, false, function (val) { $(form).remove(); @@ -835,6 +838,9 @@ define([ if ($(e.target).closest('.cp-comment-container').length) { return; } + // Add comment button? don't remove anything because this handler is called after + // the button action + if (e.target.classList.contains('cke_button__comment_icon')) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); Env.$inner.find('comment.active').removeClass('active'); Env.$container.find('.cp-comment-form').remove(); From 88cbb0d8efa42e137aee985b62f1f64502199915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Thu, 30 Apr 2020 13:20:25 +0100 Subject: [PATCH 27/39] fix comment form button layout --- customize.dist/src/less2/include/comments.less | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index f6d034967..3b157d377 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -45,8 +45,9 @@ } .cp-comment-form-actions { text-align: right; + margin-left: -30px; button:not(:last-child) { - margin-right: 10px; + margin-right: 5px; } } @@ -133,7 +134,7 @@ margin-bottom: 0 !important; } button:not(:last-child) { - margin-right: 10px; + margin-right: 5px; } } .cp-comment-active { From 861ba3b291ed5fab47365dbbbffa5172e0bd1a20 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Apr 2020 16:20:46 +0200 Subject: [PATCH 28/39] Translated using Weblate (German) Currently translated at 100.0% (1257 of 1257 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 88c555fca..5c20f1154 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -221,7 +221,7 @@ "poll_admin_button": "Admin", "poll_create_user": "Neuen Benutzer hinzufügen", "poll_create_option": "Neue Option hinzufügen", - "poll_commit": "Einchecken", + "poll_commit": "Bestätigen", "poll_closeWizardButton": "Assistenten schließen", "poll_closeWizardButtonTitle": "Assistenten schließen", "poll_wizardComputeButton": "Optionen übernehmen", From 5d15adaf8cabfda06fb03157c8b4497c4e4c0629 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Apr 2020 16:20:46 +0200 Subject: [PATCH 29/39] Translated using Weblate (English) Currently translated at 100.0% (1269 of 1269 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1268 of 1268 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1267 of 1267 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1266 of 1266 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1265 of 1265 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1264 of 1264 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1263 of 1263 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1262 of 1262 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1261 of 1261 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1260 of 1260 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1259 of 1259 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1258 of 1258 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ --- www/common/translations/messages.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index ef71487e0..dc1820640 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1353,5 +1353,17 @@ "oo_login": "Please log in or register to improve the performance of spreadsheets.", "pad_useFullWidth": "Full-width mode", "pad_usePageWidth": "Page mode", - "cba_title": "Author colors" + "cba_title": "Author colors", + "comments_notification": "Replies to your comment \"{0}\" in {1}", + "unknownPad": "Unknown pad", + "mentions_notification": "{0} has mentioned you in {1}", + "comments_deleted": "Comment deleted by its author", + "comments_edited": "Edited", + "comments_submit": "Submit", + "comments_reply": "Reply", + "comments_resolve": "Resolve", + "comments_comment": "Comment", + "settings_padNotifTitle": "Comment notifications", + "settings_padNotifHint": "Ignore notifications when someone replies to one of your comments", + "settings_padNotifCheckbox": "Disable comment notifications" } From 587a3a2e4c37f43f8ff6b9fc3ea7d69f4aa28a3b Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Apr 2020 16:20:46 +0200 Subject: [PATCH 30/39] Translated using Weblate (French) Currently translated at 100.0% (1269 of 1269 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index d337d130d..62bcf9169 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1353,5 +1353,17 @@ "oo_login": "Veuillez vous connecter ou vous inscrire pour améliorer la performance des feuilles de calcul.", "pad_usePageWidth": "Mode page", "pad_useFullWidth": "Mode large", - "cba_title": "Couleurs par auteurs" + "cba_title": "Couleurs par auteurs", + "settings_padNotifCheckbox": "Désactiver les notifications de commentaires", + "settings_padNotifHint": "Ignorer les notifications lorsque quelqu'un répond à l'un de vos commentaires", + "settings_padNotifTitle": "Notifications de commentaires", + "comments_comment": "Commenter", + "comments_resolve": "Fermer", + "comments_reply": "Répondre", + "comments_submit": "Valider", + "comments_edited": "Édité", + "comments_deleted": "Commentaire effacé par son auteur", + "mentions_notification": "{0} vous a mentionné dans {1}", + "unknownPad": "Pad inconnu", + "comments_notification": "Réponses à votre commentaire \"{0}\" sur {1}" } From 98c24aa22a7d9dbbd38cb334d507b32eb022d27e Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Apr 2020 16:20:46 +0200 Subject: [PATCH 31/39] Translated using Weblate (Japanese) Currently translated at 14.1% (177 of 1257 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ja/ --- www/common/translations/messages.ja.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.ja.json b/www/common/translations/messages.ja.json index feceb792f..3168088b3 100644 --- a/www/common/translations/messages.ja.json +++ b/www/common/translations/messages.ja.json @@ -169,5 +169,13 @@ "user_rename": "表示名を変更", "users": "ユーザー", "saved": "保存しました", - "error": "エラー" + "error": "エラー", + "deleted": "削除しました", + "notifications_title": "未読の通知があります", + "profile_editDescription": "説明を編集", + "profile_addDescription": "説明を追加", + "profileButton": "プロフィール", + "profile_urlPlaceholder": "URL", + "profile_avatar": "アバター", + "profile_upload": " 新しいアバターをアップロード" } From c381c614ba9b89a233d0d0804cd235fa5c3585c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Thu, 30 Apr 2020 16:01:40 +0100 Subject: [PATCH 32/39] fix reply button layout and mention names --- customize.dist/src/less2/include/mentions.less | 1 + www/pad/app-pad.less | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/mentions.less b/customize.dist/src/less2/include/mentions.less index 424b8b710..9ef96acba 100644 --- a/customize.dist/src/less2/include/mentions.less +++ b/customize.dist/src/less2/include/mentions.less @@ -19,6 +19,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + padding: 0px 3px; } &.cp-mentions-clickable { outline: none; diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 56b2b727a..bf607e801 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -84,7 +84,7 @@ body.cp-app-pad { } #cp-app-pad-comments { order: 3; - width: 300px; + width: 330px; //background-color: white; margin: 0px 20px; .comments_main(); From c96936329051da96aa3d8db0cf28711e390f84c9 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 11:42:45 +0200 Subject: [PATCH 33/39] Fix click, focus and selection issues --- www/pad/comments.js | 62 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 482591ca2..0ed0b0399 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -145,6 +145,15 @@ define([ } }; + // Seletc all text of a contenteditable element + var selectAll = function (element) { + var selection = window.getSelection(); + var range = document.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + }; + Messages.comments_deleted = "Comment deleted by its author"; // XXX Messages.comments_edited = "Edited"; // XXX Messages.comments_submit = "Submit"; // XXX @@ -273,10 +282,11 @@ define([ setTimeout(function () { $(textarea).focus(); + selectAll(textarea); }); return h('div.cp-comment-form' + (reply ? '.cp-comment-reply' : ''), { - 'data-uid': reply || undefined + 'data-uid': reply || '' }, [ h('div.cp-comment-form-input', [ avatar, @@ -290,12 +300,30 @@ define([ ]); }; + var isVisible = function (el, $container) { + var size = $container.outerHeight(); + var pos = el.getBoundingClientRect(); + return (pos.bottom < size) && (pos.y > 0); + }; + var redrawComments = function (Env) { // Don't redraw if there were no change var str = Sortify(Env.comments || {}); if (str === Env.oldComments) { return; } Env.oldComments = str; + // Store the cursor position if it's located in this form + var oldSelection = window.getSelection(); + var oldRangeObj; + if ($(oldSelection.anchorNode).closest('.cp-comment-form').length) { + oldRange = oldSelection.getRangeAt && oldSelection.getRangeAt(0); + oldRangeObj = { + start: oldRange.startContainer, + startO: oldRange.startOffset, + end: oldRange.endContainer, + endO: oldRange.endOffset + }; + } // Store existing input form in memory var $oldInput = Env.$container.find('.cp-comment-form').detach(); if ($oldInput.length !== 1) { $oldInput = undefined; } @@ -562,21 +590,24 @@ define([ // Scroll into view if (!$last.length) { return; } - var size = Env.$inner.outerHeight(); - var pos = $last[0].getBoundingClientRect(); - var visible = (pos.y + pos.height) < size; + var visible = isVisible($last[0], Env.$inner); if (!visible) { $last[0].scrollIntoView(); } }; $div.on('click focus', function (e) { + // Prevent the click event to propagate if we're already selected + // The propagation to #cp-app-pad-inner would trigger the "unselect" handler + e.stopPropagation(); if ($div.hasClass('cp-comment-active')) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); $div.addClass('cp-comment-active'); - div.scrollIntoView(); $actions.css('display', ''); Env.$container.find('.cp-comment-form').remove(); focusContent(); + + var visible = isVisible(div, Env.$container); + if (!visible) { div.scrollIntoView(); } }); if ($oldInput && $oldInput.attr('data-uid') === key) { @@ -588,6 +619,19 @@ define([ } }); + // Restore selection + if (oldRangeObj) { + setTimeout(function () { + if (!oldRangeObj) { return; } + var range = document.createRange(); + range.setStart(oldRangeObj.start, oldRangeObj.startO); + range.setEnd(oldRangeObj.end, oldRangeObj.endO); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }); + } + if (show) { Env.$container.show(); } else { @@ -835,9 +879,11 @@ define([ // Unselect comment when clicking outside $(window).click(function (e) { - if ($(e.target).closest('.cp-comment-container').length) { - return; - } + var $target = $(e.target); + if (!$target.length) { return; } + if ($target.is('.cp-comment-container')) { return; } + if ($target.closest('.cp-comment-container').length) { return; } + if ($target.closest('.ui-autocomplete').length) { return; } // Add comment button? don't remove anything because this handler is called after // the button action if (e.target.classList.contains('cke_button__comment_icon')) { return; } From 43fc0a37212edc98676446dcc662201b89a4129d Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 11:43:35 +0200 Subject: [PATCH 34/39] lint compliance --- www/pad/comments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 0ed0b0399..0d4f120a9 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -316,7 +316,7 @@ define([ var oldSelection = window.getSelection(); var oldRangeObj; if ($(oldSelection.anchorNode).closest('.cp-comment-form').length) { - oldRange = oldSelection.getRangeAt && oldSelection.getRangeAt(0); + var oldRange = oldSelection.getRangeAt && oldSelection.getRangeAt(0); oldRangeObj = { start: oldRange.startContainer, startO: oldRange.startOffset, From 5b337bef7d9aec8661da5eb08be308f3c0d44c3c Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 11:59:28 +0200 Subject: [PATCH 35/39] Add support for readonly and disconnected modes in comments --- customize.dist/src/less2/include/comments.less | 13 +++++++++++++ www/pad/comments.js | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 3b157d377..becc56f54 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -9,6 +9,19 @@ overflow-y: auto; color: @cryptpad_text_col; + &.cp-comments-readonly { + .cp-comment-actions { + display: none !important; + } + .cp-comment-form { + display: none !important; + } + .cp-comment-edit { + display: none !important; + } + + } + .buttons_main(); .cp-comment-reply { diff --git a/www/pad/comments.js b/www/pad/comments.js index 0d4f120a9..efb9f7b6e 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -875,6 +875,16 @@ define([ var Env = cfg; Env.comments = Util.clone(COMMENTS); + var ro = cfg.framework.isReadOnly(); + var onEditableChange = function (unlocked) { + Env.$container.removeClass('cp-comments-readonly'); + if (ro || !unlocked) { + Env.$container.addClass('cp-comments-readonly'); + } + }; + cfg.framework.onEditableChange(onEditableChange); + onEditableChange(); + addAddCommentHandler(Env); // Unselect comment when clicking outside From 977301dfc3090cd38fa8dc524d89fbbf448be4e4 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 12:16:34 +0200 Subject: [PATCH 36/39] Get the pad data from team drives when mentionned in a comment --- www/common/outer/async-store.js | 6 +++-- www/common/outer/mailbox-handlers.js | 33 +++++++++++++++++----------- www/common/outer/mailbox.js | 1 + 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 2841d8130..574b2584a 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -528,7 +528,7 @@ define([ /////////////////////// Store //////////////////////////////////// ////////////////////////////////////////////////////////////////// - var getAllStores = function () { + var getAllStores = Store.getAllStores = function () { var stores = [store]; var teamModule = store.modules['team']; if (teamModule) { @@ -2307,6 +2307,7 @@ define([ return; } store.mailbox = Mailbox.init({ + Store: Store, store: store, updateMetadata: function () { broadcast([], "UPDATE_METADATA"); @@ -2415,7 +2416,6 @@ define([ loadUniversal(Profile, 'profile', waitFor); loadUniversal(Team, 'team', waitFor); loadUniversal(History, 'history', waitFor); - loadMailbox(waitFor); cleanFriendRequests(); }).nThen(function () { var requestLogin = function () { @@ -2506,6 +2506,8 @@ define([ proxy.on('change', [Constants.tokenKey], function () { broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); }); + + loadMailbox(); }); }; diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 194d375d2..7889aa6e1 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -541,15 +541,18 @@ define([ var channel = content.channel; if (!channel) { return void cb(true); } - var res = ctx.store.manager.findChannel(channel); var title, href; - // Check if the pad is in our drive - res.some(function (obj) { - if (!obj.data) { return; } - href = obj.data.href || obj.data.roHref; - title = obj.data.filename || obj.data.title; - return true; + ctx.Store.getAllStores().some(function (s) { + var res = s.manager.findChannel(channel); + // Check if the pad is in our drive + return res.some(function (obj) { + if (!obj.data) { return; } + if (href && !obj.data.href) { return; } // We already have the VIEW url, we need EDIT + href = obj.data.href || obj.data.roHref; + title = obj.data.filename || obj.data.title; + if (obj.data.href) { return true; } // Abort only if we have the EDIT url + }); }); // If we don't have the edit url, ignore this notification @@ -597,12 +600,16 @@ define([ var res = ctx.store.manager.findChannel(channel); var title, href; - // Check if the pad is in our drive - res.some(function (obj) { - if (!obj.data) { return; } - href = obj.data.href || obj.data.roHref; - title = obj.data.filename || obj.data.title; - return true; + ctx.Store.getAllStores().some(function (s) { + var res = s.manager.findChannel(channel); + // Check if the pad is in our drive + return res.some(function (obj) { + if (!obj.data) { return; } + if (href && !obj.data.href) { return; } // We already have the VIEW url, we need EDIT + href = obj.data.href || obj.data.roHref; + title = obj.data.filename || obj.data.title; + if (obj.data.href) { return true; } // Abort only if we have the EDIT url + }); }); // Add the title diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index ce9bbe850..8e29333be 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -431,6 +431,7 @@ proxy.mailboxes = { var mailbox = {}; var store = cfg.store; var ctx = { + Store: cfg.Store, store: store, pinPads: cfg.pinPads, updateMetadata: cfg.updateMetadata, From 05f164a2e51333f95cc6d885561118756347839b Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 12:20:18 +0200 Subject: [PATCH 37/39] Fix issue with the addComment button in the toolbar --- www/pad/comments.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index efb9f7b6e..0a3eccf3e 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -896,7 +896,8 @@ define([ if ($target.closest('.ui-autocomplete').length) { return; } // Add comment button? don't remove anything because this handler is called after // the button action - if (e.target.classList.contains('cke_button__comment_icon')) { return; } + if ($target.is('.cke_button__comment')) { return; } + if ($target.closest('.cke_button__comment').length) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); Env.$inner.find('comment.active').removeClass('active'); Env.$container.find('.cp-comment-form').remove(); From 7c7bfe72d719a5997cc107739a0e880e136a71ef Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 13:30:08 +0200 Subject: [PATCH 38/39] Allow comments on mathjax --- www/pad/comment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/pad/comment.js b/www/pad/comment.js index b79be44f1..996b153dd 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -2,6 +2,9 @@ var CKEDITOR = window.CKEDITOR; function isUnstylable(el) { + if (el.hasClass('cke_widget_mathjax')) { + return false; + } if (el.hasClass('cke_widget_mediatag')) { return false; } From c26bbba73a3bb373d1589e723b0466e9fa056298 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 4 May 2020 13:34:48 +0200 Subject: [PATCH 39/39] lint compliance --- www/common/outer/mailbox-handlers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 7889aa6e1..290614718 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -597,7 +597,6 @@ define([ var channel = content.channel; if (!channel) { return void cb(true); } - var res = ctx.store.manager.findChannel(channel); var title, href; ctx.Store.getAllStores().some(function (s) {