From 7acbe4ba2c392342ca9a45f62db94ce067d0ec4d Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 8 Apr 2020 16:16:04 +0200 Subject: [PATCH 01/25] Comment plugin --- customize.dist/ckeditor-config.js | 2 +- www/pad/comment.js | 87 ++++++++++++++++++++++++++++++ www/pad/icons/comment.png | Bin 0 -> 3622 bytes www/pad/inner.js | 4 ++ 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 www/pad/comment.js create mode 100644 www/pad/icons/comment.png diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index 5c4940c52..c55f5fe99 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -10,7 +10,7 @@ CKEDITOR.editorConfig = function( config ) { // document itself and causes problems when it's sent across the wire and reflected back config.removePlugins= 'resize,elementspath'; config.resize_enabled= false; //bottom-bar - config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64,mathjax,wordcount'; + config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64,mathjax,wordcount,comments'; config.toolbarGroups= [ // {"name":"clipboard","groups":["clipboard","undo"]}, //{"name":"editing","groups":["find","selection"]}, diff --git a/www/pad/comment.js b/www/pad/comment.js new file mode 100644 index 000000000..1bd184139 --- /dev/null +++ b/www/pad/comment.js @@ -0,0 +1,87 @@ +(function () { + + function isUnstylable (el) { + console.log(el); + console.log(el.getAttribute('contentEditable')); + console.log(el.getAttribute('data-nostyle')); + var b = el.getAttribute( 'contentEditable' ) == 'false' || + el.getAttribute( 'data-nostyle' ); + console.log(b); + return b; + } + + CKEDITOR.plugins.add('comments', { + //requires: 'dialog,widget', + //icons: 'image', + //hidpi: true, + onLoad: function () { + + /* + CKEDITOR.addCss( + 'media-tag *{' + + 'width:100%; height:100%;' + + '}'); + */ + }, + init: function (editor) { + var pluginName = 'comment'; + var Messages = CKEDITOR._commentsTranslations; // XXX + var targetWidget; + + var styles = {}; + + var styleDef = { + element: 'comment', + attributes: { + 'data-uid': '#(uid)' + }, + overrides: [ { + element: 'comment' + } ], + childRule: isUnstylable + }; + + // Register the command. + var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); + editor.addCommand(pluginName, { + exec: function (editor, data) { + if (editor.readOnly) { return; } + editor.focus(); + editor.fire('saveSnapshot'); + // XXX call cryptpad code here + Object.keys(styles).forEach(function (id) { + editor.removeStyle(styles[id]); + }); + var uid = CKEDITOR.tools.getUniqueId(); + styles[uid] = new CKEDITOR.style(styleDef, { 'uid': uid }); + editor.applyStyle(styles[uid]); + + //editor.removeStyle(removeStyle); // XXX to remove comment on the selection + //editor.plugins.comments.addComment(); + // Save the undo snapshot after all changes are affected. + setTimeout( function() { + editor.fire('saveSnapshot'); + }, 0 ); + } + }); + + + // Register the toolbar button. + editor.ui.addButton && editor.ui.addButton('UnComment', { + label: 'UNCOMMENT', + command: 'uncomment', + toolbar: 'insert,10' + }); + editor.ui.addButton && editor.ui.addButton('Comment', { + label: 'COMMENT', + command: pluginName, + icon : '/pad/icons/comment.png', + toolbar: 'insert,10' + }); + }, + afterInit: function (editor) { + editor.plugins.comments.removeComment = function () {}; + } + }); + +})(); diff --git a/www/pad/icons/comment.png b/www/pad/icons/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..02570ded8a4176d5bd96121b5c6b0cb1af1e4cb2 GIT binary patch literal 3622 zcmbtX3sh5A7CkR{A&&qn(LqE-lYZ1#f6V94_fE^vNRoYa;M^Oes&N?TsMYA zY(Wnr`J;m5$F||1aO=F+=Lnl=#?=pgy4$M7xv%Gi1TNiH_pqTpxqhyvK*^oon=Puo zbv{e9V(H5>W4gM6#UH-NeLQogdfdXb!JgNP-(h!TBh>HtF(rKc8f=I>+|tIV_a{d; zyv0&5*EMAh1X%QUl`f@Y^pX_`^ z>(24GYT=vU37G+Mz80#7iMuLv`#9b3PeD~DjG~kvu@Wo&kh7Mi6#;9|&k#@}D$<++ znI&)tfe}}M9GEmEt>4G15bs6S3@Ib$1*jN->|@=dz{2pVCFh>hexwB7hy3CwYP%L) zsX6FcA(r!qD?5axSTmovhITDgTw9$87`5cZ~srCE;IfG0BXcB8_3;{AZm-QFzp{}I~sVQSU zaYi-D$#DqRX5kaFN~zP#4swdfW?cywAw=1Put`i7ieSI#!mg$y1SWO>I&+xQSF+5B zr+>H*nG%;L^q&NuH9cK?ll;iFmhw;3j)py--;;7)-8($1TaqG)sOLS|6$7vb4F4#B zx6h5AIsw)$TR|0J2CfQ+f1bS(pZ0p}6ES-rQXz`7`i&>DD+iWRv+&FfupEtnFt;)OpRa#ZThviiabF4ng>8$u*i zor^zer?e*~>!-woSomzzSp5*d;ft|kxO6h-0Z;?@E{k=`uXWLnw^M9M#D!W%&EVVB zw+Y^uxAvWagdMqUC5<0Y<71J<|c}e#rrL{XC}yGeX^S6fP(EuN;jW*VjeHvsN1i**Z$Wtb~ z!2{W~*?sO9w%brdgBEXZg!+FTgnLlJ|NKBoNo=&y5RYO{11DE(z<_^%fX{0=VSVDI ze={QOf)Dr>>U55Jj0Oq$&x=yIt8H`GJFWnCt^HHs*P)5G_er-&BpB;HK1VijJ_eJ- z+PAz{>@P$wOP{R~%V*GpfgrfH9mF?j=6AJSGku!=5ud3B~K-n<+v6k+2j=PZ^XOc$4u50V+ySH~V7M}tr zO0YIgKjlv|DBi#9J>)z;?K!{_FRUw2$2j6Q(*S>KEiKogk-th``SG_G7kJ1BLJC=?+2l?pE7W-))-`|L;0=d z2MMi#vqLG!T;OUyZ zPv+LJ;;)026G7<%g5GsTs!1A&6%8_frFL-A5g?^$h;7Ql^if$RVsyOihUOPoc+ZU_ z>(M_sQX-J6NgCJ8^W7Q2m)R}_{qR(Q+P2_^SH|F}XmVU`bYW0*_ocDGn%hP^V)T9^ zph@F(_?80$@T~_Xz2ooBOSuWtggqKw6=8|FQi3<^y8AK4n^{Fa_)flRmC9z>qOH#&A@OysTy&wS{50m7q=Z&+>DjKhVuOPFLgQkQtAJK(r|gFX4oH=sKAuHROzaZJ zh1@A2x22EH<7KW>kUZrp@0^Y|5>qN17XgP;ug?~Q%U3_NRvN+c4Z?C z)k*C;vo&@niy>h9D42RDef)=!9lMHGu3G6Kv6Au{oTMx>noEQHBOXngPf3_!!~327 zM|&27_cx`Qu|FjvqLydM>D<$jOc8L?xX#Nh$-E$`fLr)c`1OKYM*p&9=bkh-(C!k; znC6#s*c%<(XB?8YOm-HE;!93$k8Wz?V=pl*chs>UPUFE$g2bxCjUp;-46&d}zMnvE zwM{cs_~mCCAQC` z^2cCfg)sPm;Swkyxai3Uh<(XIg^}#R0pjVF2BZlZZUfSSO|?+WL`-Hg-*=pbmRqQN z%qcWk?Z%lKXLplqyA0=-AXB`QBfB8YJG42spbXFI^oU=KdRedcQ0*ByD!YdYyNzsF z0a&M*ROdek*mC3T=j`RNGBep-#^mHctoJS_x>9F|F(4D9;K`k=90Qi+y{O^=r80YP x`9e;Z2$A(XW(U-K0T14wGiPDt{art-)Gq9$hn(FTG1pFj=FM3ck{hJT_&3x6+}!{G literal 0 HcmV?d00001 diff --git a/www/pad/inner.js b/www/pad/inner.js index 632294f43..a5562af91 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -965,6 +965,7 @@ define([ }; Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js'); Ckeditor.plugins.addExternal('blockbase64','/pad/', 'disable-base64.js'); + Ckeditor.plugins.addExternal('comments','/pad/', 'comment.js'); Ckeditor.plugins.addExternal('wordcount','/pad/wordcount/', 'plugin.js'); module.ckeditor = editor = Ckeditor.replace('editor1', { customConfig: '/customize/ckeditor-config.js', @@ -973,6 +974,9 @@ define([ }).nThen(function () { editor.plugins.mediatag.import = function ($mt) { framework._.sfCommon.importMediaTag($mt); + }; + editor.plugins.comments.addComment = function (uid, cb) { + }; Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor}); }).nThen(function () { From e6af3476d912d5a3762cd0a598c0f187bf4d4ae5 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 20 Apr 2020 15:22:16 +0200 Subject: [PATCH 02/25] Upgrade ckeditor to 4.14 --- bower.json | 2 +- www/pad/inner.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 61183602b..ab99a55a8 100644 --- a/bower.json +++ b/bower.json @@ -21,7 +21,7 @@ "jquery": "2.2.4", "tweetnacl": "0.12.2", "components-font-awesome": "^4.6.3", - "ckeditor": "4.7.3", + "ckeditor": "4.14.0", "codemirror": "^5.19.0", "requirejs": "2.3.5", "marked": "0.5.0", diff --git a/www/pad/inner.js b/www/pad/inner.js index 88e6c8b96..591f546cf 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -445,11 +445,14 @@ define([ if ($faLink.length) { $html.find('iframe').contents().find('head').append($faLink.clone()); } - var ml = Ckeditor.instances.editor1.plugins.magicline.backdoor.that.line.$; + + var ml = editor._.magiclineBackdoor.that.line.$; [ml, ml.parentElement].forEach(function (el) { el.setAttribute('class', 'non-realtime'); }); + window.editor = editor; + var $iframe = $('html').find('iframe').contents(); var ifrWindow = $html.find('iframe')[0].contentWindow; From 3be884d70243c0397e17a9448150875bb34bbb46 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 20 Apr 2020 15:22:45 +0200 Subject: [PATCH 03/25] Comments prototype --- customize.dist/ckeditor-config.js | 2 - www/pad/app-pad.less | 3 + www/pad/comment.js | 64 ++++++++++++------ www/pad/comments.js | 105 ++++++++++++++++++++++++++++++ www/pad/inner.js | 49 ++++++++++++-- 5 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 www/pad/comments.js diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index 293c4b600..c55f5fe99 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -11,8 +11,6 @@ CKEDITOR.editorConfig = function( config ) { config.removePlugins= 'resize,elementspath'; config.resize_enabled= false; //bottom-bar config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64,mathjax,wordcount,comments'; - // FIXME translation for default? updating to a newer CKEditor seems like it will add 'default' by default - config.fontSize_sizes = '(Default)/unset;8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px'; config.toolbarGroups= [ // {"name":"clipboard","groups":["clipboard","undo"]}, //{"name":"editing","groups":["find","selection"]}, diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index ca8b10d18..3f6e0b455 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -24,6 +24,9 @@ body.cp-app-pad { align-items: center; padding: 4px; } + .cke_button__comment_label { + display: inline !important; + } } .cke_wysiwyg_frame { width: 100%; diff --git a/www/pad/comment.js b/www/pad/comment.js index 1bd184139..0a9e78db9 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -15,13 +15,8 @@ //icons: 'image', //hidpi: true, onLoad: function () { - - /* - CKEDITOR.addCss( - 'media-tag *{' + - 'width:100%; height:100%;' + - '}'); - */ + CKEDITOR.addCss('comment { background-color: rgba(252, 165, 3, 0.8); }' + + 'comment * { background-color: transparent !important; }'); }, init: function (editor) { var pluginName = 'comment'; @@ -41,31 +36,62 @@ childRule: isUnstylable }; +// XXX define default style +// XXX we can't uncomment if nothing has been added yet +// XXX "styles" is useless because not rebuilt on reload +// XXX and one style can remove all the other ones so no need to store all of them? + // Register the command. var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); editor.addCommand(pluginName, { exec: function (editor, data) { if (editor.readOnly) { return; } editor.focus(); - editor.fire('saveSnapshot'); - // XXX call cryptpad code here - Object.keys(styles).forEach(function (id) { - editor.removeStyle(styles[id]); - }); - var uid = CKEDITOR.tools.getUniqueId(); - styles[uid] = new CKEDITOR.style(styleDef, { 'uid': uid }); - editor.applyStyle(styles[uid]); - //editor.removeStyle(removeStyle); // XXX to remove comment on the selection - //editor.plugins.comments.addComment(); - // Save the undo snapshot after all changes are affected. + // If we're inside another comment, abort + var isComment = removeStyle.checkActive(editor.elementPath(), editor); + if (isComment) { return; } + + // We can't comment on empty text! + if (!editor.getSelection().getSelectedText()) { return; } + + var uid = CKEDITOR.tools.getUniqueId(); + editor.plugins.comments.addComment(uid, function () { + // XXX call cryptpad code here + editor.fire('saveSnapshot'); + editor.removeStyle(removeStyle); + /* + Object.keys(styles).forEach(function (id) { + editor.removeStyle(styles[id]); + }); + */ + styles[uid] = new CKEDITOR.style(styleDef, { 'uid': uid }); + editor.applyStyle(styles[uid]); + + //editor.removeStyle(removeStyle); // XXX to remove comment on the selection + //editor.plugins.comments.addComment(); + // Save the undo snapshot after all changes are affected. + setTimeout( function() { + editor.fire('saveSnapshot'); + }, 0 ); + }); + + } + }); + + // XXX Uncomment selection, remove on prod, only used for dev + editor.addCommand('uncomment', { + exec: function (editor, data) { + if (editor.readOnly) { return; } + editor.focus(); + editor.fire('saveSnapshot'); + editor.removeStyle(removeStyle); setTimeout( function() { editor.fire('saveSnapshot'); }, 0 ); } }); - // Register the toolbar button. editor.ui.addButton && editor.ui.addButton('UnComment', { label: 'UNCOMMENT', diff --git a/www/pad/comments.js b/www/pad/comments.js new file mode 100644 index 000000000..e930f65f5 --- /dev/null +++ b/www/pad/comments.js @@ -0,0 +1,105 @@ +define([ + 'json.sortify', + '/common/common-util.js', + '/common/common-interface.js', + '/customize/messages.js' +], function (Sortify, Util, UI, Messages) { + var Comments = {}; + + var COMMENTS = { + authors: {}, + messages: {} + }; + + // XXX function duplicated from www/code/markers.js + var authorUid = function (existing) { + if (!Array.isArray(existing)) { existing = []; } + var n; + var i = 0; + while (!n || existing.indexOf(n) !== -1 && i++ < 1000) { + n = Math.floor(Math.random() * 1000000); + } + // If we can't find a valid number in 1000 iterations, use 0... + if (existing.indexOf(n) !== -1) { n = 0; } + return n; + }; + var getAuthorId = function (Env, curve) { + var existing = Object.keys(Env.comments.authors || {}).map(Number); + if (!Env.common.isLoggedIn()) { return authorUid(existing); } + + var uid; + existing.some(function (id) { + var author = Env.comments.authors[id] || {}; + if (author.curvePublic !== curvePublic) { return; } + uid = Number(id); + return true; + }); + return uid || authorUid(existing); + }; + + var updateAuthorData = function (Env) { + var userData = Env.metadataMgr.getUserData(); + var myAuthorId = getAuthorId(Env, userData.curvePublic); + var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {}; + data.name = userData.name; + data.avatar = userData.avatar; + data.profile = userData.profile; + data.curvePublic = userData.curvePublic; + console.log(data); + return myAuthorId; + }; + + var onChange = function (Env) { + var md = Util.clone(Env.metadataMgr.getMetadata()); + Env.comments = md.comments; + if (!Env.comments) { Env.comments = Util.clone(COMMENTS); } + }; + + Comments.create = function (cfg) { + var Env = cfg; + Env.comments = Util.clone(COMMENTS); + + Env.editor.plugins.comments.addComment = function (uid, addMark) { + if (!Env.comments) { Env.comments = Util.clone(COMMENTS); } + + UI.prompt("Message", "", function (val) { // XXX + if (!val) { return; } + if (!editor.getSelection().getSelectedText()) { + // text has been deleted by another user while we were typing our comment? + return void UI.warn(Messages.error); + } + var myId = updateAuthorData(Env); + Env.comments.messages[uid] = { + user: myId, + time: +new Date(), + message: val + }; + var md = Util.clone(Env.metadataMgr.getMetadata()); + md.comments = Util.clone(Env.comments); + metadataMgr.updateMetadata(md); + + addMark(); + + Env.framework.localChange(); + }); + }; + + var call = function (f) { + return function () { + try { + [].unshift.call(arguments, Env); + return f.apply(null, arguments); + } catch (e) { + console.error(e); + } + }; + }; + + Env.metadataMgr.onChange(call(onChange)); + + return { + }; + }; + + return Comments; +}); diff --git a/www/pad/inner.js b/www/pad/inner.js index 591f546cf..68272f83a 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -25,6 +25,7 @@ define([ '/common/TypingTests.js', '/customize/messages.js', '/pad/links.js', + '/pad/comments.js', '/pad/export.js', '/pad/cursor.js', '/bower_components/nthen/index.js', @@ -51,6 +52,7 @@ define([ TypingTest, Messages, Links, + Comments, Exporter, Cursors, nThen, @@ -461,9 +463,9 @@ define([ framework._.sfCommon.addShortcuts(ifrWindow); - var privateData = framework._.sfCommon.getMetadataMgr().getPrivateData(); - var documentBody = ifrWindow.document.body; + var inner = window.inner = documentBody; + var $inner = $(inner); var observer = new MutationObserver(function (muts) { muts.forEach(function (mut) { @@ -483,8 +485,38 @@ define([ childList: true }); - var inner = window.inner = documentBody; - var $inner = $(inner); + var metadataMgr = framework._.sfCommon.getMetadataMgr(); + var privateData = metadataMgr.getPrivateData(); + var userData = metadataMgr.getUserData(); + var common = framework._.sfCommon; + + var comments = Comments.create({ + framework: framework, + metadataMgr: metadataMgr, + common: common, + editor: editor + }); + + editor.plugins.comments.addComment = function (uid, cb) { + if (!comments) { + comments = Util.clone(COMMENTS); + } + // XXX display input + UI.prompt("Message", "", function (val) { + if (!val) { return; } + if (!editor.getSelection().getSelectedText()) { + // text has been deleted by another user while we were typing our comment? + return void UI.warn(Messages.error); + } + var myId = updateAuthorData(); + comments.messages[uid] = { + user: myId, + time: +new Date(), + message: val + }; + cb(); + }); + }; var onLinkClicked = function (e) { var $target = $(e.target); @@ -650,6 +682,12 @@ define([ // off so that we don't end up with multiple identical handlers $links.off('click', openLink).on('click', openLink); } + + // XXX check comments + // new comments + // deleted comments + // check comment authors too + }); framework.setTextContentGetter(function () { @@ -997,9 +1035,6 @@ define([ }).nThen(function () { editor.plugins.mediatag.import = function ($mt) { framework._.sfCommon.importMediaTag($mt); - }; - editor.plugins.comments.addComment = function (uid, cb) { - }; Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor}); }).nThen(function () { From 9d03d0effb5d2acc1381408086fa15bc2ec2d109 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 21 Apr 2020 16:07:04 +0200 Subject: [PATCH 04/25] Update rich text app structure to add a comments container --- www/pad/app-pad.less | 51 +++++++++++++++++++++++++++++++--------- www/pad/comments.js | 4 ++-- www/pad/inner.html | 4 ++++ www/pad/inner.js | 56 ++++++++++---------------------------------- 4 files changed, 58 insertions(+), 57 deletions(-) diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 3f6e0b455..730517f74 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -7,10 +7,21 @@ body.cp-app-pad { @color: @colortheme_pad-color ); - #cke_1_top { - overflow: visible; - padding: 0px; + @bg-color: #e3e3e3; + + display: flex; + flex-flow: column; + max-height: 100%; + min-height: auto; + + #cp-app-pad-editor { + flex: 1; + display: flex; + flex-flow: row; + height: 100%; + overflow: hidden; } + .cke_toolbox_main { background-color: @colortheme_pad-toolbar-bg; .cke_toolbar { @@ -36,20 +47,38 @@ body.cp-app-pad { flex-flow: column; height: 100%; border: 0; + flex: 1; > .cke_inner { overflow: hidden; flex: 1; position: unset; display: flex; + flex-flow: column; margin-top: -1px; - #cke_1_contents { - flex: 1; - display: flex; - flex-flow: column; - height: auto !important; - iframe { - flex: 1; - } + } + #cke_1_top { + display: none; + } + } + #cke_1_contents { + flex: 1; + display: flex; + height: auto !important; + background-color: @bg-color; + justify-content: center; + iframe { + flex: 1; + min-width: 0; + } + #cp-app-pad-comments { + width: 400px; + background-color: white; + margin: 30px; + } + &.cke_body_width { + iframe { + margin: 0 30px; + max-width: 800px; } } } diff --git a/www/pad/comments.js b/www/pad/comments.js index e930f65f5..8c4119f92 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -30,7 +30,7 @@ define([ var uid; existing.some(function (id) { var author = Env.comments.authors[id] || {}; - if (author.curvePublic !== curvePublic) { return; } + if (author.curvePublic !== curve) { return; } uid = Number(id); return true; }); @@ -76,7 +76,7 @@ define([ }; var md = Util.clone(Env.metadataMgr.getMetadata()); md.comments = Util.clone(Env.comments); - metadataMgr.updateMetadata(md); + Env.metadataMgr.updateMetadata(md); addMark(); diff --git a/www/pad/inner.html b/www/pad/inner.html index 3e93b9028..76def97dc 100644 --- a/www/pad/inner.html +++ b/www/pad/inner.html @@ -42,7 +42,11 @@ +
+
+
+
diff --git a/www/pad/inner.js b/www/pad/inner.js index 3cbd7c1c9..b9b63a330 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -440,9 +440,8 @@ define([ var andThen2 = function (editor, Ckeditor, framework) { var mediaTagMap = {}; - var $bar = $('#cke_1_toolbox'); var $contentContainer = $('#cke_1_contents'); - var $html = $bar.closest('html'); + var $html = $('html'); var $faLink = $html.find('head link[href*="/bower_components/components-font-awesome/css/font-awesome.min.css"]'); if ($faLink.length) { $html.find('iframe').contents().find('head').append($faLink.clone()); @@ -494,30 +493,10 @@ define([ framework: framework, metadataMgr: metadataMgr, common: common, - editor: editor + editor: editor, + container: $('#cp-app-pad-comments')[0] }); - editor.plugins.comments.addComment = function (uid, cb) { - if (!comments) { - comments = Util.clone(COMMENTS); - } - // XXX display input - UI.prompt("Message", "", function (val) { - if (!val) { return; } - if (!editor.getSelection().getSelectedText()) { - // text has been deleted by another user while we were typing our comment? - return void UI.warn(Messages.error); - } - var myId = updateAuthorData(); - comments.messages[uid] = { - user: myId, - time: +new Date(), - message: val - }; - cb(); - }); - }; - var onLinkClicked = function (e) { var $target = $(e.target); if (!$target.is('a')) { return; } @@ -717,7 +696,6 @@ define([ return hjson; }); - $bar.find('#cke_1_toolbar_collapser').hide(); if (!framework.isReadOnly()) { addToolbarHideBtn(framework, $contentContainer); } else { @@ -776,7 +754,7 @@ define([ framework._.sfCommon.getAttribute(['pad', 'width'], function (err, data) { var active = data || typeof(data) === "undefined"; if (active) { - $iframe.find('html').addClass('cke_body_width'); + $contentContainer.addClass('cke_body_width'); } var $width = framework._.sfCommon.createButton('', true, { icon: 'fa-arrows-h', @@ -784,9 +762,9 @@ define([ name: "pad-width", },function () { if (active) { - $iframe.find('html').removeClass('cke_body_width'); + $contentContainer.removeClass('cke_body_width'); } else { - $iframe.find('html').addClass('cke_body_width'); + $contentContainer.addClass('cke_body_width'); } active = !active; var key = active ? Messages.pad_useFullWidth : Messages.pad_usePageWidth; @@ -959,8 +937,8 @@ define([ nThen(function (waitFor) { Framework.create({ - toolbarContainer: '#cke_1_toolbox', - contentContainer: '#cke_editor1 > .cke_inner', + toolbarContainer: '#cp-app-pad-toolbar', + contentContainer: '#cp-app-pad-editor', patchTransformer: ChainPad.NaiveJSONTransformer, /*thumbnail: { getContainer: function () { return $('iframe').contents().find('html')[0]; }, @@ -1004,14 +982,6 @@ define([ } // Used in ckeditor-config.js Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs; - var backColor = AppConfig.appBackgroundColor; - var newCss = '.cke_body_width { background: '+ backColor +'; height: 100%; overflow: auto;}' + - '.cke_body_width body {' + - 'max-width: 50em; padding: 20px 30px; margin: 0 auto; min-height: 100%;'+ - 'box-sizing: border-box; overflow: auto;'+ - '}' + - '.cke_body_width body > *:first-child { margin-top: 0; }'; - Ckeditor.addCss(newCss); Ckeditor._mediatagTranslations = { title: Messages.pad_mediatagTitle, width: Messages.pad_mediatagWidth, @@ -1037,13 +1007,11 @@ define([ Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor}); }).nThen(function () { // Move ckeditor parts to have a structure like the other apps - var $toolbarContainer = $('#cke_1_top'); var $contentContainer = $('#cke_1_contents'); - var $mainContainer = $('#cke_editor1'); - $contentContainer.prepend($toolbarContainer.find('.cke_toolbox_main')); - $mainContainer.prepend($toolbarContainer); - $contentContainer.find('.cke_toolbox_main').addClass('cke_reset_all'); - $toolbarContainer.removeClass('cke_reset_all'); + var $mainContainer = $('#cke_editor1 > .cke_inner'); + var $ckeToolbar = $('#cke_1_top').find('.cke_toolbox_main'); + $mainContainer.prepend($ckeToolbar.addClass('cke_reset_all')); + $contentContainer.append(h('div#cp-app-pad-comments')); }).nThen(waitFor()); }).nThen(function (/*waitFor*/) { From 31081eac462b084b1bc2331fad4c3ed5e02efd04 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 16:23:45 +0200 Subject: [PATCH 05/25] Add comments UI --- www/pad/app-pad.less | 6 +- www/pad/comment.js | 51 +++--- www/pad/comments.js | 371 +++++++++++++++++++++++++++++++++++++++++-- www/pad/inner.js | 15 +- 4 files changed, 402 insertions(+), 41 deletions(-) diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 730517f74..507ae0739 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -1,4 +1,5 @@ @import (reference) "../../customize/src/less2/include/framework.less"; +@import (reference) "../../customize/src/less2/include/comments.less"; body.cp-app-pad { .framework_main( @@ -71,9 +72,10 @@ body.cp-app-pad { min-width: 0; } #cp-app-pad-comments { - width: 400px; - background-color: white; + width: 300px; + //background-color: white; margin: 30px; + .comments_main(); } &.cke_body_width { iframe { diff --git a/www/pad/comment.js b/www/pad/comment.js index 0a9e78db9..884b3f749 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -36,14 +36,9 @@ childRule: isUnstylable }; -// XXX define default style -// XXX we can't uncomment if nothing has been added yet -// XXX "styles" is useless because not rebuilt on reload -// XXX and one style can remove all the other ones so no need to store all of them? - // Register the command. var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); - editor.addCommand(pluginName, { + editor.addCommand('comment', { exec: function (editor, data) { if (editor.readOnly) { return; } editor.focus(); @@ -57,19 +52,15 @@ var uid = CKEDITOR.tools.getUniqueId(); editor.plugins.comments.addComment(uid, function () { - // XXX call cryptpad code here + // Make an undo spnashot editor.fire('saveSnapshot'); + // Make sure comments won't overlap editor.removeStyle(removeStyle); - /* - Object.keys(styles).forEach(function (id) { - editor.removeStyle(styles[id]); - }); - */ - styles[uid] = new CKEDITOR.style(styleDef, { 'uid': uid }); - editor.applyStyle(styles[uid]); - //editor.removeStyle(removeStyle); // XXX to remove comment on the selection - //editor.plugins.comments.addComment(); + // Add the comment marker + var s = new CKEDITOR.style(styleDef, { 'uid': uid }); + editor.applyStyle(s); + // Save the undo snapshot after all changes are affected. setTimeout( function() { editor.fire('saveSnapshot'); @@ -79,13 +70,34 @@ } }); - // XXX Uncomment selection, remove on prod, only used for dev editor.addCommand('uncomment', { exec: function (editor, data) { if (editor.readOnly) { return; } - editor.focus(); editor.fire('saveSnapshot'); - editor.removeStyle(removeStyle); + if (!data || !data.id) { + // XXX Uncomment the selection, remove on prod, only used for dev + editor.focus(); + editor.removeStyle(removeStyle); + setTimeout( function() { + editor.fire('saveSnapshot'); + }, 0 ); + return; + } + // Uncomment provided element + + //Create style for this id + var style = new CKEDITOR.style({ + element: 'comment', + attributes: { + 'data-uid': data.id + }, + }); + // Create range for the entire document + var range = editor.createRange(); + range.selectNodeContents( editor.document.getBody() ); + // Remove style for the document + style.removeFromRange(range, editor); + setTimeout( function() { editor.fire('saveSnapshot'); }, 0 ); @@ -93,6 +105,7 @@ }); // Register the toolbar button. + // XXX Uncomment selection, remove on prod, only used for dev editor.ui.addButton && editor.ui.addButton('UnComment', { label: 'UNCOMMENT', command: 'uncomment', diff --git a/www/pad/comments.js b/www/pad/comments.js index 8c4119f92..2c1cd78a6 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -1,16 +1,43 @@ define([ 'json.sortify', '/common/common-util.js', + '/common/hyperscript.js', '/common/common-interface.js', '/customize/messages.js' -], function (Sortify, Util, UI, Messages) { +], function (Sortify, Util, h, UI, Messages) { var Comments = {}; +/* +{ + authors: { + "id": { + name: "", + curvePublic: "", + avatar: "", + profile: "" + } + }, + data: { + "uid": { + m: [{ + u: id, + m: "str", // comment + t: +new Date, + v: "str" // value of the commented content + }], + (deleted: undefined/true,) + } + } +} +*/ + var COMMENTS = { authors: {}, - messages: {} + data: {} }; + var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; + // XXX function duplicated from www/code/markers.js var authorUid = function (existing) { if (!Array.isArray(existing)) { existing = []; } @@ -49,40 +76,353 @@ define([ return myAuthorId; }; + var updateMetadata = function (Env) { + var md = Util.clone(Env.metadataMgr.getMetadata()); + md.comments = Util.clone(Env.comments); + Env.metadataMgr.updateMetadata(md); + }; + + 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'); + Env.common.displayAvatar($(avatar), userData.avatar, name); + + var cancel = h('button.btn.btn-cancel', [ + h('i.fa.fa-times'), + Messages.cancel + ]); + var submit = h('button.btn.btn-primary', [ + h('i.fa.fa-paper-plane-o'), + Messages.comments_submit + ]); + + var done = false; + + $(submit).click(function (e) { + e.stopPropagation(); + cb(textarea.value); + }); + $(cancel).click(function (e) { + e.stopPropagation(); + cb(); + }); + + $(textarea).keydown(function (e) { + if (e.which === 27) { + $(cancel).click(); + } + if (e.which === 13 && e.ctrlKey) { + $(submit).click(); + } + }); + + return h('div.cp-comment-form' + (reply ? '.cp-comment-reply' : ''), [ + h('div.cp-comment-form-input', [ + avatar, + textarea + ]), + h('div.cp-comment-form-actions', [ + cancel, + submit + ]) + ]); + }; + + 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; + + // XXX don't wipe inputs? + + var $oldInput = Env.$container.find('.cp-comment-form'); + if ($oldInput.length !== 1) { $oldInput = undefined; } + + Env.$container.html(''); + + if ($oldInput && !$oldInput.attr('data-uid')) { + Env.$container.append($oldInput); + } + + var order = Env.$inner.find('comment').map(function (i, el) { + return el.getAttribute('data-uid'); + }).toArray(); + var done = []; + + + var show = false; + order.forEach(function (key) { + // Avoir duplicates + if (done.indexOf(key) !== -1) { return; } + done.push(key); + + var obj = Env.comments.data[key]; + if (!obj || obj.deleted || !Array.isArray(obj.m) || !obj.m.length) { + return; + } + show = true; + + var content = []; + obj.m.forEach(function (msg, i) { + var author = (Env.comments.authors || {})[msg.u] || {}; + var name = Util.fixHTML(author.name || Messages.anonymous); + var date = new Date(msg.t); + var avatar = h('span.cp-avatar'); + Env.common.displayAvatar($(avatar), author.avatar, name); + + content.push(h('div.cp-comment'+(i === 0 ? '' : '.cp-comment-reply'), [ + h('div.cp-comment-header', [ + avatar, + h('span.cp-comment-metadata', [ + h('span.cp-comment-author', name), + h('span.cp-comment-time', date.toLocaleString()) + ]) + ]), + h('div.cp-comment-content', [ + msg.m + ]) + ])); + + }); + + var reply = h('button.btn.btn-secondary', [ + h('i.fa.fa-reply'), + Messages.comments_reply + ]); + var resolve = h('button.btn.btn-primary', [ + h('i.fa.fa-check'), + Messages.comments_resolve + ]); + + var actions; + content.push(actions = h('div.cp-comment-actions', [ + reply, + resolve + ])); + var $actions = $(actions); + + var div; + Env.$container.append(div = h('div.cp-comment-container', { + 'data-uid': key, + tabindex: 1 + }, content)); + var $div = $(div); + + $(reply).click(function (e) { + e.stopPropagation(); + $actions.hide(); + var form = getCommentForm(Env, true, function (val) { + $(form).remove(); + $actions.css('display', ''); + if (!val) { return; } + var obj = Env.comments.data[key]; + if (!obj || !Array.isArray(obj.m)) { return; } + + // Get the value of the commented text + var res = Env.$inner.find('comment[data-uid="'+key+'"]').toArray(); + var value = res.map(function (el) { + return el.innerText; + }).join('\n'); + + // Push the reply + var myId = updateAuthorData(Env); + obj.m.push({ + u: myId, + t: +new Date(), + m: val, + v: value + }); + + // Send to chainpad + updateMetadata(Env); + Env.framework.localChange(); + }); + $div.append(form); + }); + + UI.confirmButton(resolve, { + classes: 'btn-danger-alt' + }, function () { + // Delete the comment + delete Env.comments.data[key]; + + // Send to chainpad + updateMetadata(Env); + Env.framework.localChange(); + }); + + $div.click(function () { + Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); + $div.addClass('cp-comment-active'); + $actions.css('display', ''); + Env.$container.find('.cp-comment-form').remove(); + // XXX highlight (and scroll to) the comment in the doc? + }); + }); + + if (show) { + Env.$container.show(); + } else { + Env.$container.hide(); + } + }; + var onChange = function (Env) { var md = Util.clone(Env.metadataMgr.getMetadata()); Env.comments = md.comments; - if (!Env.comments) { Env.comments = Util.clone(COMMENTS); } + if (!Env.comments || !Env.comments.data) { Env.comments = Util.clone(COMMENTS); } + if (Env.ready === 0) { + Env.ready = true; + } + redrawComments(Env); }; - Comments.create = function (cfg) { - var Env = cfg; - Env.comments = Util.clone(COMMENTS); + // Check if comments have been deleted from the document but not from metadata + var checkDeleted = function (Env) { + if (!Env.comments || !Env.comments.data) { return; } + // 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; + }); + + var changed = false; + + // Get the comments from the document + var uids = Env.$inner.find('comment').map(function (i, el) { + var id = el.getAttribute('data-uid'); + // Empty comment: remove from dom + if (!el.innerText && el.parentElement) { + el.parentElement.removeChild(el); + changed = true; + return; + } + // Comment not in the metadata: uncomment (probably an undo) + if (comments.indexOf(id) === -1) { + console.error(id, el); + Env.editor.execCommand('uncomment', {id:id}); + changed = true; + return; + } + return id; + }).toArray(); + + // Check if a comment has been deleted + comments.forEach(function (uid) { + if (uids.indexOf(uid) !== -1) { return; } + // comment has been deleted + var data = Env.comments.data[uid]; + if (!data) { return; } + //data.deleted = true; + delete Env.comments.data[uid]; + changed = true; + }); + + if (changed) { + updateMetadata(Env); + } + }; + + var addAddCommentHandler = function (Env) { Env.editor.plugins.comments.addComment = function (uid, addMark) { if (!Env.comments) { Env.comments = Util.clone(COMMENTS); } - UI.prompt("Message", "", function (val) { // XXX + // Get all comments ID contained within the selection + var sel = Env.editor.getSelectedHtml().$.querySelectorAll('comment'); + if (sel.length) { + // Abort if our selection contains a comment + console.error("Your selection contains a comment"); + UI.warn(Messages.error); + // XXX show error + return; + } + +/* +sel.forEach(function (el) { + // For each comment ID, check if the comment will be deleted + // if we add a comment on our selection + var id = el.getAttribute('data-uid'); + + // Get all nodes for this comment + var all = Env.$inner.find('comment[data-uid="'+id+'"]'); + // Get our selection + var sel = Env.ifrWindow.getSelection(); + if (!sel.containsNode) { + // IE doesn't support this method, always allow comments for them... + sel.containsNode = function () { return false; }; + } + + var notDeleted = all.some(function (i, el) { + // If this node is completely outside of the selection, continue + if (!sel.containsNode(el, true)) { return true; } + }); + + // only continue if notDeleted is true (at least one node for + // this comment won't be deleted) +}); +*/ + Env.$container.find('.cp-comment-form').remove(); + var form = getCommentForm(Env, false, function (val) { + $(form).remove(); if (!val) { return; } if (!editor.getSelection().getSelectedText()) { // text has been deleted by another user while we were typing our comment? return void UI.warn(Messages.error); } var myId = updateAuthorData(Env); - Env.comments.messages[uid] = { - user: myId, - time: +new Date(), - message: val + Env.comments.data[uid] = { + m: [{ + u: myId, + t: +new Date(), + m: val, + v: canonicalize(editor.getSelection().getSelectedText()) + }] }; - var md = Util.clone(Env.metadataMgr.getMetadata()); - md.comments = Util.clone(Env.comments); - Env.metadataMgr.updateMetadata(md); + updateMetadata(Env); addMark(); Env.framework.localChange(); }); + Env.$container.prepend(form); }; + }; + + var onContentUpdate = function (Env) { + if (!Env.ready) { return; } + // Check deleted + checkDeleted(Env); + }; + var localChange = function (Env) { + if (!Env.ready) { return; } + // Check deleted + checkDeleted(Env); + }; + + var ready = function (Env) { + Env.ready = 0; + }; + + Comments.create = function (cfg) { + var Env = cfg; + Env.comments = Util.clone(COMMENTS); + + addAddCommentHandler(Env); + + $(window).click(function (e) { + if ($(e.target).closest('.cp-comment-container').length) { + return; + } + Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); + }); var call = function (f) { return function () { @@ -98,6 +438,9 @@ define([ Env.metadataMgr.onChange(call(onChange)); return { + onContentUpdate: call(onContentUpdate), + localChange: call(localChange), + ready: call(ready) }; }; diff --git a/www/pad/inner.js b/www/pad/inner.js index b9b63a330..09161cea1 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -494,7 +494,9 @@ define([ metadataMgr: metadataMgr, common: common, editor: editor, - container: $('#cp-app-pad-comments')[0] + ifrWindow: ifrWindow, + $inner: $inner, + $container: $('#cp-app-pad-comments') }); var onLinkClicked = function (e) { @@ -662,11 +664,7 @@ define([ $links.off('click', openLink).on('click', openLink); } - // XXX check comments - // new comments - // deleted comments - // check comment authors too - + comments.onContentUpdate(); }); framework.setTextContentGetter(function () { @@ -689,6 +687,8 @@ define([ // the text nodes and OT/ChainPad would freak out cursors.removeCursors(inner); + comments.onContentUpdate(); + displayMediaTags(framework, inner, mediaTagMap); inner.normalize(); var hjson = Hyperjson.fromDOM(inner, shouldSerialize, hjsonFilters); @@ -805,6 +805,9 @@ define([ }); } }); + + comments.ready(); + /*setTimeout(function () { $('iframe.cke_wysiwyg_frame').focus(); editor.focus(); From e30c7f3062e99e698e3b4c12f561ed865f3ebe5f Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 16:25:43 +0200 Subject: [PATCH 06/25] Add new less file --- .../src/less2/include/comments.less | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 customize.dist/src/less2/include/comments.less diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less new file mode 100644 index 000000000..375f86c3e --- /dev/null +++ b/customize.dist/src/less2/include/comments.less @@ -0,0 +1,95 @@ +@import (reference) "./colortheme-all.less"; +@import (reference) "./variables.less"; +@import (reference) "./avatar.less"; +@import (reference) "./buttons.less"; +@import (reference) "./tools.less"; + +.comments_main() { + @data-color: #888; + overflow-y: auto; + + .buttons_main(); + + .cp-comment-reply { + margin-left: 40px; + } + + + .cp-comment-form { + &:not(:last-child) { + margin-bottom: 10px; + } + } + .cp-comment-form-input { + .avatar_main(40px); + display: flex; + align-items: flex-start; + textarea { + flex: 1; + height: 50px; + padding: 2px 8px; + } + margin-bottom: 5px; + } + .cp-comment-form-actions { + text-align: right; + button:not(:last-child) { + margin-right: 10px; + } + } + + + .cp-comment-container { + outline: none; + &:not(:focus) { + cursor: pointer; + .tools_unselectable(); + } + &:not(:last-child) { + margin-bottom: 10px; + } + .cp-comment-form { + margin-top: 5px; + } + } + .cp-comment { + &:not(:first-child) { + margin-top: 5px; + } + } + .cp-comment-header { + height: 40px; + align-items: center; + display: flex; + background-color: white; + .avatar_main(40px); + .cp-comment-metadata { + flex: 1; + display: flex; + flex-flow: column; + margin-left: 5px; + .cp-comment-time { + font-size: 13px; + color: @data-color; + } + } + } + .cp-comment-content { + background-color: white; + padding: 10px 5px 5px; + } + .cp-comment-actions { + display: none; + text-align: right; + margin-top: 5px; + button:not(:last-child) { + margin-right: 10px; + } + } + .cp-comment-active { + .cp-comment-actions { + display: block; + } + } +} + From 96948ed55b18a06796a07b2e51b894ce7ba209f3 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 16:35:28 +0200 Subject: [PATCH 07/25] Fix comments section hidden when adding first comment --- www/pad/comments.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 2c1cd78a6..de3a0d385 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -148,7 +148,10 @@ define([ Env.$container.html(''); + var show = false; + if ($oldInput && !$oldInput.attr('data-uid')) { + show = true; Env.$container.append($oldInput); } @@ -158,7 +161,6 @@ define([ var done = []; - var show = false; order.forEach(function (key) { // Avoir duplicates if (done.indexOf(key) !== -1) { return; } @@ -278,9 +280,17 @@ define([ var onChange = function (Env) { var md = Util.clone(Env.metadataMgr.getMetadata()); Env.comments = md.comments; - if (!Env.comments || !Env.comments.data) { Env.comments = Util.clone(COMMENTS); } + var changed = false; + if (!Env.comments || !Env.comments.data) { + changed = true; + Env.comments = Util.clone(COMMENTS); + } if (Env.ready === 0) { Env.ready = true; + if (changed) { + updateMetadata(Env); + Env.framework.localChange(); + } } redrawComments(Env); }; @@ -392,7 +402,7 @@ sel.forEach(function (el) { Env.framework.localChange(); }); - Env.$container.prepend(form); + Env.$container.prepend(form).show();; }; }; From 97f41edd94c47fe5962f49e3c80a331dd122fa4d Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 16:46:36 +0200 Subject: [PATCH 08/25] Fix comments replies --- www/pad/comments.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/pad/comments.js b/www/pad/comments.js index de3a0d385..62ec2448b 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -262,6 +262,7 @@ define([ }); $div.click(function () { + if ($div.hasClass('cp-comment-active')) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); $div.addClass('cp-comment-active'); $actions.css('display', ''); From 0a72ed2977ac92557303cb6033696922e1e7e716 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 18:43:12 +0200 Subject: [PATCH 09/25] Fix cursor recovery in pad --- www/common/cursor-treesome.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/www/common/cursor-treesome.js b/www/common/cursor-treesome.js index c3722e8a3..29cb25646 100644 --- a/www/common/cursor-treesome.js +++ b/www/common/cursor-treesome.js @@ -3,7 +3,7 @@ define([], function () { var indexOfNode = tree.indexOfNode = function (el) { if (!(el && el.parentNode)) { - console.log("No parentNode found!"); + console.log("No parentNode found!", el); throw new Error('No parentNode found!'); } return Array.prototype.indexOf.call(el.parentNode.childNodes, el); @@ -26,6 +26,7 @@ define([], function () { leaf nodes of a tree */ var rightmostNode = tree.rightmostNode = function (el) { + if (!el) { return null; } var childNodeCount = childCount(el); if (!childNodeCount) { // no children return el; // return the element @@ -53,7 +54,7 @@ define([], function () { if (!el.parentNode) { return null; } if (i === 0) { if (root && el.parentNode === root.childNodes[0]) { return null; } - return rightmostNode(previousNode(el.parentNode)); + return rightmostNode(previousNode(el.parentNode, root)); } else { return rightmostNode(el.parentNode.childNodes[i-1]); } @@ -77,6 +78,10 @@ define([], function () { // a and b might be the same element if (a === b) { return 0; } + // If we're selecting an entire node containing a single text node, + // b can be the child of a. Order is correct. + if (b.parentNode && b.parentNode === a) { return 1; } + var cur = b; while (cur) { cur = previousNode(cur, root); From 17d78a3831c4cb73b4597c30185c447f20f95c5f Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 22 Apr 2020 18:48:49 +0200 Subject: [PATCH 10/25] Add support for comments with multiple users --- .../src/less2/include/comments.less | 1 + www/pad/comment.js | 41 +++++++++++-------- www/pad/comments.js | 18 ++++++-- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 375f86c3e..a0412700e 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -77,6 +77,7 @@ .cp-comment-content { background-color: white; padding: 10px 5px 5px; + display: pre-wrap; } .cp-comment-actions { display: none; diff --git a/www/pad/comment.js b/www/pad/comment.js index 884b3f749..914c8f53e 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -70,6 +70,29 @@ } }); + // Uncomment provided element + editor.plugins.comments.uncomment = function (id) { + if (editor.readOnly) { return; } + editor.fire('saveSnapshot'); + + //Create style for this id + var style = new CKEDITOR.style({ + element: 'comment', + attributes: { + 'data-uid': id + }, + }); + // Create range for the entire document + var range = editor.createRange(); + range.selectNodeContents( editor.document.getBody() ); + // Remove style for the document + style.removeFromRange(range, editor); + + setTimeout( function() { + editor.fire('saveSnapshot'); + }, 0 ); + }; + editor.addCommand('uncomment', { exec: function (editor, data) { if (editor.readOnly) { return; } @@ -83,24 +106,6 @@ }, 0 ); return; } - // Uncomment provided element - - //Create style for this id - var style = new CKEDITOR.style({ - element: 'comment', - attributes: { - 'data-uid': data.id - }, - }); - // Create range for the entire document - var range = editor.createRange(); - range.selectNodeContents( editor.document.getBody() ); - // Remove style for the document - style.removeFromRange(range, editor); - - setTimeout( function() { - editor.fire('saveSnapshot'); - }, 0 ); } }); diff --git a/www/pad/comments.js b/www/pad/comments.js index 62ec2448b..331cf6498 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -118,11 +118,16 @@ define([ if (e.which === 27) { $(cancel).click(); } - if (e.which === 13 && e.ctrlKey) { + if (e.which === 13 && !e.shiftKey) { $(submit).click(); + e.preventDefault(); } }); + setTimeout(function () { + $(textarea).focus(); + }); + return h('div.cp-comment-form' + (reply ? '.cp-comment-reply' : ''), [ h('div.cp-comment-form-input', [ avatar, @@ -300,6 +305,11 @@ define([ var checkDeleted = function (Env) { if (!Env.comments || !Env.comments.data) { return; } + // Don't recheck if there were no change + var str = Env.$inner[0].innerHTML; + if (str === Env.oldCheck) { return; } + Env.oldCheck = str; + // 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; @@ -318,8 +328,7 @@ define([ } // Comment not in the metadata: uncomment (probably an undo) if (comments.indexOf(id) === -1) { - console.error(id, el); - Env.editor.execCommand('uncomment', {id:id}); + Env.editor.plugins.comments.uncomment(id); changed = true; return; } @@ -383,6 +392,8 @@ sel.forEach(function (el) { Env.$container.find('.cp-comment-form').remove(); var form = getCommentForm(Env, false, function (val) { $(form).remove(); + Env.$inner.focus(); + if (!val) { return; } if (!editor.getSelection().getSelectedText()) { // text has been deleted by another user while we were typing our comment? @@ -410,6 +421,7 @@ sel.forEach(function (el) { var onContentUpdate = function (Env) { if (!Env.ready) { return; } // Check deleted + onChange(Env); checkDeleted(Env); }; var localChange = function (Env) { From 34768261b01e1fe01d4ead16da7666af690ed828 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 10:56:03 +0200 Subject: [PATCH 11/25] Recover previous comment input on redraw --- www/pad/comments.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/www/pad/comments.js b/www/pad/comments.js index 331cf6498..b5a680f0a 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -128,7 +128,9 @@ define([ $(textarea).focus(); }); - return h('div.cp-comment-form' + (reply ? '.cp-comment-reply' : ''), [ + return h('div.cp-comment-form' + (reply ? '.cp-comment-reply' : ''), { + 'data-uid': reply || undefined + }, [ h('div.cp-comment-form-input', [ avatar, textarea @@ -146,9 +148,7 @@ define([ if (str === Env.oldComments) { return; } Env.oldComments = str; - // XXX don't wipe inputs? - - var $oldInput = Env.$container.find('.cp-comment-form'); + var $oldInput = Env.$container.find('.cp-comment-form').detach(); if ($oldInput.length !== 1) { $oldInput = undefined; } Env.$container.html(''); @@ -226,9 +226,11 @@ define([ $(reply).click(function (e) { e.stopPropagation(); $actions.hide(); - var form = getCommentForm(Env, true, function (val) { + var form = getCommentForm(Env, key, function (val) { $(form).remove(); - $actions.css('display', ''); + $(form).closest('.cp-comment-container') + .find('.cp-comment-actions').css('display', ''); + if (!val) { return; } var obj = Env.comments.data[key]; if (!obj || !Array.isArray(obj.m)) { return; } @@ -260,6 +262,7 @@ define([ }, function () { // Delete the comment delete Env.comments.data[key]; + Env.editor.plugins.comments.uncomment(key); // Send to chainpad updateMetadata(Env); @@ -274,6 +277,13 @@ define([ Env.$container.find('.cp-comment-form').remove(); // XXX highlight (and scroll to) the comment in the doc? }); + + if ($oldInput && $oldInput.attr('data-uid') === key) { + $div.addClass('cp-comment-active'); + $actions.hide(); + $div.append($oldInput); + $oldInput.find('textarea').focus(); + } }); if (show) { @@ -399,6 +409,9 @@ sel.forEach(function (el) { // text has been deleted by another user while we were typing our comment? return void UI.warn(Messages.error); } + // Don't override existing data + if (Env.comments.data[uid]) { return; } + var myId = updateAuthorData(Env); Env.comments.data[uid] = { m: [{ From 70545c64fc0c04a2296a58d7cca8f98768449883 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 11:32:57 +0200 Subject: [PATCH 12/25] Fix sort issue in the drive --- www/common/drive-ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 899405fa1..6638e02db 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2842,7 +2842,7 @@ define([ var _a = props[a]; var _b = props[b]; if (_a < _b) { return mult * -1; } - if (_b > _a) { return mult; } + if (_b < _a) { return mult; } return 0; }); return keys; From c2f26775d8a062118bc024611aac9dc08a00e500 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 13:47:03 +0200 Subject: [PATCH 13/25] Visual link between comments and commented content --- .../src/less2/include/comments.less | 11 ++++-- www/pad/comment.js | 37 ++++++++++++------- www/pad/comments.js | 23 ++++++++++-- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index a0412700e..3ec5de7f7 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -45,12 +45,13 @@ cursor: pointer; .tools_unselectable(); } - &:not(:last-child) { - margin-bottom: 10px; - } + //&:not(:last-child) { + // margin-bottom: 10px; + //} .cp-comment-form { margin-top: 5px; } + padding: 5px; } .cp-comment { &:not(:first-child) { @@ -77,7 +78,8 @@ .cp-comment-content { background-color: white; padding: 10px 5px 5px; - display: pre-wrap; + white-space: pre-wrap; + word-break: break-all; } .cp-comment-actions { display: none; @@ -88,6 +90,7 @@ } } .cp-comment-active { + background-color: rgba(0,0,0,0.2); .cp-comment-actions { display: block; } diff --git a/www/pad/comment.js b/www/pad/comment.js index 914c8f53e..7dcd162fb 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -1,21 +1,23 @@ (function () { + var CKEDITOR = window.CKEDITOR; function isUnstylable (el) { - console.log(el); - console.log(el.getAttribute('contentEditable')); - console.log(el.getAttribute('data-nostyle')); - var b = el.getAttribute( 'contentEditable' ) == 'false' || + var b = el.getAttribute( 'contentEditable' ) === 'false' || el.getAttribute( 'data-nostyle' ); - console.log(b); return b; } + var color1 = 'rgba(252, 165, 3, 0.8);'; + var color2 = 'rgba(252, 231, 3, 0.8);'; + CKEDITOR.plugins.add('comments', { //requires: 'dialog,widget', //icons: 'image', //hidpi: true, onLoad: function () { - CKEDITOR.addCss('comment { background-color: rgba(252, 165, 3, 0.8); }' + + CKEDITOR.addCss('comment { background-color: '+color1+' }' + + '@keyframes color { 0% { background-color: '+color2+' } 50% { background-color: '+color1+' } 100% { background-color: '+color2+' } }' + + 'comment:focus { animation-name: color; animation-duration: 1s; animation-iteration-count: 2; background-color: '+color2+' outline: none;}' + 'comment * { background-color: transparent !important; }'); }, init: function (editor) { @@ -28,7 +30,8 @@ var styleDef = { element: 'comment', attributes: { - 'data-uid': '#(uid)' + 'data-uid': '#(uid)', + 'tabindex': '1' }, overrides: [ { element: 'comment' @@ -71,7 +74,7 @@ }); // Uncomment provided element - editor.plugins.comments.uncomment = function (id) { + editor.plugins.comments.uncomment = function (id, els) { if (editor.readOnly) { return; } editor.fire('saveSnapshot'); @@ -79,14 +82,20 @@ var style = new CKEDITOR.style({ element: 'comment', attributes: { - 'data-uid': id + 'data-uid': id, + 'tabindex': '1' }, }); - // Create range for the entire document - var range = editor.createRange(); - range.selectNodeContents( editor.document.getBody() ); - // Remove style for the document - style.removeFromRange(range, editor); + style.alwaysRemoveElement = true; + els.forEach(function (el) { + // Create range for the entire document + var node = new CKEDITOR.dom.node(el); + var range = editor.createRange(); + range.setStart(node, 0); + range.setEnd(node, Number.MAX_SAFE_INTEGER); + // Remove style for the document + style.removeFromRange(range, editor); + }); setTimeout( function() { editor.fire('saveSnapshot'); diff --git a/www/pad/comments.js b/www/pad/comments.js index b5a680f0a..ea2a65f3a 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -1,10 +1,11 @@ define([ + 'jquery', 'json.sortify', '/common/common-util.js', '/common/hyperscript.js', '/common/common-interface.js', '/customize/messages.js' -], function (Sortify, Util, h, UI, Messages) { +], function ($, Sortify, Util, h, UI, Messages) { var Comments = {}; /* @@ -262,20 +263,27 @@ define([ }, function () { // Delete the comment delete Env.comments.data[key]; - Env.editor.plugins.comments.uncomment(key); + var els = Env.$inner.find('comment[data-uid="'+key+'"]').toArray(); + Env.editor.plugins.comments.uncomment(key, els); // Send to chainpad updateMetadata(Env); Env.framework.localChange(); }); + var focusContent = function () { + Env.$inner.find('comment[data-uid="'+key+'"]').focus(); + }; + $div.click(function () { 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(); - // XXX highlight (and scroll to) the comment in the doc? + + focusContent(); }); if ($oldInput && $oldInput.attr('data-uid') === key) { @@ -283,6 +291,7 @@ define([ $actions.hide(); $div.append($oldInput); $oldInput.find('textarea').focus(); + focusContent(); } }); @@ -338,7 +347,7 @@ define([ } // Comment not in the metadata: uncomment (probably an undo) if (comments.indexOf(id) === -1) { - Env.editor.plugins.comments.uncomment(id); + Env.editor.plugins.comments.uncomment(id, [el]); changed = true; return; } @@ -459,6 +468,12 @@ sel.forEach(function (el) { } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); }); + Env.$inner.on('click', 'comment', function (e) { + var $comment = $(e.target); + var uid = $comment.attr('data-uid'); + if (!uid) { return; } + Env.$container.find('.cp-comment-container[data-uid="'+uid+'"]').click(); + }); var call = function (f) { return function () { From 448edbf972031e40326d56338685741b920c7cf8 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 13:52:21 +0200 Subject: [PATCH 14/25] lint compliance --- .../src/less2/include/comments.less | 2 +- www/pad/comment.js | 35 +++++++++---------- www/pad/comments.js | 8 ++--- www/pad/inner.js | 1 - 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 3ec5de7f7..b217cdae1 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -16,7 +16,7 @@ .cp-comment-form { - &:not(:last-child) { + &:not(:last-child) { margin-bottom: 10px; } } diff --git a/www/pad/comment.js b/www/pad/comment.js index 7dcd162fb..0adeb14f2 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -22,10 +22,6 @@ }, init: function (editor) { var pluginName = 'comment'; - var Messages = CKEDITOR._commentsTranslations; // XXX - var targetWidget; - - var styles = {}; var styleDef = { element: 'comment', @@ -42,7 +38,7 @@ // Register the command. var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); editor.addCommand('comment', { - exec: function (editor, data) { + exec: function (editor) { if (editor.readOnly) { return; } editor.focus(); @@ -120,20 +116,21 @@ // Register the toolbar button. // XXX Uncomment selection, remove on prod, only used for dev - editor.ui.addButton && editor.ui.addButton('UnComment', { - label: 'UNCOMMENT', - command: 'uncomment', - toolbar: 'insert,10' - }); - editor.ui.addButton && editor.ui.addButton('Comment', { - label: 'COMMENT', - command: pluginName, - icon : '/pad/icons/comment.png', - toolbar: 'insert,10' - }); - }, - afterInit: function (editor) { - editor.plugins.comments.removeComment = function () {}; + if (editor.ui.addButton) { + editor.ui.addButton('UnComment', { + label: 'UNCOMMENT', + command: 'uncomment', + toolbar: 'insert,10' + }); + } + if (editor.ui.addButton) { + editor.ui.addButton('Comment', { + label: 'COMMENT', + command: pluginName, + icon : '/pad/icons/comment.png', + toolbar: 'insert,10' + }); + } } }); diff --git a/www/pad/comments.js b/www/pad/comments.js index ea2a65f3a..4c0598272 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -104,8 +104,6 @@ define([ Messages.comments_submit ]); - var done = false; - $(submit).click(function (e) { e.stopPropagation(); cb(textarea.value); @@ -414,7 +412,7 @@ sel.forEach(function (el) { Env.$inner.focus(); if (!val) { return; } - if (!editor.getSelection().getSelectedText()) { + if (!Env.editor.getSelection().getSelectedText()) { // text has been deleted by another user while we were typing our comment? return void UI.warn(Messages.error); } @@ -427,7 +425,7 @@ sel.forEach(function (el) { u: myId, t: +new Date(), m: val, - v: canonicalize(editor.getSelection().getSelectedText()) + v: canonicalize(Env.editor.getSelection().getSelectedText()) }] }; updateMetadata(Env); @@ -436,7 +434,7 @@ sel.forEach(function (el) { Env.framework.localChange(); }); - Env.$container.prepend(form).show();; + Env.$container.prepend(form).show(); }; }; diff --git a/www/pad/inner.js b/www/pad/inner.js index 09161cea1..4d2df4339 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -486,7 +486,6 @@ define([ var metadataMgr = framework._.sfCommon.getMetadataMgr(); var privateData = metadataMgr.getPrivateData(); - var userData = metadataMgr.getUserData(); var common = framework._.sfCommon; var comments = Comments.create({ From a71d75bad3ff02aea115e1ca80da3747745b45b6 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 16:19:58 +0200 Subject: [PATCH 15/25] Better representation of active comments --- www/pad/comment.js | 9 ++++----- www/pad/comments.js | 46 +++++++++++++++++++++------------------------ www/pad/inner.js | 13 +++++++++++++ 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 0adeb14f2..549b348f3 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -17,7 +17,7 @@ onLoad: function () { CKEDITOR.addCss('comment { background-color: '+color1+' }' + '@keyframes color { 0% { background-color: '+color2+' } 50% { background-color: '+color1+' } 100% { background-color: '+color2+' } }' + - 'comment:focus { animation-name: color; animation-duration: 1s; animation-iteration-count: 2; background-color: '+color2+' outline: none;}' + + 'comment.active { animation-name: color; animation-duration: 1s; animation-iteration-count: 2; background-color: '+color2+' outline: none;}' + 'comment * { background-color: transparent !important; }'); }, init: function (editor) { @@ -27,7 +27,6 @@ element: 'comment', attributes: { 'data-uid': '#(uid)', - 'tabindex': '1' }, overrides: [ { element: 'comment' @@ -79,17 +78,17 @@ element: 'comment', attributes: { 'data-uid': id, - 'tabindex': '1' }, }); style.alwaysRemoveElement = true; els.forEach(function (el) { - // Create range for the entire document + // Create range for this element + el.removeAttribute('class'); var node = new CKEDITOR.dom.node(el); var range = editor.createRange(); range.setStart(node, 0); range.setEnd(node, Number.MAX_SAFE_INTEGER); - // Remove style for the document + // Remove style for the comment style.removeFromRange(range, editor); }); diff --git a/www/pad/comments.js b/www/pad/comments.js index 4c0598272..52d627a26 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -270,7 +270,17 @@ define([ }); var focusContent = function () { - Env.$inner.find('comment[data-uid="'+key+'"]').focus(); + // Add class "active" + Env.$inner.find('comment.active').removeClass('active'); + Env.$inner.find('comment[data-uid="'+key+'"]').addClass('active'); + var $last = Env.$inner.find('comment[data-uid="'+key+'"]').last(); + + // Scroll into view + if (!$last.length) { return; } + var size = Env.$inner.outerHeight(); + var pos = $last[0].getBoundingClientRect(); + var visible = (pos.y + pos.height) < size; + if (!visible) { $last[0].scrollIntoView(); } }; $div.click(function () { @@ -382,30 +392,6 @@ define([ return; } -/* -sel.forEach(function (el) { - // For each comment ID, check if the comment will be deleted - // if we add a comment on our selection - var id = el.getAttribute('data-uid'); - - // Get all nodes for this comment - var all = Env.$inner.find('comment[data-uid="'+id+'"]'); - // Get our selection - var sel = Env.ifrWindow.getSelection(); - if (!sel.containsNode) { - // IE doesn't support this method, always allow comments for them... - sel.containsNode = function () { return false; }; - } - - var notDeleted = all.some(function (i, el) { - // If this node is completely outside of the selection, continue - if (!sel.containsNode(el, true)) { return true; } - }); - - // only continue if notDeleted is true (at least one node for - // this comment won't be deleted) -}); -*/ Env.$container.find('.cp-comment-form').remove(); var form = getCommentForm(Env, false, function (val) { $(form).remove(); @@ -460,11 +446,21 @@ sel.forEach(function (el) { addAddCommentHandler(Env); + // Unselect comment when clicking outside $(window).click(function (e) { if ($(e.target).closest('.cp-comment-container').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(); + }); + // 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); diff --git a/www/pad/inner.js b/www/pad/inner.js index 4d2df4339..683b62b7e 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -155,8 +155,13 @@ define([ if (hj[0] === 'MEDIA-TAG') { hj[2] = []; } return hj; }; + var commentActiveFilter = function (hj) { + if (hj[0] === 'COMMENT') { delete (hj[1] || {}).class; } + return hj; + }; brFilter(hj); mediatagContentFilter(hj); + commentActiveFilter(hj); widgetFilter(hj); return hj; }; @@ -280,6 +285,14 @@ define([ } } + // Don't remote the "active" class of our comments + if (info.node && info.node.tagName === 'COMMENT') { + if (info.diff.action === 'removeAttribute' && + ['class'].indexOf(info.diff.name) !== -1) { + return true; + } + } + if (info.node && info.node.tagName === 'BODY') { if (info.diff.action === 'removeAttribute' && ['class', 'spellcheck'].indexOf(info.diff.name) !== -1) { From 372272546243397d024f41b914f08b122ff9f3c4 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 17:22:36 +0200 Subject: [PATCH 16/25] Fix race condition when adding a new comment --- www/pad/comment.js | 2 +- www/pad/comments.js | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 549b348f3..7f8da3021 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -125,7 +125,7 @@ if (editor.ui.addButton) { editor.ui.addButton('Comment', { label: 'COMMENT', - command: pluginName, + command: 'comment', icon : '/pad/icons/comment.png', toolbar: 'insert,10' }); diff --git a/www/pad/comments.js b/www/pad/comments.js index 52d627a26..f0f0e9cfc 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -73,7 +73,6 @@ define([ data.avatar = userData.avatar; data.profile = userData.profile; data.curvePublic = userData.curvePublic; - console.log(data); return myAuthorId; }; @@ -143,7 +142,8 @@ define([ var redrawComments = function (Env) { // Don't redraw if there were no change - var str = Sortify(Env.comments || {}); + var str = Sortify((Env.comments || {}).data || {}); + if (str === Env.oldComments) { return; } Env.oldComments = str; @@ -164,7 +164,6 @@ define([ }).toArray(); var done = []; - order.forEach(function (key) { // Avoir duplicates if (done.indexOf(key) !== -1) { return; } @@ -414,11 +413,18 @@ define([ v: canonicalize(Env.editor.getSelection().getSelectedText()) }] }; + // There may be a race condition between updateMetadata and addMark that causes + // * updateMetadata first: comment not rendered (redrawComments called + // before addMark) + // * addMark first: comment deleted (checkDeleted called before updateMetadata) + // ==> we're going to call updateMetadata first, and we'll invalidate the cache + // of rendered comments to display them properly in redrawComments updateMetadata(Env); - addMark(); Env.framework.localChange(); + + Env.oldComments = undefined; }); Env.$container.prepend(form).show(); }; @@ -430,11 +436,6 @@ define([ onChange(Env); checkDeleted(Env); }; - var localChange = function (Env) { - if (!Env.ready) { return; } - // Check deleted - checkDeleted(Env); - }; var ready = function (Env) { Env.ready = 0; @@ -453,14 +454,12 @@ 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); @@ -484,7 +483,6 @@ define([ return { onContentUpdate: call(onContentUpdate), - localChange: call(localChange), ready: call(ready) }; }; From a8eeac6e5ab8e10081815673b5622d9254790e0d Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Apr 2020 17:42:28 +0200 Subject: [PATCH 17/25] Use tab to move between comments --- customize.dist/src/less2/include/buttons.less | 12 +++++----- .../src/less2/include/comments.less | 3 +++ www/pad/comments.js | 22 ++++++++++++++----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/customize.dist/src/less2/include/buttons.less b/customize.dist/src/less2/include/buttons.less index c3ef85b1c..7936a5ee6 100644 --- a/customize.dist/src/less2/include/buttons.less +++ b/customize.dist/src/less2/include/buttons.less @@ -99,7 +99,7 @@ margin: 0; } - &:hover, &:active { + &:hover, &:active, &:focus { background-color: lighten(@alertify-fore, 35%); } @@ -113,7 +113,7 @@ background-color: @colortheme_alertify-red; border-color: @colortheme_alertify-red-border; color: @colortheme_alertify-red-color; - &:hover, &:active { + &:hover, &:active, &:focus { background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); } } @@ -121,7 +121,7 @@ &.danger-alt, &.btn-danger-alt { border-color: @colortheme_alertify-red; color: @colortheme_alertify-red; - &:hover, &:active { + &:hover, &:active, &:focus { color: @colortheme_alertify-red-color; background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); } @@ -131,7 +131,7 @@ background-color: @colortheme_alertify-green; border-color: @colortheme_alertify-green-border; color: @colortheme_alertify-green-color; - &:hover, &:active { + &:hover, &:active, &:focus { background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%)); } } @@ -141,7 +141,7 @@ color: @colortheme_alertify-primary-text; border-color: @colortheme_alertify-primary-border; font-weight: bold; - &:hover, &:active { + &:hover, &:active, &:focus { background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); } } @@ -149,7 +149,7 @@ &.cancel, &.btn-cancel { border-color: @colortheme_alertify-cancel-border; color: @colortheme_alertify-cancel-border; - &:hover, &:hover { + &:hover, &:hover, &:focus { background-color: fade(@colortheme_alertify-cancel-border, 25%); } } diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index b217cdae1..be72e71f6 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -85,6 +85,9 @@ display: none; text-align: right; margin-top: 5px; + button { + margin-bottom: 0 !important; + } button:not(:last-child) { margin-right: 10px; } diff --git a/www/pad/comments.js b/www/pad/comments.js index f0f0e9cfc..6a75d1575 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -91,14 +91,20 @@ define([ var userData = Env.metadataMgr.getUserData(); var name = Util.fixHTML(userData.name || Messages.anonymous); var avatar = h('span.cp-avatar'); - var textarea = h('textarea'); + var textarea = h('textarea', { + tabindex: 1 + }); Env.common.displayAvatar($(avatar), userData.avatar, name); - var cancel = h('button.btn.btn-cancel', [ + var cancel = h('button.btn.btn-cancel', { + tabindex: 1 + }, [ h('i.fa.fa-times'), Messages.cancel ]); - var submit = h('button.btn.btn-primary', [ + var submit = h('button.btn.btn-primary', { + tabindex: 1 + }, [ h('i.fa.fa-paper-plane-o'), Messages.comments_submit ]); @@ -198,11 +204,15 @@ define([ }); - var reply = h('button.btn.btn-secondary', [ + var reply = h('button.btn.btn-secondary', { + tabindex: 1 + }, [ h('i.fa.fa-reply'), Messages.comments_reply ]); - var resolve = h('button.btn.btn-primary', [ + var resolve = h('button.btn.btn-primary', { + tabindex: 1 + }, [ h('i.fa.fa-check'), Messages.comments_resolve ]); @@ -282,7 +292,7 @@ define([ if (!visible) { $last[0].scrollIntoView(); } }; - $div.click(function () { + $div.on('click focus', function () { if ($div.hasClass('cp-comment-active')) { return; } Env.$container.find('.cp-comment-active').removeClass('cp-comment-active'); $div.addClass('cp-comment-active'); From 8e4ede3bb802cc99459366749c6611a223084b25 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 10:50:07 +0200 Subject: [PATCH 18/25] Fix mediatags in comments --- www/pad/comment.js | 15 ++++++++++----- www/pad/comments.js | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 7f8da3021..040de6573 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -2,22 +2,27 @@ var CKEDITOR = window.CKEDITOR; function isUnstylable (el) { + if (el.hasClass('cke_widget_mediatag')) { + return false; + } 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(252, 165, 3, 0.8)'; + var color2 = 'rgba(252, 231, 3, 0.8)'; CKEDITOR.plugins.add('comments', { //requires: 'dialog,widget', //icons: 'image', //hidpi: true, 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;}' + + 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) { diff --git a/www/pad/comments.js b/www/pad/comments.js index 6a75d1575..356fc8685 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -357,7 +357,7 @@ define([ var uids = Env.$inner.find('comment').map(function (i, el) { var id = el.getAttribute('data-uid'); // Empty comment: remove from dom - if (!el.innerText && el.parentElement) { + if (!el.innerHTML && el.parentElement) { el.parentElement.removeChild(el); changed = true; return; From 45d3c1304f7f40b075ad7296b93bcfad34f962d1 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 11:16:56 +0200 Subject: [PATCH 19/25] Restore a comment with undo --- www/pad/comment.js | 10 ++++++---- www/pad/comments.js | 25 +++++++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 040de6573..7240b880d 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -14,9 +14,6 @@ var color2 = 'rgba(252, 231, 3, 0.8)'; CKEDITOR.plugins.add('comments', { - //requires: 'dialog,widget', - //icons: 'image', - //hidpi: true, onLoad: function () { CKEDITOR.addCss('comment { background-color: '+color1+'; }' + '@keyframes color { 0% { background-color: '+color2+'; } 50% { background-color: '+color1+'; } 100% { background-color: '+color2+'; } }' + @@ -94,7 +91,12 @@ range.setStart(node, 0); range.setEnd(node, Number.MAX_SAFE_INTEGER); // Remove style for the comment - style.removeFromRange(range, editor); + console.log(range); + try { + style.removeFromRange(range, editor); + } catch (e) { + console.error(e); + } }); setTimeout( function() { diff --git a/www/pad/comments.js b/www/pad/comments.js index 356fc8685..cf946e4d0 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -270,8 +270,6 @@ define([ }, function () { // Delete the comment delete Env.comments.data[key]; - var els = Env.$inner.find('comment[data-uid="'+key+'"]').toArray(); - Env.editor.plugins.comments.uncomment(key, els); // Send to chainpad updateMetadata(Env); @@ -354,6 +352,7 @@ define([ var changed = false; // Get the comments from the document + var toUncomment = {}; var uids = Env.$inner.find('comment').map(function (i, el) { var id = el.getAttribute('data-uid'); // Empty comment: remove from dom @@ -363,22 +362,36 @@ define([ return; } // Comment not in the metadata: uncomment (probably an undo) - if (comments.indexOf(id) === -1) { - Env.editor.plugins.comments.uncomment(id, [el]); + var obj = Env.comments.data[id]; + if (!obj) { + toUncomment[id] = toUncomment[id] || []; + toUncomment[id].push(el); changed = true; return; } + // 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; + changed = true; + } return id; }).toArray(); + if (Object.keys(toUncomment).length) { + Object.keys(toUncomment).forEach(function (id) { + Env.editor.plugins.comments.uncomment(id, toUncomment[id]); + }); + } + // Check if a comment has been deleted comments.forEach(function (uid) { if (uids.indexOf(uid) !== -1) { return; } // comment has been deleted var data = Env.comments.data[uid]; if (!data) { return; } - //data.deleted = true; - delete Env.comments.data[uid]; + data.deleted = true; + //delete Env.comments.data[uid]; changed = true; }); From 685be534ec14ea7b08d3cc0f83dbe00ca68a9957 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 11:37:35 +0200 Subject: [PATCH 20/25] Clear ghost data on ready if you're alone --- www/pad/comments.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/www/pad/comments.js b/www/pad/comments.js index cf946e4d0..514c1aae7 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -462,6 +462,22 @@ define([ var ready = function (Env) { Env.ready = 0; + + // If you're the only edit user online, clear "deleted" comments + if (!Env.common.isLoggedIn()) { return; } + var users = Env.metadataMgr.getMetadata().users || {}; + var isNotAlone = Object.keys(users).length > 1; + if (isNotAlone) { return; } + + // Clear data + var data = (Env.comments && Env.comments.data) || {}; + Object.keys(data).forEach(function (uid) { + if (data[uid].deleted) { delete data[uid]; } + }); + + // Commit + updateMetadata(Env); + Env.framework.localChange(); }; Comments.create = function (cfg) { From f3e921d7a5ed76a1bdef6e563fccdd853f5299a4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 12:49:21 +0200 Subject: [PATCH 21/25] Comment from context menu --- www/pad/comment.js | 52 ++++++++++++++++++++++++++++++++++++---------- www/pad/inner.js | 4 ++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index 7240b880d..ccab47a74 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -23,6 +23,7 @@ 'comment * { background-color: transparent !important; }'); }, init: function (editor) { + var Messages = CKEDITOR._commentsTranslations; var pluginName = 'comment'; var styleDef = { @@ -48,7 +49,7 @@ if (isComment) { return; } // We can't comment on empty text! - if (!editor.getSelection().getSelectedText()) { return; } + if (!editor.getSelection().getSelectedText()) { console.warn('there');return; } var uid = CKEDITOR.tools.getUniqueId(); editor.plugins.comments.addComment(uid, function () { @@ -104,12 +105,12 @@ }, 0 ); }; + // Uncomment from context menu, disabled for now... editor.addCommand('uncomment', { exec: function (editor, data) { if (editor.readOnly) { return; } editor.fire('saveSnapshot'); if (!data || !data.id) { - // XXX Uncomment the selection, remove on prod, only used for dev editor.focus(); editor.removeStyle(removeStyle); setTimeout( function() { @@ -121,22 +122,51 @@ }); // Register the toolbar button. - // XXX Uncomment selection, remove on prod, only used for dev - if (editor.ui.addButton) { - editor.ui.addButton('UnComment', { - label: 'UNCOMMENT', - command: 'uncomment', - toolbar: 'insert,10' - }); - } if (editor.ui.addButton) { editor.ui.addButton('Comment', { - label: 'COMMENT', + label: Messages.comment, command: 'comment', icon : '/pad/icons/comment.png', toolbar: 'insert,10' }); } + + if (editor.addMenuItems) { + editor.addMenuGroup('comments'); + editor.addMenuItem('comment', { + label: Messages.comment, + icon : '/pad/icons/comment.png', + command: 'comment', + group: 'comments' + }); + /* + editor.addMenuItem('uncomment', { + label: Messages.uncomment, + icon : '/pad/icons/uncomment.png', + command: 'uncomment', + group: 'comments' + }); + */ + } + if (editor.contextMenu) { + /* + editor.contextMenu.addListener(function (element, sel, path) { + var isComment = removeStyle.checkActive(path, editor); + if (!isComment) { return; } + return { + uncomment: CKEDITOR.TRISTATE_OFF, + }; + }); + */ + editor.contextMenu.addListener(function (element, sel, path) { + var applicable = removeStyle.checkApplicable(path, editor); + var empty = !sel.getSelectedText(); + if (!applicable || empty) { return; } + return { + comment: CKEDITOR.TRISTATE_OFF, + }; + }); + } } }); diff --git a/www/pad/inner.js b/www/pad/inner.js index 683b62b7e..a8fc98924 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -1007,6 +1007,10 @@ define([ 'import': Messages.pad_mediatagImport, options: Messages.pad_mediatagOptions }; + Messages.comments_comment = "COMMENT"; // XXX + Ckeditor._commentsTranslations = { + comment: Messages.comments_comment, + }; Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js'); Ckeditor.plugins.addExternal('blockbase64','/pad/', 'disable-base64.js'); Ckeditor.plugins.addExternal('comments','/pad/', 'comment.js'); From cdd90cea82bdcbcc14d8c2b9e596795b34dc706e Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 13:17:56 +0200 Subject: [PATCH 22/25] Clean links plugin code for ckeditor --- www/pad/comment.js | 1 - www/pad/inner.js | 50 +--------------- www/pad/links.js | 143 +++++++++++++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 94 deletions(-) diff --git a/www/pad/comment.js b/www/pad/comment.js index ccab47a74..9d91a5a72 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -24,7 +24,6 @@ }, init: function (editor) { var Messages = CKEDITOR._commentsTranslations; - var pluginName = 'comment'; var styleDef = { element: 'comment', diff --git a/www/pad/inner.js b/www/pad/inner.js index a8fc98924..c52e51264 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -511,54 +511,6 @@ define([ $container: $('#cp-app-pad-comments') }); - var onLinkClicked = function (e) { - var $target = $(e.target); - if (!$target.is('a')) { return; } - var href = $target.attr('href'); - if (!href || href[0] === '#') { return; } - e.preventDefault(); - e.stopPropagation(); - - var rect = e.target.getBoundingClientRect(); - var rect0 = inner.getBoundingClientRect(); - var l = (rect.left - rect0.left)+'px'; - var t = rect.bottom + $iframe.scrollTop() +'px'; - - var a = h('a', { href: href}, href); - var link = h('div.cp-link-clicked.non-realtime', { - contenteditable: false, - style: 'top:'+t+';left:'+l - }, [ a ]); - var $link = $(link); - $inner.append(link); - - if (rect.left + $link.outerWidth() - rect0.left > $inner.width()) { - $link.css('left', 'unset'); - $link.css('right', 0); - } - - $(a).click(function (ee) { - ee.preventDefault(); - ee.stopPropagation(); - framework._.sfCommon.openUnsafeURL(href); - $link.remove(); - }); - $link.on('mouseleave', function () { - $link.remove(); - }); - }; - var removeClickedLink = function () { - $inner.find('.cp-link-clicked').remove(); - }; - - $inner.click(function (e) { - if (e.target.nodeName.toUpperCase() === 'A') { - removeClickedLink(); - return void onLinkClicked(e); - } - removeClickedLink(); - }); - // My cursor var cursor = module.cursor = Cursor(inner); @@ -1023,7 +975,7 @@ define([ editor.plugins.mediatag.import = function ($mt) { framework._.sfCommon.importMediaTag($mt); }; - Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor}); + Links.init(Ckeditor, editor); }).nThen(function () { // Move ckeditor parts to have a structure like the other apps var $contentContainer = $('#cke_1_contents'); diff --git a/www/pad/links.js b/www/pad/links.js index bc582f959..15fc4df66 100644 --- a/www/pad/links.js +++ b/www/pad/links.js @@ -1,14 +1,75 @@ -define(['/customize/messages.js'], function (Messages) { - // Adds a context menu entry to open the selected link in a new tab. - // See https://github.com/xwiki-contrib/application-ckeditor/commit/755d193497bf23ed874d874b4ae92fbee887fc10 +define([ + 'jquery', + '/common/hyperscript.js', + '/customize/messages.js' +], function ($, h, Messages) { + + var onLinkClicked = function (e, inner) { + var $target = $(e.target); + if (!$target.is('a')) { return; } + var href = $target.attr('href'); + if (!href || href[0] === '#') { return; } + e.preventDefault(); + e.stopPropagation(); + + var $iframe = $('html').find('iframe').contents(); + var $inner = $(inner); + + var rect = e.target.getBoundingClientRect(); + var rect0 = inner.getBoundingClientRect(); + var l = (rect.left - rect0.left)+'px'; + var t = rect.bottom + $iframe.scrollTop() +'px'; + + var a = h('a', { href: href}, href); + var link = h('div.cp-link-clicked.non-realtime', { + contenteditable: false, + style: 'top:'+t+';left:'+l + }, [ a ]); + var $link = $(link); + $inner.append(link); + + if (rect.left + $link.outerWidth() - rect0.left > $inner.width()) { + $link.css('left', 'unset'); + $link.css('right', 0); + } + + $(a).click(function (ee) { + ee.preventDefault(); + ee.stopPropagation(); + var bounceHref = window.location.origin + '/bounce/#' + encodeURIComponent(href); + window.open(bounceHref); + $link.remove(); + }); + $link.on('mouseleave', function () { + $link.remove(); + }); + }; + var removeClickedLink = function ($inner) { + $inner.find('.cp-link-clicked').remove(); + }; + return { - addSupportForOpeningLinksInNewTab : function (Ckeditor) { - // Returns the DOM element of the active (currently focused) link. It has also support for linked image widgets. - // @return {CKEDITOR.dom.element} - var getActiveLink = function(editor) { - var anchor = Ckeditor.plugins.link.getSelectedLink(editor), - // We need to do some special checking against widgets availability. - activeWidget = editor.widgets && editor.widgets.focused; + init : function (Ckeditor, editor) { + if (!Ckeditor.plugins.link) { return; } + + var inner = editor.document.$.body; + var $inner = $(inner); + console.log($inner, inner); + // Bubble to open the link in a new tab + $inner.click(function (e) { + removeClickedLink($inner); + if (e.target.nodeName.toUpperCase() === 'A') { + return void onLinkClicked(e, inner); + } + }); + + + + // Adds a context menu entry to open the selected link in a new tab. + var getActiveLink = function() { + var anchor = Ckeditor.plugins.link.getSelectedLink(editor); + // We need to do some special checking against widgets availability. + var activeWidget = editor.widgets && editor.widgets.focused; // If default way of getting links didn't return anything useful.. if (!anchor && activeWidget && activeWidget.name === 'image' && activeWidget.parts.link) { // Since CKEditor 4.4.0 image widgets may be linked. @@ -17,44 +78,38 @@ define(['/customize/messages.js'], function (Messages) { return anchor; }; - return function(event) { - var editor = event.editor; - if (!Ckeditor.plugins.link) { - return; + editor.addCommand( 'openLink', { + exec: function() { + var anchor = getActiveLink(); + if (anchor) { + var href = anchor.getAttribute('href'); + if (href) { + var bounceHref = window.location.origin + '/bounce/#' + encodeURIComponent(href); + window.open(bounceHref); + } + } } - editor.addCommand( 'openLink', { - exec: function(editor) { - var anchor = getActiveLink(editor); - if (anchor) { - var href = anchor.getAttribute('href'); - if (href) { - var bounceHref = window.location.origin + '/bounce/#' + encodeURIComponent(href); - window.open(bounceHref); - } + }); + if (typeof editor.addMenuItem === 'function') { + editor.addMenuItem('openLink', { + label: Messages.openLinkInNewTab, + command: 'openLink', + group: 'link', + order: -1 + }); + } + if (editor.contextMenu) { + editor.contextMenu.addListener(function(startElement) { + if (startElement) { + var anchor = getActiveLink(); + if (anchor && anchor.getAttribute('href')) { + return {openLink: Ckeditor.TRISTATE_OFF}; } } }); - if (typeof editor.addMenuItem === 'function') { - editor.addMenuItem('openLink', { - label: Messages.openLinkInNewTab, - command: 'openLink', - group: 'link', - order: -1 - }); - } - if (editor.contextMenu) { - editor.contextMenu.addListener(function(startElement) { - if (startElement) { - var anchor = getActiveLink(editor); - if (anchor && anchor.getAttribute('href')) { - return {openLink: Ckeditor.TRISTATE_OFF}; - } - } - }); - editor.contextMenu._.panelDefinition.css.push('.cke_button__openLink_icon {' + - Ckeditor.skin.getIconStyle('link') + '}'); - } - }; + editor.contextMenu._.panelDefinition.css.push('.cke_button__openLink_icon {' + + Ckeditor.skin.getIconStyle('link') + '}'); + } } }; }); From 5630b78763019b87bd388aaec3dbfdd84a74461a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 13:18:03 +0200 Subject: [PATCH 23/25] Add icon --- www/pad/icons/uncomment.png | Bin 0 -> 17283 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 www/pad/icons/uncomment.png diff --git a/www/pad/icons/uncomment.png b/www/pad/icons/uncomment.png new file mode 100644 index 0000000000000000000000000000000000000000..3944927f63f2eb4dd37df05d068d00c074fca2a6 GIT binary patch literal 17283 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 Date: Fri, 24 Apr 2020 17:53:33 +0200 Subject: [PATCH 24/25] Inline button to add comments --- www/pad/app-pad.less | 23 ++++++++++++-- www/pad/comment.js | 25 +++++++-------- www/pad/comments.js | 73 ++++++++++++++++++++++++++++++++++++++++---- www/pad/inner.js | 2 ++ www/pad/links.js | 1 - 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 507ae0739..1be1d61ce 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -1,5 +1,6 @@ @import (reference) "../../customize/src/less2/include/framework.less"; @import (reference) "../../customize/src/less2/include/comments.less"; +@import (reference) "../../customize/src/less2/include/buttons.less"; body.cp-app-pad { .framework_main( @@ -36,9 +37,6 @@ body.cp-app-pad { align-items: center; padding: 4px; } - .cke_button__comment_label { - display: inline !important; - } } .cke_wysiwyg_frame { width: 100%; @@ -70,14 +68,33 @@ body.cp-app-pad { iframe { flex: 1; min-width: 0; + order: 1; + } + div.cp-comment-bubble { + .buttons_main(); + position: relative; + order: 2; + button { + .fa { + margin: 0 !important; + } + right: 20px; + position: absolute; + } } #cp-app-pad-comments { + order: 3; width: 300px; //background-color: white; margin: 30px; .comments_main(); } &.cke_body_width { + div.cp-comment-bubble { + button { + right: 0px; + } + } iframe { margin: 0 30px; max-width: 800px; diff --git a/www/pad/comment.js b/www/pad/comment.js index 9d91a5a72..0f54ff8ac 100644 --- a/www/pad/comment.js +++ b/www/pad/comment.js @@ -35,21 +35,24 @@ } ], childRule: isUnstylable }; + var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); + + var isApplicable = editor.plugins.comments.isApplicable = function (path, sel) { + path = path || editor.elementPath(); + sel = sel || editor.getSelection(); + var applicable = removeStyle.checkApplicable(path, editor); + var hasComments = editor.getSelectedHtml().$.querySelectorAll('comment').length; + var isComment = removeStyle.checkActive(path, editor); + var empty = !sel.getSelectedText(); + return applicable && !empty && !hasComments && !isComment; + }; // Register the command. - var removeStyle = new CKEDITOR.style(styleDef, { 'uid': '' }); editor.addCommand('comment', { exec: function (editor) { if (editor.readOnly) { return; } editor.focus(); - // If we're inside another comment, abort - var isComment = removeStyle.checkActive(editor.elementPath(), editor); - if (isComment) { return; } - - // We can't comment on empty text! - if (!editor.getSelection().getSelectedText()) { console.warn('there');return; } - var uid = CKEDITOR.tools.getUniqueId(); editor.plugins.comments.addComment(uid, function () { // Make an undo spnashot @@ -91,7 +94,6 @@ range.setStart(node, 0); range.setEnd(node, Number.MAX_SAFE_INTEGER); // Remove style for the comment - console.log(range); try { style.removeFromRange(range, editor); } catch (e) { @@ -158,9 +160,8 @@ }); */ editor.contextMenu.addListener(function (element, sel, path) { - var applicable = removeStyle.checkApplicable(path, editor); - var empty = !sel.getSelectedText(); - if (!applicable || empty) { return; } + var applicable = isApplicable(path, sel); + if (!applicable) { return; } return { comment: CKEDITOR.TRISTATE_OFF, }; diff --git a/www/pad/comments.js b/www/pad/comments.js index 514c1aae7..233bb3a13 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -2,10 +2,11 @@ define([ 'jquery', 'json.sortify', '/common/common-util.js', + '/common/common-hash.js', '/common/hyperscript.js', '/common/common-interface.js', '/customize/messages.js' -], function ($, Sortify, Util, h, UI, Messages) { +], function ($, Sortify, Util, Hash, h, UI, Messages) { var Comments = {}; /* @@ -188,6 +189,12 @@ define([ var date = new Date(msg.t); var avatar = h('span.cp-avatar'); Env.common.displayAvatar($(avatar), author.avatar, name); + if (author.profile) { + $(avatar).click(function (e) { + Env.common.openURL(Hash.hashToHref(author.profile, 'profile')); + e.stopPropagation(); + }); + } content.push(h('div.cp-comment'+(i === 0 ? '' : '.cp-comment-reply'), [ h('div.cp-comment-header', [ @@ -400,17 +407,58 @@ define([ } }; + var removeCommentBubble = function (Env) { + Env.bubble = undefined; + Env.$contentContainer.find('.cp-comment-bubble').remove(); + }; + var updateBubble = function (Env) { + if (!Env.bubble) { return; } + var pos = Env.bubble.node.getBoundingClientRect(); + if (pos.y < 0 || pos.y > Env.$inner.outerHeight()) { + //removeCommentBubble(Env); + } + Env.bubble.button.setAttribute('style', 'top:'+pos.y+'px'); + }; + var addCommentBubble = function (Env) { + var ranges = Env.editor.getSelectedRanges(); + if (!ranges.length) { return; } + var el = ranges[0].endContainer || ranges[0].startContainer; + var node = el && el.$; + if (!node) { return; } + if (node.nodeType === Node.TEXT_NODE) { + node = node.parentNode; + if (!node) { return; } + } + var pos = node.getBoundingClientRect(); + var y = pos.y; + if (y < 0 || y > Env.$inner.outerHeight()) { return; } + var button = h('button.btn.btn-secondary', { + style: 'top:'+y+'px;', + title: Messages.comments_comment + },h('i.fa.fa-comment')); + Env.bubble = { + node: node, + button: button + }; + $(button).click(function () { + Env.editor.execCommand('comment'); + Env.bubble = undefined; + }); + Env.$contentContainer.append(h('div.cp-comment-bubble', button)); + }; + var addAddCommentHandler = function (Env) { Env.editor.plugins.comments.addComment = function (uid, addMark) { + if (!Env.ready) { return; } if (!Env.comments) { Env.comments = Util.clone(COMMENTS); } // Get all comments ID contained within the selection - var sel = Env.editor.getSelectedHtml().$.querySelectorAll('comment'); - if (sel.length) { + var applicable = Env.editor.plugins.comments.isApplicable(); + if (!applicable) { // Abort if our selection contains a comment - console.error("Your selection contains a comment"); - UI.warn(Messages.error); + console.error("Can't add a comment here"); // XXX show error + UI.warn(Messages.error); return; } @@ -420,10 +468,12 @@ define([ Env.$inner.focus(); if (!val) { return; } - if (!Env.editor.getSelection().getSelectedText()) { + var applicable = Env.editor.plugins.comments.isApplicable(); + if (!applicable) { // text has been deleted by another user while we were typing our comment? return void UI.warn(Messages.error); } + // Don't override existing data if (Env.comments.data[uid]) { return; } @@ -451,6 +501,17 @@ define([ }); Env.$container.prepend(form).show(); }; + + + Env.$iframe.on('scroll', function () { + updateBubble(Env); + }); + $(Env.ifrWindow.document).on('selectionchange', function () { + removeCommentBubble(Env); + var applicable = Env.editor.plugins.comments.isApplicable(); + if (!applicable) { return; } + addCommentBubble(Env); + }); }; var onContentUpdate = function (Env) { diff --git a/www/pad/inner.js b/www/pad/inner.js index c52e51264..2a6840dca 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -507,7 +507,9 @@ define([ common: common, editor: editor, ifrWindow: ifrWindow, + $iframe: $iframe, $inner: $inner, + $contentContainer: $contentContainer, $container: $('#cp-app-pad-comments') }); diff --git a/www/pad/links.js b/www/pad/links.js index 15fc4df66..6445de68b 100644 --- a/www/pad/links.js +++ b/www/pad/links.js @@ -54,7 +54,6 @@ define([ var inner = editor.document.$.body; var $inner = $(inner); - console.log($inner, inner); // Bubble to open the link in a new tab $inner.click(function (e) { removeClickedLink($inner); From 9929ce3568dbaa01d0a05ef0761d2311e8e8ca9c Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Apr 2020 18:42:50 +0200 Subject: [PATCH 25/25] Load less files only once --- www/common/LessLoader.js | 2 +- www/common/less.min.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 www/common/less.min.js diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js index c04006e12..b463a126c 100644 --- a/www/common/LessLoader.js +++ b/www/common/LessLoader.js @@ -99,7 +99,7 @@ define([ if (lessEngine) { cb(lessEngine); } else { - require(['/bower_components/less/dist/less.min.js'], function (Less) { + require(['/common/less.min.js'], function (Less) { if (lessEngine) { return void cb(lessEngine); } lessEngine = Less; Less.functions.functionRegistry.add('LessLoader_currentFile', function () { diff --git a/www/common/less.min.js b/www/common/less.min.js new file mode 100644 index 000000000..16d78fb03 --- /dev/null +++ b/www/common/less.min.js @@ -0,0 +1,11 @@ +/** + * Less - Leaner CSS v3.11.1 + * http://lesscss.org + * + * Copyright (c) 2009-2020, Alexis Sellier + * Licensed under the Apache-2.0 License. + * + * @license Apache-2.0 + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).less=t()}(this,(function(){"use strict";function e(e){return e.replace(/^[a-z-]+:\/+?[^\/]+/,"").replace(/[\?\&]livereload=\w+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function t(e,t){for(var n in t.dataset)if(t.dataset.hasOwnProperty(n))if("env"===n||"dumpLineNumbers"===n||"rootpath"===n||"errorReporting"===n)e[n]=t.dataset[n];else try{e[n]=JSON.parse(t.dataset[n])}catch(e){}}var n=function(t,n,i){var r=i.href||"",o="less:"+(i.title||e(r)),s=t.getElementById(o),a=!1,l=t.createElement("style");l.setAttribute("type","text/css"),i.media&&l.setAttribute("media",i.media),l.id=o,l.styleSheet||(l.appendChild(t.createTextNode(n)),a=null!==s&&s.childNodes.length>0&&l.childNodes.length>0&&s.firstChild.nodeValue===l.firstChild.nodeValue);var u=t.getElementsByTagName("head")[0];if(null===s||!1===a){var c=i&&i.nextSibling||null;c?c.parentNode.insertBefore(l,c):u.appendChild(l)}if(s&&!1===a&&s.parentNode.removeChild(s),l.styleSheet)try{l.styleSheet.cssText=n}catch(e){throw new Error("Couldn't reassign styleSheet.cssText.")}},i=function(e){var t,n=e.document;return n.currentScript||(t=n.getElementsByTagName("script"))[t.length-1]},r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function o(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function s(){for(var e=0,t=0,n=arguments.length;tt?1:void 0};var h=function(e){function t(t,n,i){var r=e.call(this)||this,o=r;return Array.isArray(t)?r.rgb=t:t.length>=6?(r.rgb=[],t.match(/.{2}/g).map((function(e,t){t<3?o.rgb.push(parseInt(e,16)):o.alpha=parseInt(e,16)/255}))):(r.rgb=[],t.split("").map((function(e,t){t<3?o.rgb.push(parseInt(e+e,16)):o.alpha=parseInt(e+e,16)/255}))),r.alpha=r.alpha||("number"==typeof n?n:1),void 0!==i&&(r.value=i),r}return o(t,e),t.prototype.luma=function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255;return.2126*(e=e<=.03928?e/12.92:Math.pow((e+.055)/1.055,2.4))+.7152*(t=t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4))+.0722*(n=n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))},t.prototype.genCSS=function(e,t){t.add(this.toCSS(e))},t.prototype.toCSS=function(e,t){var n,i,r,o=e&&e.compress&&!t,s=[];if(i=this.fround(e,this.alpha),this.value)if(0===this.value.indexOf("rgb"))i<1&&(r="rgba");else{if(0!==this.value.indexOf("hsl"))return this.value;r=i<1?"hsla":"hsl"}else i<1&&(r="rgba");switch(r){case"rgba":s=this.rgb.map((function(e){return f(Math.round(e),255)})).concat(f(i,1));break;case"hsla":s.push(f(i,1));case"hsl":n=this.toHSL(),s=[this.fround(e,n.h),this.fround(e,100*n.s)+"%",this.fround(e,100*n.l)+"%"].concat(s)}if(r)return r+"("+s.join(","+(o?"":" "))+")";if(n=this.toRGB(),o){var a=n.split("");a[1]===a[2]&&a[3]===a[4]&&a[5]===a[6]&&(n="#"+a[1]+a[3]+a[5])}return n},t.prototype.operate=function(e,n,i){for(var r=new Array(3),o=this.alpha*(1-i.alpha)+i.alpha,s=0;s<3;s++)r[s]=this._operate(e,n,this.rgb[s],i.rgb[s]);return new t(r,o)},t.prototype.toRGB=function(){return p(this.rgb)},t.prototype.toHSL=function(){var e,t,n=this.rgb[0]/255,i=this.rgb[1]/255,r=this.rgb[2]/255,o=this.alpha,s=Math.max(n,i,r),a=Math.min(n,i,r),l=(s+a)/2,u=s-a;if(s===a)e=t=0;else{switch(t=l>.5?u/(2-s-a):u/(s+a),s){case n:e=(i-r)/u+(i=0&&"\n"!==t.charAt(n);)r++;return"number"==typeof e&&(i=(t.slice(0,e).match(/\n/g)||"").length),{line:i,column:r}}function C(e){var t,n=e.length,i=new Array(n);for(t=0;t|Function):(\d+):(\d+)/,R=function(e,t,n){Error.call(this);var i=e.filename||n;if(this.message=e.message,this.stack=e.stack,t&&i){var r=t.contents[i],o=I(e.index,r),s=o.line,a=o.column,l=e.call&&I(e.call,r).line,u=r?r.split("\n"):"";if(this.type=e.type||"Syntax",this.filename=i,this.index=e.index,this.line="number"==typeof s?s+1:null,this.column=a,!this.line&&this.stack){var c=this.stack.match(E),h=new Function("a","throw new Error()"),f=0;try{h()}catch(e){var p=e.stack.match(E);f=1-parseInt(p[2])}c&&(c[2]&&(this.line=parseInt(c[2])+f),c[3]&&(this.column=parseInt(c[3])))}this.callLine=l+1,this.callExtract=u[l],this.extract=[u[this.line-2],u[this.line-1],u[this.line]]}};if(void 0===Object.create){var V=function(){};V.prototype=Error.prototype,R.prototype=new V}else R.prototype=Object.create(Error.prototype);R.prototype.constructor=R,R.prototype.toString=function(e){void 0===e&&(e={});var t="",n=this.extract||[],i=[],r=function(e){return e};if(e.stylize){var o=typeof e.stylize;if("function"!==o)throw Error("options.stylize should be a function, got a "+o+"!");r=e.stylize}if(null!==this.line){if("string"==typeof n[0]&&i.push(r(this.line-1+" "+n[0],"grey")),"string"==typeof n[1]){var s=this.line+" ";n[1]&&(s+=n[1].slice(0,this.column)+r(r(r(n[1].substr(this.column,1),"bold")+n[1].slice(this.column+1),"red"),"inverse")),i.push(s)}"string"==typeof n[2]&&i.push(r(this.line+1+" "+n[2],"grey")),i=i.join("\n")+r("","reset")+"\n"}return t+=r(this.type+"Error: "+this.message,"red"),this.filename&&(t+=r(" in ","red")+this.filename),this.line&&(t+=r(" on line "+this.line+", column "+(this.column+1)+":","grey")),t+="\n"+i,this.callLine&&(t+=r("from ","red")+(this.filename||"")+"/n",t+=r(this.callLine,"grey")+" "+this.callExtract+"/n"),t};var F=function(e){function t(t,n,i,r,o,s){var a=e.call(this)||this;return a.extendList=n,a.condition=i,a.evaldCondition=!i,a._index=r,a._fileInfo=o,a.elements=a.getElements(t),a.mixinElements_=void 0,a.copyVisibilityInfo(s),a.setParent(a.elements,a),a}return o(t,e),t.prototype.accept=function(e){this.elements&&(this.elements=e.visitArray(this.elements)),this.extendList&&(this.extendList=e.visitArray(this.extendList)),this.condition&&(this.condition=e.visit(this.condition))},t.prototype.createDerived=function(e,n,i){var r=new t(e=this.getElements(e),n||this.extendList,null,this.getIndex(),this.fileInfo(),this.visibilityInfo());return r.evaldCondition=null!=i?i:this.evaldCondition,r.mediaEmpty=this.mediaEmpty,r},t.prototype.getElements=function(e){return e?("string"==typeof e&&this.parse.parseNode(e,["selector"],this._index,this._fileInfo,(function(t,n){if(t)throw new R({index:t.index,message:t.message},this.parse.imports,this._fileInfo.filename);e=n[0].elements})),e):[new g("","&",!1,this._index,this._fileInfo)]},t.prototype.createEmptySelectors=function(){var e=[new t([new g("","&",!1,this._index,this._fileInfo)],null,null,this._index,this._fileInfo)];return e[0].mediaEmpty=!0,e},t.prototype.match=function(e){var t,n,i=this.elements,r=i.length;if(0===(t=(e=e.mixinElements()).length)||ry.PARENS_DIVISION)||this.parensStack&&this.parensStack.length))},e.prototype.pathRequiresRewrite=function(e){return(this.rewriteUrls===w?W:G)(e)},e.prototype.rewritePath=function(e,t){var n;return t=t||"",n=this.normalizePath(t+e),W(e)&&G(t)&&!1===W(n)&&(n="./"+n),n},e.prototype.normalizePath=function(e){var t,n=e.split("/").reverse();for(e=[];0!==n.length;)switch(t=n.pop()){case".":break;case"..":0===e.length||".."===e[e.length-1]?e.push(t):e.pop();break;default:e.push(t)}return e.join("/")},e}();var J=function e(t){return{_data:{},add:function(e,t){e=e.toLowerCase(),this._data.hasOwnProperty(e),this._data[e]=t},addMultiple:function(e){var t=this;Object.keys(e).forEach((function(n){t.add(n,e[n])}))},get:function(e){return this._data[e]||t&&t.get(e)},getLocalFunctions:function(){return this._data},inherit:function(){return e(this)},create:function(t){return e(t)}}}(null),H={eval:function(){var e=this.value_,t=this.error_;if(t)throw t;if(null!=e)return e?$.True:$.False},value:function(e){this.value_=e},error:function(e){this.error_=e},reset:function(){this.value_=this.error_=null}},Q=function(e){function t(t,n,i,r){var o=e.call(this)||this;return o.selectors=t,o.rules=n,o._lookups={},o._variables=null,o._properties=null,o.strictImports=i,o.copyVisibilityInfo(r),o.allowRoot=!0,o.setParent(o.selectors,o),o.setParent(o.rules,o),o}return o(t,e),t.prototype.isRulesetLike=function(){return!0},t.prototype.accept=function(e){this.paths?this.paths=e.visitArray(this.paths,!0):this.selectors&&(this.selectors=e.visitArray(this.selectors)),this.rules&&this.rules.length&&(this.rules=e.visitArray(this.rules))},t.prototype.eval=function(e){var n,i,r,o,s,a=!1;if(this.selectors&&(i=this.selectors.length)){for(n=new Array(i),H.error({type:"Syntax",message:"it is currently only allowed in parametric mixin guards,"}),o=0;o0;e--){var t=this.rules[e-1];if(t instanceof N)return this.parseValue(t)}},t.prototype.parseValue=function(e){var t=this;function n(e){return e.value instanceof L&&!e.parsed?("string"==typeof e.value.value?this.parse.parseNode(e.value.value,["value","important"],e.value.getIndex(),e.fileInfo(),(function(t,n){t&&(e.parsed=!0),n&&(e.value=n[0],e.important=n[1]||"",e.parsed=!0)})):e.parsed=!0,e):e}if(Array.isArray(e)){var i=[];return e.forEach((function(e){i.push(n.call(t,e))})),i}return n.call(t,e)},t.prototype.rulesets=function(){if(!this.rules)return[];var e,t,n=[],i=this.rules;for(e=0;t=i[e];e++)t.isRuleset&&n.push(t);return n},t.prototype.prependRule=function(e){var t=this.rules;t?t.unshift(e):this.rules=[e],this.setParent(e,this)},t.prototype.find=function(e,t,n){void 0===t&&(t=this);var i,r,o=[],s=e.toCSS();return s in this._lookups?this._lookups[s]:(this.rulesets().forEach((function(s){if(s!==t)for(var a=0;ai){if(!n||n(s)){r=s.find(new F(e.elements.slice(i)),t,n);for(var l=0;l0&&t.add(l),e.firstSelector=!0,s[0].genCSS(e,t),e.firstSelector=!1,i=1;i0?(o=(r=C(e)).pop(),s=i.createDerived(C(o.elements))):s=i.createDerived([]),t.length>0){var a=n.combinator,l=t[0].elements[0];a.emptyOrWhitespace&&!l.combinator.emptyOrWhitespace&&(a=l.combinator),s.elements.push(new g(a,l.value,n.isVariable,n._index,n._fileInfo)),s.elements=s.elements.concat(t[0].elements.slice(1))}if(0!==s.elements.length&&r.push(s),t.length>1){var u=t.slice(1);u=u.map((function(e){return e.createDerived(e.elements,[])})),r=r.concat(u)}return r}function s(e,t,n,i,r){var s;for(s=0;s0?i[i.length-1]=i[i.length-1].createDerived(i[i.length-1].elements.concat(e)):i.push(new F(e));else t.push([new F(e)])}function l(e,t){var n=t.createDerived(t.elements,t.extendList,t.evaldCondition);return n.copyVisibilityInfo(e),n}var u,c;if(!function e(t,n,l){var u,c,h,f,p,d,m,y,b,w,x,S,I=!1;for(f=[],p=[[]],u=0;y=l.elements[u];u++)if("&"!==y.value){var C=(S=void 0,(x=y).value instanceof v&&(S=x.value.value)instanceof F?S:null);if(null!=C){a(f,p);var _,k=[],A=[];for(_=e(k,n,C),I=I||_,h=0;h0&&m[0].elements.push(new g(y.combinator,"",y.isVariable,y._index,y._fileInfo)),d.push(m);else for(h=0;h0&&(t.push(p[u]),w=p[u][b-1],p[u][b-1]=w.createDerived(w.elements,l.extendList));return I}(c=[],t,n))if(t.length>0)for(c=[],u=0;u0)for(t=0;t-1e-6&&(i=n.toFixed(20).replace(/0+$/,"")),e&&e.compress){if(0===n&&this.unit.isLength())return void t.add(i);n>0&&n<1&&(i=i.substr(1))}t.add(i),this.unit.genCSS(e,t)},t.prototype.operate=function(e,n,i){var r=this._operate(e,n,this.value,i.value),o=this.unit.clone();if("+"===n||"-"===n)if(0===o.numerator.length&&0===o.denominator.length)o=i.unit.clone(),this.unit.backupUnit&&(o.backupUnit=this.unit.backupUnit);else if(0===i.unit.numerator.length&&0===o.denominator.length);else{if(i=i.convertTo(this.unit.usedUnits()),e.strictUnits&&i.unit.toString()!==o.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+o.toString()+"' and '"+i.unit.toString()+"'.");r=this._operate(e,n,this.value,i.value)}else"*"===n?(o.numerator=o.numerator.concat(i.unit.numerator).sort(),o.denominator=o.denominator.concat(i.unit.denominator).sort(),o.cancel()):"/"===n&&(o.numerator=o.numerator.concat(i.unit.denominator).sort(),o.denominator=o.denominator.concat(i.unit.numerator).sort(),o.cancel());return new t(r,o)},t.prototype.compare=function(e){var n,i;if(e instanceof t){if(this.unit.isEmpty()||e.unit.isEmpty())n=this,i=e;else if(n=this.unify(),i=e.unify(),0!==n.unit.compare(i.unit))return;return c.numericCompare(n.value,i.value)}},t.prototype.unify=function(){return this.convertTo({length:"px",duration:"s",angle:"rad"})},t.prototype.convertTo=function(e){var n,i,r,o,s,a=this.value,u=this.unit.clone(),c={};if("string"==typeof e){for(n in l)l[n].hasOwnProperty(e)&&((c={})[n]=e);e=c}for(i in s=function(e,t){return r.hasOwnProperty(e)?(t?a/=r[e]/r[o]:a*=r[e]/r[o],o):e},e)e.hasOwnProperty(i)&&(o=e[i],r=l[i],u.map(s));return u.cancel(),new t(a,u)},t}(c);X.prototype.type="Dimension";var ee=y,te=function(e){function t(t,n,i){var r=e.call(this)||this;return r.op=t.trim(),r.operands=n,r.isSpaced=i,r}return o(t,e),t.prototype.accept=function(e){this.operands=e.visitArray(this.operands)},t.prototype.eval=function(e){var n,i=this.operands[0].eval(e),r=this.operands[1].eval(e);if(e.isMathOn(this.op)){if(n="./"===this.op?"/":this.op,i instanceof X&&r instanceof h&&(i=i.toColor()),r instanceof X&&i instanceof h&&(r=r.toColor()),!i.operate){if(i instanceof t&&"/"===i.op&&e.math===ee.PARENS_DIVISION)return new t(this.op,[i,r],this.isSpaced);throw{type:"Operation",message:"Operation on an invalid type"}}return i.operate(e,n,r)}return new t(this.op,[i,r],this.isSpaced)},t.prototype.genCSS=function(e,t){this.operands[0].genCSS(e,t),this.isSpaced&&t.add(" "),t.add(this.op),this.isSpaced&&t.add(" "),this.operands[1].genCSS(e,t)},t}(c);te.prototype.type="Operation";var ne=y,ie=function(e){function t(t,n){var i=e.call(this)||this;if(i.value=t,i.noSpacing=n,!t)throw new Error("Expression requires an array parameter");return i}return o(t,e),t.prototype.accept=function(e){this.value=e.visitArray(this.value)},t.prototype.eval=function(e){var n,i=e.isMathOn(),r=this.parens&&(e.math!==ne.STRICT_LEGACY||!this.parensInOp),o=!1;return r&&e.inParenthesis(),this.value.length>1?n=new t(this.value.map((function(t){return t.eval?t.eval(e):t})),this.noSpacing):1===this.value.length?(!this.value[0].parens||this.value[0].parensInOp||e.inCalc||(o=!0),n=this.value[0].eval(e)):n=this,r&&e.outOfParenthesis(),!this.parens||!this.parensInOp||i||o||n instanceof X||(n=new v(n)),n},t.prototype.genCSS=function(e,t){for(var n=0;n1){var n=new F([],null,null,this.getIndex(),this.fileInfo()).createEmptySelectors();(t=new Q(n,e.mediaBlocks)).multiMedia=!0,t.copyVisibilityInfo(this.visibilityInfo()),this.setParent(t,this)}return delete e.mediaBlocks,delete e.mediaPath,t},t.prototype.evalNested=function(e){var t,n,i=e.mediaPath.concat([this]);for(t=0;t0;t--)e.splice(t,0,new L("and"));return new ie(e)}))),this.setParent(this.features,this),new Q([],[])},t.prototype.permute=function(e){if(0===e.length)return[];if(1===e.length)return e[0];for(var t=[],n=this.permute(e.slice(1)),i=0;i1?"["+e.value.map((function(e){return e.toCSS()})).join(", ")+"]":e.toCSS()},t}(c));pe.prototype.type="JavaScript";var ve=function(e){function t(t,n){var i=e.call(this)||this;return i.key=t,i.value=n,i}return o(t,e),t.prototype.accept=function(e){this.value=e.visit(this.value)},t.prototype.eval=function(e){return this.value.eval?new t(this.key,this.value.eval(e)):this},t.prototype.genCSS=function(e,t){t.add(this.key+"="),this.value.genCSS?this.value.genCSS(e,t):t.add(this.value)},t}(c);ve.prototype.type="Assignment";var de=function(e){function t(t,n,i,r,o){var s=e.call(this)||this;return s.op=t.trim(),s.lvalue=n,s.rvalue=i,s._index=r,s.negate=o,s}return o(t,e),t.prototype.accept=function(e){this.lvalue=e.visit(this.lvalue),this.rvalue=e.visit(this.rvalue)},t.prototype.eval=function(e){var t=function(e,t,n){switch(e){case"and":return t&&n;case"or":return t||n;default:switch(c.compare(t,n)){case-1:return"<"===e||"=<"===e||"<="===e;case 0:return"="===e||">="===e||"=<"===e||"<="===e;case 1:return">"===e||">="===e;default:return!1}}}(this.op,this.lvalue.eval(e),this.rvalue.eval(e));return this.negate?!t:t},t}(c);de.prototype.type="Condition";var me=function(e){function t(t){var n=e.call(this)||this;return n.value=t,n}return o(t,e),t}(c);me.prototype.type="UnicodeDescriptor";var ge=function(e){function t(t){var n=e.call(this)||this;return n.value=t,n}return o(t,e),t.prototype.genCSS=function(e,t){t.add("-"),this.value.genCSS(e,t)},t.prototype.eval=function(e){return e.isMathOn()?new te("*",[new X(-1),this.value]).eval(e):new t(this.value.eval(e))},t}(c);ge.prototype.type="Negative";var ye=function(e){function t(n,i,r,o,s){var a=e.call(this)||this;switch(a.selector=n,a.option=i,a.object_id=t.next_id++,a.parent_ids=[a.object_id],a._index=r,a._fileInfo=o,a.copyVisibilityInfo(s),a.allowRoot=!0,i){case"all":a.allowBefore=!0,a.allowAfter=!0;break;default:a.allowBefore=!1,a.allowAfter=!1}return a.setParent(a.selector,a),a}return o(t,e),t.prototype.accept=function(e){this.selector=e.visit(this.selector)},t.prototype.eval=function(e){return new t(this.selector.eval(e),this.option,this.getIndex(),this.fileInfo(),this.visibilityInfo())},t.prototype.clone=function(e){return new t(this.selector,this.option,this.getIndex(),this.fileInfo(),this.visibilityInfo())},t.prototype.findSelfSelectors=function(e){var t,n,i=[];for(t=0;t0&&n.length&&""===n[0].combinator.value&&(n[0].combinator.value=" "),i=i.concat(e[t].elements);this.selfSelectors=[new F(i)],this.selfSelectors[0].copyVisibilityInfo(this.visibilityInfo())},t}(c);ye.next_id=0,ye.prototype.type="Extend";var be=function(e){function t(t,n,i){var r=e.call(this)||this;return r.variable=t,r._index=n,r._fileInfo=i,r.allowRoot=!0,r}return o(t,e),t.prototype.eval=function(e){var t,n=new se(this.variable,this.getIndex(),this.fileInfo()).eval(e),i=new R({message:"Could not evaluate variable call "+this.variable});if(!n.ruleset){if(n.rules)t=n;else if(Array.isArray(n))t=new Q("",n);else{if(!Array.isArray(n.value))throw i;t=new Q("",n.value)}n=new Z(t)}if(n.ruleset)return n.callEval(e);throw i},t}(c);be.prototype.type="VariableCall";var we=function(e){function t(t,n,i,r){var o=e.call(this)||this;return o.value=t,o.lookups=n,o._index=i,o._fileInfo=r,o}return o(t,e),t.prototype.eval=function(e){var t,n,i=this.value.eval(e);for(t=0;tthis.params.length)return!1}n=Math.min(o,this.arity);for(var s=0;s0){for(c=!0,a=0;a0)f=2;else if(f=1,p[1]+p[2]>1)throw{type:"Runtime",message:"Ambiguous use of `default()` found when matching for `"+this.format(m)+"`",index:this.getIndex(),filename:this.fileInfo().filename};for(a=0;a=0;s--){var a=o[s];if(a[r?"supportsSync":"supports"](e,t,n,i))return a}return null},e.prototype.addFileManager=function(e){this.fileManagers.push(e)},e.prototype.clearFileManagers=function(){this.fileManagers=[]},e}(),ke=function(){function e(){}return e.prototype.getPath=function(e){var t=e.lastIndexOf("?");return t>0&&(e=e.slice(0,t)),(t=e.lastIndexOf("/"))<0&&(t=e.lastIndexOf("\\")),t<0?"":e.slice(0,t+1)},e.prototype.tryAppendExtension=function(e,t){return/(\.[a-z]*$)|([\?;].*)$/.test(e)?e:e+t},e.prototype.tryAppendLessExtension=function(e){return this.tryAppendExtension(e,".less")},e.prototype.supportsSync=function(){return!1},e.prototype.alwaysMakePathsAbsolute=function(){return!1},e.prototype.isPathAbsolute=function(e){return/^(?:[a-z-]+:|\/|\\|#)/i.test(e)},e.prototype.join=function(e,t){return e?e+t:t},e.prototype.pathDiff=function(e,t){var n,i,r,o,s=this.extractUrlParts(e),a=this.extractUrlParts(t),l="";if(s.hostPart!==a.hostPart)return"";for(i=Math.max(a.directories.length,s.directories.length),n=0;nparseInt(t[n])?-1:1;return 0},e.prototype.versionToString=function(e){for(var t="",n=0;n0;){var e=this.imports[0];if(!e.isReady)return;this.imports=this.imports.slice(1),e.callback.apply(null,e.args)}if(0===this.variableImports.length)break;var t=this.variableImports[0];this.variableImports=this.variableImports.slice(1),t()}}finally{this._currentDepth--}0===this._currentDepth&&this._onSequencerEmpty&&this._onSequencerEmpty()},e}(),Fe=function(e,t){this._visitor=new Re(this),this._importer=e,this._finish=t,this.context=new j.Eval,this.importCount=0,this.onceFileDetectionMap={},this.recursionDetector={},this._sequencer=new Ve(this._onSequencerEmpty.bind(this))};Fe.prototype={isReplacing:!1,run:function(e){try{this._visitor.visit(e)}catch(e){this.error=e}this.isFinished=!0,this._sequencer.tryRun()},_onSequencerEmpty:function(){this.isFinished&&this._finish(this.error)},visitImport:function(e,t){var n=e.options.inline;if(!e.css||n){var i=new j.Eval(this.context,C(this.context.frames)),r=i.frames[0];this.importCount++,e.isVariableImport()?this._sequencer.addVariableImport(this.processImportNode.bind(this,e,i,r)):this.processImportNode(e,i,r)}t.visitDeeper=!1},processImportNode:function(e,t,n){var i,r=e.options.inline;try{i=e.evalForImport(t)}catch(t){t.filename||(t.index=e.getIndex(),t.filename=e.fileInfo().filename),e.css=!0,e.error=t}if(!i||i.css&&!r)this.importCount--,this.isFinished&&this._sequencer.tryRun();else{i.options.multiple&&(t.importMultiple=!0);for(var o=void 0===i.css,s=0;s=0||(a=[u.selfSelectors[0]],(o=f.findMatch(l,a)).length&&(l.hasFoundMatches=!0,l.selfSelectors.forEach((function(e){var t=u.visibilityInfo();s=f.extendSelector(o,a,e,l.isVisible()),(c=new Ie.Extend(u.selector,u.option,0,u.fileInfo(),t)).selfSelectors=s,s[s.length-1].extendList=[c],h.push(c),c.ruleset=u.ruleset,c.parent_ids=c.parent_ids.concat(u.parent_ids,l.parent_ids),u.firstExtendOnThisSelectorPath&&(c.firstExtendOnThisSelectorPath=!0,u.ruleset.paths.push(s))}))));if(h.length){if(this.extendChainCount++,n>100){var p="{unable to calculate}",v="{unable to calculate}";try{p=h[0].selfSelectors[0].toCSS(),v=h[0].selector.toCSS()}catch(e){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+p+":extend("+v+")"}}return h.concat(f.doExtendChaining(h,t,n+1))}return h},e.prototype.visitDeclaration=function(e,t){t.visitDeeper=!1},e.prototype.visitMixinDefinition=function(e,t){t.visitDeeper=!1},e.prototype.visitSelector=function(e,t){t.visitDeeper=!1},e.prototype.visitRuleset=function(e,t){if(!e.root){var n,i,r,o,s=this.allExtendsStack[this.allExtendsStack.length-1],a=[],l=this;for(r=0;r0&&u[l.matched].combinator.value!==s?l=null:l.matched++,l&&(l.finished=l.matched===u.length,l.finished&&!e.allowAfter&&(r+1u&&c>0&&(h[h.length-1].elements=h[h.length-1].elements.concat(t[u].elements.slice(c)),c=0,u++),l=o.elements.slice(c,a.index).concat([s]).concat(n.elements.slice(1)),u===a.pathIndex&&r>0?h[h.length-1].elements=h[h.length-1].elements.concat(l):(h=h.concat(t.slice(u,a.pathIndex))).push(new Ie.Selector(l)),u=a.endPathIndex,(c=a.endPathElementIndex)>=t[u].elements.length&&(c=0,u++);return u0&&(h[h.length-1].elements=h[h.length-1].elements.concat(t[u].elements.slice(c)),u++),h=(h=h.concat(t.slice(u,t.length))).map((function(e){var t=e.createDerived(e.elements);return i?t.ensureVisibility():t.ensureInvisibility(),t}))},e.prototype.visitMedia=function(e,t){var n=e.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);n=n.concat(this.doExtendChaining(n,e.allExtends)),this.allExtendsStack.push(n)},e.prototype.visitMediaOut=function(e){var t=this.allExtendsStack.length-1;this.allExtendsStack.length=t},e.prototype.visitAtRule=function(e,t){var n=e.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);n=n.concat(this.doExtendChaining(n,e.allExtends)),this.allExtendsStack.push(n)},e.prototype.visitAtRuleOut=function(e){var t=this.allExtendsStack.length-1;this.allExtendsStack.length=t},e}(),De=function(){function e(){this.contexts=[[]],this._visitor=new Re(this)}return e.prototype.run=function(e){return this._visitor.visit(e)},e.prototype.visitDeclaration=function(e,t){t.visitDeeper=!1},e.prototype.visitMixinDefinition=function(e,t){t.visitDeeper=!1},e.prototype.visitRuleset=function(e,t){var n,i=this.contexts[this.contexts.length-1],r=[];this.contexts.push(r),e.root||((n=e.selectors)&&(n=n.filter((function(e){return e.getIsOutput()})),e.selectors=n.length?n:n=null,n&&e.joinSelectors(r,i,n)),n||(e.rules=null),e.paths=r)},e.prototype.visitRulesetOut=function(e){this.contexts.length=this.contexts.length-1},e.prototype.visitMedia=function(e,t){var n=this.contexts[this.contexts.length-1];e.rules[0].root=0===n.length||n[0].multiMedia},e.prototype.visitAtRule=function(e,t){var n=this.contexts[this.contexts.length-1];e.rules&&e.rules.length&&(e.rules[0].root=e.isRooted||0===n.length||null)},e}(),Ne=function(){function e(e){this._visitor=new Re(this),this._context=e}return e.prototype.containsSilentNonBlockedChild=function(e){var t;if(!e)return!1;for(var n=0;n0},e.prototype.resolveVisibility=function(e,t){if(!e.blocksVisibility()){if(this.isEmpty(e)&&!this.containsSilentNonBlockedChild(t))return;return e}var n=e.rules[0];if(this.keepOnlyVisibleChilds(n),!this.isEmpty(n))return e.ensureVisibility(),e.removeVisibilityBlock(),e},e.prototype.isVisibleRuleset=function(e){return!!e.firstRoot||!this.isEmpty(e)&&!(!e.root&&!this.hasVisibleSelector(e))},e}(),Be=function(e){this._visitor=new Re(this),this._context=e,this.utils=new Ne(e)};Be.prototype={isReplacing:!0,run:function(e){return this._visitor.visit(e)},visitDeclaration:function(e,t){if(!e.blocksVisibility()&&!e.variable)return e},visitMixinDefinition:function(e,t){e.frames=[]},visitExtend:function(e,t){},visitComment:function(e,t){if(!e.blocksVisibility()&&!e.isSilent(this._context))return e},visitMedia:function(e,t){var n=e.rules[0].rules;return e.accept(this._visitor),t.visitDeeper=!1,this.utils.resolveVisibility(e,n)},visitImport:function(e,t){if(!e.blocksVisibility())return e},visitAtRule:function(e,t){return e.rules&&e.rules.length?this.visitAtRuleWithBody(e,t):this.visitAtRuleWithoutBody(e,t)},visitAnonymous:function(e,t){if(!e.blocksVisibility())return e.accept(this._visitor),e},visitAtRuleWithBody:function(e,t){var n=function(e){var t=e.rules;return function(e){var t=e.rules;return 1===t.length&&(!t[0].paths||0===t[0].paths.length)}(e)?t[0].rules:t}(e);return e.accept(this._visitor),t.visitDeeper=!1,this.utils.isEmpty(e)||this._mergeRules(e.rules[0].rules),this.utils.resolveVisibility(e,n)},visitAtRuleWithoutBody:function(e,t){if(!e.blocksVisibility()){if("@charset"===e.name){if(this.charset){if(e.debugInfo){var n=new Ie.Comment("/* "+e.toCSS(this._context).replace(/\n/g,"")+" */\n");return n.debugInfo=e.debugInfo,this._visitor.visit(n)}return}this.charset=!0}return e}},checkValidNodes:function(e,t){if(e)for(var n=0;n0?e.accept(this._visitor):e.rules=null,t.visitDeeper=!1}return e.rules&&(this._mergeRules(e.rules),this._removeDuplicateRules(e.rules)),this.utils.isVisibleRuleset(e)&&(e.ensureVisibility(),i.splice(0,0,e)),1===i.length?i[0]:i},_compileRulesetPaths:function(e){e.paths&&(e.paths=e.paths.filter((function(e){var t;for(" "===e[0].elements[0].combinator.value&&(e[0].elements[0].combinator=new Ie.Combinator("")),t=0;t=0;i--)if((n=e[i])instanceof Ie.Declaration)if(r[n.name]){(t=r[n.name])instanceof Ie.Declaration&&(t=r[n.name]=[r[n.name].toCSS(this._context)]);var o=n.toCSS(this._context);-1!==t.indexOf(o)?e.splice(i,1):t.push(o)}else r[n.name]=n}},_mergeRules:function(e){if(e){for(var t={},n=[],i=0;i0){var t=e[0],n=[],i=[new Ie.Expression(n)];e.forEach((function(e){"+"===e.merge&&n.length>0&&i.push(new Ie.Expression(n=[])),n.push(e.value),t.important=t.important||e.important})),t.value=new Ie.Value(i)}}))}}};var Ue={Visitor:Re,ImportVisitor:Fe,MarkVisibleSelectorsVisitor:Oe,ExtendVisitor:Le,JoinSelectorVisitor:De,ToCSSVisitor:Be},je=function(){var e,t,n,i,r,o,s,a=[],l={};function u(n){for(var i,a,c,h=l.i,f=t,p=l.i-s,v=l.i+o.length-p,d=l.i+=n,m=e;l.i=0){c={index:l.i,text:m.substr(l.i,y+2-l.i),isLineComment:!1},l.i+=c.text.length-1,l.commentStore.push(c);continue}}break}if(32!==i&&10!==i&&9!==i&&13!==i)break}if(o=o.slice(n+l.i-d+p),s=l.i,!o.length){if(tn||l.i===n&&e&&!i)&&(n=l.i,i=e);var r=a.pop();o=r.current,s=l.i=r.i,t=r.j},l.forget=function(){a.pop()},l.isWhitespace=function(t){var n=l.i+(t||0),i=e.charCodeAt(n);return 32===i||13===i||9===i||10===i},l.$re=function(e){l.i>s&&(o=o.slice(l.i-s),s=l.i);var t=e.exec(o);return t?(u(t[0].length),"string"==typeof t?t:1===t.length?t[0]:t):null},l.$char=function(t){return e.charAt(l.i)!==t?null:(u(1),t)},l.$str=function(t){for(var n=t.length,i=0;ih&&(d=!1)}}while(d);return r||null},l.autoCommentAbsorb=!0,l.commentStore=[],l.finished=!1,l.peek=function(t){if("string"==typeof t){for(var n=0;n57||t<43||47===t||44===t},l.start=function(i,a,c){e=i,l.i=t=s=n=0,r=a?function(e,t){var n,i,r,o,s,a,l,u,c,h=e.length,f=0,p=0,v=[],d=0;function m(t){var n=s-d;n<512&&!t||!n||(v.push(e.slice(d,s+1)),d=s+1)}for(s=0;s=97&&l<=122||l<34))switch(l){case 40:p++,i=s;continue;case 41:if(--p<0)return t("missing opening `(`",s);continue;case 59:p||m();continue;case 123:f++,n=s;continue;case 125:if(--f<0)return t("missing opening `{`",s);f||p||m();continue;case 92:if(s96)){if(u==l){c=1;break}if(92==u){if(s==h-1)return t("unescaped `\\`",s);s++}}if(c)continue;return t("unmatched `"+String.fromCharCode(l)+"`",a);case 47:if(p||s==h-1)continue;if(47==(u=e.charCodeAt(s+1)))for(s+=2;sn&&o>r?"missing closing `}` or `*/`":"missing closing `}`",n):0!==p?t("missing closing `)`",i):(m(!0),v)}(i,c):[i],o=r[0],u(0)},l.end=function(){var t,r=l.i>=e.length;return l.i=e.length-1,furthestChar:e[l.i]}},l},qe=function e(t,n,i){var r,o=je();function s(e,t){throw new R({index:o.i,filename:i.filename,type:t||"Syntax",message:e},n)}function a(e,t){var n=e instanceof Function?e.call(r):o.$re(e);if(n)return n;s(t||("string"==typeof e?"expected '"+e+"' got '"+o.currentChar()+"'":"unexpected token"))}function l(e,t){if(o.$char(e))return e;s(t||"expected '"+e+"' got '"+o.currentChar()+"'")}function u(e){var t=i.filename;return{lineNumber:I(e,o.getInput()).line+1,fileName:t}}return{parserInput:o,imports:n,fileInfo:i,parseNode:function(e,t,i,s,a){var l,u=[],c=o;try{c.start(e,!1,(function(e,t){a({message:e,index:t+i})}));for(var h,f=0,p=void 0;p=t[f];f++)if(h=c.i,l=r[p]()){try{l._index=h+i,l._fileInfo=s}catch(e){}u.push(l)}else u.push(null);c.end().isFinished?a(null,u):a(!0,null)}catch(e){throw new R({index:e.index+i,message:e.message},n,s.filename)}},parse:function(r,s,a){var l,u,c,h,f=null,p="";if(u=a&&a.globalVars?e.serializeVars(a.globalVars)+"\n":"",c=a&&a.modifyVars?"\n"+e.serializeVars(a.modifyVars):"",t.pluginManager)for(var v=t.pluginManager.getPreProcessors(),d=0;d");return e},args:function(e){var t,n,i,a,l,u,c,h=r.entities,f={args:null,variadic:!1},p=[],v=[],d=[],m=!0;for(o.save();;){if(e)u=r.detachedRuleset()||r.expression();else{if(o.commentStore.length=0,o.$str("...")){f.variadic=!0,o.$char(";")&&!t&&(t=!0),(t?v:d).push({variadic:!0});break}u=h.variable()||h.property()||h.literal()||h.keyword()||this.call(!0)}if(!u||!m)break;a=null,u.throwAwayComments&&u.throwAwayComments(),l=u;var g=null;if(e?u.value&&1==u.value.length&&(g=u.value[0]):g=u,g&&(g instanceof Ie.Variable||g instanceof Ie.Property))if(o.$char(":")){if(p.length>0&&(t&&s("Cannot mix ; and , as delimiter types"),n=!0),!(l=r.detachedRuleset()||r.expression())){if(!e)return o.restore(),f.args=[],f;s("could not understand value for named argument")}a=i=g.name}else if(o.$str("...")){if(!e){f.variadic=!0,o.$char(";")&&!t&&(t=!0),(t?v:d).push({name:u.name,variadic:!0});break}c=!0}else e||(i=a=g.name,l=null);l&&p.push(l),d.push({name:a,value:l,expand:c}),o.$char(",")?m=!0:((m=";"===o.$char(";"))||t)&&(n&&s("Cannot mix ; and , as delimiter types"),t=!0,p.length>1&&(l=new Ie.Value(p)),v.push({name:i,value:l,expand:c}),i=null,p=[],n=!1)}return o.forget(),f.args=t?v:d,f},definition:function(){var e,t,n,i,s=[],l=!1;if(!("."!==o.currentChar()&&"#"!==o.currentChar()||o.peek(/^[^{]*\}/)))if(o.save(),t=o.$re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=t[1];var u=this.args(!1);if(s=u.args,l=u.variadic,!o.$char(")"))return void o.restore("Missing closing ')'");if(o.commentStore.length=0,o.$str("when")&&(i=a(r.conditions,"expected condition")),n=r.block())return o.forget(),new Ie.mixin.Definition(e,s,n,i,l);o.restore()}else o.restore()},ruleLookups:function(){var e,t=[];if("["===o.currentChar()){for(;;){if(o.save(),!(e=this.lookupValue())&&""!==e){o.restore();break}t.push(e),o.forget()}return t.length>0?t:void 0}},lookupValue:function(){if(o.save(),o.$char("[")){var e=o.$re(/^(?:[@$]{0,2})[_a-zA-Z0-9-]*/);if(o.$char("]"))return e||""===e?(o.forget(),e):void o.restore();o.restore()}else o.restore()}},entity:function(){var e=this.entities;return this.comment()||e.literal()||e.variable()||e.url()||e.property()||e.call()||e.keyword()||this.mixin.call(!0)||e.javascript()},end:function(){return o.$char(";")||o.peek("}")},ieAlpha:function(){var e;if(o.$re(/^opacity=/i))return(e=o.$re(/^\d+/))||(e="@{"+(e=a(r.entities.variable,"Could not parse alpha")).name.slice(1)+"}"),l(")"),new Ie.Quoted("","alpha(opacity="+e+")")},element:function(){var e,t,n,r=o.i;if(t=this.combinator(),(e=o.$re(/^(?:\d+\.\d+|\d+)%/)||o.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||o.$char("*")||o.$char("&")||this.attribute()||o.$re(/^\([^&()@]+\)/)||o.$re(/^[\.#:](?=@)/)||this.entities.variableCurly())||(o.save(),o.$char("(")?(n=this.selector(!1))&&o.$char(")")?(e=new Ie.Paren(n),o.forget()):o.restore("Missing closing ')'"):o.forget()),e)return new Ie.Element(t,e,e instanceof Ie.Variable,r,i)},combinator:function(){var e=o.currentChar();if("/"===e){o.save();var t=o.$re(/^\/[a-z]+\//i);if(t)return o.forget(),new Ie.Combinator(t);o.restore()}if(">"===e||"+"===e||"~"===e||"|"===e||"^"===e){for(o.i++,"^"===e&&"^"===o.currentChar()&&(e="^^",o.i++);o.isWhitespace();)o.i++;return new Ie.Combinator(e)}return o.isWhitespace(-1)?new Ie.Combinator(" "):new Ie.Combinator(null)},selector:function(e){var t,n,r,l,u,c,h,f=o.i;for(e=!1!==e;(e&&(n=this.extend())||e&&(c=o.$str("when"))||(l=this.element()))&&(c?h=a(this.conditions,"expected condition"):h?s("CSS guard can only be used at the end of selector"):n?u=u?u.concat(n):n:(u&&s("Extend can only be used at the end of selector"),r=o.currentChar(),t?t.push(l):t=[l],l=null),"{"!==r&&"}"!==r&&";"!==r&&","!==r&&")"!==r););if(t)return new Ie.Selector(t,u,h,f,i);u&&s("Extend must be used to extend a selector, it cannot be used on its own")},selectors:function(){for(var e,t;(e=this.selector())&&(t?t.push(e):t=[e],o.commentStore.length=0,e.condition&&t.length>1&&s("Guards are only currently allowed on a single selector."),o.$char(","));)e.condition&&s("Guards are only currently allowed on a single selector."),o.commentStore.length=0;return t},attribute:function(){if(o.$char("[")){var e,t,n,i=this.entities;return(e=i.variableCurly())||(e=a(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/)),(n=o.$re(/^[|~*$^]?=/))&&(t=i.quoted()||o.$re(/^[0-9]+%/)||o.$re(/^[\w-]+/)||i.variableCurly()),l("]"),new Ie.Attribute(e,n,t)}},block:function(){var e;if(o.$char("{")&&(e=this.primary())&&o.$char("}"))return e},blockRuleset:function(){var e=this.block();return e&&(e=new Ie.Ruleset(null,e)),e},detachedRuleset:function(){var e,t,n;if(o.save(),!o.$re(/^[.#]\(/)||(t=(e=this.mixin.args(!1)).args,n=e.variadic,o.$char(")"))){var i=this.blockRuleset();if(i)return o.forget(),t?new Ie.mixin.Definition(null,t,i,null,n):new Ie.DetachedRuleset(i);o.restore()}else o.restore()},ruleset:function(){var e,n,i;if(o.save(),t.dumpLineNumbers&&(i=u(o.i)),(e=this.selectors())&&(n=this.block())){o.forget();var r=new Ie.Ruleset(e,n,t.strictImports);return t.dumpLineNumbers&&(r.debugInfo=i),r}o.restore()},declaration:function(){var e,t,n,r,s,a,l=o.i,u=o.currentChar();if("."!==u&&"#"!==u&&"&"!==u&&":"!==u)if(o.save(),e=this.variable()||this.ruleProperty()){if((a="string"==typeof e)&&(t=this.detachedRuleset())&&(n=!0),o.commentStore.length=0,!t){if(s=!a&&e.length>1&&e.pop().value,t=e[0].value&&"--"===e[0].value.slice(0,2)?this.permissiveValue():this.anonymousValue())return o.forget(),new Ie.Declaration(e,t,!1,s,l,i);t||(t=this.value()),t?r=this.important():a&&(t=this.permissiveValue())}if(t&&(this.end()||n))return o.forget(),new Ie.Declaration(e,t,r,s,l,i);o.restore()}else o.restore()},anonymousValue:function(){var e=o.i,t=o.$re(/^([^.#@\$+\/'"*`(;{}-]*);/);if(t)return new Ie.Anonymous(t[1],e)},permissiveValue:function(e){var t,n,r,a,l=e||";",u=o.i,c=[];function h(){var e=o.currentChar();return"string"==typeof l?e===l:l.test(e)}if(!h()){a=[];do{(n=this.comment())?a.push(n):(n=this.entity())&&a.push(n)}while(n);if(r=h(),a.length>0){if(a=new Ie.Expression(a),r)return a;c.push(a)," "===o.prevChar()&&c.push(new Ie.Anonymous(" ",u))}if(o.save(),a=o.$parseUntil(l)){if("string"==typeof a&&s("Expected '"+a+"'","Parse"),1===a.length&&" "===a[0])return o.forget(),new Ie.Anonymous("",u);var f=void 0;for(t=0;t0)return new Ie.Expression(r)},mediaFeatures:function(){var e,t=this.entities,n=[];do{if(e=this.mediaFeature()){if(n.push(e),!o.$char(","))break}else if((e=t.variable()||t.mixinLookup())&&(n.push(e),!o.$char(",")))break}while(e);return n.length>0?n:null},media:function(){var e,n,r,a,l=o.i;if(t.dumpLineNumbers&&(a=u(l)),o.save(),o.$str("@media"))return e=this.mediaFeatures(),(n=this.block())||s("media definitions require block statements after any features"),o.forget(),r=new Ie.Media(n,e,l,i),t.dumpLineNumbers&&(r.debugInfo=a),r;o.restore()},plugin:function(){var e,t,n,r=o.i;if(o.$re(/^@plugin?\s+/)){if(n=(t=this.pluginArgs())?{pluginArgs:t,isPlugin:!0}:{isPlugin:!0},e=this.entities.quoted()||this.entities.url())return o.$char(";")||(o.i=r,s("missing semi-colon on @plugin")),new Ie.Import(e,null,n,r,i);o.i=r,s("malformed @plugin statement")}},pluginArgs:function(){if(o.save(),!o.$char("("))return o.restore(),null;var e=o.$re(/^\s*([^\);]+)\)\s*/);return e[1]?(o.forget(),e[1].trim()):(o.restore(),null)},atrule:function(){var e,n,r,a,l,c,h,f=o.i,p=!0,v=!0;if("@"===o.currentChar()){if(n=this.import()||this.plugin()||this.media())return n;if(o.save(),e=o.$re(/^@[a-z-]+/)){switch(a=e,"-"==e.charAt(1)&&e.indexOf("-",2)>0&&(a="@"+e.slice(e.indexOf("-",2)+1)),a){case"@charset":l=!0,p=!1;break;case"@namespace":c=!0,p=!1;break;case"@keyframes":case"@counter-style":l=!0;break;case"@document":case"@supports":h=!0,v=!1;break;default:h=!0}if(o.commentStore.length=0,l?(n=this.entity())||s("expected "+e+" identifier"):c?(n=this.expression())||s("expected "+e+" expression"):h&&(n=this.permissiveValue(/^[{;]/),p="{"===o.currentChar(),n?n.value||(n=null):p||";"===o.currentChar()||s(e+" rule is missing block or ending semi-colon")),p&&(r=this.blockRuleset()),r||!p&&n&&o.$char(";"))return o.forget(),new Ie.AtRule(e,n,r,f,i,t.dumpLineNumbers?u(f):null,v);o.restore("at-rule options not recognised")}}},value:function(){var e,t=[],n=o.i;do{if((e=this.expression())&&(t.push(e),!o.$char(",")))break}while(e);if(t.length>0)return new Ie.Value(t,n)},important:function(){if("!"===o.currentChar())return o.$re(/^! *important/)},sub:function(){var e,t;if(o.save(),o.$char("("))return(e=this.addition())&&o.$char(")")?(o.forget(),(t=new Ie.Expression([e])).parens=!0,t):void o.restore("Expected ')'");o.restore()},multiplication:function(){var e,t,n,i,r;if(e=this.operand()){for(r=o.isWhitespace(-1);!o.peek(/^\/[*\/]/);){if(o.save(),!(n=o.$char("/")||o.$char("*")||o.$str("./"))){o.forget();break}if(!(t=this.operand())){o.restore();break}o.forget(),e.parensInOp=!0,t.parensInOp=!0,i=new Ie.Operation(n,[i||e,t],r),r=o.isWhitespace(-1)}return i||e}},addition:function(){var e,t,n,i,r;if(e=this.multiplication()){for(r=o.isWhitespace(-1);(n=o.$re(/^[-+]\s+/)||!r&&(o.$char("+")||o.$char("-")))&&(t=this.multiplication());)e.parensInOp=!0,t.parensInOp=!0,i=new Ie.Operation(n,[i||e,t],r),r=o.isWhitespace(-1);return i||e}},conditions:function(){var e,t,n,i=o.i;if(e=this.condition(!0)){for(;o.peek(/^,\s*(not\s*)?\(/)&&o.$char(",")&&(t=this.condition(!0));)n=new Ie.Condition("or",n||e,t,i);return n||e}},condition:function(e){var t,n,i;if(t=this.conditionAnd(e)){if(n=o.$str("or")){if(!(i=this.condition(e)))return;t=new Ie.Condition(n,t,i)}return t}},conditionAnd:function(e){var t,n,i,r,s=this;if(t=(r=s.negatedCondition(e)||s.parenthesisCondition(e))||e?r:s.atomicCondition(e)){if(n=o.$str("and")){if(!(i=this.conditionAnd(e)))return;t=new Ie.Condition(n,t,i)}return t}},negatedCondition:function(e){if(o.$str("not")){var t=this.parenthesisCondition(e);return t&&(t.negate=!t.negate),t}},parenthesisCondition:function(e){var t;if(o.save(),o.$str("(")){if(t=function(t){var n;if(o.save(),n=t.condition(e)){if(o.$char(")"))return o.forget(),n;o.restore()}else o.restore()}(this))return o.forget(),t;if(t=this.atomicCondition(e)){if(o.$char(")"))return o.forget(),t;o.restore("expected ')' got '"+o.currentChar()+"'")}else o.restore()}else o.restore()},atomicCondition:function(e){var t,n,i,r,a=this.entities,l=o.i;function u(){return this.addition()||a.keyword()||a.quoted()||a.mixinLookup()}if(t=(u=u.bind(this))())return o.$char(">")?r=o.$char("=")?">=":">":o.$char("<")?r=o.$char("=")?"<=":"<":o.$char("=")&&(r=o.$char(">")?"=>":o.$char("<")?"=<":"="),r?(n=u())?i=new Ie.Condition(r,t,n,l,!1):s("expected expression"):i=new Ie.Condition("=",t,new Ie.Keyword("true"),l,!1),i},operand:function(){var e,t=this.entities;o.peek(/^-[@\$\(]/)&&(e=o.$char("-"));var n=this.sub()||t.dimension()||t.color()||t.variable()||t.property()||t.call()||t.quoted(!0)||t.colorKeyword()||t.mixinLookup();return e&&(n.parensInOp=!0,n=new Ie.Negative(n)),n},expression:function(){var e,t,n=[],i=o.i;do{(e=this.comment())?n.push(e):(e=this.addition()||this.entity())&&(n.push(e),o.peek(/^\/[\/*]/)||(t=o.$char("/"))&&n.push(new Ie.Anonymous(t,i)))}while(e);if(n.length>0)return new Ie.Expression(n)},property:function(){var e=o.$re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);if(e)return e[1]},ruleProperty:function(){var e,t,n=[],r=[];o.save();var s=o.$re(/^([_a-zA-Z0-9-]+)\s*:/);if(s)return n=[new Ie.Keyword(s[1])],o.forget(),n;function a(e){var t=o.i,i=o.$re(e);if(i)return r.push(t),n.push(i[1])}for(a(/^(\*?)/);a(/^((?:[\w-]+)|(?:[@\$]\{[\w-]+\}))/););if(n.length>1&&a(/^((?:\+_|\+)?)\s*:/)){for(o.forget(),""===n[0]&&(n.shift(),r.shift()),t=0;t1?e-1:e)<1?r+(o-r)*e*6:2*e<1?o:3*e<2?r+(o-r)*(2/3-e)*6:r}e=Qe(e)%360/360,t=Ge(Qe(t)),n=Ge(Qe(n)),i=Ge(Qe(i)),r=2*n-(o=n<=.5?n*(t+1):n+t-n*t);var a=[255*s(e+1/3),255*s(e),255*s(e-1/3)];return i=Qe(i),new h(a,i,"hsla")}catch(e){}},hsv:function(e,t,n){return Te.hsva(e,t,n,1)},hsva:function(e,t,n,i){var r,o;e=Qe(e)%360/360*360,t=Qe(t),n=Qe(n),i=Qe(i);var s=[n,n*(1-t),n*(1-(o=e/60-(r=Math.floor(e/60%6)))*t),n*(1-(1-o)*t)],a=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return Te.rgba(255*s[a[r][0]],255*s[a[r][1]],255*s[a[r][2]],i)},hue:function(e){return new X(Je(e).h)},saturation:function(e){return new X(100*Je(e).s,"%")},lightness:function(e){return new X(100*Je(e).l,"%")},hsvhue:function(e){return new X(He(e).h)},hsvsaturation:function(e){return new X(100*He(e).s,"%")},hsvvalue:function(e){return new X(100*He(e).v,"%")},red:function(e){return new X(e.rgb[0])},green:function(e){return new X(e.rgb[1])},blue:function(e){return new X(e.rgb[2])},alpha:function(e){return new X(Je(e).a)},luma:function(e){return new X(e.luma()*e.alpha*100,"%")},luminance:function(e){var t=.2126*e.rgb[0]/255+.7152*e.rgb[1]/255+.0722*e.rgb[2]/255;return new X(t*e.alpha*100,"%")},saturate:function(e,t,n){if(!e.rgb)return null;var i=Je(e);return void 0!==n&&"relative"===n.value?i.s+=i.s*t.value/100:i.s+=t.value/100,i.s=Ge(i.s),We(e,i)},desaturate:function(e,t,n){var i=Je(e);return void 0!==n&&"relative"===n.value?i.s-=i.s*t.value/100:i.s-=t.value/100,i.s=Ge(i.s),We(e,i)},lighten:function(e,t,n){var i=Je(e);return void 0!==n&&"relative"===n.value?i.l+=i.l*t.value/100:i.l+=t.value/100,i.l=Ge(i.l),We(e,i)},darken:function(e,t,n){var i=Je(e);return void 0!==n&&"relative"===n.value?i.l-=i.l*t.value/100:i.l-=t.value/100,i.l=Ge(i.l),We(e,i)},fadein:function(e,t,n){var i=Je(e);return void 0!==n&&"relative"===n.value?i.a+=i.a*t.value/100:i.a+=t.value/100,i.a=Ge(i.a),We(e,i)},fadeout:function(e,t,n){var i=Je(e);return void 0!==n&&"relative"===n.value?i.a-=i.a*t.value/100:i.a-=t.value/100,i.a=Ge(i.a),We(e,i)},fade:function(e,t){var n=Je(e);return n.a=t.value/100,n.a=Ge(n.a),We(e,n)},spin:function(e,t){var n=Je(e),i=(n.h+t.value)%360;return n.h=i<0?360+i:i,We(e,n)},mix:function(e,t,n){n||(n=new X(50));var i=n.value/100,r=2*i-1,o=Je(e).a-Je(t).a,s=((r*o==-1?r:(r+o)/(1+r*o))+1)/2,a=1-s,l=[e.rgb[0]*s+t.rgb[0]*a,e.rgb[1]*s+t.rgb[1]*a,e.rgb[2]*s+t.rgb[2]*a],u=e.alpha*i+t.alpha*(1-i);return new h(l,u)},greyscale:function(e){return Te.desaturate(e,new X(100))},contrast:function(e,t,n,i){if(!e.rgb)return null;if(void 0===n&&(n=Te.rgba(255,255,255,1)),void 0===t&&(t=Te.rgba(0,0,0,1)),t.luma()>n.luma()){var r=n;n=t,t=r}return i=void 0===i?.43:Qe(i),e.luma().5&&(i=1,n=e>.25?Math.sqrt(e):((16*e-12)*e+4)*e),e-(1-2*t)*i*(n-e)},hardlight:function(e,t){return Ye.overlay(t,e)},difference:function(e,t){return Math.abs(e-t)},exclusion:function(e,t){return e+t-2*e*t},average:function(e,t){return(e+t)/2},negation:function(e,t){return 1-Math.abs(e+t-1)}};for(var Xe in Ye)Ye.hasOwnProperty(Xe)&&(Ze[Xe]=Ze.bind(null,Ye[Xe]));var et=function(e){return Array.isArray(e.value)?e.value:Array(e)},tt={_SELF:function(e){return e},extract:function(e,t){return t=t.value-1,et(e)[t]},length:function(e){return new X(et(e).length)},range:function(e,t,n){var i,r,o=1,s=[];t?(r=t,i=e.value,n&&(o=n.value)):(i=1,r=e);for(var a=i;a<=r.value;a+=o)s.push(new X(a,r.unit));return new ie(s)},each:function(e,t){var n,i,r=[];i=!e.value||e instanceof ue?e.ruleset?e.ruleset.rules:e.rules?e.rules:Array.isArray(e)?e:[e]:Array.isArray(e.value)?e.value:[e.value];var o="@value",s="@key",a="@index";t.params?(o=t.params[0]&&t.params[0].name,s=t.params[1]&&t.params[1].name,a=t.params[2]&&t.params[2].name,t=t.rules):t=t.ruleset;for(var l=0;ls.value)&&(c[i]=r);else{if(void 0!==l&&a!==l)throw{type:"Argument",message:"incompatible types"};h[a]=c.length,c.push(r)}else Array.isArray(t[n].value)&&Array.prototype.push.apply(t,Array.prototype.slice.call(t[n].value));return 1==c.length?c[0]:(t=c.map((function(e){return e.toCSS(this.context)})).join(this.context.compress?",":", "),new L((e?"min":"max")+"("+t+")"))},at={min:function(){for(var e=[],t=0;t",r=0;r";return i+="',i=encodeURIComponent(i),new ce(new ue("'"+(i="data:image/svg+xml,"+i)+"'",i,!1,this.index,this.currentFileInfo),this.index,this.currentFileInfo)}}),J.addMultiple(ht),t},pt=function(e,t){var n;void 0===t&&(t={});var i=t.variables,r=new j.Eval(t);"object"!=typeof i||Array.isArray(i)||(i=Object.keys(i).map((function(e){var t=i[e];return t instanceof Ie.Value||(t instanceof Ie.Expression||(t=new Ie.Expression([t])),t=new Ie.Value([t])),new Ie.Declaration("@"+e,t,!1,null,0)})),r.frames=[new Ie.Ruleset(null,i)]);var o,s,a=[new Ue.JoinSelectorVisitor,new Ue.MarkVisibleSelectorsVisitor(!0),new Ue.ExtendVisitor,new Ue.ToCSSVisitor({compress:Boolean(t.compress)})],l=[];if(t.pluginManager){s=t.pluginManager.visitor();for(var u=0;u<2;u++)for(s.first();o=s.get();)o.isPreEvalVisitor?0!==u&&-1!==l.indexOf(o)||(l.push(o),o.run(e)):0!==u&&-1!==a.indexOf(o)||(o.isPreVisitor?a.unshift(o):a.push(o))}n=e.eval(r);for(u=0;u=t);n++);this.preProcessors.splice(n,0,{preProcessor:e,priority:t})},e.prototype.addPostProcessor=function(e,t){var n;for(n=0;n=t);n++);this.postProcessors.splice(n,0,{postProcessor:e,priority:t})},e.prototype.addFileManager=function(e){this.fileManagers.push(e)},e.prototype.getPreProcessors=function(){for(var e=[],t=0;t0){var i=void 0,r=JSON.stringify(this._sourceMapGenerator.toJSON());this.sourceMapURL?i=this.sourceMapURL:this._sourceMapFilename&&(i=this._sourceMapFilename),this.sourceMapURL=i,this.sourceMap=r}return this._css.join("")},t}()}(e=new _e(e,t)),r=function(e,t){return function(){function n(e){this.options=e}return n.prototype.toCSS=function(t,n,i){var r=new e({contentsIgnoredCharsMap:i.contentsIgnoredChars,rootNode:t,contentsMap:i.contents,sourceMapFilename:this.options.sourceMapFilename,sourceMapURL:this.options.sourceMapURL,outputFilename:this.options.sourceMapOutputFilename,sourceMapBasepath:this.options.sourceMapBasepath,sourceMapRootpath:this.options.sourceMapRootpath,outputSourceFiles:this.options.outputSourceFiles,sourceMapGenerator:this.options.sourceMapGenerator,sourceMapFileInline:this.options.sourceMapFileInline}),o=r.toCSS(n);return this.sourceMap=r.sourceMap,this.sourceMapURL=r.sourceMapURL,this.options.sourceMapInputFilename&&(this.sourceMapInputFilename=r.normalizeFilename(this.options.sourceMapInputFilename)),void 0!==this.options.sourceMapBasepath&&void 0!==this.sourceMapURL&&(this.sourceMapURL=r.removeBasepath(this.sourceMapURL)),o+this.getCSSAppendage()},n.prototype.getCSSAppendage=function(){var e=this.sourceMapURL;if(this.options.sourceMapFileInline){if(void 0===this.sourceMap)return"";e="data:application/json;base64,"+t.encodeBase64(this.sourceMap)}return e?"/*# sourceMappingURL="+e+" */":""},n.prototype.getExternalSourceMap=function(){return this.sourceMap},n.prototype.setExternalSourceMap=function(e){this.sourceMap=e},n.prototype.isInline=function(){return this.options.sourceMapFileInline},n.prototype.getSourceMapURL=function(){return this.sourceMapURL},n.prototype.getOutputFilename=function(){return this.options.sourceMapOutputFilename},n.prototype.getInputFilename=function(){return this.sourceMapInputFilename},n}()}(i,e),o=function(e){return function(){function t(e,t){this.root=e,this.imports=t}return t.prototype.toCSS=function(t){var n,i,r={};try{n=pt(this.root,t)}catch(e){throw new R(e,this.imports)}try{var o=Boolean(t.compress);o&&Ce.warn("The compress option has been deprecated. We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.");var s={compress:o,dumpLineNumbers:t.dumpLineNumbers,strictUnits:Boolean(t.strictUnits),numPrecision:8};t.sourceMap?(i=new e(t.sourceMap),r.css=i.toCSS(n,s,this.imports)):r.css=n.toCSS(s)}catch(e){throw new R(e,this.imports)}if(t.pluginManager)for(var a=t.pluginManager.getPostProcessors(),l=0;l=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):"function"==typeof i&&i(t.status,e)}"function"==typeof r.overrideMimeType&&r.overrideMimeType("text/css"),gt.debug("XHR: Getting '"+e+"'"),r.open("GET",e,o),r.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),r.send(null),mt.isFileProtocol&&!mt.fileAsync?0===r.status||r.status>=200&&r.status<300?n(r.responseText):i(r.status,e):o?r.onreadystatechange=function(){4==r.readyState&&s(r,n,i)}:s(r,n,i)},t.prototype.supports=function(){return!0},t.prototype.clearFileCache=function(){bt={}},t.prototype.loadFile=function(e,t,n,i){t&&!this.isPathAbsolute(e)&&(e=t+e),e=n.ext?this.tryAppendExtension(e,n.ext):e,n=n||{};var r=this.extractUrlParts(e,window.location.href).url,o=this;return new Promise((function(e,t){if(n.useFileCache&&bt[r])try{var i=bt[r];return e({contents:i,filename:r,webInfo:{lastModified:new Date}})}catch(e){return t({filename:r,message:"Error loading file "+r+" error was "+e.message})}o.doXHR(r,n.mime,(function(t,n){bt[r]=t,e({contents:t,filename:r,webInfo:{lastModified:n}})}),(function(e,n){t({type:"File",message:"'"+n+"' wasn't found ("+e+")",href:r})}))}))},t}(ke),xt=function(e,t){return mt=e,gt=t,wt},St=function(e){function t(t){var n=e.call(this)||this;return n.less=t,n}return o(t,e),t.prototype.loadPlugin=function(e,t,n,i,r){return new Promise((function(o,s){r.loadFile(e,t,n,i).then(o).catch(s)}))},t}(Ae),It=function(t,i,r){return{add:function(o,s){r.errorReporting&&"html"!==r.errorReporting?"console"===r.errorReporting?function(e,t){var n=e.filename||t,o=[],s=(e.type||"Syntax")+"Error: "+(e.message||"There is an error in your .less file")+" in "+n,a=function(e,t,n){void 0!==e.extract[t]&&o.push("{line} {content}".replace(/\{line\}/,(parseInt(e.line,10)||0)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.line&&(a(e,0,""),a(e,1,"line"),a(e,2,""),s+=" on line "+e.line+", column "+(e.column+1)+":\n"+o.join("\n")),e.stack&&(e.extract||r.logLevel>=4)&&(s+="\nStack Trace\n"+e.stack),i.logger.error(s)}(o,s):"function"==typeof r.errorReporting&&r.errorReporting("add",o,s):function(i,o){var s,a,l="less-error-message:"+e(o||""),u=t.document.createElement("div"),c=[],h=i.filename||o,f=h.match(/([^\/]+(\?.*)?)$/)[1];u.id=l,u.className="less-error-message",a="

"+(i.type||"Syntax")+"Error: "+(i.message||"There is an error in your .less file")+'

in '+f+" ";var p=function(e,t,n){void 0!==e.extract[t]&&c.push('

  • {content}
  • '.replace(/\{line\}/,(parseInt(e.line,10)||0)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};i.line&&(p(i,0,""),p(i,1,"line"),p(i,2,""),a+="on line "+i.line+", column "+(i.column+1)+":

      "+c.join("")+"
    "),i.stack&&(i.extract||r.logLevel>=4)&&(a+="
    Stack Trace
    "+i.stack.split("\n").slice(1).join("
    ")),u.innerHTML=a,n(t.document,[".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),u.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),"development"===r.env&&(s=setInterval((function(){var e=t.document,n=e.body;n&&(e.getElementById(l)?n.replaceChild(u,e.getElementById(l)):n.insertBefore(u,n.firstChild),clearInterval(s))}),10))}(o,s)},remove:function(n){r.errorReporting&&"html"!==r.errorReporting?"console"===r.errorReporting||"function"==typeof r.errorReporting&&r.errorReporting("remove",n):function(n){var i=t.document.getElementById("less-error-message:"+e(n));i&&i.parentNode.removeChild(i)}(n)}}},Ct={javascriptEnabled:!1,depends:!1,compress:!1,lint:!1,paths:[],color:!0,strictImports:!1,insecure:!1,rootpath:"",rewriteUrls:!1,math:0,strictUnits:!1,globalVars:null,modifyVars:null,urlArgs:""};if(window.less)for(var _t in window.less)window.less.hasOwnProperty(_t)&&(Ct[_t]=window.less[_t]);!function(e,n){t(n,i(e)),void 0===n.isFileProtocol&&(n.isFileProtocol=/^(file|(chrome|safari)(-extension)?|resource|qrc|app):/.test(e.location.protocol)),n.async=n.async||!1,n.fileAsync=n.fileAsync||!1,n.poll=n.poll||(n.isFileProtocol?1e3:1500),n.env=n.env||("127.0.0.1"==e.location.hostname||"0.0.0.0"==e.location.hostname||"localhost"==e.location.hostname||e.location.port&&e.location.port.length>0||n.isFileProtocol?"development":"production");var r=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(e.location.hash);r&&(n.dumpLineNumbers=r[1]),void 0===n.useFileCache&&(n.useFileCache=!0),void 0===n.onReady&&(n.onReady=!0),n.relativeUrls&&(n.rewriteUrls="all")}(window,Ct),Ct.plugins=Ct.plugins||[],window.LESS_PLUGINS&&(Ct.plugins=Ct.plugins.concat(window.LESS_PLUGINS));var kt,At,Mt,Pt=function(e,i){var r=e.document,o=yt();o.options=i;var s=o.environment,a=xt(i,o.logger),l=new a;s.addFileManager(l),o.FileManager=a,o.PluginLoader=St,function(e,t){t.logLevel=void 0!==t.logLevel?t.logLevel:"development"===t.env?3:1,t.loggers||(t.loggers=[{debug:function(e){t.logLevel>=4&&console.log(e)},info:function(e){t.logLevel>=3&&console.log(e)},warn:function(e){t.logLevel>=2&&console.warn(e)},error:function(e){t.logLevel>=1&&console.error(e)}}]);for(var n=0;n