From 3b031593853c6907b7c670b142133b04329840e5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 13 Nov 2019 13:12:06 +0000 Subject: [PATCH 01/43] Translated using Weblate (German) Currently translated at 100.0% (1148 of 1148 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 1ba5c1c03..9c2c67ab1 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1233,5 +1233,14 @@ "driveOfflineError": "Die Verbindung zu CryptPad ist verloren gegangen. Änderungen an diesem Pad werden nicht in deinem CryptDrive gespeichert. Bitte schließe alle CryptPad-Tabs und versuche es in einem neuen Fenster erneut. ", "storageStatus": "Speicher:
{0} von {1} belegt", "teams_table": "Rollen", - "teams_table_generic": "Rollen und Berechtigungen" + "teams_table_generic": "Rollen und Berechtigungen", + "teams_table_generic_view": "Ansehen: Zugriff auf Ordner und Pads (nur Lesen)", + "teams_table_generic_edit": "Bearbeiten: Erstellen, Ändern und Löschen von Ordnern und Pads", + "teams_table_generic_admin": "Mitglieder verwalten: Einladen und Entfernen von Mitgliedern, Ändern von Benutzerrollen bis maximal Admin", + "teams_table_generic_own": "Team verwalten: Ändern von Namen und Avatar des Teams, Hinzufügen oder Entfernen von Eigentümerschaften des Teams, Löschung des Teams", + "teams_table_specific": "Ausnahmen", + "teams_table_specificHint": "Dies sind ältere geteilte Ordner, wo Benutzer noch Bearbeitungsrechte haben. Für hier erstellte oder hierhin kopierte Pads gelten Standard-Berechtigungen.", + "teams_table_admins": "Mitglieder verwalten", + "teams_table_owners": "Team verwalten", + "teams_table_role": "Rolle" } From 4a5caabd2d24d8750c27e76700ccb6621389153d Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 14:13:57 +0100 Subject: [PATCH 02/43] Fix 'Invited' members in team roster --- www/teams/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/teams/inner.js b/www/teams/inner.js index 23c333891..d33a214c1 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -754,7 +754,7 @@ define([ }); var pending = Object.keys(roster).filter(function (k) { if (!roster[k].pending) { return; } - return roster[k].role === "MEMBER" || !roster[k].role; + return roster[k].role === "VIEWER" || !roster[k].role; }).map(function (k) { return makeMember(common, roster[k], me); }); From 68f0a5fda0af13736fd9f1aa4b98ddd0fee1c44b Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 14:29:23 +0100 Subject: [PATCH 03/43] Fix 'Invited' members in team roster again --- www/teams/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/teams/inner.js b/www/teams/inner.js index d33a214c1..974f75d28 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -754,7 +754,7 @@ define([ }); var pending = Object.keys(roster).filter(function (k) { if (!roster[k].pending) { return; } - return roster[k].role === "VIEWER" || !roster[k].role; + return roster[k].role === "MEMBER" || roster[k].role === "VIEWER" || !roster[k].role; }).map(function (k) { return makeMember(common, roster[k], me); }); From 6b81bba3a5aeeaf871c9d33e683a7fabb88444cb Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 16:52:54 +0100 Subject: [PATCH 04/43] Press enter to add a new item in kanban --- www/kanban/inner.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 74abb7f8d..fb3ed93c1 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -152,6 +152,9 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + if (!$(el).closest('.kanban-item').is(':last-child')) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { @@ -301,6 +304,8 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { From 63ea882545110cb0da693d7ad0a620c390a4e9f9 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 18:01:53 +0100 Subject: [PATCH 05/43] Preserve cursor with remote changes in kanban --- www/kanban/inner.js | 72 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index fb3ed93c1..2b469cb3f 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -390,6 +390,75 @@ define([ $container.addClass('cp-app-readonly'); }); + var getSelectedElement = function () { + var node = document.getSelection().anchorNode; + return (node.nodeType === 3 ? node.parentNode : node); + }; + var getCursor = function () { + if (!kanban || !kanban.inEditMode) { return; } + try { + var el = getSelectedElement(); + var input = $(el).is('input') ? el : $(el).find('input')[0]; + var $item = $(el).closest('.kanban-item'); + + var pos = kanban.findElementPosition($item[0]); + var board = $item.closest('.kanban-board').attr('data-id'); + var val = ($(input).val && $(input).val()) || ''; + var start = input.selectionStart; + var end = input.selectionEnd; + + var boardEl = kanban.options.boards.find(function (b) { + return b.id === board; + }); + var oldVal = boardEl.item[pos] || {}; + + return { + board: board, + pos: pos, + value: val, + start: start, + end: end, + oldValue: oldVal.title + }; + } catch (e) { + return {}; + } + }; + var restoreCursor = function (data) { + try { + var boardEl = kanban.options.boards.find(function (b) { + return b.id === data.board; + }); + if (!boardEl) { return; } + + // An item was added: add a new item + if (!data.oldValue) { + var $board = $('.kanban-board[data-id="'+data.board+'"'); + $board.find('.kanban-title-button.fa-plus').click(); + var $newInput = $board.find('.kanban-item:last-child input'); + $newInput.val(data.value); + $newInput[0].selectionStart = data.start; + $newInput[0].selectionEnd = data.end; + return; + } + + // An item was edited: click on the correct item + var newVal = boardEl.item[data.pos]; + if (!newVal || newVal.title !== data.oldValue) { return; } + var $el = $('.kanban-board[data-id="' + data.board + '"]') + .find('.kanban-item:nth-child('+(data.pos + 1)+')'); + $el.find('.kanban-item-text').click(); + var $input = $el.find('input'); + if ($input.length) { + $input.val(data.value); + $input[0].selectionStart = data.start; + $input[0].selectionEnd = data.end; + } + } catch (e) { + return; + } + }; + framework.onContentUpdate(function (newContent) { // Init if needed if (!kanban) { @@ -404,11 +473,12 @@ define([ var remoteContent = newContent.content; if (Sortify(currentContent) !== Sortify(remoteContent)) { - // reinit kanban (TODO: optimize to diff only) + var cursor = getCursor(); verbose("Content is different.. Applying content"); kanban.setBoards(remoteContent); kanban.inEditMode = false; addRemoveItemButton(framework, kanban); + restoreCursor(cursor); } }); From b4cc525ca614e6466ab3486d6f999b67a2ec63ae Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 16:52:54 +0100 Subject: [PATCH 06/43] Press enter to add a new item in kanban --- www/kanban/inner.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 74abb7f8d..fb3ed93c1 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -152,6 +152,9 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + if (!$(el).closest('.kanban-item').is(':last-child')) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { @@ -301,6 +304,8 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { From 8d3e6293cef9aafbab0601a362679b4195a29e54 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 13 Nov 2019 16:52:54 +0100 Subject: [PATCH 07/43] Press enter to add a new item in kanban --- www/kanban/inner.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 74abb7f8d..fb3ed93c1 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -152,6 +152,9 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + if (!$(el).closest('.kanban-item').is(':last-child')) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { @@ -301,6 +304,8 @@ define([ e.preventDefault(); e.stopPropagation(); save(); + if (!$input.val()) { return; } + $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click(); return; } if (e.which === 27) { From c9c19b83950c993419baba0a85d90553c2a7e23a Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 14 Nov 2019 11:44:23 +0100 Subject: [PATCH 08/43] Offline drive (or team) detection --- www/common/outer/async-store.js | 14 ++++++++------ www/common/outer/team.js | 18 ++++++++++++------ www/drive/inner.js | 9 ++++----- www/drive/main.js | 4 ++-- www/teams/inner.js | 11 ++++++----- www/teams/main.js | 17 ++++++++++++----- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 8d29353c9..5900601e8 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1835,8 +1835,9 @@ define([ //var data = cmdData.data; var s = getStore(cmdData.teamId); if (s.offline) { - broadcast([], 'NETWORK_DISCONNECT'); - return void cb({ error: 'OFFLINE' }); + var send = s.id ? s.sendEvent : sendDriveEvent; + send('NETWORK_DISCONNECT'); + return void cb({ error: 'OFFLINE' }); } var cb2 = function (data2) { // Send the CHANGE event to all the stores because the command may have @@ -2291,17 +2292,18 @@ define([ if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { rt.network.disconnect(); rt.realtime.abort(); - broadcast([], 'NETWORK_DISCONNECT'); + sendDriveEvent('NETWORK_DISCONNECT'); } }); + // Proxy handlers (reconnect only called when the proxy is ready) rt.proxy.on('disconnect', function () { store.offline = true; - broadcast([], 'NETWORK_DISCONNECT'); + sendDriveEvent('NETWORK_DISCONNECT'); }); - rt.proxy.on('reconnect', function (info) { + rt.proxy.on('reconnect', function () { store.offline = false; - broadcast([], 'NETWORK_RECONNECT', {myId: info.myId}); + sendDriveEvent('NETWORK_RECONNECT'); }); // Ping clients regularly to make sure one tab was not closed without sending a removeClient() diff --git a/www/common/outer/team.js b/www/common/outer/team.js index b1be2baea..9c33a722a 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -57,6 +57,14 @@ define([ return false; } }); + proxy.on('disconnect', function () { + team.offline = true; + team.sendEvent('NETWORK_DISCONNECT'); + }); + proxy.on('reconnect', function () { + team.offline = false; + team.sendEvent('NETWORK_RECONNECT'); + }); } proxy.on('change', [], function (o, n, p) { if (fId) { @@ -101,12 +109,6 @@ define([ path: p }); }); - proxy.on('disconnect', function () { - team.offline = true; - }); - proxy.on('reconnect', function (/* info */) { - team.offline = false; - }); }; var closeTeam = function (ctx, teamId) { @@ -1363,6 +1365,10 @@ define([ removeClient(ctx, clientId); }; team.execCommand = function (clientId, obj, cb) { + if (ctx.store.offline) { + return void cb({ error: 'OFFLINE' }); + } + var cmd = obj.cmd; var data = obj.data; if (cmd === 'SUBSCRIBE') { diff --git a/www/drive/inner.js b/www/drive/inner.js index e2415d2d0..03bf73697 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -274,10 +274,10 @@ define([ APP.toolbar.failed(); if (!noAlert) { UI.alert(Messages.common_connectionLost, undefined, true); } }; - var onReconnect = function (info) { + var onReconnect = function () { setEditable(true); if (drive.refresh) { drive.refresh(); } - APP.toolbar.reconnecting(info.myId); + APP.toolbar.reconnecting(); UI.findOKButton().click(); }; @@ -287,9 +287,8 @@ define([ sframeChan.on('EV_NETWORK_DISCONNECT', function () { onDisconnect(); }); - sframeChan.on('EV_NETWORK_RECONNECT', function (data) { - // data.myId; - onReconnect(data); + sframeChan.on('EV_NETWORK_RECONNECT', function () { + onReconnect(); }); common.onLogout(function () { setEditable(false); }); }); diff --git a/www/drive/main.js b/www/drive/main.js index 0cda8a9cc..bd2e506d4 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -94,8 +94,8 @@ define([ Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); }); - Cryptpad.onNetworkReconnect.reg(function (data) { - sframeChan.event('EV_NETWORK_RECONNECT', data); + Cryptpad.onNetworkReconnect.reg(function () { + sframeChan.event('EV_NETWORK_RECONNECT'); }); Cryptpad.drive.onLog.reg(function (msg) { sframeChan.event('EV_DRIVE_LOG', msg); diff --git a/www/teams/inner.js b/www/teams/inner.js index 974f75d28..4ba4cf2f4 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -368,6 +368,7 @@ define([ var content = []; APP.module.execCommand('LIST_TEAMS', null, function (obj) { if (!obj) { return; } + if (obj.error === "OFFLINE") { return UI.alert(Messages.driveOfflineError); } if (obj.error) { return void console.error(obj.error); } var list = []; var keys = Object.keys(obj).slice(0,3); @@ -453,6 +454,7 @@ define([ name: name }, function (obj) { if (obj && obj.error) { + if (obj.error === "OFFLINE") { return UI.alert(Messages.driveOfflineError); } console.error(obj.error); $spinner.hide(); return void UI.warn(Messages.error); @@ -1125,10 +1127,10 @@ define([ toolbar.failed(); if (!noAlert) { UI.alert(Messages.common_connectionLost, undefined, true); } }; - var onReconnect = function (info) { + var onReconnect = function () { setEditable(true); if (APP.team && driveAPP.refresh) { driveAPP.refresh(); } - toolbar.reconnecting(info.myId); + toolbar.reconnecting(); UI.findOKButton().click(); }; @@ -1138,9 +1140,8 @@ define([ sframeChan.on('EV_NETWORK_DISCONNECT', function () { onDisconnect(); }); - sframeChan.on('EV_NETWORK_RECONNECT', function (data) { - // data.myId; - onReconnect(data); + sframeChan.on('EV_NETWORK_RECONNECT', function () { + onReconnect(); }); common.onLogout(function () { setEditable(false); }); }); diff --git a/www/teams/main.js b/www/teams/main.js index d37de141c..f12308207 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -69,14 +69,21 @@ define([ Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); }); - Cryptpad.onNetworkReconnect.reg(function (data) { - sframeChan.event('EV_NETWORK_RECONNECT', data); + Cryptpad.onNetworkReconnect.reg(function () { + sframeChan.event('EV_NETWORK_RECONNECT'); }); Cryptpad.universal.onEvent.reg(function (obj) { // Intercept events for the team drive and send them the required way - if (obj.type !== 'team' || - ['DRIVE_CHANGE', 'DRIVE_LOG', 'DRIVE_REMOVE'].indexOf(obj.data.ev) === -1) { return; } - sframeChan.event('EV_'+obj.data.ev, obj.data.data); + if (obj.type !== 'team') { return; } + if (['DRIVE_CHANGE', 'DRIVE_LOG', 'DRIVE_REMOVE'].indexOf(obj.data.ev) !== -1) { + sframeChan.event('EV_'+obj.data.ev, obj.data.data); + } + if (obj.data.ev === 'NETWORK_RECONNECT') { + sframeChan.event('EV_NETWORK_RECONNECT'); + } + if (obj.data.ev === 'NETWORK_DISCONNECT') { + sframeChan.event('EV_NETWORK_DISCONNECT'); + } }); }; SFCommonO.start({ From 99f46888d37b688c4aa1dfa87e6c13f4c99a9fd9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 14 Nov 2019 08:30:17 -0500 Subject: [PATCH 09/43] add changelog that I forgot to commit/push --- CHANGELOG.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714d02d59..12df2ec6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,43 @@ -# Elasmotherium release notes +# FalklandWolf release (3.5.0) + +## Goals + +This release features work that we've been planning for a long time centered around sharing collections of documents in a more granular way. + +This is our first release since David Benqué joined our team, so in addition to these team-centric updates we also worked on integrating some UI/UX improvements. + +## Update notes + +Updating to 3.5.0 from 3.4.0 is simple. + +1. stop your server +2. pull the latest code via git +3. run `bower update` +4. restart your server + +## Features + +* We restyled some elements throughout the platform: + * our tooltips have a sleeker flat design + * the quota bar which appears in the drive, teams, and settings pages has also been improved + * we've begun improving the look and feel of various popup dialogs +* We've added support for password-change for owned uploaded files and owned shared folders: + * changing passwords for encrypted files means that the original file will be removed from the server and a new file will be encrypted with a new key and uploaded to a new location on the server. References to the original file will be broken. This includes links, media-tags embedded within pads, and items in other users' drives or shared folders to which you do not have access. + * the process is very similar for shared folders stored in users' CryptDrives, except that users will have the opportunity to enter the new password when they visit the platform. +* We're very happy to finally introduce the notion of _read-only shared folders_. While we've had the capacity to make shared folders read-only for some time, it was only in the same sense as pads were read-only. + * This is to say that while a viewer cannot modify the document, any links to encrypted documents within that document would confer their natural editing rights to viewers, making it possible to accidentally leak access when a single pad was shared. + * Our new read-only shared folders encrypt the editing keys for the documents they contain, such that only those with the ability to change the folder structure itself have the inherent capacity to edit the documents contained within. We think this is more intuitive than the alternative, but it took a lot of work to make it happen! + * Unfortunately, older shared folders created before this release will already contain the cryptographic keys which confer editing rights. Pads which are added to shared folders from this release onward will have the keys for their editing rights encrypted. We'll offer the ability for owners to migrate these shared folders in an upcoming release once we've added the ability to selectively trim document history. +* Similarly, we've introduced the notion of _viewers_ in teams. Viewers are listed in the team roster and have the ability to view the contents of the team's drive, but not to edit them or add new documents. + * Unfortunately, the notion of viewers is also complicated by the fact that documents added to team drives or shared folders in team drives did not have their editing keys encrypted. The first team member to open the team drive since we've deployed this release will run a migration that will encrypt the keys saved within the team drive, however, the encryption keys will remain in the drive's history until we develop a means of selectively trimming history. + +## Bug fixes + +* We discovered and fixed some bugs in the serverside code responsible for handling some aspects of file upload related to starting a new upload after having cancelled a previous session. +* We also identified a regression in Our _slides_ app related to the rendering of `
` tags, such as you might create with a `****` sequence in the corresponding markdown. This was introduced with some overly broad CSS that was intended to style our notifications page. We've since made the notifications styles more specific such that they can't interfere with other applications. +* We've become aware of some mysterious behaviour in Firefox that seems to cause some tabs or functionality to reconnect to the server after going offline while other aspects of the platform did not. Until now we've always assumed that users were connected or not, and this partial connection has revealed some bugs in our implementation. Consequently, we've begun adding some measures to detect odd behaviour if it occurs. We expect to have determined the cause of this behaviour and to have proposed a solution by our next release. + +# Elasmotherium release (3.4.0) ## Goals From 2fd31e8d8929fcf6fa5bf576403cb6240fab648a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 18 Nov 2019 10:56:24 +0100 Subject: [PATCH 10/43] Improve password prompt UI --- .../src/less2/include/password-input.less | 23 +++++++++- www/common/common-interface.js | 42 +++++++++++++------ www/common/common-ui-elements.js | 2 +- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less index a2f2fb044..79824bac7 100644 --- a/customize.dist/src/less2/include/password-input.less +++ b/customize.dist/src/less2/include/password-input.less @@ -9,8 +9,27 @@ flex: 1; min-width: 0; } - label, .fa { - margin-left: 10px; + .fa { + width: 30px; + height: 30px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + &:hover { + background-color: rgba(0,0,0,0.1); + } + } + } + .cp-password-change-container { + display: flex; + align-items: center; + .cp-password-container { + margin-bottom: 0 !important; + flex: 1; + } + button { + margin: 0 !important; } } } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 72851a8cc..fbeb6a3cc 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -590,9 +590,10 @@ define([ }, opts); var input = h('input.cp-password-input', attributes); - var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show); + //var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show); var eye = h('span.fa.fa-eye.cp-password-reveal'); + /* $(reveal).find('input').on('change', function () { if($(this).is(':checked')) { $(input).prop('type', 'text'); @@ -602,26 +603,41 @@ define([ $(input).prop('type', 'password'); $(input).focus(); }); + */ - $(eye).mousedown(function () { - $(input).prop('type', 'text'); - $(input).focus(); - }).mouseup(function(){ - $(input).prop('type', 'password'); - $(input).focus(); - }).mouseout(function(){ - $(input).prop('type', 'password'); - $(input).focus(); - }); if (displayEye) { + $(eye).mousedown(function () { + $(input).prop('type', 'text'); + $(input).focus(); + }).mouseup(function(){ + $(input).prop('type', 'password'); + $(input).focus(); + }).mouseout(function(){ + $(input).prop('type', 'password'); + $(input).focus(); + }); + } else { + $(eye).click(function () { + if ($(this).hasClass('fa-eye')) { + $(input).prop('type', 'text'); + $(input).focus(); + $(this).removeClass('fa-eye').addClass('fa-eye-slash'); + return; + } + $(input).prop('type', 'password'); + $(input).focus(); + $(this).removeClass('fa-eye-slash').addClass('fa-eye'); + }); + } + /*if (displayEye) { $(reveal).hide(); } else { $(eye).hide(); - } + }*/ return h('span.cp-password-container', [ input, - reveal, + //reveal, eye ]); }; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index df8836788..8943f67b5 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -570,7 +570,7 @@ define([ style: 'flex: 1;' }); var passwordOk = h('button', Messages.properties_changePasswordButton); - var changePass = h('span.cp-password-container', [ + var changePass = h('span.cp-password-change-container', [ newPassword, passwordOk ]); From e7ba8904793e0764ab7f3129fa5c92e4ea9baef5 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 18 Nov 2019 14:56:46 +0100 Subject: [PATCH 11/43] Fix href re-encryption on setPadTitle --- www/common/outer/userObject.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 6ba0cd6d2..dd1f30692 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -45,6 +45,8 @@ define([ var ids = id ? [id] : exp.findChannels([channel]); ids.forEach(function (i) { var data = exp.getFileData(i, true); + var oldHref = exp.getHref(data); + if (oldHref === href) { return; } data.href = exp.cryptor.encrypt(href); }); }; From 3bdca60c7697fdc67dac834cfbaec2f27fe073d4 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 10:40:55 +0100 Subject: [PATCH 12/43] Display the prefilled password page when receiving a password-protected pad --- www/common/common-ui-elements.js | 9 +++++++-- www/common/sframe-common-outer.js | 13 ++++++++----- www/common/sframe-common.js | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8943f67b5..126450f0f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3463,19 +3463,24 @@ define([ (cb || function () {})(); }; - UIElements.displayPasswordPrompt = function (common, isError) { + UIElements.displayPasswordPrompt = function (common, cfg, isError) { var error; if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } var info = h('p.cp-password-info', Messages.password_info); var password = UI.passwordInput({placeholder: Messages.password_placeholder}); var button = h('button', Messages.password_submit); + cfg = cfg || {}; + + if (cfg.value && !isError) { + $(password).find('.cp-password-input').val(cfg.value); + } var submit = function () { var value = $(password).find('.cp-password-input').val(); UI.addLoadingScreen(); common.getSframeChannel().query('Q_PAD_PASSWORD_VALUE', value, function (err, data) { if (!data) { - UIElements.displayPasswordPrompt(common, true); + UIElements.displayPasswordPrompt(common, cfg, true); } }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 224a9f73c..a2c50ca6b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -206,7 +206,7 @@ define([ // 2c: 'view' pad and '/p/' and a wrong password stored --> the seed is incorrect // 2d: 'view' pad and '/p/' and password never stored (security feature) --> password-prompt - var askPassword = function (wrongPasswordStored) { + var askPassword = function (wrongPasswordStored, cfg) { // Ask for the password and check if the pad exists // If the pad doesn't exist, it means the password isn't correct // or the pad has been deleted @@ -250,11 +250,14 @@ define([ // Not a file, so we can use `isNewChannel` Cryptpad.isNewChannel(window.location.href, password, next); }); - sframeChan.event("EV_PAD_PASSWORD"); + sframeChan.event("EV_PAD_PASSWORD", cfg); }; var done = waitFor(); var stored = false; + var passwordCfg = { + value: '' + }; nThen(function (w) { Cryptpad.getPadAttribute('title', w(function (err, data) { stored = (!err && typeof (data) === "string"); @@ -264,7 +267,7 @@ define([ }), parsed.getUrl()); }).nThen(function (w) { if (!password && !stored && sessionStorage.newPadPassword) { - password = sessionStorage.newPadPassword; + passwordCfg.value = sessionStorage.newPadPassword; delete sessionStorage.newPadPassword; } @@ -274,7 +277,7 @@ define([ Cryptpad.getFileSize(window.location.href, password, w(function (e, size) { if (size !== 0) { return void todo(); } // Wrong password or deleted file? - askPassword(true); + askPassword(true, passwordCfg); })); return; } @@ -293,7 +296,7 @@ define([ return void todo(); } // Wrong password or deleted file? - askPassword(true); + askPassword(true, passwordCfg); })); }).nThen(done); } diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index c73e43996..f8bbb205b 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -595,8 +595,8 @@ define([ UI.addTooltips(); - ctx.sframeChan.on("EV_PAD_PASSWORD", function () { - UIElements.displayPasswordPrompt(funcs); + ctx.sframeChan.on("EV_PAD_PASSWORD", function (cfg) { + UIElements.displayPasswordPrompt(funcs, cfg); }); ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () { From fd36825f451af0407aa242f2546ef07553f3fbaa Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 12:07:05 +0100 Subject: [PATCH 13/43] Fix Team Drive is read only even for owners #464 --- www/common/outer/sharedfolder.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index e4d8d152c..b6b961428 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -309,7 +309,9 @@ define([ isNewChannel: Store.isNewChannel }, id, sf, waitFor()); }); - }).nThen(waitFor()); + }).nThen(function () { + setTimeout(waitFor()); + }); }; return SF; From a297336746870d398be11da7ac3421802e7cbaf0 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 14:01:56 +0100 Subject: [PATCH 14/43] Fix broken link in cryptpad-docker.md --- docs/cryptpad-docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cryptpad-docker.md b/docs/cryptpad-docker.md index 12018902e..5998dc143 100644 --- a/docs/cryptpad-docker.md +++ b/docs/cryptpad-docker.md @@ -2,7 +2,7 @@ Cryptpad includes support for building a Docker image and running it to provide a Cryptpad instance. You can manage the container manually, or let Docker Compose manage it for you. -A full tutorial is available [on the Cryptpad Github wiki](https://github.com/xwiki-labs/cryptpad/wiki/Docker-(with-Nginx-and-Traefik)). This document provides a brief overview. +A full tutorial is available [on the Cryptpad Github wiki](https://github.com/xwiki-labs/cryptpad/wiki/Docker). This document provides a brief overview. ## Features From 870607c338a802a0bd16acab7ffb8222ed4e8263 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 15:20:39 +0100 Subject: [PATCH 15/43] Preserve kanban title edition when receiving remote changes --- www/kanban/inner.js | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 2b469cb3f..c2d845557 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -399,18 +399,24 @@ define([ try { var el = getSelectedElement(); var input = $(el).is('input') ? el : $(el).find('input')[0]; - var $item = $(el).closest('.kanban-item'); + if (!input) { return; } + var $input = $(input); - var pos = kanban.findElementPosition($item[0]); - var board = $item.closest('.kanban-board').attr('data-id'); - var val = ($(input).val && $(input).val()) || ''; + var pos; + var $item = $(el).closest('.kanban-item'); + if ($item.length) { + pos = kanban.findElementPosition($item[0]); + } + var board = $input.closest('.kanban-board').attr('data-id'); + var val = ($input.val && $input.val()) || ''; var start = input.selectionStart; var end = input.selectionEnd; + var boardEl = kanban.options.boards.find(function (b) { return b.id === board; }); - var oldVal = boardEl.item[pos] || {}; + var oldVal = ((pos ? boardEl.item[pos] : boardEl) || {}).title; return { board: board, @@ -418,7 +424,7 @@ define([ value: val, start: start, end: end, - oldValue: oldVal.title + oldValue: oldVal }; } catch (e) { return {}; @@ -431,9 +437,25 @@ define([ }); if (!boardEl) { return; } + var $board = $('.kanban-board[data-id="'+data.board+'"'); + + // Editing a board title... + if (!data.pos && $board.length) { + if (boardEl.title !== data.oldValue) { return; } + $board.find('.kanban-title-board').click(); + var $boardInput = $board.find('.kanban-title-board input'); + $boardInput.val(data.value); + $boardInput[0].selectionStart = data.start; + $boardInput[0].selectionEnd = data.end; + return; + } + // Editing a deleted board title: abort + if (!data.pos) { + return; + } + // An item was added: add a new item if (!data.oldValue) { - var $board = $('.kanban-board[data-id="'+data.board+'"'); $board.find('.kanban-title-button.fa-plus').click(); var $newInput = $board.find('.kanban-item:last-child input'); $newInput.val(data.value); From 741acbd6efb2ff12952981efa409cab1fca8453b Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 15:21:11 +0100 Subject: [PATCH 16/43] Remove XXX --- www/common/sframe-common-outer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index a2c50ca6b..efa904d6e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -223,7 +223,6 @@ define([ if (wrongPasswordStored) { // Store the correct password nThen(function (w) { - // XXX noPasswordStored: return; ? Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl()); Cryptpad.setPadAttribute('channel', secret.channel, w(), parsed.getUrl()); if (parsed.hashData.mode === 'edit') { From da328644132aebfc11850d660da875a1d7c57428 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 15:45:37 +0100 Subject: [PATCH 17/43] Fix other read-only teams issues --- www/common/outer/profile.js | 4 ++-- www/common/outer/team.js | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/www/common/outer/profile.js b/www/common/outer/profile.js index 0e1e1f35e..dd56db6e9 100644 --- a/www/common/outer/profile.js +++ b/www/common/outer/profile.js @@ -20,11 +20,11 @@ define([ } profile.edit = Hash.getEditHashFromKeys(secret); profile.view = Hash.getViewHashFromKeys(secret); - cb(); + setTimeout(cb); }); return; } - cb(); + setTimeout(cb); }; var openChannel = function (ctx) { diff --git a/www/common/outer/team.js b/www/common/outer/team.js index b1be2baea..314d33f88 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -25,10 +25,6 @@ define([ var Nacl = window.nacl; - var initializeTeams = function (ctx, cb) { - cb(); - }; - var registerChangeEvents = function (ctx, team, proxy, fId) { if (!team) { return; } if (!fId) { @@ -338,7 +334,7 @@ define([ }; var openChannel = function (ctx, teamData, id, _cb) { - var cb = Util.once(_cb); + var cb = Util.once(Util.mkAsync(_cb)); var hash = teamData.hash || teamData.roHash; var secret = Hash.getSecrets('team', hash, teamData.password); @@ -1277,10 +1273,6 @@ define([ var teams = store.proxy.teams = store.proxy.teams || {}; - initializeTeams(ctx, waitFor(function (err) { - if (err) { return; } - })); - // Listen for changes in our access rights (if another worker receives edit access) ctx.store.proxy.on('change', ['teams'], function (o, n, p) { if (p[2] !== 'hash') { return; } From 0044d622f134702307b29f6da0f7e95d8a819569 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 15:33:12 +0100 Subject: [PATCH 18/43] Fix missing title in notification history --- www/common/common-ui-elements.js | 1 + www/common/notifications.js | 3 ++- www/notifications/inner.js | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 126450f0f..80dcb0e63 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4035,6 +4035,7 @@ define([ common.mailbox.sendTo("INVITE_TO_TEAM_ANSWER", { answer: yes, teamChannel: msg.content.team.channel, + teamName: teamName, user: { displayName: user.name, avatar: user.avatar, diff --git a/www/common/notifications.js b/www/common/notifications.js index ed44c2917..192d2b4c6 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -294,7 +294,8 @@ define([ // Display the notification var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; - var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || ''); + var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '') || + Util.fixHTML(Util.find(msg, ['content', 'teamName'])); var key = 'team_' + (msg.content.answer ? 'accept' : 'decline') + 'Invitation'; content.getFormatText = function () { return Messages._getKey(key, [name, teamName]); diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 6fc8bf1ca..2e89c5078 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -93,6 +93,10 @@ define([ return ud.content.hash === data.content.hash; }); notifsData.push(data); + if (data.content.msg.type === 'REQUEST_PAD_ACCESS') { return; } // FIXME find a way to display this notifications wihtout knowing the title + if (data.content.msg.type === 'INVITE_TO_TEAM_ANSWER') { console.log(data); } + if (data.content.msg.type === 'INVITE_TO_TEAM_ANSWER' + && !data.content.msg.content.teamName) { return; } // FIXME find a way to display this notifications wihtout knowing the team name var el = common.mailbox.createElement(data); var time = new Date(data.content.time); $(el).find(".cp-notification-content").append(h("span.notification-time", time.toLocaleString())); From 1e2b2a27cfc4118b55a7d85c5a6c1da188ee7fcd Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2019 17:55:33 +0100 Subject: [PATCH 19/43] Fix synchronous nthen callback --- www/common/outer/sharedfolder.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index b6b961428..28780065a 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -21,7 +21,8 @@ define([ // No version: visible edit // Version 2: encrypted edit links - SF.checkMigration = function (secondaryKey, proxy, uo, cb) { + SF.checkMigration = function (secondaryKey, proxy, uo, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); var drive = proxy.drive || proxy; // View access: can't migrate if (!secondaryKey) { return void cb(); } From 2b4c3e9e90783ced8a760c40e806eb64b15ca062 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 20 Nov 2019 17:15:38 +0100 Subject: [PATCH 20/43] Fix team quota --- www/common/outer/sharedfolder.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index 28780065a..907c49cd0 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -83,7 +83,7 @@ define([ }; SF.load = function (config, id, data, _cb) { - var cb = Util.once(_cb); + var cb = Util.once(Util.mkAsync(_cb)); var network = config.network; var store = config.store; var isNew = config.isNew; @@ -189,7 +189,7 @@ define([ return; } sf.teams.forEach(function (obj) { - var leave = function () { SF.leave(secret.channel, teamId); }; + var leave = function () { SF.leave(secret.channel, obj.store.id); }; /* var uo = obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey); // NOTE: Shared folder migration, disable for now @@ -301,6 +301,7 @@ define([ */ SF.loadSharedFolders = function (Store, network, store, userObject, waitFor) { var shared = Util.find(store.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; + var w = waitFor(); nThen(function (waitFor) { Object.keys(shared).forEach(function (id) { var sf = shared[id]; @@ -311,7 +312,7 @@ define([ }, id, sf, waitFor()); }); }).nThen(function () { - setTimeout(waitFor()); + setTimeout(w); }); }; From dc6ca9098e22626e657fee970e7153d6643589eb Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 21 Nov 2019 11:24:51 +0100 Subject: [PATCH 21/43] Improve small screen detection in toolbar --- customize.dist/src/less2/include/toolbar.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index ff588ff77..0df5823a0 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -507,7 +507,8 @@ } } .cp-toolbar-top { - @media screen and (max-width: @browser_media-medium-screen) { + @media screen and (max-width: @browser_media-medium-screen), + screen and (max-height: 500px) { flex-wrap: wrap; height: @toolbar_line-height; .cp-pad-not-pinned { From 9f903b645ac1dcb1ad098adf00a40326ef14607d Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 12:20:07 +0100 Subject: [PATCH 22/43] ckeditor wordcount --- customize.dist/ckeditor-config.js | 2 +- www/pad/inner.js | 1 + www/pad/wordcount/LICENSE.md | 21 + www/pad/wordcount/README.md | 106 +++++ www/pad/wordcount/css/wordcount.css | 3 + www/pad/wordcount/lang/ar.js | 12 + www/pad/wordcount/lang/bg.js | 17 + www/pad/wordcount/lang/ca.js | 14 + www/pad/wordcount/lang/cs.js | 15 + www/pad/wordcount/lang/da.js | 14 + www/pad/wordcount/lang/de.js | 14 + www/pad/wordcount/lang/el.js | 14 + www/pad/wordcount/lang/en.js | 17 + www/pad/wordcount/lang/es.js | 14 + www/pad/wordcount/lang/eu.js | 17 + www/pad/wordcount/lang/fa.js | 13 + www/pad/wordcount/lang/fi.js | 15 + www/pad/wordcount/lang/fr.js | 12 + www/pad/wordcount/lang/he.js | 14 + www/pad/wordcount/lang/hr.js | 14 + www/pad/wordcount/lang/hu.js | 14 + www/pad/wordcount/lang/it.js | 15 + www/pad/wordcount/lang/ja.js | 14 + www/pad/wordcount/lang/ko.js | 16 + www/pad/wordcount/lang/nl.js | 14 + www/pad/wordcount/lang/no.js | 11 + www/pad/wordcount/lang/pl.js | 14 + www/pad/wordcount/lang/pt-br.js | 14 + www/pad/wordcount/lang/pt.js | 10 + www/pad/wordcount/lang/ro.js | 15 + www/pad/wordcount/lang/ru.js | 14 + www/pad/wordcount/lang/sk.js | 15 + www/pad/wordcount/lang/sv.js | 15 + www/pad/wordcount/lang/tr.js | 14 + www/pad/wordcount/lang/uk.js | 17 + www/pad/wordcount/lang/zh-cn.js | 14 + www/pad/wordcount/lang/zh.js | 14 + www/pad/wordcount/plugin.js | 596 ++++++++++++++++++++++++++++ 38 files changed, 1184 insertions(+), 1 deletion(-) create mode 100644 www/pad/wordcount/LICENSE.md create mode 100644 www/pad/wordcount/README.md create mode 100644 www/pad/wordcount/css/wordcount.css create mode 100644 www/pad/wordcount/lang/ar.js create mode 100644 www/pad/wordcount/lang/bg.js create mode 100644 www/pad/wordcount/lang/ca.js create mode 100644 www/pad/wordcount/lang/cs.js create mode 100644 www/pad/wordcount/lang/da.js create mode 100644 www/pad/wordcount/lang/de.js create mode 100644 www/pad/wordcount/lang/el.js create mode 100644 www/pad/wordcount/lang/en.js create mode 100644 www/pad/wordcount/lang/es.js create mode 100644 www/pad/wordcount/lang/eu.js create mode 100644 www/pad/wordcount/lang/fa.js create mode 100644 www/pad/wordcount/lang/fi.js create mode 100644 www/pad/wordcount/lang/fr.js create mode 100644 www/pad/wordcount/lang/he.js create mode 100644 www/pad/wordcount/lang/hr.js create mode 100644 www/pad/wordcount/lang/hu.js create mode 100644 www/pad/wordcount/lang/it.js create mode 100644 www/pad/wordcount/lang/ja.js create mode 100644 www/pad/wordcount/lang/ko.js create mode 100644 www/pad/wordcount/lang/nl.js create mode 100644 www/pad/wordcount/lang/no.js create mode 100644 www/pad/wordcount/lang/pl.js create mode 100644 www/pad/wordcount/lang/pt-br.js create mode 100644 www/pad/wordcount/lang/pt.js create mode 100644 www/pad/wordcount/lang/ro.js create mode 100644 www/pad/wordcount/lang/ru.js create mode 100644 www/pad/wordcount/lang/sk.js create mode 100644 www/pad/wordcount/lang/sv.js create mode 100644 www/pad/wordcount/lang/tr.js create mode 100644 www/pad/wordcount/lang/uk.js create mode 100644 www/pad/wordcount/lang/zh-cn.js create mode 100644 www/pad/wordcount/lang/zh.js create mode 100644 www/pad/wordcount/plugin.js diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index dc138a558..5c4940c52 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'; + config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64,mathjax,wordcount'; config.toolbarGroups= [ // {"name":"clipboard","groups":["clipboard","undo"]}, //{"name":"editing","groups":["find","selection"]}, diff --git a/www/pad/inner.js b/www/pad/inner.js index b3e687228..5b11beaef 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -949,6 +949,7 @@ define([ }; Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js'); Ckeditor.plugins.addExternal('blockbase64','/pad/', 'disable-base64.js'); + Ckeditor.plugins.addExternal('wordcount','/pad/wordcount/', 'plugin.js'); module.ckeditor = editor = Ckeditor.replace('editor1', { customConfig: '/customize/ckeditor-config.js', }); diff --git a/www/pad/wordcount/LICENSE.md b/www/pad/wordcount/LICENSE.md new file mode 100644 index 000000000..be0064ffb --- /dev/null +++ b/www/pad/wordcount/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ingo Herbote + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/www/pad/wordcount/README.md b/www/pad/wordcount/README.md new file mode 100644 index 000000000..7605bb622 --- /dev/null +++ b/www/pad/wordcount/README.md @@ -0,0 +1,106 @@ +CKEditor-WordCount-Plugin +========================= + +WordCount Plugin for CKEditor v4 (or above) that counts the words/characters an shows the word count and/or char count in the footer of the editor. + +![Screenshot](http://www.watchersnet.de/Portals/0/screenshots/dnn/CKEditorWordCountPlugin.png) + +#### Demo + +http://w8tcha.github.com/CKEditor-WordCount-Plugin/ + +DISCLAIMER: This is a forked Version, i can not find the original Author anymore if anyone knows the original Author please contact me and i can include the Author in the Copyright Notices. + +#### License + +Licensed under the terms of the MIT License. + +#### Installation + + If building a new editor using the CKBuilder from http://ckeditor.com/, there is no need to follow numbers steps below. If adding the Word Count & Char Count plugin to an already established CKEditor, follow the numbered steps below. + +1. Download the Word Count & Char Count plugin from http://ckeditor.com/addon/wordcount or https://github.com/w8tcha/CKEditor-WordCount-Plugin. This will download a folder named **wordcount_*version*.zip** or **CKEditor-WordCount-Plugin-master.zip** to your Downloads folder. +2. Download the Notification plugin from http://ckeditor.com/addon/notification. This will download a folder named **notification_*version*.zip** to your Downloads folder. +3. Extract the .zip folders for both the Word Count & Char Count and Notification plugin. After extraction, you should have a folder named **wordcount** and a folder named **notification**. +4. Move the wordcount folder to /web/server/root/ckeditor/plugins/. Move the notification folder to /web/server/root/ckeditor/plugins/. +5. Add the following line of text to the config.js file, which is located at /web/server/root/ckeditor/. + +```javascript +config.extraPlugins = 'wordcount,notification'; +``` + +Below is an example of what your config.js file might look like after adding config.extraPlugins = 'wordcount,notification'; + +```javascript +CKEDITOR.editorConfig = function( config ) { + config.extraPlugins = 'wordcount,notification'; + config.toolbar [ + et cetera . . . + ]; +}; +``` + +There now should be text in the bottom right-hand corner of your CKEditor which counts the number of Paragraphs and number of Words in your CKEditor. + +To modify the behavior of the Word Count & Char Count text at the bottom right-hand corner of your CKEditor, add the following text to your config.js file located at /web/server/root/ckeditor/config.js. + +````js +config.wordcount = { + + // Whether or not you want to show the Paragraphs Count + showParagraphs: true, + + // Whether or not you want to show the Word Count + showWordCount: true, + + // Whether or not you want to show the Char Count + showCharCount: false, + + // Whether or not you want to count Spaces as Chars + countSpacesAsChars: false, + + // Whether or not to include Html chars in the Char Count + countHTML: false, + + // Whether or not to include Line Breaks in the Char Count + countLineBreaks: false, + + // Maximum allowed Word Count, -1 is default for unlimited + maxWordCount: -1, + + // Maximum allowed Char Count, -1 is default for unlimited + maxCharCount: -1, + + // Maximum allowed Paragraphs Count, -1 is default for unlimited + maxParagraphs: -1, + + // How long to show the 'paste' warning, 0 is default for not auto-closing the notification + pasteWarningDuration: 0, + + + // Add filter to add or remove element before counting (see CKEDITOR.htmlParser.filter), Default value : null (no filter) + filter: new CKEDITOR.htmlParser.filter({ + elements: { + div: function( element ) { + if(element.attributes.class == 'mediaembed') { + return false; + } + } + } + }) +}; +```` + +**Note:** If you plan to change some of the JavaScript, you probably will not want to use the CKBuilder, because this will place the JavaScript of the Word Count & Char Count plugin in the ckeditor.js file located at /web/server/root/ckeditor/ckeditor.js. The JavaScript for the Word Count & Char Count plugin in the ckeditor.js file is different than the JavaScript used when manually adding the Word Count & Char Count plugin. When manually adding the Word Count & Char Count plugin, the JavaScript will be in the plugin.js file located at + +--- + +If you want to query the current wordcount you can do it via + +```javascript +// get the word count +CKEDITOR.instances.editor1.wordCount.wordCount + +// get the char count +CKEDITOR.instances.editor1.wordCount.charCount +``` diff --git a/www/pad/wordcount/css/wordcount.css b/www/pad/wordcount/css/wordcount.css new file mode 100644 index 000000000..36f86d41d --- /dev/null +++ b/www/pad/wordcount/css/wordcount.css @@ -0,0 +1,3 @@ +.cke_wordcount {display:block;float:right;margin-top:-2px;margin-right:3px;color:black;} + +.cke_wordcountLimitReached {color:red! important} \ No newline at end of file diff --git a/www/pad/wordcount/lang/ar.js b/www/pad/wordcount/lang/ar.js new file mode 100644 index 000000000..fd9125c46 --- /dev/null +++ b/www/pad/wordcount/lang/ar.js @@ -0,0 +1,12 @@ +// Arabic Translation by Amine BENHAMIDA + +CKEDITOR.plugins.setLang('wordcount', 'ar', { + WordCount: 'كلمات:', + CharCount: 'حروف:', + CharCountWithHTML: 'حروف مع إتش تي إم إل', + Paragraphs: 'فقرات', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'لا يمكن اضافة هذا المحتوى لانه تجاوز الحد الاقصى', + Selected: 'محدد: ', + title: 'احصائيات' +}); diff --git a/www/pad/wordcount/lang/bg.js b/www/pad/wordcount/lang/bg.js new file mode 100644 index 000000000..69e1e3f77 --- /dev/null +++ b/www/pad/wordcount/lang/bg.js @@ -0,0 +1,17 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'bg', { + WordCount: 'Думи:', + WordCountRemaining: 'Оставащи думи', + CharCount: 'Знаци:', + CharCountRemaining: 'Знаци', + CharCountWithHTML: 'Знаци (с HTML):', + CharCountWithHTMLRemaining: 'Оставащи знаци (с HTML)', + Paragraphs: 'Параграфи:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Съдържанието не може да бъде поставено, защото е над разрешения лимит', + Selected: 'Избрани: ', + title: 'Статистика' +}); diff --git a/www/pad/wordcount/lang/ca.js b/www/pad/wordcount/lang/ca.js new file mode 100644 index 000000000..2c8f4948a --- /dev/null +++ b/www/pad/wordcount/lang/ca.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'ca', { + WordCount: 'Paraules:', + CharCount: 'Caràcters:', + CharCountWithHTML: 'Caràcters (including HTML):', + Paragraphs: 'Paragraphs:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Estadístiques' +}); diff --git a/www/pad/wordcount/lang/cs.js b/www/pad/wordcount/lang/cs.js new file mode 100644 index 000000000..616c64365 --- /dev/null +++ b/www/pad/wordcount/lang/cs.js @@ -0,0 +1,15 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'cs', + { + WordCount: 'Slov: ', + CharCount: 'Znaků: ', + CharCountWithHTML: 'Znaků (s HTML): ', + Paragraphs: 'Odstavců: ', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Obsah nelze vložit', + Selected: 'Výběr: ', + title: 'Statistika' + }); \ No newline at end of file diff --git a/www/pad/wordcount/lang/da.js b/www/pad/wordcount/lang/da.js new file mode 100644 index 000000000..ada03a553 --- /dev/null +++ b/www/pad/wordcount/lang/da.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'da', { + WordCount: 'Ord:', + CharCount: 'Karakterer:', + CharCountWithHTML: 'Karakterer (med HTML):', + Paragraphs: 'Afsnit:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Indholdet kan ikke indsættes da det er længere end den tilladte grænse.', + Selected: 'Markeret: ', + title: 'Statistik' +}); diff --git a/www/pad/wordcount/lang/de.js b/www/pad/wordcount/lang/de.js new file mode 100644 index 000000000..47501ab40 --- /dev/null +++ b/www/pad/wordcount/lang/de.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'de', { + WordCount: 'Wörter:', + CharCount: 'Zeichen:', + CharCountWithHTML: 'Zeichen (inkl. HTML):', + Paragraphs: 'Absätze:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Statistik' +}); diff --git a/www/pad/wordcount/lang/el.js b/www/pad/wordcount/lang/el.js new file mode 100644 index 000000000..76d0cc421 --- /dev/null +++ b/www/pad/wordcount/lang/el.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'el', { + WordCount: 'Λέξεις:', + CharCount: 'Χαρακτήρες:', + CharCountWithHTML: 'Χαρακτήρες (μαζί με HTML):', + Paragraphs: 'Paragraphs:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Στατιστικά' +}); diff --git a/www/pad/wordcount/lang/en.js b/www/pad/wordcount/lang/en.js new file mode 100644 index 000000000..bb566ee47 --- /dev/null +++ b/www/pad/wordcount/lang/en.js @@ -0,0 +1,17 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'en', { + WordCount: 'Words:', + WordCountRemaining: 'Words remaining', + CharCount: 'Characters:', + CharCountRemaining: 'Characters remaining', + CharCountWithHTML: 'Characters (with HTML):', + CharCountWithHTMLRemaining: 'Characters (with HTML) remaining', + Paragraphs: 'Paragraphs:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content cannot be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Statistics' +}); diff --git a/www/pad/wordcount/lang/es.js b/www/pad/wordcount/lang/es.js new file mode 100644 index 000000000..03abaa7ce --- /dev/null +++ b/www/pad/wordcount/lang/es.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'es', { + WordCount: 'Palabras:', + CharCount: 'Carácteres:', + CharCountWithHTML: 'Carácteres (con HTML):', + Paragraphs: 'Párrafos:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'El contenido no se puede pegar, ya que se encuentra fuera del límite permitido', + Selected: 'Seleccionado: ', + title: 'Estadísticas' +}); diff --git a/www/pad/wordcount/lang/eu.js b/www/pad/wordcount/lang/eu.js new file mode 100644 index 000000000..a8b61e66d --- /dev/null +++ b/www/pad/wordcount/lang/eu.js @@ -0,0 +1,17 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'eu', { + WordCount: 'Hitzak:', + WordCountRemaining: 'Gelditzen diren hitzak', + CharCount: 'Karaktereak:', + CharCountRemaining: 'Gelditzen diren karaktereak', + CharCountWithHTML: 'Karaktereak (HTMLarekin):', + CharCountWithHTMLRemaining: 'Gelditzen diren karaktereak (HTMLarekin)', + Paragraphs: 'Paragrafoak:', + ParagraphsRemaining: 'Gelditzen diren paragrafoak', + pasteWarning: 'Ezin da edukia itsatsi, onartutako muga gainditu duelako', + Selected: 'Hautatuta: ', + title: 'Estatistikak' +}); diff --git a/www/pad/wordcount/lang/fa.js b/www/pad/wordcount/lang/fa.js new file mode 100644 index 000000000..46060bcdb --- /dev/null +++ b/www/pad/wordcount/lang/fa.js @@ -0,0 +1,13 @@ +/* +Its The Persian (Farsi) Language Translate For Iranian By "Mohsen Esmaili" +*/ +CKEDITOR.plugins.setLang('wordcount', 'fa', { + WordCount: 'لغت:', + CharCount: 'کاراکتر:', + CharCountWithHTML: 'کاراکترها (با HTML):', + Paragraphs: 'پاراگراف:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'محتوای مورد نظر را نمی توان چسباند. زیرا این بیشتر از حد مجاز است.', + Selected: 'انتخاب شده: ', + title: 'آمار' +}); \ No newline at end of file diff --git a/www/pad/wordcount/lang/fi.js b/www/pad/wordcount/lang/fi.js new file mode 100644 index 000000000..ef0f1fa4b --- /dev/null +++ b/www/pad/wordcount/lang/fi.js @@ -0,0 +1,15 @@ +/** + * Finnish localisation. + * + * @author Joel Posti / Response200.pro + */ +CKEDITOR.plugins.setLang('wordcount', 'fi', { + WordCount: 'Sanoja:', + CharCount: 'Merkkejä:', + CharCountWithHTML: 'Merkkejä (ml. HTML):', + Paragraphs: 'Kappaleita:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Sisältöä ei voida liittää, koska se ylittää sallitun rajan.', + Selected: 'Valittuna: ', + title: 'Statistiikkaa' +}); diff --git a/www/pad/wordcount/lang/fr.js b/www/pad/wordcount/lang/fr.js new file mode 100644 index 000000000..45c56644d --- /dev/null +++ b/www/pad/wordcount/lang/fr.js @@ -0,0 +1,12 @@ +// French Translation by Nicolas M. et Pierre-Luc Auclair + +CKEDITOR.plugins.setLang('wordcount', 'fr', { + WordCount: 'Mots :', + CharCount: 'Caractères :', + CharCountWithHTML: 'Caractères (incluant HTML) :', + Paragraphs: 'Paragraphes :', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Le contenu ne peut pas être collé car il dépasse la limite autorisée', + Selected: 'Sélectionné :', + title: 'Statistiques' +}); diff --git a/www/pad/wordcount/lang/he.js b/www/pad/wordcount/lang/he.js new file mode 100644 index 000000000..4da45ae02 --- /dev/null +++ b/www/pad/wordcount/lang/he.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'he', { + WordCount: 'מילים:', + CharCount: 'תווים:', + CharCountWithHTML: 'תווים (כולל HTML):', + Paragraphs: 'פסקאות:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'לא ניתן להדביק תוכן בשל עודף תווים', + Selected: 'נבחר: ', + title: 'סטטיסטיקות' +}); \ No newline at end of file diff --git a/www/pad/wordcount/lang/hr.js b/www/pad/wordcount/lang/hr.js new file mode 100644 index 000000000..01020b79a --- /dev/null +++ b/www/pad/wordcount/lang/hr.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'hr', { + WordCount: 'Riječi:', + CharCount: 'Znakova:', + CharCountWithHTML: 'Znakova (uključujući HTML):', + Paragraphs: 'Paragraphs:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Statistika' +}); diff --git a/www/pad/wordcount/lang/hu.js b/www/pad/wordcount/lang/hu.js new file mode 100644 index 000000000..11c251e01 --- /dev/null +++ b/www/pad/wordcount/lang/hu.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'hu', { + WordCount: 'Szavak:', + CharCount: 'Karakaterek:', + CharCountWithHTML: 'Karakterek (HTML tagekkel):', + Paragraphs: 'Bekezdések:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'A szöveget nem lehet beilleszteni, mert a megadott limit felett van', + Selected: 'Kiválasztva: ', + title: 'Statisztika' +}); diff --git a/www/pad/wordcount/lang/it.js b/www/pad/wordcount/lang/it.js new file mode 100644 index 000000000..a8b451df9 --- /dev/null +++ b/www/pad/wordcount/lang/it.js @@ -0,0 +1,15 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +@author translation: Davide Montorio +*/ +CKEDITOR.plugins.setLang('wordcount', 'it', { + WordCount: 'Parole:', + CharCount: 'Caratteri:', + CharCountWithHTML: 'Caratteri (HTML incluso):', + Paragraphs: 'Paragrafi:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Il contenuto non può essere incollato poiché supera il limite massimo di caratteri disponibili', + Selected: 'Selezionato: ', + title: 'Statistiche' +}); diff --git a/www/pad/wordcount/lang/ja.js b/www/pad/wordcount/lang/ja.js new file mode 100644 index 000000000..c12f1e19a --- /dev/null +++ b/www/pad/wordcount/lang/ja.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'ja', { + WordCount: '単語数:', + CharCount: '文字数:', + CharCountWithHTML: '文字数 (HTMLタグを含む):', + Paragraphs: '段落数:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: '文字数/単語数の上限を超えるため、貼り付けできません。', + Selected: '選択中の字数:', + title: 'ワードカウント' +}); diff --git a/www/pad/wordcount/lang/ko.js b/www/pad/wordcount/lang/ko.js new file mode 100644 index 000000000..9cff8e1fa --- /dev/null +++ b/www/pad/wordcount/lang/ko.js @@ -0,0 +1,16 @@ +/* +Korean translation by Maxime Houdais +*/ +CKEDITOR.plugins.setLang('wordcount', 'ko', { + WordCount: '단어:', + WordCountRemaining: '남은 단어', + CharCount: '글자:', + CharCountRemaining: '남은 글자', + CharCountWithHTML: '글자 와 HTML:', + CharCountWithHTMLRemaining: '남은 글자 와 HTML', + Paragraphs: '단락:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: '허용 된 한도를 초과하여 콘텐츠를 붙여 넣을 수 없습니다.', + Selected: '선택:', + title: '통계' +}); diff --git a/www/pad/wordcount/lang/nl.js b/www/pad/wordcount/lang/nl.js new file mode 100644 index 000000000..9bcad987a --- /dev/null +++ b/www/pad/wordcount/lang/nl.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'nl', { + WordCount: 'Woorden:', + CharCount: 'Tekens:', + CharCountWithHTML: 'Tekens (inclusief HTML):', + Paragraphs: 'Paragrafen:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'De tekst kan niet worden geplakt omdat de limiet is overschreden', + Selected: 'Geselecteerd: ', + title: 'Statistieken' +}); diff --git a/www/pad/wordcount/lang/no.js b/www/pad/wordcount/lang/no.js new file mode 100644 index 000000000..2c33fe528 --- /dev/null +++ b/www/pad/wordcount/lang/no.js @@ -0,0 +1,11 @@ +// Norwegian translation by Vegard S. +CKEDITOR.plugins.setLang('wordcount', 'no', { + WordCount: 'Ord:', + CharCount: 'Tegn:', + CharCountWithHTML: 'Tegn (including HTML):', + Paragraphs: 'Paragraphs:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'Statistikk' +}); diff --git a/www/pad/wordcount/lang/pl.js b/www/pad/wordcount/lang/pl.js new file mode 100644 index 000000000..e6a3d2e07 --- /dev/null +++ b/www/pad/wordcount/lang/pl.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'pl', { + WordCount: 'Słów:', + CharCount: 'Znaków:', + CharCountWithHTML: 'Znaków (wraz z kodem HTML):', + Paragraphs: 'Paragrafy:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Zawartość nie może zostać wklejona, ponieważ przekracza dozwolony limit', + Selected: 'Zaznaczono: ', + title: 'Statystyka' +}); diff --git a/www/pad/wordcount/lang/pt-br.js b/www/pad/wordcount/lang/pt-br.js new file mode 100644 index 000000000..393ac5eca --- /dev/null +++ b/www/pad/wordcount/lang/pt-br.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'pt-br', { + WordCount: 'Contagem de palavras:', + CharCount: 'Contagem de carateres:', + CharCountWithHTML: 'Carateres (incluindo HTML):', + Paragraphs: 'Parágrafos:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Conteúdo não pode ser colado porque ultrapassa o limite permitido', + Selected: 'Selecionado: ', + title: 'Estatísticas' +}); diff --git a/www/pad/wordcount/lang/pt.js b/www/pad/wordcount/lang/pt.js new file mode 100644 index 000000000..8d28496e2 --- /dev/null +++ b/www/pad/wordcount/lang/pt.js @@ -0,0 +1,10 @@ +CKEDITOR.plugins.setLang('wordcount', 'pt', { + WordCount: 'Palavras:', + CharCount: 'Caracteres:', + CharCountWithHTML: 'Carateres (incluindo HTML):', + Paragraphs: 'Parágrafos:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'O conteúdo não pode ser colado porque ultrapassa o limite permitido', + Selected: 'Selecionado: ', + title: 'Estatísticas' +}); diff --git a/www/pad/wordcount/lang/ro.js b/www/pad/wordcount/lang/ro.js new file mode 100644 index 000000000..4f214065a --- /dev/null +++ b/www/pad/wordcount/lang/ro.js @@ -0,0 +1,15 @@ +// Romanian Translation by Bogdanov Mihail + +CKEDITOR.plugins.setLang('wordcount', 'ro', { + WordCount: 'Numar cuvinte', + WordCountRemaining: 'Cuvinte ramase', + CharCount: 'Numar caracter:', + CharCountRemaining: 'Caractere ramase:', + CharCountWithHTML: 'Numar caractere (cu HTML):', + CharCountWithHTMLRemaining: 'Caractere (cu HTML) ramase', + Paragraphs: 'Paragrafe:', + ParagraphsRemaining: 'Paragrafe ramase', + pasteWarning: 'Continutul nu poate fi adaugat deoarece este mai mare decat limita setata', + Selected: 'Selectat:', + title: 'Statistici' +}); \ No newline at end of file diff --git a/www/pad/wordcount/lang/ru.js b/www/pad/wordcount/lang/ru.js new file mode 100644 index 000000000..d5dcb42a9 --- /dev/null +++ b/www/pad/wordcount/lang/ru.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'ru', { + WordCount: 'Слов:', + CharCount: 'Символов:', + CharCountWithHTML: ' (включая HTML-разметку):', + Paragraphs: 'Параграфов:', + ParagraphsRemaining: 'Параграфов осталось', + pasteWarning: 'Контент не может быть вставлен, т.к. привышает допустимый лимит', + Selected: 'Выделено: ', + title: 'Статистика' +}); diff --git a/www/pad/wordcount/lang/sk.js b/www/pad/wordcount/lang/sk.js new file mode 100644 index 000000000..87388ad78 --- /dev/null +++ b/www/pad/wordcount/lang/sk.js @@ -0,0 +1,15 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'sk', { + WordCount: 'Slov:', + CharCount: 'Znakov:', + CharCountWithHTML: 'Znakov (vrátane HTML):', + Paragraphs: 'Odstavcov:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Obsah sa nedá prilepiť.', + Selected: 'Výber: ', + title: 'Štatistika' +}); + diff --git a/www/pad/wordcount/lang/sv.js b/www/pad/wordcount/lang/sv.js new file mode 100644 index 000000000..8d4b12ee3 --- /dev/null +++ b/www/pad/wordcount/lang/sv.js @@ -0,0 +1,15 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'sv', { + WordCount: 'Ord:', + CharCount: 'Tecken:', + CharCountRemaining: 'tecken återstår', + CharCountWithHTML: 'Tecken (inklusive HTML):', + Paragraphs: 'Paragraf:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Innehåll kan inte klistras in eftersom det överskrider den tillåtna gränsen', + Selected: 'Valt: ', + title: 'Statistik' +}); diff --git a/www/pad/wordcount/lang/tr.js b/www/pad/wordcount/lang/tr.js new file mode 100644 index 000000000..33953f037 --- /dev/null +++ b/www/pad/wordcount/lang/tr.js @@ -0,0 +1,14 @@ +/* +Mesut ÇAKIR +mesut.cakir@hotmail.com.tr +*/ +CKEDITOR.plugins.setLang('wordcount', 'tr', { + WordCount: 'Kelime:', + CharCount: 'Karakter:', + CharCountWithHTML: 'Karakter (HTML dahil):', + Paragraphs: 'Paragraf:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: 'Content can not be pasted because it is above the allowed limit', + Selected: 'Selected: ', + title: 'İstatistik' +}); diff --git a/www/pad/wordcount/lang/uk.js b/www/pad/wordcount/lang/uk.js new file mode 100644 index 000000000..d5914be3b --- /dev/null +++ b/www/pad/wordcount/lang/uk.js @@ -0,0 +1,17 @@ +/* + Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. + For licensing, see LICENSE.html or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang('wordcount', 'uk', { + WordCount: 'Слів:', + WordCountRemaining: 'Слів залишилося', + CharCount: 'Символів:', + CharCountRemaining: 'Символів залишилося', + CharCountWithHTML: 'Символів (включаючи HTML-розмітку):', + CharCountWithHTMLRemaining: 'Символів (включаючи HTML-розмітку) залишилося', + Paragraphs: 'Параграфів:', + ParagraphsRemaining: 'Параграфів залишилося', + pasteWarning: 'Контент не може бути вставлено, оскільки перевищує допустимий ліміт', + Selected: 'Виділено: ', + title: 'Статистика' +}); diff --git a/www/pad/wordcount/lang/zh-cn.js b/www/pad/wordcount/lang/zh-cn.js new file mode 100644 index 000000000..288a7f34a --- /dev/null +++ b/www/pad/wordcount/lang/zh-cn.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'zh-cn', { + WordCount: '词数:', + CharCount: '字符:', + CharCountWithHTML: '字符 (含HTML)', + Paragraphs: '段落:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: '由于上限允许,内容不能粘贴', + Selected: '已选择: ', + title: '统计' +}); diff --git a/www/pad/wordcount/lang/zh.js b/www/pad/wordcount/lang/zh.js new file mode 100644 index 000000000..8d0df17b6 --- /dev/null +++ b/www/pad/wordcount/lang/zh.js @@ -0,0 +1,14 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang('wordcount', 'zh', { + WordCount: '詞數:', + CharCount: '字數:', + CharCountWithHTML: '字數 (含HTML)', + Paragraphs: '段落:', + ParagraphsRemaining: 'Paragraphs remaining', + pasteWarning: '由於字數達到上限,內容不能粘貼', + Selected: '已選擇: ', + title: '統計' +}); \ No newline at end of file diff --git a/www/pad/wordcount/plugin.js b/www/pad/wordcount/plugin.js new file mode 100644 index 000000000..c47cb694a --- /dev/null +++ b/www/pad/wordcount/plugin.js @@ -0,0 +1,596 @@ +/** + * @license Copyright (c) CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + */ + +CKEDITOR.plugins.add("wordcount", + { + lang: "ar,bg,ca,cs,da,de,el,en,es,eu,fa,fi,fr,he,hr,hu,it,ko,ja,nl,no,pl,pt,pt-br,ru,sk,sv,tr,uk,zh-cn,zh,ro", // %REMOVE_LINE_CORE% + version: "1.17.6", + requires: 'htmlwriter,notification,undo', + bbcodePluginLoaded: false, + onLoad: function() { + CKEDITOR.document.appendStyleSheet(this.path + "css/wordcount.css"); + }, + init: function(editor) { + var defaultFormat = "", + lastWordCount = -1, + lastCharCount = -1, + lastParagraphs = -1, + limitReachedNotified = false, + limitRestoredNotified = false, + timeoutId = 0, + notification = null; + + + var dispatchEvent = function(type, currentLength, maxLength) { + if (typeof document.dispatchEvent == 'undefined') { + return; + } + + type = 'ckeditor.wordcount.' + type; + + var cEvent; + var eventInitDict = { + bubbles: false, + cancelable: true, + detail: { + currentLength: currentLength, + maxLength: maxLength + } + }; + + try { + cEvent = new CustomEvent(type, eventInitDict); + } catch (o_O) { + cEvent = document.createEvent('CustomEvent'); + cEvent.initCustomEvent( + type, + eventInitDict.bubbles, + eventInitDict.cancelable, + eventInitDict.detail + ); + } + + document.dispatchEvent(cEvent); + }; + + // Default Config + var defaultConfig = { + showRemaining: false, + showParagraphs: true, + showWordCount: true, + showCharCount: false, + countBytesAsChars: false, + countSpacesAsChars: false, + countHTML: false, + countLineBreaks: false, + hardLimit: true, + warnOnLimitOnly: false, + + //MAXLENGTH Properties + maxWordCount: -1, + maxCharCount: -1, + maxParagraphs: -1, + + // Filter + filter: null, + + // How long to show the 'paste' warning + pasteWarningDuration: 0, + + //DisAllowed functions + wordCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) { + dispatchEvent('wordCountGreaterThanMaxLengthEvent', currentLength, maxLength); + }, + charCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) { + dispatchEvent('charCountGreaterThanMaxLengthEvent', currentLength, maxLength); + }, + + //Allowed Functions + wordCountLessThanMaxLengthEvent: function(currentLength, maxLength) { + dispatchEvent('wordCountLessThanMaxLengthEvent', currentLength, maxLength); + }, + charCountLessThanMaxLengthEvent: function(currentLength, maxLength) { + dispatchEvent('charCountLessThanMaxLengthEvent', currentLength, maxLength); + } + }; + + // Get Config & Lang + var config = CKEDITOR.tools.extend(defaultConfig, editor.config.wordcount || {}, true); + + if (config.showParagraphs) { + if (config.maxParagraphs > -1) { + if (config.showRemaining) { + defaultFormat += "%paragraphsCount% " + editor.lang.wordcount.ParagraphsRemaining; + } else { + defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%"; + + defaultFormat += "/" + config.maxParagraphs; + } + } else { + defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%"; + } + } + + if (config.showParagraphs && (config.showWordCount || config.showCharCount)) { + defaultFormat += ", "; + } + + if (config.showWordCount) { + if (config.maxWordCount > -1) { + if (config.showRemaining) { + defaultFormat += "%wordCount% " + editor.lang.wordcount.WordCountRemaining; + } else { + defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%"; + + defaultFormat += "/" + config.maxWordCount; + } + } else { + defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%"; + } + } + + if (config.showCharCount && config.showWordCount) { + defaultFormat += ", "; + } + + if (config.showCharCount) { + if (config.maxCharCount > -1) { + if (config.showRemaining) { + defaultFormat += "%charCount% " + + editor.lang.wordcount[config.countHTML + ? "CharCountWithHTMLRemaining" + : "CharCountRemaining"]; + } else { + defaultFormat += editor.lang.wordcount[config.countHTML + ? "CharCountWithHTML" + : "CharCount"] + + " %charCount%"; + + defaultFormat += "/" + config.maxCharCount; + } + } else { + defaultFormat += editor.lang.wordcount[config.countHTML ? "CharCountWithHTML" : "CharCount"] + + " %charCount%"; + } + } + + var format = defaultFormat; + + bbcodePluginLoaded = typeof editor.plugins.bbcode != 'undefined'; + + function counterId(editorInstance) { + return "cke_wordcount_" + editorInstance.name; + } + + function counterElement(editorInstance) { + return document.getElementById(counterId(editorInstance)); + } + + function strip(html) { + if (bbcodePluginLoaded) { + // stripping out BBCode tags [...][/...] + return html.replace(/\[.*?\]/gi, ''); + } + + var tmp = document.createElement("div"); + + // Add filter before strip + html = filter(html); + + tmp.innerHTML = html; + + if (tmp.textContent == "" && typeof tmp.innerText == "undefined") { + return ""; + } + + return tmp.textContent || tmp.innerText; + } + + /** + * Implement filter to add or remove before counting + * @param html + * @returns string + */ + function filter(html) { + if (config.filter instanceof CKEDITOR.htmlParser.filter) { + var fragment = CKEDITOR.htmlParser.fragment.fromHtml(html), + writer = new CKEDITOR.htmlParser.basicWriter(); + config.filter.applyTo(fragment); + fragment.writeHtml(writer); + return writer.getHtml(); + } + return html; + } + + function countCharacters(text) { + if (config.countHTML) { + return config.countBytesAsChars ? countBytes(filter(text)) : filter(text).length; + } + + var normalizedText; + + // strip body tags + if (editor.config.fullPage) { + var i = text.search(new RegExp("", "i")); + if (i != -1) { + var j = text.search(new RegExp("", "i")); + text = text.substring(i + 6, j); + } + + } + + normalizedText = text; + + if (!config.countSpacesAsChars) { + normalizedText = text.replace(/\s/g, "").replace(/ /g, ""); + } + + if (config.countLineBreaks) { + normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, " "); + } else { + normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, "").replace(/ /gi, " "); + } + + normalizedText = strip(normalizedText).replace(/^([\t\r\n]*)$/, ""); + + return config.countBytesAsChars ? countBytes(normalizedText) : normalizedText.length; + } + + function countBytes(text) { + var count = 0, stringLength = text.length, i; + text = String(text || ""); + for (i = 0; i < stringLength; i++) { + var partCount = encodeURI(text[i]).split("%").length; + count += partCount == 1 ? 1 : partCount - 1; + } + return count; + } + + function countParagraphs(text) { + return (text.replace(/ /g, " ").replace(/(<([^>]+)>)/ig, "").replace(/^\s*$[\n\r]{1,}/gm, "++") + .split("++").length); + } + + function countWords(text) { + var normalizedText = text.replace(/(\r\n|\n|\r)/gm, " ").replace(/^\s+|\s+$/g, "") + .replace(" ", " "); + + normalizedText = strip(normalizedText); + + var words = normalizedText.split(/\s+/); + + for (var wordIndex = words.length - 1; wordIndex >= 0; wordIndex--) { + if (words[wordIndex].match(/^([\s\t\r\n]*)$/)) { + words.splice(wordIndex, 1); + } + } + + return (words.length); + } + + function limitReached(editorInstance, notify) { + limitReachedNotified = true; + limitRestoredNotified = false; + + if (!config.warnOnLimitOnly) { + if (config.hardLimit) { + editorInstance.execCommand('undo'); + } + } + + if (!notify) { + counterElement(editorInstance).className = "cke_path_item cke_wordcountLimitReached"; + editorInstance.fire("limitReached", { firedBy: "wordCount.limitReached" }, editor); + } + } + + function limitRestored(editorInstance) { + limitRestoredNotified = true; + limitReachedNotified = false; + + if (!config.warnOnLimitOnly) { + editorInstance.fire('saveSnapshot'); + } + + counterElement(editorInstance).className = "cke_path_item"; + } + + function updateCounter(editorInstance) { + if (!counterElement(editorInstance)) { + return; + } + + var paragraphs = 0, + wordCount = 0, + charCount = 0, + text; + + // BeforeGetData and getData events are fired when calling + // getData(). We can prevent this by passing true as an + // argument to getData(). This allows us to fire the events + // manually with additional event data: firedBy. This additional + // data helps differentiate calls to getData() made by + // wordCount plugin from calls made by other plugins/code. + editorInstance.fire("beforeGetData", { firedBy: "wordCount.updateCounter" }, editor); + text = editorInstance.getData(true); + editorInstance.fire("getData", { dataValue: text, firedBy: "wordCount.updateCounter" }, editor); + + if (text) { + if (config.showCharCount) { + charCount = countCharacters(text); + } + + if (config.showParagraphs) { + paragraphs = countParagraphs(text); + } + + if (config.showWordCount) { + wordCount = countWords(text); + } + } + + var html = format; + if (config.showRemaining) { + if (config.maxCharCount >= 0) { + html = html.replace("%charCount%", config.maxCharCount - charCount); + } else { + html = html.replace("%charCount%", charCount); + } + + if (config.maxWordCount >= 0) { + html = html.replace("%wordCount%", config.maxWordCount - wordCount); + } else { + html = html.replace("%wordCount%", wordCount); + } + + if (config.maxParagraphs >= 0) { + html = html.replace("%paragraphsCount%", config.maxParagraphs - paragraphs); + } else { + html = html.replace("%paragraphsCount%", paragraphs); + } + } else { + html = html.replace("%wordCount%", wordCount).replace("%charCount%", charCount).replace("%paragraphsCount%", paragraphs); + } + + (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).wordCount = wordCount; + (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).charCount = charCount; + + if (CKEDITOR.env.gecko) { + counterElement(editorInstance).innerHTML = html; + } else { + counterElement(editorInstance).innerText = html; + } + + if (charCount == lastCharCount && wordCount == lastWordCount && paragraphs == lastParagraphs) { + if (charCount == config.maxCharCount || wordCount == config.maxWordCount || paragraphs > config.maxParagraphs) { + editorInstance.fire('saveSnapshot'); + } + return true; + } + + //If the limit is already over, allow the deletion of characters/words. Otherwise, + //the user would have to delete at one go the number of offending characters + var deltaWord = wordCount - lastWordCount; + var deltaChar = charCount - lastCharCount; + var deltaParagraphs = paragraphs - lastParagraphs; + + lastWordCount = wordCount; + lastCharCount = charCount; + lastParagraphs = paragraphs; + + if (lastWordCount == -1) { + lastWordCount = wordCount; + } + if (lastCharCount == -1) { + lastCharCount = charCount; + } + if (lastParagraphs == -1) { + lastParagraphs = paragraphs; + } + + // Check for word limit and/or char limit + if ((config.maxWordCount > -1 && wordCount > config.maxWordCount && deltaWord > 0) || + (config.maxCharCount > -1 && charCount > config.maxCharCount && deltaChar > 0) || + (config.maxParagraphs > -1 && paragraphs > config.maxParagraphs && deltaParagraphs > 0)) { + + limitReached(editorInstance, limitReachedNotified); + } else if ((config.maxWordCount == -1 || wordCount <= config.maxWordCount) && + (config.maxCharCount == -1 || charCount <= config.maxCharCount) && + (config.maxParagraphs == -1 || paragraphs <= config.maxParagraphs)) { + + limitRestored(editorInstance); + } else { + editorInstance.fire('saveSnapshot'); + } + + // update instance + editorInstance.wordCount = + { + paragraphs: paragraphs, + wordCount: wordCount, + charCount: charCount + }; + + + // Fire Custom Events + if (config.charCountGreaterThanMaxLengthEvent && config.charCountLessThanMaxLengthEvent) { + if (charCount > config.maxCharCount && config.maxCharCount > -1) { + config.charCountGreaterThanMaxLengthEvent(charCount, config.maxCharCount); + } else { + config.charCountLessThanMaxLengthEvent(charCount, config.maxCharCount); + } + } + + if (config.wordCountGreaterThanMaxLengthEvent && config.wordCountLessThanMaxLengthEvent) { + if (wordCount > config.maxWordCount && config.maxWordCount > -1) { + config.wordCountGreaterThanMaxLengthEvent(wordCount, config.maxWordCount); + + } else { + config.wordCountLessThanMaxLengthEvent(wordCount, config.maxWordCount); + } + } + + return true; + } + + function isCloseToLimits() { + if (config.maxWordCount > -1 && config.maxWordCount - lastWordCount < 5) { + return true; + } + + if (config.maxCharCount > -1 && config.maxCharCount - lastCharCount < 20) { + return true; + } + + if (config.maxParagraphs > -1 && config.maxParagraphs - lastParagraphs < 1) { + return true; + } + + return false; + } + + editor.on("key", + function(event) { + if (editor.mode === "source") { + clearTimeout(timeoutId); + timeoutId = setTimeout( + updateCounter.bind(this, event.editor), + 250 + ); + } + }, + editor, + null, + 100); + + editor.on("change", + function(event) { + var ms = isCloseToLimits() ? 5 : 250; + clearTimeout(timeoutId); + timeoutId = setTimeout( + updateCounter.bind(this, event.editor), + ms + ); + }, + editor, + null, + 100); + + editor.on("uiSpace", + function(event) { + if (editor.elementMode === CKEDITOR.ELEMENT_MODE_INLINE) { + if (event.data.space == "top") { + event.data.html += "
 
"; + } + } else { + if (event.data.space == "bottom") { + event.data.html += "
 
"; + } + } + + }, + editor, + null, + 100); + + editor.on("dataReady", + function(event) { + updateCounter(event.editor); + }, + editor, + null, + 100); + + editor.on("paste", + function(event) { + if (!config.warnOnLimitOnly && (config.maxWordCount > 0 || config.maxCharCount > 0 || config.maxParagraphs > 0)) { + + // Check if pasted content is above the limits + var wordCount = -1, + charCount = -1, + paragraphs = -1; + + // BeforeGetData and getData events are fired when calling + // getData(). We can prevent this by passing true as an + // argument to getData(). This allows us to fire the events + // manually with additional event data: firedBy. This additional + // data helps differentiate calls to getData() made by + // wordCount plugin from calls made by other plugins/code. + event.editor.fire("beforeGetData", { firedBy: "wordCount.onPaste" }, event.editor); + var text = event.editor.getData(true); + event.editor.fire("getData", { dataValue: text, firedBy: "wordCount.onPaste" }, event.editor); + + text += event.data.dataValue; + + if (config.showCharCount) { + charCount = countCharacters(text); + } + + if (config.showWordCount) { + wordCount = countWords(text); + } + + if (config.showParagraphs) { + paragraphs = countParagraphs(text); + } + + + // Instantiate the notification when needed and only have one instance + if (notification === null) { + notification = new CKEDITOR.plugins.notification(event.editor, + { + message: event.editor.lang.wordcount.pasteWarning, + type: 'warning', + duration: config.pasteWarningDuration + }); + } + + if (config.maxCharCount > 0 && charCount > config.maxCharCount && config.hardLimit) { + if (!notification.isVisible()) { + notification.show(); + } + event.cancel(); + } + + if (config.maxWordCount > 0 && wordCount > config.maxWordCount && config.hardLimit) { + if (!notification.isVisible()) { + notification.show(); + } + event.cancel(); + } + + if (config.maxParagraphs > 0 && paragraphs > config.maxParagraphs && config.hardLimit) { + if (!notification.isVisible()) { + notification.show(); + } + event.cancel(); + } + } + }, + editor, + null, + 100); + + editor.on("afterPaste", + function(event) { + updateCounter(event.editor); + }, + editor, + null, + 100); + } + }); From a573255072647d27356c3642adcd38eadf0ebc8e Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 15:50:32 +0100 Subject: [PATCH 23/43] Word count integration --- www/pad/app-pad.less | 7 + www/pad/inner.js | 13 ++ www/pad/wordcount/plugin.js | 282 +----------------------------------- 3 files changed, 28 insertions(+), 274 deletions(-) diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index 51b8f7d0b..ca8b10d18 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -17,6 +17,13 @@ body.cp-app-pad { height: 28px; padding: 2px 0; } + .cp-app-pad-wordCount { + float: right; + display: inline-flex; + height: 24px; + align-items: center; + padding: 4px; + } } .cke_wysiwyg_frame { width: 100%; diff --git a/www/pad/inner.js b/www/pad/inner.js index 5b11beaef..8b73510b2 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -616,6 +616,8 @@ define([ var patch = (DD).diff(inner, userDocStateDom); (DD).apply(inner, patch); + editor.fire('cp-wc'); // Update word count + // Restore cursor position var newText = inner.outerHTML; var ops = ChainPad.Diff.diff(oldText, newText); @@ -835,9 +837,20 @@ define([ inner.addEventListener('input', function () { framework.localChange(); updateCursor(); + editor.fire('cp-wc'); // Update word count }); editor.on('change', framework.localChange); + var wordCount = h('span.cp-app-pad-wordCount'); + $('.cke_toolbox_main').append(wordCount); + editor.on('cp-wc-update', function (event) { + if (!editor.wordCount || typeof (editor.wordCount.wordCount) === "undefined") { + wordCount.innerText = ''; + return; + } + wordCount.innerText = Messages._getKey('pad_wordCount', [editor.wordCount.wordCount]); + }); + // export the typing tests to the window. // call like `test = easyTest()` // terminate the test like `test.cancel()` diff --git a/www/pad/wordcount/plugin.js b/www/pad/wordcount/plugin.js index c47cb694a..c544ea9f5 100644 --- a/www/pad/wordcount/plugin.js +++ b/www/pad/wordcount/plugin.js @@ -17,44 +17,9 @@ CKEDITOR.plugins.add("wordcount", lastWordCount = -1, lastCharCount = -1, lastParagraphs = -1, - limitReachedNotified = false, - limitRestoredNotified = false, timeoutId = 0, notification = null; - - var dispatchEvent = function(type, currentLength, maxLength) { - if (typeof document.dispatchEvent == 'undefined') { - return; - } - - type = 'ckeditor.wordcount.' + type; - - var cEvent; - var eventInitDict = { - bubbles: false, - cancelable: true, - detail: { - currentLength: currentLength, - maxLength: maxLength - } - }; - - try { - cEvent = new CustomEvent(type, eventInitDict); - } catch (o_O) { - cEvent = document.createEvent('CustomEvent'); - cEvent.initCustomEvent( - type, - eventInitDict.bubbles, - eventInitDict.cancelable, - eventInitDict.detail - ); - } - - document.dispatchEvent(cEvent); - }; - // Default Config var defaultConfig = { showRemaining: false, @@ -75,25 +40,6 @@ CKEDITOR.plugins.add("wordcount", // Filter filter: null, - - // How long to show the 'paste' warning - pasteWarningDuration: 0, - - //DisAllowed functions - wordCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) { - dispatchEvent('wordCountGreaterThanMaxLengthEvent', currentLength, maxLength); - }, - charCountGreaterThanMaxLengthEvent: function(currentLength, maxLength) { - dispatchEvent('charCountGreaterThanMaxLengthEvent', currentLength, maxLength); - }, - - //Allowed Functions - wordCountLessThanMaxLengthEvent: function(currentLength, maxLength) { - dispatchEvent('wordCountLessThanMaxLengthEvent', currentLength, maxLength); - }, - charCountLessThanMaxLengthEvent: function(currentLength, maxLength) { - dispatchEvent('charCountLessThanMaxLengthEvent', currentLength, maxLength); - } }; // Get Config & Lang @@ -160,14 +106,6 @@ CKEDITOR.plugins.add("wordcount", bbcodePluginLoaded = typeof editor.plugins.bbcode != 'undefined'; - function counterId(editorInstance) { - return "cke_wordcount_" + editorInstance.name; - } - - function counterElement(editorInstance) { - return document.getElementById(counterId(editorInstance)); - } - function strip(html) { if (bbcodePluginLoaded) { // stripping out BBCode tags [...][/...] @@ -270,38 +208,7 @@ CKEDITOR.plugins.add("wordcount", return (words.length); } - function limitReached(editorInstance, notify) { - limitReachedNotified = true; - limitRestoredNotified = false; - - if (!config.warnOnLimitOnly) { - if (config.hardLimit) { - editorInstance.execCommand('undo'); - } - } - - if (!notify) { - counterElement(editorInstance).className = "cke_path_item cke_wordcountLimitReached"; - editorInstance.fire("limitReached", { firedBy: "wordCount.limitReached" }, editor); - } - } - - function limitRestored(editorInstance) { - limitRestoredNotified = true; - limitReachedNotified = false; - - if (!config.warnOnLimitOnly) { - editorInstance.fire('saveSnapshot'); - } - - counterElement(editorInstance).className = "cke_path_item"; - } - function updateCounter(editorInstance) { - if (!counterElement(editorInstance)) { - return; - } - var paragraphs = 0, wordCount = 0, charCount = 0, @@ -357,12 +264,6 @@ CKEDITOR.plugins.add("wordcount", (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).wordCount = wordCount; (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).charCount = charCount; - if (CKEDITOR.env.gecko) { - counterElement(editorInstance).innerHTML = html; - } else { - counterElement(editorInstance).innerText = html; - } - if (charCount == lastCharCount && wordCount == lastWordCount && paragraphs == lastParagraphs) { if (charCount == config.maxCharCount || wordCount == config.maxWordCount || paragraphs > config.maxParagraphs) { editorInstance.fire('saveSnapshot'); @@ -390,21 +291,6 @@ CKEDITOR.plugins.add("wordcount", lastParagraphs = paragraphs; } - // Check for word limit and/or char limit - if ((config.maxWordCount > -1 && wordCount > config.maxWordCount && deltaWord > 0) || - (config.maxCharCount > -1 && charCount > config.maxCharCount && deltaChar > 0) || - (config.maxParagraphs > -1 && paragraphs > config.maxParagraphs && deltaParagraphs > 0)) { - - limitReached(editorInstance, limitReachedNotified); - } else if ((config.maxWordCount == -1 || wordCount <= config.maxWordCount) && - (config.maxCharCount == -1 || charCount <= config.maxCharCount) && - (config.maxParagraphs == -1 || paragraphs <= config.maxParagraphs)) { - - limitRestored(editorInstance); - } else { - editorInstance.fire('saveSnapshot'); - } - // update instance editorInstance.wordCount = { @@ -412,25 +298,7 @@ CKEDITOR.plugins.add("wordcount", wordCount: wordCount, charCount: charCount }; - - - // Fire Custom Events - if (config.charCountGreaterThanMaxLengthEvent && config.charCountLessThanMaxLengthEvent) { - if (charCount > config.maxCharCount && config.maxCharCount > -1) { - config.charCountGreaterThanMaxLengthEvent(charCount, config.maxCharCount); - } else { - config.charCountLessThanMaxLengthEvent(charCount, config.maxCharCount); - } - } - - if (config.wordCountGreaterThanMaxLengthEvent && config.wordCountLessThanMaxLengthEvent) { - if (wordCount > config.maxWordCount && config.maxWordCount > -1) { - config.wordCountGreaterThanMaxLengthEvent(wordCount, config.maxWordCount); - - } else { - config.wordCountLessThanMaxLengthEvent(wordCount, config.maxWordCount); - } - } + editor.fire('cp-wc-update'); return true; } @@ -451,146 +319,12 @@ CKEDITOR.plugins.add("wordcount", return false; } - editor.on("key", - function(event) { - if (editor.mode === "source") { - clearTimeout(timeoutId); - timeoutId = setTimeout( - updateCounter.bind(this, event.editor), - 250 - ); - } - }, - editor, - null, - 100); - - editor.on("change", - function(event) { - var ms = isCloseToLimits() ? 5 : 250; - clearTimeout(timeoutId); - timeoutId = setTimeout( - updateCounter.bind(this, event.editor), - ms - ); - }, - editor, - null, - 100); - - editor.on("uiSpace", - function(event) { - if (editor.elementMode === CKEDITOR.ELEMENT_MODE_INLINE) { - if (event.data.space == "top") { - event.data.html += "
 
"; - } - } else { - if (event.data.space == "bottom") { - event.data.html += "
 
"; - } - } - - }, - editor, - null, - 100); - - editor.on("dataReady", - function(event) { - updateCounter(event.editor); - }, - editor, - null, - 100); - - editor.on("paste", - function(event) { - if (!config.warnOnLimitOnly && (config.maxWordCount > 0 || config.maxCharCount > 0 || config.maxParagraphs > 0)) { - - // Check if pasted content is above the limits - var wordCount = -1, - charCount = -1, - paragraphs = -1; - - // BeforeGetData and getData events are fired when calling - // getData(). We can prevent this by passing true as an - // argument to getData(). This allows us to fire the events - // manually with additional event data: firedBy. This additional - // data helps differentiate calls to getData() made by - // wordCount plugin from calls made by other plugins/code. - event.editor.fire("beforeGetData", { firedBy: "wordCount.onPaste" }, event.editor); - var text = event.editor.getData(true); - event.editor.fire("getData", { dataValue: text, firedBy: "wordCount.onPaste" }, event.editor); - - text += event.data.dataValue; - - if (config.showCharCount) { - charCount = countCharacters(text); - } - - if (config.showWordCount) { - wordCount = countWords(text); - } - - if (config.showParagraphs) { - paragraphs = countParagraphs(text); - } - - - // Instantiate the notification when needed and only have one instance - if (notification === null) { - notification = new CKEDITOR.plugins.notification(event.editor, - { - message: event.editor.lang.wordcount.pasteWarning, - type: 'warning', - duration: config.pasteWarningDuration - }); - } - - if (config.maxCharCount > 0 && charCount > config.maxCharCount && config.hardLimit) { - if (!notification.isVisible()) { - notification.show(); - } - event.cancel(); - } - - if (config.maxWordCount > 0 && wordCount > config.maxWordCount && config.hardLimit) { - if (!notification.isVisible()) { - notification.show(); - } - event.cancel(); - } - - if (config.maxParagraphs > 0 && paragraphs > config.maxParagraphs && config.hardLimit) { - if (!notification.isVisible()) { - notification.show(); - } - event.cancel(); - } - } - }, - editor, - null, - 100); - - editor.on("afterPaste", - function(event) { - updateCounter(event.editor); - }, - editor, - null, - 100); + editor.on('cp-wc', function(event) { + clearTimeout(timeoutId); + timeoutId = setTimeout( + updateCounter.bind(this, event.editor), + 250 + ); + }, editor, null, 250); } }); From 65585f6ce8dabfc2451f623b36415556ca0b26ae Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 15:52:30 +0100 Subject: [PATCH 24/43] lint compliance --- .jshintignore | 1 + www/pad/inner.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.jshintignore b/.jshintignore index 3dce1dfc9..9a7d8b1c7 100644 --- a/.jshintignore +++ b/.jshintignore @@ -19,6 +19,7 @@ www/pad/wysiwygarea-plugin.js www/pad/mediatag-plugin.js www/pad/mediatag-plugin-dialog.js www/pad/disable-base64.js +www/pad/wordcount/ www/kanban/jkanban.js www/common/jscolor.js diff --git a/www/pad/inner.js b/www/pad/inner.js index 8b73510b2..5db93837c 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -843,7 +843,7 @@ define([ var wordCount = h('span.cp-app-pad-wordCount'); $('.cke_toolbox_main').append(wordCount); - editor.on('cp-wc-update', function (event) { + editor.on('cp-wc-update', function () { if (!editor.wordCount || typeof (editor.wordCount.wordCount) === "undefined") { wordCount.innerText = ''; return; From be7b184d97ca73446d98af43991e71c8e75f7e5b Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 16:01:32 +0100 Subject: [PATCH 25/43] Fix space bar shortcut to dselect a radio input --- www/common/common-interface.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index fbeb6a3cc..3cfc24de7 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1007,6 +1007,7 @@ define([ if (e.which === 32) { e.stopPropagation(); e.preventDefault(); + if ($(input).is(':checked')) { return; } $(input).prop('checked', !$(input).is(':checked')); $(input).change(); } From 47342dd1c09469eab379e96bbfa34cfbf73b98a1 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:45 +0000 Subject: [PATCH 26/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.ca.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.ca.json b/www/common/translations/messages.ca.json index dd4c9c4ff..1b38959a6 100644 --- a/www/common/translations/messages.ca.json +++ b/www/common/translations/messages.ca.json @@ -170,7 +170,6 @@ "viewOpenTitle": "Obre aquest document en mode només de lectura en una pestanya nova", "fileShare": "Copia l'enllaç", "getEmbedCode": "Obté el codi d'incrustat", - "viewEmbedTitle": "Inscrusta el document en una pàgina externa", "viewEmbedTag": "Per incrustar aquest document, poseu aquest iframe allà on vulgueu de la vostra pàgina. Podeu donar-li l'estil desitjat utilitzant els atributs CSS o HTML.", "fileEmbedTitle": "Incrusta el fitxer en una pàgina externa", "fileEmbedScript": "Per incrustar aquest fitxer, poseu aquest script un cop a la vostra pàgina per carregar l'Etiqueta Multimèdia:", From 0eb5c3e5b5d1d6682f22fd804ce6eed7fc8c7aea Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 27/43] Translated using Weblate (French) Currently translated at 100.0% (1147 of 1147 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.fr.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index bd15572e7..618562d11 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -170,7 +170,6 @@ "viewOpenTitle": "Ouvrir le lien en lecture seule dans un nouvel onglet", "fileShare": "Copier le lien", "getEmbedCode": "Obtenir le code d'intégration", - "viewEmbedTitle": "Intégrer le pad dans une page web", "viewEmbedTag": "Pour intégrer ce pad, veuillez inclure l'iframe suivant dans votre page là où vous souhaitez l'afficher. Vous pouvez changer sa taille en utilisant du code CSS ou des attributs HTML.", "fileEmbedTitle": "Intégrer le fichier dans une page web", "fileEmbedScript": "Pour intégrer un fichier, veuillez inclure le script suivant une fois dans votre page afin de pouvoir charger le Media Tag :", @@ -962,15 +961,14 @@ "properties_passwordWarning": "Le mot de passe a été modifié avec succès mais nous n'avons pas réussi à mettre à jour votre CryptDrive avec les nouvelles informations. Vous devrez peut-être supprimer manuellement l'ancienne version de ce pad.
Appuyez sur OK pour recharger le pad et mettre à jour vos droits d'accès.", "properties_passwordSuccess": "Le mot de passe a été modifié avec succès.
Appuyez sur OK pour mettre à jour vos droits d'accès.", "properties_changePasswordButton": "Valider", - "share_linkCategory": "Partage", + "share_linkCategory": "Lien", "share_linkAccess": "Droits d'accès", "share_linkEdit": "Édition", "share_linkView": "Lecture-seule", - "share_linkOptions": "Options du lien", - "share_linkEmbed": "Mode intégration (barre d'outils cachée)", - "share_linkPresent": "Mode présentation (sections d'édition cachées)", - "share_linkOpen": "Ouvrir le lien", - "share_linkCopy": "Copier le lien", + "share_linkEmbed": "Mode intégration (cache la barre d'outils)", + "share_linkPresent": "Présenter", + "share_linkOpen": "Apperçu", + "share_linkCopy": "Copier", "share_embedCategory": "Intégration", "share_mediatagCopy": "Copier le mediatag", "loading_pad_1": "Initialisation du pad", @@ -984,7 +982,7 @@ "sharedFolders_create_name": "Nom du dossier", "sharedFolders_create_owned": "Être propriétaire du dossier", "sharedFolders_create_password": "Mot de passe du dossier", - "sharedFolders_share": "Partager cette URL avec d'autres utilisateurs enregistrés leur donne accès au dossier partagé. Une fois l'URL ouverte, le dossier partagé sera ajouté au répertoire racine de leur CryptDrive.", + "sharedFolders_share": "Partager ce lien avec d'autres utilisateurs enregistrés leur donne accès au dossier partagé. Une fois le lien ouvert, le dossier partagé sera ajouté à leur CryptDrive.", "chrome68": "Il semblerait que vous utilisiez le navigateur Chrome version 68. Ce navigateur contient un bug rendant certaines pages entièrement blanches après quelques secondes ou bloquant les clics. Pour corriger ce problème, vous pouvez vous déplacer vers un nouvel onglet et revenir ou vous pouvez essayer de faire défiler la page. Ce bug devrait être corrigé dans la prochaine version du navigateur.", "autostore_file": "fichier", "autostore_sf": "dossier", @@ -1242,5 +1240,6 @@ "teams_table_specificHint": "Dans ces dossiers partagés qui sont d'une version antérieure, les lecteurs peuvent modifier les pads existants. Les pads créés ou copiés dans ces dossiers auront les permissions standard.", "teams_table_admins": "Gérer les membres", "teams_table_owners": "Gérer l'équipe", - "teams_table_role": "Rôle" + "teams_table_role": "Rôle", + "share_contactCategory": "Contacts" } From 1193933509bffce53387c0969bd918c390eea177 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 28/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.de.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 9c2c67ab1..a6deb8059 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -168,7 +168,6 @@ "viewOpenTitle": "Pad schreibgeschützt in neuem Tab öffnen", "fileShare": "Link kopieren", "getEmbedCode": "Einbettungscode anzeigen", - "viewEmbedTitle": "Das Pad in eine externe Webseite einbetten", "viewEmbedTag": "Um dieses Pad einzubetten, platziere diesen iframe an der gewünschten Stelle deiner HTML-Seite. Du kannst es mit CSS oder HTML-Attributen gestalten.", "fileEmbedTitle": "Die Datei in einer externen Seite einbetten", "fileEmbedScript": "Um diese Datei einzubetten, füge dieses Skript einmal in deiner Webseite ein, damit das Media-Tag geladen wird:", @@ -959,7 +958,6 @@ "share_linkAccess": "Zugriffsrechte", "share_linkEdit": "Bearbeiten", "share_linkView": "Ansehen", - "share_linkOptions": "Linkoptionen", "share_linkEmbed": "Einbettungsmodus (Werkzeugleiste und Benutzerliste sind verborgen)", "share_linkPresent": "Anzeigemodus (Bearbeitbare Abschnitte sind verborgen)", "share_linkOpen": "In einem neuen Tab öffnen", From 8f00077df259822f1979a40e545658dc4f4a9ee9 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 29/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.el.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.el.json b/www/common/translations/messages.el.json index 593c5ff30..e5fbf232e 100644 --- a/www/common/translations/messages.el.json +++ b/www/common/translations/messages.el.json @@ -141,7 +141,6 @@ "viewOpenTitle": "Άνοιγμα αυτού του pad μόνο για ανάγνωση σε νέα καρτέλα", "fileShare": "Αντιγραφή συνδέσμου", "getEmbedCode": "Κώδικας ενσωμάτωσης", - "viewEmbedTitle": "Ενσωματώστε αυτό το pad σε μία εξωτερική σελίδα", "viewEmbedTag": "Για να ενσωματώσετε αυτό το pad, συμπεριλάβετε αυτό το iframe στη σελίδα σας, στο σημείο που θέλετε. Μπορείτε να το διαμορφώσετε χρησιμοποιώντας CSS η HTML παραμέτρους.", "fileEmbedTitle": "Ενσωματώστε το αρχείο σε μια εξωτερική σελίδα", "fileEmbedScript": "Για να ενσωματώσετε αυτό το αρχείο, συμπεριλάβετε αυτό το script στη σελίδα σας για να φορτωθεί το Media Tag:", From b58fe94170a567110a113926ff12062ffb8eab89 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 30/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.it.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index 673c40fd1..64451d416 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -169,7 +169,6 @@ "viewOpenTitle": "Apri questo pad in modalità solo lettura in una nuova finestra", "fileShare": "Copia il link", "getEmbedCode": "Mostra il codice per embedding", - "viewEmbedTitle": "Fai l'embed di questo pad in una pagina esterna", "viewEmbedTag": "Per fare l'embed di questo pad, includi questo iframe nella tua pagina dovunque tu voglia. Puoi modificarne lo stile con gli attributi HTML o CSS.", "fileEmbedTitle": "Fai l'embed di questo file in una pagina esterna", "fileEmbedScript": "Per fare l'embed di questo file, includi questo script una volta nella tua pagina per caricare il Media Tag:", From 5f3fd9858ad185fa9b25488d6dc7d1ed3774fc05 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 31/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.nb.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.nb.json b/www/common/translations/messages.nb.json index 14790aee9..9d5cd285f 100644 --- a/www/common/translations/messages.nb.json +++ b/www/common/translations/messages.nb.json @@ -161,7 +161,6 @@ "viewOpenTitle": "Åpne denne paden i lesemodus i en ny fane", "fileShare": "Kopier linken", "getEmbedCode": "Hent innlagt kode", - "viewEmbedTitle": "Legg inn paden i en ekstern side", "canvas_width": "Bredde", "canvas_opacity": "Gjennomsiktighet", "canvas_opacityLabel": "Gjennomsiktighet:{0}", From 422078736676f4fe80143675ff5bb106ab32f866 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 32/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.pt-br.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.pt-br.json b/www/common/translations/messages.pt-br.json index 5764745d9..59b70b275 100644 --- a/www/common/translations/messages.pt-br.json +++ b/www/common/translations/messages.pt-br.json @@ -404,7 +404,6 @@ "viewOpen": "", "viewOpenTitle": "", "getEmbedCode": "", - "viewEmbedTitle": "", "viewEmbedTag": "", "fileEmbedTitle": "", "fileEmbedScript": "", From 4dae892a461f6c0d840c0a549187f1bddb776653 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 33/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.ro.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.ro.json b/www/common/translations/messages.ro.json index 9b8ad7c57..56eab943a 100644 --- a/www/common/translations/messages.ro.json +++ b/www/common/translations/messages.ro.json @@ -392,7 +392,6 @@ "themeButton": "Temă", "themeButtonTitle": "Alege tema de culori de folosit pentru cod si editorul slide-urilor", "fileShare": "Copiază linkul", - "viewEmbedTitle": "Include pad-ul într-o pagină externă", "fileEmbedTitle": "Include fișierul într-o pagină externă", "fileEmbedTag": "După care plasează această etichetă Media oriunde pe pagina unde vrei sa o plasezi", "ok": "Ok", From c36038a0b2882151f0853c2eb2ed0b4f3ec3ad68 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 34/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.ru.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.ru.json b/www/common/translations/messages.ru.json index 62914d82a..66cce1d63 100644 --- a/www/common/translations/messages.ru.json +++ b/www/common/translations/messages.ru.json @@ -168,7 +168,6 @@ "viewOpenTitle": "Открыть данный документ для чтения в новой вкладке", "fileShare": "Скопировать ссылку", "getEmbedCode": "Получить код для встраивания", - "viewEmbedTitle": "Встроить документ во внешнюю страницу", "notifyJoined": "{0} присоединился к совместной сессии", "notifyRenamed": "{0} теперь известен как {1}", "notifyLeft": "{0} покинул совместную сессию", From 2940366d70760380d28a7cd0cf714c13da8cbfe8 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:32:46 +0000 Subject: [PATCH 35/43] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ --- www/common/translations/messages.es.json | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/translations/messages.es.json b/www/common/translations/messages.es.json index 37811d0c9..87ad7134e 100644 --- a/www/common/translations/messages.es.json +++ b/www/common/translations/messages.es.json @@ -496,7 +496,6 @@ "kanban_removeItemConfirm": "Estás seguro que quieres eliminar este ítem?", "printBackgroundNoValue": "No se muestra fondo de pantalla", "getEmbedCode": "Obtener el código insertado", - "viewEmbedTitle": "Insertar la nota en una página externa", "viewEmbedTag": "Para insertar esta nota, incluya este iframe en su página donde usted quiera. Puede darle estilo usando CSS o atributos HTML.", "fileEmbedTitle": "Insertar el archivo en una pagina externa", "fileEmbedScript": "Para insertar este archivo, incluya este código una vez en su página para cargar el Etiqueta de Media:", From ce08b5234c6d392472159cd7edf023eb24a5439b Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:39:17 +0000 Subject: [PATCH 36/43] Translated using Weblate (English) Currently translated at 100.0% (1148 of 1148 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ Translated using Weblate (English) Currently translated at 100.0% (1148 of 1148 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ --- www/common/translations/messages.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index ab9d2a0f3..3c2e39e5b 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1241,5 +1241,6 @@ "teams_table_specificHint": "These are older shared folders where viewers still have permission to edit existing pads. Pads created or copied into these folders will have standard permissions.", "teams_table_admins": "Manage members", "teams_table_owners": "Manage team", - "teams_table_role": "Role" + "teams_table_role": "Role", + "pad_wordCount": "Words: {0}" } From 552a7d53e7be9042060a86beb2ff3abbdc675a4e Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 22 Nov 2019 16:39:17 +0000 Subject: [PATCH 37/43] Translated using Weblate (French) Currently translated at 100.0% (1148 of 1148 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 618562d11..b39e76785 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1241,5 +1241,6 @@ "teams_table_admins": "Gérer les membres", "teams_table_owners": "Gérer l'équipe", "teams_table_role": "Rôle", - "share_contactCategory": "Contacts" + "share_contactCategory": "Contacts", + "pad_wordCount": "Mots : {0}" } From a6589d0d06dc58c5c20ce668d0a1063b3007f3a5 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 17:58:21 +0100 Subject: [PATCH 38/43] Ability to restore an old version from the debug app --- www/debug/inner.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/debug/inner.js b/www/debug/inner.js index 3373b4803..03e4d8d7f 100644 --- a/www/debug/inner.js +++ b/www/debug/inner.js @@ -439,7 +439,12 @@ define([ console.log(doc); }; - config.onLocal = function () { }; + var toRestore; + + config.onLocal = function () { + if (!toRestore) { return; } + cpNfInner.chainpad.contentUpdate(toRestore); + }; config.onInit = function (info) { Title = common.createTitle({}); @@ -463,6 +468,7 @@ define([ onRemote: config.onRemote, setHistory: setHistory, applyVal: function (val) { + toRestore = val; displayDoc(JSON.parse(val) || {}); }, $toolbar: $bar, From 9a8604dfb471f1ddf3b197fca29e81438747adca Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 22 Nov 2019 18:02:33 +0100 Subject: [PATCH 39/43] Fix debug app restore history --- www/debug/inner.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/www/debug/inner.js b/www/debug/inner.js index 03e4d8d7f..3a98b883e 100644 --- a/www/debug/inner.js +++ b/www/debug/inner.js @@ -441,8 +441,8 @@ define([ var toRestore; - config.onLocal = function () { - if (!toRestore) { return; } + config.onLocal = function (a, restore) { + if (!toRestore || !restore) { return; } cpNfInner.chainpad.contentUpdate(toRestore); }; @@ -464,7 +464,9 @@ define([ /* add a history button */ var histConfig = { - onLocal: config.onLocal, + onLocal: function () { + config.onLocal(null, true); + }, onRemote: config.onRemote, setHistory: setHistory, applyVal: function (val) { From 9e9099f6d8d40d5a684c78ca9a7bd3a2fe780203 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 25 Nov 2019 09:45:33 +0000 Subject: [PATCH 40/43] Translated using Weblate (German) Currently translated at 100.0% (1148 of 1148 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index a6deb8059..de40bbfec 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -954,14 +954,14 @@ "properties_passwordWarning": "Das Passwort wurde erfolgreich geändert, aber dein CryptDrive konnte nicht aktualisiert werden. Du musst möglicherweise die alte Version des Pads manuell entfernen.
Klicke auf OK, um die Seite neu zu laden und die Zugriffsrechte zu aktualisieren.", "properties_passwordSuccess": "Das Passwort wurde erfolgreich geändert.
Klicke auf OK, um die Seite neu zu laden und die Zugriffsrechte zu aktualisieren.", "properties_changePasswordButton": "Absenden", - "share_linkCategory": "Link teilen", + "share_linkCategory": "Link", "share_linkAccess": "Zugriffsrechte", "share_linkEdit": "Bearbeiten", "share_linkView": "Ansehen", - "share_linkEmbed": "Einbettungsmodus (Werkzeugleiste und Benutzerliste sind verborgen)", - "share_linkPresent": "Anzeigemodus (Bearbeitbare Abschnitte sind verborgen)", - "share_linkOpen": "In einem neuen Tab öffnen", - "share_linkCopy": "In die Zwischenablage kopieren", + "share_linkEmbed": "Einbettungsmodus (Werkzeugleiste und Benutzerliste verbergen)", + "share_linkPresent": "Anzeigemodus", + "share_linkOpen": "Vorschau", + "share_linkCopy": "Kopieren", "share_embedCategory": "Einbetten", "share_mediatagCopy": "Media-Tag in die Zwischenablage kopieren", "loading_pad_1": "Initialisiere Pad", @@ -975,7 +975,7 @@ "sharedFolders_create_name": "Neuer Ordner", "sharedFolders_create_owned": "Eigener Ordner", "sharedFolders_create_password": "Ordnerpasswort", - "sharedFolders_share": "Teile diese URL mit anderen registrierten Benutzern, um ihnen Zugriff auf den geteilten Ordner zu geben. Sobald sie diese URL öffnen, wird der geteilte Ordner zu ihrem CryptDrive hinzugefügt.", + "sharedFolders_share": "Teile diesen Link mit anderen registrierten Benutzern, um ihnen Zugriff auf den geteilten Ordner zu geben. Sobald sie diesen Link öffnen, wird der geteilte Ordner zu ihrem CryptDrive hinzugefügt.", "chrome68": "Anscheinend benutzt du Chrome oder Chromium in Version 68. Ein darin enthaltener Fehler führt dazu, dass nach ein paar Sekunden die Seite komplett weiß wird oder nicht mehr auf Klicks reagiert. Um das Problem zu beheben, wechsle den Tab und kehre zu CryptPad zurück, oder versuche zu scrollen. Dieser Fehler sollte in der nächsten Version deines Browsers behoben sein.", "autostore_file": "Diese Datei", "autostore_sf": "Dieser Ordner", @@ -1240,5 +1240,7 @@ "teams_table_specificHint": "Dies sind ältere geteilte Ordner, wo Benutzer noch Bearbeitungsrechte haben. Für hier erstellte oder hierhin kopierte Pads gelten Standard-Berechtigungen.", "teams_table_admins": "Mitglieder verwalten", "teams_table_owners": "Team verwalten", - "teams_table_role": "Rolle" + "teams_table_role": "Rolle", + "share_contactCategory": "Kontakte", + "pad_wordCount": "Wörter: {0}" } From 5d7886325d55d3ae47a5d7d6b1a81b0c20ce8e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 25 Nov 2019 09:48:08 +0000 Subject: [PATCH 41/43] style secondary and cancel buttons --- customize.dist/src/less2/include/alertify.less | 16 +++++++++++++--- customize.dist/src/less2/include/colortheme.less | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index bf30d5bfe..51ab824c2 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -282,12 +282,16 @@ border-radius: 0; color: @alertify-btn-fg; - border: 1px solid @colortheme_alertify-cancel-border; + border: 1px solid @alertify-btn-fg; &.no-margin { margin: 0; } + &:hover, &:active { + background-color: @alertify-light-bg; + } + &.safe, &.danger { color: @colortheme_old-base; white-space: normal; @@ -321,10 +325,16 @@ } } - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%)); + &.cancel{ + border-color: @colortheme_alertify-cancel-border; + color: @colortheme_alertify-cancel-border; + &:hover, &:hover{ + background-color: fade(@colortheme_alertify-cancel-border, 25%); + } } + + &:focus { //border: 1px dotted @alertify-base; box-shadow: 0px 0px 5px @colortheme_alertify-primary; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index 2e0c6371c..23dca6fe8 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -56,7 +56,7 @@ @colortheme_alertify-disabled-text: #ffffff; @colortheme_alertify-disabled-border: #6c757d; @colortheme_alertify-cancel: @colortheme_modal-bg; -@colortheme_alertify-cancel-border: #ccc; +@colortheme_alertify-cancel-border: #949494; @colortheme_notification-log: fade(@colortheme_logo-1, 90%); @colortheme_notification-color: #fff;; From baf911c3c2271b8a23d5e9f5288d4185389cb4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 25 Nov 2019 09:49:14 +0000 Subject: [PATCH 42/43] lint --- customize.dist/src/less2/include/alertify.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 51ab824c2..9cd904c45 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -325,10 +325,10 @@ } } - &.cancel{ + &.cancel { border-color: @colortheme_alertify-cancel-border; color: @colortheme_alertify-cancel-border; - &:hover, &:hover{ + &:hover, &:hover { background-color: fade(@colortheme_alertify-cancel-border, 25%); } } From 6ee98cac5bddfe080039d8b9592e526b7addc6da Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 25 Nov 2019 11:47:52 +0100 Subject: [PATCH 43/43] Fix premium account detection --- www/common/common-constants.js | 2 -- www/common/outer/async-store.js | 3 ++- www/common/sframe-common-outer.js | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/www/common/common-constants.js b/www/common/common-constants.js index a083ceb90..553665574 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -18,8 +18,6 @@ define(['/customize/application_config.js'], function (AppConfig) { deprecatedKey: 'deprecated', MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 3, MAX_TEAMS_OWNED: AppConfig.maxOwnedTeams || 1, - // Sub - plan: 'CryptPad_plan', // Apps criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications'] }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 5900601e8..0cf362e5d 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -575,7 +575,8 @@ define([ support: Util.find(store.proxy, ['mailboxes', 'support', 'channel']), pendingFriends: store.proxy.friends_pending || {}, supportPrivateKey: Util.find(store.proxy, ['mailboxes', 'supportadmin', 'keys', 'curvePrivate']), - teams: teams + teams: teams, + plan: account.plan } }; cb(JSON.parse(JSON.stringify(metadata))); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index efa904d6e..e439d04c1 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -366,7 +366,6 @@ define([ donateURL: Cryptpad.donateURL, upgradeURL: Cryptpad.upgradeURL }, - plan: localStorage[Utils.Constants.plan], isNewFile: isNewFile, isDeleted: isNewFile && window.location.hash.length > 0, forceCreationScreen: forceCreationScreen,