diff --git a/CHANGELOG.md b/CHANGELOG.md index f98c1ea68..26dc534d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +# 4.9.0 + +## Goals and announcements + +We allocated most of this release cycle towards a schedule of one-on-one user interviews and some broad usage studies leveraging our new Form app. The remainder of our time was spent on some minor improvements. We'll continue at a slightly slower pace of implementation for the coming weeks while we complete our scheduled interviews and take some much-needed vacations. + +## Update notes + +It appears our promotion of the checkup page through our recent release notes and the inclusion of a link to it from the instance admin have been moderately successful. We've observed that more instance admins are noticing and fixing some common configuration issues. + +This release features some minor changes to one instance configuration test which incorrectly provided an exemption for the use of `http://localhost:3000` as an `httpUnsafeOrigin` value. This exemption was provided because it's valid for local development, however, it suppressed errors when this configuration was used for production instances where it could cause a variety of problems. As usual, we recommend checking your instance's admin page after updating to confirm that you are passing the latest tests. Information about the checkup page is included in [our documentation](https://docs.cryptpad.fr/en/admin_guide/admin_panel.html#network). + +To update from 4.8.0 to 4.9.0: + +1. Stop your server +2. Get the latest code with git +3. Install the latest dependencies with `bower update` and `npm i` +4. Restart your server +5. Confirm that your instance is passing all the tests included on the `/checkup/` page + +## Features + +* We've added the ability to store URLs in user and team drives as requested in a private support ticket and [this issue](https://github.com/xwiki-labs/cryptpad/issues/732). Links can be shared directly with contacts. Unlike pads, links are not collaborative objects, so updating a link's name will not update the entry in another user's drive if you've already shared it with them. Links are integrated into our apps' _insert_ menu to facilitate quick insertion of links you've stored into your documents. We're interested in measuring how this functionality is used in practice so we can decide whether it's worth spending more time on it, so we've added some telemetry to measure (in aggregate) how often its components are used. We anonymize IP addresses in the logs for CryptPad.fr, but as always, you can disable telemetry via your settings panel. +* Our rich text editor now supports indentation with the tab key, as per [this ticket](https://github.com/xwiki-labs/cryptpad/issues/634). +* Forms received another round of improvements to styles, workflows, and some basic survey functionality to yield more accurate results. + * Ordered lists are now shuffled for each survey participant so that their initial order has less effect on the final results. + * CSV export now uses a layout that makes poll options easier to read. + * Unregistered users can now add a name to their response. + * Form results are displayed automatically (when available) to those who have answered. + * Authors and auditors can now click on some types of answers to jump directly to other answers from the same user. +* Users with very large drives might notice that their account loads slightly faster now, due to some minor optimizations in an integrity check that the client performs when loading accounts. + +## Bugs + +* We've added a guard against a type error that could be triggered when loading teams under certain rare conditions. +* Unregistered users' drives now show the "bread-crumb" UI for navigating between folders when viewing a shared folder in read-only mode. We've also suppressed the "Files" button for displaying the tree view which was non-functional for such users. +* A change in the format of support tickets caused tickets recently created by premium users to not be recognized as such. We've fixed the categorization in the admin panel's support ticket view. +* We've fixed a number of minor issues with forms: + * The maximum number of selectable choices for checkbox questions can no longer exceed the number of available choices. + * We guard against a type error that could occur when parsing dates. + * Forms imported from templates now have their initial title corrected. + * We've disabled the use of our indexedDB caching system for form results, since it was quietly dropping older responses when more than 100 responses had been submitted. We plan to re-enable caching for results once we've updated the eviction metric to better handle the response format. + # 4.8.0 ## Goals diff --git a/customize.dist/images/YannFlory.jpg b/customize.dist/images/YannFlory.jpg deleted file mode 100644 index 9c358d1a6..000000000 Binary files a/customize.dist/images/YannFlory.jpg and /dev/null differ diff --git a/customize.dist/images/logo_white.png b/customize.dist/images/logo_white.png deleted file mode 100644 index 8219cc2b0..000000000 Binary files a/customize.dist/images/logo_white.png and /dev/null differ diff --git a/customize.dist/images/logo_white.svg b/customize.dist/images/logo_white.svg deleted file mode 100644 index d1cd2bc73..000000000 --- a/customize.dist/images/logo_white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/customize.dist/old_logos/CryptPad-white-logo.svg b/customize.dist/old_logos/CryptPad-white-logo.svg deleted file mode 100644 index 26131b502..000000000 --- a/customize.dist/old_logos/CryptPad-white-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/customize.dist/old_logos/CryptPad_logo_color.svg b/customize.dist/old_logos/CryptPad_logo_color.svg deleted file mode 100644 index 64fc86682..000000000 --- a/customize.dist/old_logos/CryptPad_logo_color.svg +++ /dev/null @@ -1 +0,0 @@ -CryptPad_logo_color \ No newline at end of file diff --git a/customize.dist/old_logos/CryptPadlogo_op5.svg b/customize.dist/old_logos/CryptPadlogo_op5.svg deleted file mode 100644 index aa5acb201..000000000 --- a/customize.dist/old_logos/CryptPadlogo_op5.svg +++ /dev/null @@ -1 +0,0 @@ -CryptPadlogo \ No newline at end of file diff --git a/customize.dist/old_logos/cryptofist_mini.png b/customize.dist/old_logos/cryptofist_mini.png deleted file mode 100644 index 73845e455..000000000 Binary files a/customize.dist/old_logos/cryptofist_mini.png and /dev/null differ diff --git a/customize.dist/old_logos/cryptofist_small.png b/customize.dist/old_logos/cryptofist_small.png deleted file mode 100644 index 9c818eb45..000000000 Binary files a/customize.dist/old_logos/cryptofist_small.png and /dev/null differ diff --git a/customize.dist/old_logos/cryptpad-new-logo-colors-logoonly.png b/customize.dist/old_logos/cryptpad-new-logo-colors-logoonly.png deleted file mode 100644 index 722438d54..000000000 Binary files a/customize.dist/old_logos/cryptpad-new-logo-colors-logoonly.png and /dev/null differ diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 18632f6ea..18d65c680 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -105,7 +105,7 @@ define([ var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? '/imprint.html' : AppConfig.imprint); - Pages.versionString = "v4.8.0"; + Pages.versionString = "v4.9.0"; // used for the about menu diff --git a/lib/env.js b/lib/env.js index 3d58f11f7..4db282d77 100644 --- a/lib/env.js +++ b/lib/env.js @@ -123,7 +123,7 @@ module.exports.create = function (config) { maxWorkers: config.maxWorkers, disableIntegratedTasks: config.disableIntegratedTasks || false, - disableIntegratedEviction: typeof(config.disableIntegratedEviction) === 'undefined'? true: config.disableIntegratedEviction, // XXX false, + disableIntegratedEviction: typeof(config.disableIntegratedEviction) === 'undefined'? true: config.disableIntegratedEviction, // XXX 4.10.0 false, lastEviction: +new Date(), evictionReport: {}, commandTimers: {}, diff --git a/package-lock.json b/package-lock.json index 4d7992f82..42136e238 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "4.8.0", + "version": "4.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9e18f05f0..68a315891 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "4.8.0", + "version": "4.9.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/www/admin/inner.js b/www/admin/inner.js index 61cf05896..b35059df0 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -833,7 +833,8 @@ define([ var premium = t.some(function (msg) { var _ed = Util.find(msg, ['content', 'msg', 'content', 'sender', 'edPublic']); if (ed !== _ed) { return; } - return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']); + return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']) || + Util.find(msg, ['content', 'msg', 'content', 'sender', 'quota', 'plan']); }); var lastMsg = t[t.length - 1]; var lastMsgEd = Util.find(lastMsg, ['content', 'msg', 'content', 'sender', 'edPublic']); diff --git a/www/checkup/main.js b/www/checkup/main.js index a8a69d737..4413d3b18 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -710,17 +710,19 @@ define([ var isOnion = function (host) { return /\.onion$/.test(host); }; + var isLocalhost = function (host) { + return /^http:\/\/localhost/.test(host); + }; + assert(function (cb, msg) { // provide an exception for development instances - if (/http:\/\/localhost/.test(trimmedUnsafe)) { return void cb(true); } + if (isLocalhost(trimmedUnsafe) && isLocalhost(window.location.href)) { return void cb(true); } // if both the main and sandbox domains are onion addresses // then the HTTPS requirement is unnecessary if (isOnion(trimmedUnsafe) && isOnion(trimmedSafe)) { return void cb(true); } // otherwise expect that both inner and outer domains use HTTPS - setWarningClass(msg); - msg.appendChild(h('span', [ "Both ", code('httpUnsafeOrigin'), diff --git a/www/code/inner.js b/www/code/inner.js index 785e82788..4f6c0308e 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -368,7 +368,7 @@ define([ var mkFilePicker = function (framework, editor, evModeChange) { evModeChange.reg(function (mode) { if (MEDIA_TAG_MODES.indexOf(mode) !== -1) { - // Embedding is endabled + // Embedding is enabled framework.setMediaTagEmbedder(function (mt) { editor.focus(); editor.replaceSelection($(mt)[0].outerHTML); diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 15e5b5e34..3be979f17 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -119,6 +119,7 @@ define(function() { file: 'cptools-file', fileupload: 'cptools-file-upload', folderupload: 'cptools-folder-upload', + link: 'fa-link', pad: 'cptools-richtext', code: 'cptools-code', slide: 'cptools-slide', diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 4d3520426..5aea35c78 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1050,6 +1050,7 @@ define([ var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa'; if (type === 'fileupload') { type = 'file'; } if (type === 'folderupload') { type = 'file'; } + if (type === 'link') { type = 'drive'; } var appClass = ' cp-icon cp-icon-color-'+type; $icon = $('', {'class': font + ' ' + icon + appClass}); } @@ -1061,6 +1062,7 @@ define([ if (!data) { return $icon; } var href = data.href || data.roHref; var type = data.type; + if (data.static) { type = 'link'; } if (!href && !type) { return $icon; } if (!type) { type = Hash.parsePadUrl(href).type; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 0ab010cb5..99d506347 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1032,10 +1032,19 @@ define([ icon: 'fa-picture-o', action: function () { var _cfg = { - types: ['file'], + types: ['file', 'link'], where: ['root'] }; common.openFilePicker(_cfg, function (data) { + // Embed links + if (data.static) { + var a = h('a', { + href: data.href + }, data.name); + cfg.embed(a, data); + return; + } + // Embed files if (data.type !== 'file') { console.log("Unexpected data type picked " + data.type); return; @@ -3021,6 +3030,75 @@ define([ UI.proposal(content, todo); }; + UIElements.displayOpenLinkModal = function (common, data, dismiss) { + var name = Util.fixHTML(data.title); + var url = data.href; + var user = data.name; + //Messages.link_open = "Open URL"; + // openLinkInNewTab ("Open Link in New Tab") + // fc_open ("Open") + // share_linkOpen ("Preview") + // resources_openInNewTab ("Open it in a new tab") + Messages.link_open = Messages.fc_open; // XXX 4.10.0 + + //Messages.link_store = "Store link in drive"; + // toolbar_storeInDrive ? ("Store in CryptDrive") + // autostore_store ? ("Store") + Messages.link_store = Messages.toolbar_storeInDrive; // XXX 4.10.0 + + + var content = h('div', [ + UI.setHTML(h('p'), Messages._getKey('notification_openLink', [name, user])), + h('pre', url), + UIElements.getVerifiedFriend(common, data.curve, user) + ]); + var clicked = false; + var modal; + var buttons = [{ + name: Messages.friendRequest_later, + onClick: function () { + if (clicked) { return true; } + clicked = true; + Feedback.send('LINK_RECEIVED_LATER'); + }, + keys: [27] + }, { + className: 'primary', + name: Messages.link_open, + onClick: function () { + if (clicked) { return true; } + clicked = true; + common.openUnsafeURL(url); + Feedback.send("LINK_RECEIVED_OPEN"); + }, + keys: [13] + }, { + className: 'primary', + name: Messages.link_store, + onClick: function () { + if (clicked) { return; } + clicked = true; + common.getSframeChannel().query("Q_DRIVE_USEROBJECT", { + cmd: "addLink", + data: { + name: name, + href: url, + path: ['root'] + } + }, function () { + modal.closeModal(); + dismiss(); + Feedback.send("LINK_RECEIVED_STORE"); + }); + return true; + }, + keys: [[13, 'ctrl']] + }]; + var _modal = UI.dialog.customModal(content, {buttons: buttons}); + modal = UI.openCustomModal(_modal); + return modal; + }; + UIElements.displayAddOwnerModal = function (common, data) { var priv = common.getMetadataMgr().getPrivateData(); var sframeChan = common.getSframeChannel(); diff --git a/www/common/common-util.js b/www/common/common-util.js index 51540a660..ac50a1f24 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -9,6 +9,15 @@ return Array.prototype.slice.call(A, start, end); }; + Util.shuffleArray = function (a) { + for (var i = a.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + }; + Util.bake = function (f, args) { if (typeof(args) === 'undefined') { args = []; } if (!Array.isArray(args)) { args = [args]; } @@ -152,6 +161,10 @@ }; }; + Util.inc = function (map, key, val) { + map[key] = (map[key] || 0) + (typeof(val) === 'number'? val: 1); + }; + Util.find = function (map, path) { var l = path.length; for (var i = 0; i < l; i++) { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index b2489c22b..15558cf18 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -443,6 +443,11 @@ define([ 'data-icon': AppConfig.applicationsIcon.poll, 'data-type': 'poll' }, Messages.button_newpoll)), + h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { + 'tabindex': '-1', + 'data-icon': AppConfig.applicationsIcon.link, + 'data-type': 'link' + }, Messages.fm_link_new)), ]), ]), $separator.clone()[0], @@ -1106,11 +1111,24 @@ define([ common.getMediaTagPreview(mts, idx); }; + var refresh = APP.refresh = function () { + APP.displayDirectory(currentPath); + }; + // `app`: true (force open wiht the app), false (force open in preview), // falsy (open in preview if default is not using the app) var defaultInApp = ['application/pdf']; var openFile = function (el, isRo, app) { var data = manager.getFileData(el); + + if (data.static) { + if (data.href) { + common.openUnsafeURL(data.href); + manager.updateStaticAccess(el, refresh); + } + return; + } + if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } @@ -1147,10 +1165,6 @@ define([ common.openURL(Hash.getNewPadURL(href, obj)); }; - var refresh = APP.refresh = function () { - APP.displayDirectory(currentPath); - }; - var pickFolderColor = function ($element, currentColor, cb) { var colors = ["", "#f23c38", "#ff0073", "#da0eba", "#9d00ac", "#6c19b3", "#4a42b1", "#3d8af0", "#30a0f1", "#1fb9d1", "#009686", "#45b354", "#84c750", "#c6e144", "#faf147", "#fbc423", "#fc9819", "#fd5227", "#775549", "#9c9c9c", "#607a89"]; @@ -1263,6 +1277,9 @@ define([ if ($element.is('.cp-border-color-sheet')) { hide.push('download'); } + if ($element.is('.cp-app-drive-static')) { + hide.push('access', 'hashtag', 'properties', 'download'); + } if ($element.is('.cp-app-drive-element-file')) { // No folder in files hide.push('color'); @@ -1899,6 +1916,31 @@ define([ // In list mode, display metadata from the filesData object + var addStaticData = function (element, $element, data) { + $element.addClass('cp-border-color-drive'); + var name = data.name; + var $name = $('', {'class': 'cp-app-drive-element-name'}).text(name); + $element.append($name); + if (getViewMode() === 'grid') { + //console.error(name, Util.fixHTML(name)); + // this is only safe because our build of tippy sets titles as + // 'textContent' instead of innerHTML, otherwise + // we would need to use Util.fixHTML + $element.attr('title', name); + } + + var type = Messages.fm_link_type; + var $type = $('', { + 'class': 'cp-app-drive-element-type cp-app-drive-element-list' + }).text(type); + var $adate = $('', { + 'class': 'cp-app-drive-element-atime cp-app-drive-element-list' + }).text(getDate(data.atime)); + var $cdate = $('', { + 'class': 'cp-app-drive-element-ctime cp-app-drive-element-list' + }).text(getDate(data.ctime)); + $element.append($type).append($adate).append($cdate); + }; var _addOwnership = function ($span, $state, data) { if (data && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1) { var $owned = $ownedIcon.clone().appendTo($state); @@ -1914,6 +1956,9 @@ define([ if (!manager.isFile(element)) { return; } var data = manager.getFileData(element); + if (data.static) { + return addStaticData(element, $element, data); + } if (!Object.keys(data).length) { return true; @@ -2124,7 +2169,9 @@ define([ $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); $icon.css("color", getFolderColor(path.concat(elPath))); } - var classes = restrictedClass + roClass + liClass; + + var staticClass = manager.isStaticFile(element) ? '.cp-app-drive-static' : ''; + var classes = restrictedClass + roClass + liClass + staticClass; var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, { draggable: true })); @@ -2459,8 +2506,7 @@ define([ setViewMode(viewMode || 'grid'); showMode(viewMode); - $button.click(function (e) { - console.error(e); + $button.click(function () { var viewMode = getViewMode(); var newViewMode = getOppositeViewMode(viewMode); setViewMode(newViewMode); @@ -2686,6 +2732,74 @@ define([ }); $input.click(); }; + var showLinkModal = function () { + var name, url; + var warning = h('div.alert.alert-warning', [ + h('i.fa.fa-exclamation-triangle'), + h('span', Messages.fm_link_warning) + ]); + var content = h('p', [ + h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name), + name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder }), + h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url), + url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url }), + warning, + ]); + + var protocolPattern = /https*:\/\//; + var fragmentPattern = /#.*$/; + var setNamePlaceholder = function (val) { + var temp = val.replace(protocolPattern, '').replace(fragmentPattern, '').trim().slice(0, 48); + if (!protocolPattern.test(val) || !temp) { + temp = Messages.fm_link_name_placeholder; + } + name.setAttribute('placeholder', temp); + }; + + var $warning = $(warning).hide(); + var $url = $(url).on('change keypress keyup keydown', function () { + var v = $url.val().trim(); + if (v.length > 200) { + $warning.show(); + return; + } + setNamePlaceholder(v); + $warning.hide(); + }); + var buttons = [{ + className: 'cancel', + name: Messages.cancelButton, + onClick: function () {}, + keys: [27] + }]; + buttons.push({ + className: 'primary', + // We may want to use a new key here + iconClass: '.fa.fa-plus', + name: Messages.tag_add, + onClick: function () { + var $name = $(name); + var n = $name.val().trim() || $name.attr('placeholder'); + var u = $url.val().trim(); + if (!n || !u) { return true; } + if (!Util.isValidURL(u)) { + // XXX 4.10.0 add style for invalid input? input:invalid + UI.warn(Messages.fm_link_invalid); + return true; + } + manager.addLink(currentPath, { + name: n, + url: u + }, refresh); + Feedback.send("LINK_CREATED"); + }, + keys: [13] + }); + var m = UI.dialog.customModal(content, { + buttons: buttons + }); + UI.openCustomModal(m); + }; var addNewPadHandlers = function ($block, isInRoot) { // Handlers if (isInRoot) { @@ -2714,6 +2828,7 @@ define([ } $block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload').click(showUploadFilesModal); $block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload').click(showUploadFolderModal); + $block.find('a.cp-app-drive-new-link, li.cp-app-drive-new-link').click(showLinkModal); } $block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc') .click(function () { @@ -2757,6 +2872,12 @@ define([ }); } options.push({tag: 'hr'}); + options.push({ + tag: 'a', + attributes: {'class': 'cp-app-drive-new-link'}, + content: $('
').append(getIcon('link')).html() + Messages.fm_link_new + }); + options.push({tag: 'hr'}); } getNewPadTypes().forEach(function (type) { var attributes = { @@ -3073,6 +3194,13 @@ define([ $elementFolderUpload.append($('', {'class': 'cp-app-drive-new-name'}) .text(Messages.uploadFolderButton)); } + // Link + var $elementLink = $('
  • ', { + 'class': 'cp-app-drive-new-link cp-app-drive-element-row ' + + 'cp-app-drive-element-grid' + }).prepend(getIcon('link')).appendTo($container); + $elementLink.append($('', {'class': 'cp-app-drive-new-name'}) + .text(Messages.fm_link_type)); } // Pads getNewPadTypes().forEach(function (type) { @@ -3479,6 +3607,7 @@ define([ var path = paths[0]; if (manager.isPathIn(path, [TRASH])) { return; } + if (!file.channel) { file.channel = id; } if (channels.indexOf(file.channel) !== -1) { return; } channels.push(file.channel); @@ -4482,8 +4611,9 @@ define([ password: data.password }, isTemplate: paths[0].path[0] === 'template', - title: data.title, + title: data.title || data.name, sharedFolder: sf, + static: data.static ? data.href : undefined, common: common }; if (padType === 'file') { @@ -4501,6 +4631,20 @@ define([ data = manager.getSharedFolderData(el); } if (!data) { return; } + if (data.static) { + sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "addLink", + teamId: -1, + data: { + name: data.name, + href: data.href, + path: ['root'] + } + }, function () { + UI.log(Messages.saved); + }); + return; + } sframeChan.query('Q_STORE_IN_TEAM', { href: data.href || data.rohref, password: data.password, @@ -4539,6 +4683,9 @@ define([ } else if ($this.hasClass("cp-app-drive-context-newdoc")) { var ntype = $this.data('type') || 'pad'; + if (ntype === 'link') { + return void showLinkModal(); + } var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; openIn(ntype, path2, APP.team); } diff --git a/www/common/inner/share.js b/www/common/inner/share.js index 9ba75f867..4bcf0d621 100644 --- a/www/common/inner/share.js +++ b/www/common/inner/share.js @@ -90,7 +90,11 @@ define([ setTimeout(w); }); if (res && /^http/.test(res)) { - href = Hash.getRelativeHref(res); + var _href = Hash.getRelativeHref(res); + if (_href) { href = _href; } + else { + href = res; + } setTimeout(w); return; } @@ -109,6 +113,7 @@ define([ if (mailbox.notifications && mailbox.curvePublic) { common.mailbox.sendTo("SHARE_PAD", { href: href, + isStatic: Boolean(config.static), password: config.password, isTemplate: config.isTemplate, name: myName, @@ -119,6 +124,9 @@ define([ channel: mailbox.notifications, curvePublic: mailbox.curvePublic }); + if (config.static) { + Feedback.send("LINK_SHARED_WITH_CONTACT"); + } return; } } @@ -137,6 +145,21 @@ define([ }); return; } + if (config.static) { + common.getSframeChannel().query("Q_DRIVE_USEROBJECT", { + cmd: "addLink", + teamId: team.id, + data: { + name: title, + href: href, + path: ['root'] + } + }, function () { + UI.log(Messages.saved); + }); + Feedback.send("LINK_ADDED_TO_DRIVE"); + return; + } sframeChan.query('Q_STORE_IN_TEAM', { href: href, password: config.password, @@ -346,6 +369,9 @@ define([ ] : [ UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }), ]; + + if (opts.static) { linkContent = []; } + linkContent.push(h('div.cp-spacer')); linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3})); @@ -361,7 +387,7 @@ define([ // warning about sharing links // when sharing a version hash, there is a similar warning and we want // to avoid alert fatigue - if (!opts.versionHash) { + if (!opts.versionHash && !opts.static) { var localStore = window.cryptpadStore; var dismissButton = h('span.fa.fa-times'); var shareLinkWarning = h('div.alert.alert-warning.dismissable', @@ -405,6 +431,10 @@ define([ var v = opts.getLinkValue({ embed: Util.isChecked($link.find('#cp-share-embed')) }); + if (opts.static) { + common.openUnsafeURL(v); + return true; + } window.open(v); return true; }, @@ -562,6 +592,7 @@ define([ }); }; opts.getLinkValue = function (initValue, cb) { + if (opts.static) { return opts.static; } var val = initValue || {}; var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true')); var embed = val.embed; @@ -686,7 +717,7 @@ define([ opts.access = true; // Allow the use of the modal even if the pad is not stored var hashes = opts.hashes; - if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; } + if (!hashes || (!hashes.editHash && !hashes.viewHash && !opts.static)) { return; } var teams = getEditableTeams(common, opts); opts.teams = teams; @@ -705,19 +736,23 @@ define([ var $rights = opts.$rights = getRightsHeader(common, opts); var resetTab = function () { + if (opts.static) { return; } $rights.show(); $rights.find('label.cp-radio').show(); }; var onShowEmbed = function () { + if (opts.static) { return; } $rights.find('#cp-share-bar').closest('label').hide(); $rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked'); $rights.find('input[type="radio"]').trigger('change'); }; var onShowContacts = function () { + if (opts.static) { return; } if (!hasFriends || priv.offline) { $rights.hide(); } }; + if (opts.static) { $rights.hide(); } var contactsActive = hasFriends && !priv.offline; var tabs = [{ @@ -732,13 +767,16 @@ define([ title: Messages.share_linkCategory, icon: "fa fa-link", active: !contactsActive, - }, { - getTab: getEmbedTab, - title: Messages.share_embedCategory, - icon: "fa fa-code", - onShow: onShowEmbed, - onHide: resetTab }]; + if (!opts.static) { + tabs.push({ + getTab: getEmbedTab, + title: Messages.share_embedCategory, + icon: "fa fa-code", + onShow: onShowEmbed, + onHide: resetTab + }); + } Modal.getModal(common, opts, tabs, function (err, modal) { // Hide the burn-after-reading option by default var $modal = $(modal); diff --git a/www/common/notifications.js b/www/common/notifications.js index 20738e237..83052ff68 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -92,6 +92,10 @@ define([ (type === 'file' ? 'notification_fileShared' : // Msg.notification_fileSharedTeam 'notification_padShared'); // Msg.notification_padSharedTeam + if (msg.content.isStatic) { + key = 'notification_linkShared'; // Msg.notification_linkShared; + } + var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5)); var teamName = ''; if (teamNotification) { @@ -109,6 +113,15 @@ define([ return Messages._getKey(key, [name, title, teamName]); }; content.handler = function() { + if (msg.content.isStatic) { + UIElements.displayOpenLinkModal(common, { + curve: msg.author, + href: msg.content.href, + name: name, + title: title + }, defaultDismiss(common, data)); + return; + } var obj = { p: msg.content.isTemplate ? ['template'] : undefined, t: teamNotification || undefined, diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 9f779a0a8..d8e558dad 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1306,9 +1306,14 @@ define([ getAllStores().forEach(function (s) { s.manager.getSecureFilesList(where).forEach(function (obj) { var data = obj.data; - if (channels.indexOf(data.channel) !== -1) { return; } + if (channels.indexOf(data.channel || data.id) !== -1) { return; } var id = obj.id; - if (data.channel) { channels.push(data.channel); } + if (data.channel) { channels.push(data.channel || data.id); } + // Only include static links if "link" is requested + if (data.static) { + if (types.indexOf('link') !== -1) { list[id] = data; } + return; + } var parsed = Hash.parsePadUrl(data.href || data.roHref); if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && !isFiltered(parsed.type, data)) { @@ -2053,8 +2058,17 @@ define([ } catch (e) { console.error(e); } + // Tell all the owners that the pad was deleted from the server - var curvePublic = store.proxy.curvePublic; + var curvePublic; + try { + // users in noDrive mode don't have a proxy and + // unregistered users don't have a curvePublic + curvePublic = store.proxy.curvePublic; + } catch (err) { + console.error(err); + return; + } m.forEach(function (obj) { var mb = JSON.parse(obj); if (mb.curvePublic === curvePublic) { return; } diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 514e18ab7..93bc0d0c6 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -97,7 +97,7 @@ define([ var checkCheckpoints = function (array) { if (!Array.isArray(array)) { return; } // Keep the last 100 messages - if (array.length > 100) { // XXX + if (array.length > 100) { // XXX 4.10.0 array.splice(0, array.length - 100); } // Remove every message before the first checkpoint diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 7f7e20d2a..88e43352b 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -238,8 +238,9 @@ define([ // content.name, content.title, content.href, content.password if (isMuted(ctx, data)) { return void cb(true); } - - var channel = Hash.hrefToHexChannelId(content.href, content.password); + // if the shared content is a 'link' then we can't use the channel to deduplicate notifications + // use href instead. + var channel = content.isStatic ? content.href : Hash.hrefToHexChannelId(content.href, content.password); var parsed = Hash.parsePadUrl(content.href); var mode = parsed.hashData && parsed.hashData.mode || 'n/a'; diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 4184b8482..94f91c807 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -195,7 +195,7 @@ define([ Pinpad.create(ctx.store.network, data, function (e, call) { if (e) { return void cb(e); } team.rpc = call; - team.onRpcReadyEvt.fire(); + if (team && team.onRpcReadyEvt) { team.onRpcReadyEvt.fire(); } cb(); }, Cache); }); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index e7c55da67..7108da936 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -20,6 +20,7 @@ define([ var ROOT = exp.ROOT; var FILES_DATA = exp.FILES_DATA; + var STATIC_DATA = exp.STATIC_DATA; var OLD_FILES_DATA = exp.OLD_FILES_DATA; var UNSORTED = exp.UNSORTED; var TRASH = exp.TRASH; @@ -78,6 +79,14 @@ define([ files[FILES_DATA][id] = data; cb(null, id); }; + exp.pushLink = function (_data, cb) { + if (typeof cb !== "function") { cb = function () {}; } + if (readOnly) { return void cb('EFORBIDDEN'); } + var id = Util.createRandomInteger(); + var data = clone(_data); + files[STATIC_DATA][id] = data; + cb(null, id); + }; exp.pushSharedFolder = function (_data, cb) { if (typeof cb !== "function") { cb = function () {}; } @@ -136,7 +145,7 @@ define([ var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var toClean = []; - exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) { + exp.getFiles([FILES_DATA, SHARED_FOLDERS, STATIC_DATA]).forEach(function (id) { if (filesList.indexOf(id) === -1) { var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id); var channelId = fd.channel; @@ -146,6 +155,8 @@ define([ if (exp.isSharedFolder(id)) { delete files[SHARED_FOLDERS][id]; if (config.removeProxy) { config.removeProxy(id); } + } else if (files[STATIC_DATA][id]) { + delete files[STATIC_DATA][id]; } else { spliceFileData(id); } @@ -242,6 +253,12 @@ define([ id = Number(id); // Find and maybe update existing pads with the same channel id var d = data[id]; + // If we were given a static link, copy to STATIC_DATA + if (d.static) { + delete d.static; + files[STATIC_DATA][id] = d; + return; + } // If we were given an edit link, encrypt its value if needed if (d.href) { d.href = exp.cryptor.encrypt(d.href); } var found = false; @@ -398,7 +415,7 @@ define([ if (!loggedIn && !config.testMode) { return; } id = Number(id); - var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id]; + var data = files[FILES_DATA][id] || files[STATIC_DATA][id] || files[SHARED_FOLDERS][id]; if (!data || typeof(data) !== "object") { return; } var newPath = path, parentEl; if (path && !Array.isArray(path)) { @@ -599,13 +616,18 @@ define([ var element = elem || files[ROOT]; if (!element) { return console.error("Invalid element in root"); } var nbMetadataFolders = 0; + // caching this variables saves a lot of hashmap lookups in this loop + var static_data = files[STATIC_DATA]; + var files_data = files[FILES_DATA]; + var element_el; for (var el in element) { - if (element[el] === null) { + element_el = element[el]; + if (element_el === null) { console.error('element[%s] is null', el); delete element[el]; continue; } - if (exp.isFolderData(element[el])) { + if (exp.isFolderData(element_el)) { if (nbMetadataFolders !== 0) { debug("Multiple metadata files in folder"); delete element[el]; @@ -613,30 +635,30 @@ define([ nbMetadataFolders++; continue; } - if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) { - debug("An element in ROOT was not a folder nor a file. ", element[el]); + if (!exp.isFile(element_el, true) && !exp.isFolder(element_el)) { + debug("An element in ROOT was not a folder nor a file. ", element_el); delete element[el]; continue; } - if (exp.isFolder(element[el])) { - fixRoot(element[el]); + if (exp.isFolder(element_el)) { + fixRoot(element_el); continue; } - if (typeof element[el] === "string") { + if (typeof element_el === "string") { // We have an old file (href) which is not in filesData: add it var id = Util.createRandomInteger(); var key = Hash.createChannelId(); - files[FILES_DATA][id] = { - href: exp.cryptor.encrypt(element[el]), + files_data[id] = { + href: exp.cryptor.encrypt(element_el), filename: el }; element[key] = id; delete element[el]; } - if (typeof element[el] === "number") { - var data = files[FILES_DATA][element[el]]; + if (typeof element_el === "number") { + var data = files_data[element_el] || static_data[element_el]; if (!data) { - debug("An element in ROOT doesn't have associated data", element[el], el); + debug("An element in ROOT doesn't have associated data", element_el, el); delete element[el]; } } @@ -845,6 +867,26 @@ define([ toClean.forEach(function (id) { spliceFileData(id); }); + // make sure that links are displayed at least once in your drive if you are going to keep them + var sd = files[STATIC_DATA]; + var toCleanSD = []; + for (var id2 in sd) { + id2 = Number(id2); + var el2 = sd[id2]; + if (!el2 || typeof(el2) !== "object" || !el2.href) { + toCleanSD.push(id2); + continue; + } + if ((loggedIn || config.testMode) && rootFiles.indexOf(id2) === -1) { + toCleanSD.push(id2); + continue; + } + } + var spliceSD = function (id) { + if (readOnly) { return; } + delete files[STATIC_DATA][id]; + }; + toCleanSD.forEach(spliceSD); }; var fixSharedFolders = function () { if (sharedFolder) { return; } @@ -892,6 +934,12 @@ define([ } } }; + var fixStaticData = function () { + if (!Util.isObject(files[STATIC_DATA])) { + debug("STATIC_DATA was not an object"); + files[STATIC_DATA] = {}; + } + }; var fixDrive = function () { @@ -900,6 +948,7 @@ define([ }); }; + fixStaticData(); fixRoot(); fixTrashRoot(); fixTemplate(); diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 0e799de56..d6492c68c 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -22,11 +22,11 @@ define([ // a cached version if (Env.folders[id].offline && !lm.cache) { Env.folders[id].offline = false; + if (Env.folders[id].userObject.fixFiles) { Env.folders[id].userObject.fixFiles(); } Env.Store.refreshDriveUI(); } return; } - if (Env.folders[id]) { console.warn(Env.folders[id]); } var cfg = getConfig(Env); cfg.sharedFolder = true; cfg.id = id; @@ -584,6 +584,24 @@ define([ }); }); }; + // Add a link + var _addLink = function (Env, data, cb) { + data = data || {}; + var resolved = _resolvePath(Env, data.path); + if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); } + var uo = resolved.userObject; + var now = +new Date(); + uo.pushLink({ + name: data.name, + href: data.href, + atime: now, + ctime: now + }, function (e, id) { + if (e) { return void cb({error: e}); } + uo.add(id, resolved.path); + Env.onSync(cb); + }); + }; var _restoreSharedFolder = function (Env, _data, cb) { var fId = _data.id; @@ -1019,35 +1037,40 @@ define([ }); }; + + var _updateStaticAccess = function (Env, id, cb) { + var uo = _getUserObjectFromId(Env, id); + var sd = uo.getFileData(id, true); + sd.atime = +new Date(); + Env.onSync(cb); + }; + + var COMMANDS = { + move: _move, + restore: _restore, + addFolder: _addFolder, + addSharedFolder: _addSharedFolder, + addLink: _addLink, + restoreSharedFolder: _restoreSharedFolder, + convertFolderToSharedFolder: _convertFolderToSharedFolder, + delete: _delete, + deleteOwned: _deleteOwned, + emptyTrash: _emptyTrash, + rename: _rename, + setFolderData: _setFolderData, + updateStaticAccess: _updateStaticAccess, + }; + var onCommand = function (Env, cmdData, cb) { var cmd = cmdData.cmd; var data = cmdData.data || {}; - switch (cmd) { - case 'move': - _move(Env, data, cb); break; - case 'restore': - _restore(Env, data, cb); break; - case 'addFolder': - _addFolder(Env, data, cb); break; - case 'addSharedFolder': - _addSharedFolder(Env, data, cb); break; - case 'restoreSharedFolder': - _restoreSharedFolder(Env, data, cb); break; - case 'convertFolderToSharedFolder': - _convertFolderToSharedFolder(Env, data, cb); break; - case 'delete': - _delete(Env, data, cb); break; - case 'deleteOwned': - _deleteOwned(Env, data, cb); break; - case 'emptyTrash': - _emptyTrash(Env, data, cb); break; - case 'rename': - _rename(Env, data, cb); break; - case 'setFolderData': - _setFolderData(Env, data, cb); break; - default: - cb(); + var method = COMMANDS[cmd]; + + if (typeof(method) === 'function') { + return void method(Env, data, cb); } + // if the command was not handled then call back + cb(); }; // Set the value everywhere the given pad is stored (main and shared folders) @@ -1129,8 +1152,8 @@ define([ data: uo.getFileData(id) }; }).filter(function (d) { - if (channels.indexOf(d.data.channel) === -1) { - channels.push(d.data.channel); + if (channels.indexOf(d.data.channel || d.id) === -1) { + channels.push(d.data.channel || d.id); return true; } }); @@ -1233,7 +1256,10 @@ define([ Array.prototype.push.apply(result, sfChannels); } - return result; + return result.filter(function (channel) { + if (typeof(channel) !== 'string') { return; } + return [32, 48].indexOf(channel.length) !== -1; + }); }; var addPad = function (Env, path, pad, cb) { @@ -1383,6 +1409,16 @@ define([ } }, cb); }; + var addLinkInner = function (Env, path, data, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "addLink", + data: { + path: path, + name: data.name, + href: data.url + } + }, cb); + }; var restoreSharedFolderInner = function (Env, fId, password, cb) { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "restoreSharedFolder", @@ -1433,6 +1469,14 @@ define([ }, cb); }; + var updateStaticAccessInner = function (Env, id, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "updateStaticAccess", + data: id + }, cb); + + }; + /* Tools */ var findChannels = _findChannels; @@ -1450,6 +1494,11 @@ define([ return String(uo.getTitle(id, type)); }; + var isStaticFile = function (Env, id) { + var uo = _getUserObjectFromId(Env, id); + return uo.isStaticFile(id); + }; + var isReadOnlyFile = function (Env, id) { var uo = _getUserObjectFromId(Env, id); return uo.isReadOnlyFile(id); @@ -1491,7 +1540,7 @@ define([ var files = []; var userObjects = _getUserObjects(Env); userObjects.forEach(function (uo) { - var data = uo.getFiles([UserObject.FILES_DATA]).map(function (id) { + var data = uo.getFiles([UserObject.FILES_DATA, UserObject.STATIC_DATA]).map(function (id) { return [Number(id), uo.getFileData(id)]; }); Array.prototype.push.apply(files, data); @@ -1608,17 +1657,20 @@ define([ emptyTrash: callWithEnv(emptyTrashInner), addFolder: callWithEnv(addFolderInner), addSharedFolder: callWithEnv(addSharedFolderInner), + addLink: callWithEnv(addLinkInner), restoreSharedFolder: callWithEnv(restoreSharedFolderInner), convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner), delete: callWithEnv(deleteInner), deleteOwned: callWithEnv(deleteOwnedInner), restore: callWithEnv(restoreInner), setFolderData: callWithEnv(setFolderDataInner), + updateStaticAccess: callWithEnv(updateStaticAccessInner), // Tools getFileData: callWithEnv(getFileData), find: callWithEnv(find), getTitle: callWithEnv(getTitle), isReadOnlyFile: callWithEnv(isReadOnlyFile), + isStaticFile: callWithEnv(isStaticFile), getFiles: callWithEnv(getFiles), search: callWithEnv(search), getRecentPads: callWithEnv(getRecentPads), diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 0fd250f80..96997cd06 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -733,11 +733,20 @@ define([ if (!common.isLoggedIn()) { return; } $embedButton = common.createButton('mediatag', true).click(function () { var cfg = { - types: ['file'], + types: ['file', 'link'], where: ['root'] }; if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); } common.openFilePicker(cfg, function (data) { + // Embed links + if (data.static) { + var a = h('a', { + href: data.href + }, data.name); + mediaTagEmbedder($(a), data); + return; + } + // Embed files if (data.type !== 'file') { console.log("Unexpected data type picked " + data.type); return; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b0759b5ec..575971368 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1925,7 +1925,6 @@ define([ var cryptputCfg = $.extend(true, {}, rtConfig, {password: password}); if (data.templateContent) { Cryptget.put(currentPad.hash, JSON.stringify(data.templateContent), function () { - console.error(arguments); startRealtime(); cb(); }, cryptputCfg); @@ -2004,6 +2003,8 @@ define([ sframeChan.on('EV_BURN_AFTER_READING', function () { startRealtime(); + // feedback fails for users in noDrive mode + Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive)); }); sframeChan.ready(); diff --git a/www/common/sframe-common-title.js b/www/common/sframe-common-title.js index 2539d1542..d1b06df73 100644 --- a/www/common/sframe-common-title.js +++ b/www/common/sframe-common-title.js @@ -23,6 +23,12 @@ define([ var $title; exp.setToolbar = function (toolbar) { $title = toolbar && (toolbar.title || toolbar.pageTitle); + if ($title && exp.defaultTitle) { + var md = metadataMgr.getMetadata(); + $title.find('input').prop('placeholder', md.defaultTitle); + $title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle); + $title.find('input').val(md.title || md.defaultTitle); + } }; exp.getTitle = function () { return exp.title; }; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 785d1bc7e..5b53aa9a1 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -867,10 +867,6 @@ MessengerUI, Messages, Pages) { 'class': "cp-toolbar-link-logo" }).append(UIElements.getSvgLogo()); - /*.append($('', { - //src: '/customize/images/logo_white.png?' + ApiConfig.requireConf.urlArgs - src: '/customize/favicon/main-favicon.png?' + ApiConfig.requireConf.urlArgs - }));*/ var onClick = function (e) { e.preventDefault(); if (e.ctrlKey) { diff --git a/www/common/translations/messages.ca.json b/www/common/translations/messages.ca.json index 0132646a2..a45919644 100644 --- a/www/common/translations/messages.ca.json +++ b/www/common/translations/messages.ca.json @@ -13,7 +13,8 @@ "todo": "Tasques", "contacts": "Contactes", "sheet": "Full de càlcul", - "teams": "Equips" + "teams": "Equips", + "form": "Formulari" }, "button_newpad": "Nou document", "button_newcode": "Nova pàgina de codi", @@ -28,18 +29,18 @@ "padNotPinned": "Aquest document caducarà després de 3 mesos d'inactivitat, {0}connecteu-vos{1} o {2}registreu-vos{3} per conservar-lo.", "anonymousStoreDisabled": "L'administració d'aquesta instància de CryptPad ha desactivat l'emmagatzematge pels comptes anònims. Cal que inicieu la sessió per utilitzar el CryptDrive.", "expiredError": "Aquest document ha caducat i ja no està disponible.", - "deletedError": "La persona que va crear aquest document l'ha esborrat i ja no està disponible.", + "deletedError": "Aquest document ha estat esborrat i ja no està disponible.", "inactiveError": "Donada la seva inactivitat, aquest document s'ha esborrat. Premeu Esc per crear un nou document.", "chainpadError": "Hi ha hagut un error crític mentre s'actualitzava el vostre contingut. Aquesta pàgina es manté en mode només de lectura per assegurar que no perdreu el que ja heu fet.
    Premeu Esc per continuar veient aquest document o torneu a carregar la pàgina per provar de continuar editant-lo.", "invalidHashError": "El document que heu demanat té una adreça URL no vàlida.", - "errorCopy": " Encara podeu accedir al contingut prement Esc.
    Un cop tanqueu aquesta finestra no hi podreu tornar a accedir.", + "errorCopy": " Encara podeu usar la versió actual en mode \"només lectura\" prement Esc.", "errorRedirectToHome": "Premeu Esc per tornar al vostre CryptDrive.", "newVersionError": "Hi ha una nova versió disponible de CryptPad.
    Torneu a carregar la pàgina per utilitzar la versió nova o premeu Esc per accedir al vostre contingut en mode fora de línia.", "loading": "Carregant...", "error": "Error", "saved": "Desat", "deleted": "Esborrat", - "deletedFromServer": "Document esborrat del servidor", + "deletedFromServer": "Document destruït", "mustLogin": "Cal que inicieu la sessió per accedir a aquesta pàgina", "disabledApp": "Aquesta aplicació està dehabilitada. Per a més informació, contacteu l'administració d'aquest CryptPad.", "realtime_unrecoverableError": "Hi ha hagut un error irreparable. Cliqueu D'acord per tornar a carregar la pàgina.", @@ -93,7 +94,7 @@ "newButton": "Nou/Nova", "newButtonTitle": "Crea un nou document", "uploadButton": "Carregar fitxers", - "uploadButtonTitle": "Carrega un fitxer nou a la carpeta actual", + "uploadButtonTitle": "Carrega un fitxer nou al vostre CryptDrive", "saveTemplateButton": "Desa com una plantilla", "saveTemplatePrompt": "Trieu un títol per la plantilla", "templateSaved": "Plantilla desada!", @@ -128,7 +129,7 @@ "filePicker_description": "Trieu un fitxer del vostre CryptDrive per incrustar-lo o carregueu-ne un de nou", "filePicker_filter": "Filtra els fitxers pel nom", "tags_title": "Etiquetes (només vostres)", - "tags_add": "Actualitza les etiquetes d'aquesta pàgina", + "tags_add": "Actualitza les etiquetes dels documents seleccionats", "tags_notShared": "Les vostres etiquetes no es comparteixen amb altres persones usuàries", "tags_duplicate": "Etiquetes duplicades: {0}", "tags_noentry": "No podeu etiquetar un document esborrat!", @@ -155,12 +156,12 @@ "help_button": "Ajuda", "historyText": "Historial", "historyButton": "Mostra l'historial del document", - "history_next": "La versió més nova", - "history_prev": "La versió més antiga", + "history_next": "Següent versió", + "history_prev": "Versió anterior", "history_loadMore": "Carrega més historial", "history_closeTitle": "Tanca l'historial", "history_restoreTitle": "Restaura la versió seleccionada del document", - "history_restorePrompt": "Segur que voleu reemplaçar la versió actual del document per la que es mostra?", + "history_restorePrompt": "De debò voleu substituir la versió actual del document per la versió que es mostra?", "history_restoreDone": "S'ha restaurat el document", "openLinkInNewTab": "Obre l'enllaç en una pestanya nova", "pad_mediatagTitle": "Configuració de l'Etiqueta Multimèdia", @@ -252,7 +253,7 @@ "fm_sharedFolderName": "Carpeta compartida", "fm_searchPlaceholder": "Cercant...", "fm_newButton": "Nou", - "fm_newButtonTitle": "Crea un nou document o carpeta, importa un fitxer a la carpeta actual", + "fm_newButtonTitle": "Crea un nou document o carpeta, importa un fitxer a la carpeta actual.", "fm_newFolder": "Carpeta nova", "fm_newFile": "Document de text nou", "fm_folder": "Carpeta", @@ -269,8 +270,8 @@ "fm_openParent": "Veure a la carpeta", "fm_noname": "Document sense títol", "fm_emptyTrashDialog": "De debò voleu buidar la paperera?", - "fm_removeSeveralPermanentlyDialog": "De debò voleu suprimir permanentment aquests {0} elements del vostre CryptDrive?", - "fm_removePermanentlyDialog": "De debò voleu suprimir permanentment aquest element del vostre CryptDrive?", + "fm_removeSeveralPermanentlyDialog": "De debò voleu suprimir aquests {0} elements de la vostra unitat? Es mantindran a les unitats de qui els hagis desat.", + "fm_removePermanentlyDialog": "De debò voleu suprimir permanentment aquest element de la vostra unitat? Es mantindrà a les unitats de qui l'hagi desat.", "fm_deleteOwnedPad": "De debò voleu suprimir permanentment aquest document del servidor?", "fm_deleteOwnedPads": "De debò voleu suprimir permanentment aquests documents del servidor?", "fm_restoreDialog": "De debò voleu restaurar {0} a la seva ubicació prèvia?", @@ -282,7 +283,7 @@ "fm_info_template": "Conté tots els documents desats com plantilles i que podeu reutilitzar quan vulgueu crear un nou document.", "fm_info_recent": "Aquests documents s'han modificat o obert darrerament, per vós o per alguna persona col·laboradora.", "fm_info_trash": "Buideu la paperera per alliberar espai al vostre CryptDrive.", - "fm_info_anonymous": "No heu iniciat la sessió, per tant, els vostres documents caducaran d'aquí a 3 mesos. Es desen al vostre navegador, per tant, si netegeu el vostre historial podríeu perdre'ls.
    Registreu-vos o Inicieu la sessió per mantenir-los accessibles.
    ", + "fm_info_anonymous": "No heu iniciat la sessió, per tant, els vostres documents caducaran d'aquí a {0} dies. Si esborreu l'historial del vostre navegador podríeu perdre'ls.
    Registreu-vos (no es demanen dades personals) o Inicieu la sessió per desar-los indefinidament a la vostra unitat. Llegiu més sobre els comptes registrats", "fm_info_sharedFolder": "Aquesta és una carpeta compartida. No heu iniciat cap sessió, pel que només podeu accedir en mode només de lectura.
    Registreu-vos o Inicieu la sessió per poder importar-ho al vostre CryptDrive i modificar-ho.", "fm_info_owned": "Es documents que es mostren són de la vostra propietat. Això vol dir que podeu eliminar-los permanentment del servidor quan vulgueu. Si ho feu, la resta de persones no podran accedir-hi mai més.", "fm_error_cantPin": "Error intern del servidor. Si us plau, torneu a garregar la pàgina i torneu a provar-ho.", @@ -307,9 +308,9 @@ "fc_open": "Obre", "fc_open_ro": "Obre (només de lectura)", "fc_delete": "Desplaça a la paperera", - "fc_delete_owned": "Elimina del servidor", + "fc_delete_owned": "Destrueix", "fc_restore": "Restaura", - "fc_remove": "Suprimeix del vostre CryptDrive", + "fc_remove": "Suprimeix", "fc_remove_sharedfolder": "Suprimeix", "fc_empty": "Buida la paperera", "fc_prop": "Propietats", @@ -330,16 +331,16 @@ "login_invalUser": "Cal introduir l'identificador", "login_invalPass": "Cal introduir la contrasenya", "login_unhandledError": "Hi ha hagut un error inesperat :(", - "register_importRecent": "Importeu els documents de la vostra sessió anònima", + "register_importRecent": "Importa documents de la vostra sessió no registrada", "register_acceptTerms": "Accepto les condicions del servei", "register_passwordsDontMatch": "Les contrasenyes no coincideixen!", "register_passwordTooShort": "Les contrasenyes han de tenir, com a mínim, {0} caràcters.", "register_mustAcceptTerms": "Cal que accepteu les condicions del servei.", "register_whyRegister": "Per què cal registrar-se?", - "register_header": "Us donem la benvinguda a CryptPad", + "register_header": "Registreu-vos", "register_writtenPassword": "He introduït el meu identificador i la contrasenya, continua", - "register_cancel": "Torna", - "register_warning": "Sense Rastre significa que no podem recuperar les vostres dades si perdeu o oblideu la vostra contrasenya.", + "register_cancel": "Cancel·la", + "register_warning": "Atenció", "register_alreadyRegistered": "Aquest identificador ja existeix, voleu iniciar la sessió?", "settings_cat_account": "Compte", "settings_cat_drive": "CryptDrive", @@ -353,7 +354,7 @@ "settings_backupHint": "Deseu o recupereu tot el contingut del vostre CryptDrive. No es desarà el contingut dels vostres documents, només les claus per accedir-hi.", "settings_backup": "Còpia de seguretat", "settings_restore": "Recupera", - "settings_backupHint2": "Descarregueu el contingut actual de tots els vostres documents. Els documents es descarregaran en un format llegible, sempre que el format estigui disponible.", + "settings_backupHint2": "Descarregueu tots els documents. a la vostra unitat. Els documents es descarregaran en un format llegible per altres aplicacions sempre que el format estigui disponible. Quan el format no estigui disponible, els documents es descarregaran en un format llegible per CryptPad.", "settings_backup2": "Descarrega el meu CryptDrive", "settings_backup2Confirm": "Això descarregarà tots els documents i fitxers des del vostre CryptDrive. Si voleu continuar, seleccioneu un nom i premeu D'acord", "settings_exportTitle": "Exporta el vostre CryptDrive", @@ -431,7 +432,7 @@ "settings_codeUseTabs": "Espaiar utilitzant tabulacions (enlloc d'espais)", "settings_codeFontSize": "Mida de la lletra a l'editor de codi", "settings_padWidth": "Amplada màxima de l'editor", - "settings_padWidthHint": "Els documents de text enriquit utilitzen, per defecte, l'amplada màxima de la vostra pantalla, fet que pot dificultar la lectura. Aquí podeu reduir l'amplada de l'editor.", + "settings_padWidthHint": "Canvieu entre el mode pàgina (per defecte) que limita l'amplada de l'editor de text i el mode pantalla completa que utilitza tota l'amplada.", "settings_padWidthLabel": "Redueix l'amplada de l'editor", "settings_padSpellcheckTitle": "Correcció ortogràfica", "settings_padSpellcheckHint": "Aquesta opció us permet habilitar la correcció ortogràfica als documents de text. Les errades es subratllaran en vermell i haureu de mantenir apretada la tecla Ctrl o Meta mentre cliqueu el botó dret per veure les opcions correctes.", @@ -473,7 +474,7 @@ "upload_notEnoughSpace": "No hi ha prou espai al CryptDrive per aquest fitxer.", "upload_notEnoughSpaceBrief": "No hi ha prou espai", "upload_tooLarge": "Aquest fitxer supera la mida màxima permesa pel vostre compte", - "upload_tooLargeBrief": "El fitxer és massa gran", + "upload_tooLargeBrief": "El fitxer supera el límit de {0}MB", "upload_choose": "Trieu un fitxer", "upload_pending": "Pendent", "upload_cancelled": "Cancel·lat", @@ -505,14 +506,39 @@ "download_step2": "Desxifrant", "download_step1": "Descarregant", "download_dl": "Descarrega", - "main_catch_phrase": "El Núvol Coneixement Zero", + "main_catch_phrase": "Utilitats col·laboratives
    encriptades d'extrem a extrem i de codi obert", "footer_aboutUs": "Sobre nosaltres", "about": "Sobre", "contact": "Contacte", "blog": "Bloc", "topbar_whatIsCryptpad": "Què és CryptPad", - "whatis_collaboration": "Col·laboració fàcil i ràpida", - "whatis_title": "Què és CryptPad", + "whatis_collaboration": "Col·laboració privada", + "whatis_title": "Què és CryptPad?", "terms": "Condicions d'ús", - "privacy": "Privacitat" + "privacy": "Política de privacitat", + "settings_padOpenLinkLabel": "Activa l'obertura directa d'enllaços", + "features": "Característiques", + "features_f_cryptdrive0_note": "Possibilitat d'emmagatzemar els documents visitats al vostre navegador per poder-los obrir més tard", + "features_f_storage0": "Durada d'emmagatzematge limitada", + "settings_padOpenLinkTitle": "Obre els enllaços amb només un clic", + "settings_padOpenLinkHint": "Aquesta opció us permet d'obrir els enllaços dins d'un document de text sense obrir cap finestra", + "features_premium": "Prèmium", + "features_f_core": "Característiques comunes", + "features_f_file0_note": "Veure i descarregar documents compartits per altres membres", + "features_f_anon": "Totes les característiques dels membres anònims", + "features_f_cryptdrive1": "Funcionalitat completa de CryptDrive", + "whatis_drive": "Organització amb CryptPad", + "features_f_cryptdrive0": "Accés limitat a CryptDrive", + "features_f_core_note": "Editar, importar i exportar, historial, llistat de membres, xat", + "features_anon": "No registrat", + "features_f_apps": "Accés a totes les aplicacions", + "features_f_file0": "Obrir documents", + "features_f_storage0_note": "Els documents s'eliminen després de {0} dies d'inactivitat", + "features_registered": "Registrat", + "features_f_anon_note": "Amb funcionalitat addicional", + "features_title": "Característiques", + "features_f_social": "Característiques socials", + "features_f_devices_note": "Accés al CryptDrive des de tot arreu amb el teu compte", + "features_f_devices": "Les teves llibretes en tots els teus dispositius", + "features_f_cryptdrive1_note": "Carpetes, carpetes compartides, models, etiquetes" } diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 226e57c7c..259a19928 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1332,7 +1332,7 @@ "form_cantFindAnswers": "Deine vorigen Antworten für dieses Formular konnten nicht geladen werden.", "form_updateWarning": "Trotzdem aktualisieren", "form_submitWarning": "Trotzdem absenden", - "form_sent": "Gesendet", + "form_sent": "Deine Antwort wurde gesendet", "form_update": "Aktualisieren", "form_submit": "Absenden", "form_type_checkbox": "Mehrfachauswahl", @@ -1368,5 +1368,16 @@ "admin_purpose_public": "Zur Bereitstellung eines kostenlosen Dienstes für die Allgemeinheit", "resources_learnWhy": "Mehr über die Gründe erfahren", "team_leaveOwner": "Bitte entferne dich von der Rolle des Eigentümers, bevor du das Teams verlässt. Beachte, dass Teams mindestens einen Eigentümer haben müssen. Bitte füge daher zunächst einen weiteren Eigentümer hinzu, sofern du derzeit der alleinige Eigentümer bist.", - "form_exportCSV": "Als CSV exportieren" + "form_exportCSV": "Als CSV exportieren", + "form_answerAs": "Antworten als", + "notification_openLink": "Du hast einen Link {0} von {1} erhalten:", + "fm_link_warning": "Warnung: URL ist länger als 200 Zeichen", + "notification_linkShared": "{0} hat einen Link mit dir geteilt: {1}", + "fm_link_name": "Bezeichnung des Links", + "fm_link_invalid": "Ungültige URL", + "form_anonName": "Dein Name", + "fm_link_name_placeholder": "Mein Link", + "fm_link_url": "URL", + "fm_link_type": "Link", + "fm_link_new": "Neuer Link" } diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 1ea01009b..5974401e7 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1315,7 +1315,7 @@ "form_updateWarning": "Mettre à jour avec erreurs", "form_submitWarning": "Envoyer avec erreurs", "form_delete": "Supprimer", - "form_sent": "Envoyé", + "form_sent": "Votre réponse a été envoyée", "form_reset": "Effacer", "form_update": "Mettre à jour", "form_submit": "Envoyer", @@ -1368,5 +1368,16 @@ "admin_purpose_business": "Usage en entreprise", "admin_instancePurposeHint": "À quel usage cette instance est-elle destinée ? Votre réponse sera utilisée pour définir la planification de nouvelles fonctionnalités (si votre télémétrie est activée).", "team_leaveOwner": "Veuillez vous rétrograder de votre rôle de propriétaire avant de quitter l'équipe. Notez que les équipes doivent avoir au moins un propriétaire, veuillez en ajouter un autre avant de poursuivre si vous êtes actuellement le seul propriétaire.", - "form_exportCSV": "Exporter en CSV" + "form_exportCSV": "Exporter en CSV", + "fm_link_invalid": "URL invalide", + "fm_link_warning": "Attention : l'URL dépasse 200 caractères", + "form_answerAs": "Répondre en tant que", + "form_anonName": "Votre nom", + "notification_linkShared": "{0} a partagé un lien avec vous : {1}", + "fm_link_name_placeholder": "Nouveau lien", + "fm_link_url": "URL", + "fm_link_name": "Titre du lien", + "fm_link_type": "Lien", + "fm_link_new": "Nouveau Lien", + "notification_openLink": "Vous avez reçu un lien {0} de {1} :" } diff --git a/www/common/translations/messages.hi.json b/www/common/translations/messages.hi.json index 2c63c0851..56d0531fc 100644 --- a/www/common/translations/messages.hi.json +++ b/www/common/translations/messages.hi.json @@ -1,2 +1,9 @@ { + "type": { + "pad": "रिच टेक्स्ट", + "code": "कोड", + "poll": "मतदान", + "kanban": "कानबन" + }, + "main_title": "क्रिप्टपैड: शून्य ज्ञान, सहयोगात्मक रीयल टाइम संपादन" } diff --git a/www/common/translations/messages.ja.json b/www/common/translations/messages.ja.json index 64daa2428..3d68dbab9 100644 --- a/www/common/translations/messages.ja.json +++ b/www/common/translations/messages.ja.json @@ -1,7 +1,7 @@ { "common_connectionLost": "サーバーとの接続が切断しました
    再接続するまで閲覧モードになります。", "button_newsheet": "新規スプレッドシート", - "button_newkanban": "新規のカンバン", + "button_newkanban": "新規カンバン", "button_newwhiteboard": "新規ホワイトボード", "button_newslide": "新規プレゼンテーション", "button_newpoll": "新規投票・アンケート", @@ -67,7 +67,7 @@ "creation_owners": "オーナー", "download_mt_button": "ダウンロード", "fc_rename": "名前を変更", - "forgotten": "ごみ箱へ移動", + "forgotten": "ごみ箱へ移動しました", "filePicker_close": "閉じる", "upload_size": "容量", "propertiesButton": "プロパティ", @@ -111,7 +111,7 @@ "settings_backupCategory": "バックアップ", "settings_backup": "バックアップ", "settings_title": "設定", - "settings_cat_subscription": "サブスクリプション", + "settings_cat_subscription": "定額利用", "settings_cat_drive": "CryptDrive", "settings_cat_account": "アカウント", "settings_save": "保存", @@ -125,7 +125,7 @@ "login_invalUser": "ユーザー名を入力してください", "register_importRecent": "匿名セッションのドキュメントをインポート", "importButton": "インポート", - "main_catch_phrase": "コラボレーションスイート
    暗号化されかつオープンソース", + "main_catch_phrase": "コラボレーションスイート
    端末間暗号化とオープンソース", "tos_3rdparties": "私たちは、法令に基づく場合を除き、個人情報を第三者に提供しません。", "tos_logs": "あなたのブラウザからサーバーに送信されたメタデータは、サービスを維持するために記録される場合があります。", "tos_availability": "私たちはこのサービスがあなたの役に立つことを願っていますが、可用性や性能は保証できません。定期的にデータをエクスポートしてください。", @@ -150,7 +150,7 @@ "logoutButton": "ログアウト", "login_login": "ログイン", "autostore_hide": "保存しない", - "autostore_store": "保存する", + "autostore_store": "保存", "autostore_notstored": "この{0}はあなたのCryptDriveに保存されていません。今すぐ保存しますか?", "user_displayName": "表示名", "exportButton": "エクスポート", @@ -166,13 +166,13 @@ "profile_upload": " 新しいアバターをアップロード", "teams_table_generic_edit": "編集: フォルダとパッドの作成、変更、削除が可能。", "teams_table_generic_view": "表示: フォルダとパッドへのアクセス(閲覧のみ)。", - "teams_table_generic_own": "チームの管理: チーム名とチームのアバターの変更、オーナーの追加または削除、チームのサブスクリプションの変更、チームの削除が可能。", + "teams_table_generic_own": "チームの管理: チーム名とチームのアバターの変更、オーナーの追加または削除、チームの定額利用に関する変更、チームの削除が可能。", "teams_table_owners": "チームの管理", "teams_table_generic_admin": "メンバーの管理: メンバーの招待および取り消し、メンバーに管理者までの権限の付与が可能。", "teams_table_admins": "メンバーの管理", "teams_table_generic": "権限一覧", "teams_table": "権限", - "contacts_fetchHistory": "古い履歴を取得する", + "contacts_fetchHistory": "古い履歴を取得", "contacts_warning": "ここに入力した全てのメッセージは永続的であり、このパッドの現在および将来の全てのユーザーが確認できます。機密情報の入力には注意してください!", "contacts_typeHere": "ここにメッセージを入力...", "team_cat_drive": "ドライブ", @@ -228,14 +228,14 @@ "notifications_cat_friends": "連絡先リクエスト", "notifications_dismiss": "確認済みにする", "settings_autostoreMaybe": "手動 (確認しない)", - "settings_autostoreNo": "手動 (常に確認する)", + "settings_autostoreNo": "手動(常に確認)", "settings_autostoreHint": "自動 アクセスした全てのパッドをCryptDriveに保存します。
    手動(常に確認) 保存していないパッドにアクセスした際、CryptDriveに保存するかどうかを確認します。
    手動(確認しない) アクセス先のパッドはCryptDriveに自動で保存されません。保存オプションは表示されません。", "settings_userFeedback": "ユーザーフィードバックを有効にする", "settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。", "settings_userFeedbackHint1": "CryptPadは、ユーザーエクスペリエンスの向上のため、いくつかの非常に基本的なフィードバックを、サーバーに提供します。 ", "settings_userFeedbackTitle": "フィードバック", "settings_autostoreYes": "自動", - "settings_importConfirm": "このブラウザで最近使用したパッドを、あなたのユーザーアカウントのCryptDriveにインポートしますか?", + "settings_importConfirm": "このブラウザで最近利用したパッドを、あなたのユーザーアカウントのCryptDriveにインポートしますか?", "settings_importDone": "インポートが完了しました", "settings_import": "インポート", "settings_importTitle": "このブラウザでの最近のパッドをあなたのCryptDriveにインポートします", @@ -246,7 +246,7 @@ "fc_delete": "ごみ箱へ移動", "fc_remove": "削除", "fc_restore": "復元", - "fc_delete_owned": "完全削除", + "fc_delete_owned": "破棄", "creation_create": "作成", "creation_password": "パスワード\n", "creation_expireMonths": "か月", @@ -266,7 +266,7 @@ "features_f_cryptdrive1": "CryptDriveの全機能", "features_f_anon_note": "追加機能あり", "features_f_anon": "匿名ユーザーの全機能", - "features_f_storage0_note": "ドキュメントは{0}日以上使用されないと削除されます", + "features_f_storage0_note": "ドキュメントは{0}日以上利用されないと削除されます", "features_f_storage0": "一時的な保存", "features_f_cryptdrive0_note": "アクセスしたパッドをブラウザに保存して、後で開くことができます", "features_f_cryptdrive0": "CryptDriveへの限定的なアクセス", @@ -322,7 +322,7 @@ "properties_passwordSuccessFile": "パスワードは正常に変更されました。", "drive_sfPasswordError": "誤ったパスワードです", "team_title": "チーム: {0}", - "password_error": "ドキュメントが存在しません!
    このエラーは、誤ったパスワードが入力された場合、またはドキュメントがサーバーから完全削除された場合に発生します。", + "password_error": "ドキュメントが存在しません!
    このエラーは、誤ったパスワードが入力された場合、またはドキュメントがサーバーから破棄された場合に発生します。", "password_error_seed": "パッドが存在しません!
    このエラーは「パスワードが追加・変更された」場合、または「パッドがサーバーから削除された」場合に発生します。", "password_submit": "送信", "password_placeholder": "パスワードを入力...", @@ -332,7 +332,7 @@ "properties_addPassword": "パスワードを設定", "history_close": "閉じる", "history_restore": "復元", - "fm_emptyTrashOwned": "ごみ箱に、あなたが所有しているドキュメントが入っています。あなたのドライブからのみ削除するか、全てのユーザーから完全削除するかを選択できます。", + "fm_emptyTrashOwned": "ごみ箱に、あなたの所有するドキュメントが入っています。あなたのドライブからのみ削除するか、全てのユーザーから破棄するかを選択できます。", "access_destroyPad": "このドキュメントまたはフォルダを完全に削除する", "accessButton": "アクセス", "access_allow": "リスト", @@ -417,7 +417,7 @@ "fm_noname": "無題のドキュメント", "fm_openParent": "フォルダに表示", "fm_newButtonTitle": "新しいドキュメントやフォルダを作成したり、ファイルを現在のフォルダにインポートしたりできます。", - "contacts_info4": "チャットの参加者のどちらも履歴を削除できます", + "contacts_info4": "チャットの参加者は誰でも履歴を消去できます", "contacts_info2": "連絡先のアイコンをクリックしてチャットを開始", "contacts_confirmRemove": "{0}をあなたの連絡先から削除してよろしいですか?", "profile_error": "プロフィールの作成時にエラーが発生しました: {0}", @@ -475,7 +475,7 @@ "poll_create_option": "新しいオプションを追加", "poll_create_user": "新しいユーザーを追加", "pad_mediatagImport": "あなたのCryptDriveに保存", - "admin_listMyInstanceLabel": "このインスタンスをリストに表示する", + "admin_listMyInstanceLabel": "このインスタンスをリストに表示", "admin_checkupTitle": "インスタンスの設定を検証", "cba_disable": "消去して無効にする", "upload_pending": "保留中", @@ -595,7 +595,7 @@ "toolbar_file": "ファイル", "drive_treeButton": "ファイル", "toolbar_insert": "挿入", - "fm_sort": "並び替え", + "fm_sort": "並び替える", "comments_comment": "コメント", "comments_resolve": "解決", "comments_reply": "返信", @@ -765,25 +765,25 @@ "errorState": "重大なエラー: {0}", "realtime_unrecoverableError": "回復不能なエラーが発生しました。OKをクリックして再読み込みを行ってください。", "disabledApp": "このアプリケーションは無効になっています。詳細については、このCryptPadの管理者にお問い合わせください。", - "deletedFromServer": "パッドは完全削除されました", + "deletedFromServer": "ドキュメントが破棄されました", "newVersionError": "新しいバージョンのCryptPadがあります。
    リロードすると新しいバージョンを読み込みます。Escキーを押すとオフラインモードでコンテンツにアクセスします。", "errorRedirectToHome": "Escキーを押すとCryptDriveにリダイレクトします。", "errorCopy": " Escキーを押すと、閲覧モードで引き続きコンテンツにアクセスできます。", - "invalidHashError": "要求したドキュメントの URL が無効です。", + "invalidHashError": "要求したドキュメントのURLが無効です。", "chainpadError": "コンテンツを更新する際に重大なエラーが発生しました。コンテンツが失われないよう、閲覧モードで表示されています。
    このパッドを表示し続けるにはEscキーを押し、再度編集を試みるにはリロードをしてください。", - "inactiveError": "このパッドは使用されていなかったため削除されました。Escキーを押して新しいパッドを作成します。", - "deletedError": "このパッドは所有者によって削除されたため、使用できなくなりました。", - "expiredError": "このパッドは使用期限が過ぎてしまったため、使用できなくなりました。", + "inactiveError": "このパッドは利用されていなかったため削除されました。Escキーを押すと新しいパッドを作成します。", + "deletedError": "このドキュメントは削除されたため、利用できなくなりました。", + "expiredError": "このパッドは利用期限を過ぎてしまったため、利用できなくなりました。", "anonymousStoreDisabled": "このCryptPadのインスタンスの管理者は、匿名ユーザーによる保存を無効に設定しています。CryptDriveを使用するにはログインする必要があります。", - "padNotPinnedVariable": "このパッドは{4}日使用しないと期限切れになります。{0}ログイン{1}または{2}登録{3}し保存してください。", - "padNotPinned": "このパッドは3ヶ月間使用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。", + "padNotPinnedVariable": "このパッドは{4}日間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。", + "padNotPinned": "このパッドは3か月間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。", "onLogout": "ログアウトしました。{0}ここをクリック{1}するか
    Escapeキーを押すと、閲覧モードでパッドにアクセスできます。", "typeError": "このパッドは選択したアプリケーションと互換性がありません", "form_type_page": "ページ分割", "form_description_default": "ここにテキストを入力", "team_pcsSelectHelp": "所有するパッドをチームのドライブに作成すると、そのパッドのオーナー権はチームに与えられます。", - "sharedFolders_create_owned": "フォルダを所有する", - "creation_owned1": "所有している項目は、オーナーの望むときにいつでも完全削除できます。完全削除すると、他のユーザーのCryptDriveからも削除されます。", + "sharedFolders_create_owned": "所有するフォルダ", + "creation_owned1": "所有している項目は、オーナーの望むときにいつでも破棄できます。破棄すると、他のユーザーのCryptDriveからも削除されます。", "creation_owned": "パッドを所有", "uploadFolder_modal_owner": "所有するファイル", "upload_modal_owner": "所有するファイル", @@ -824,7 +824,7 @@ "pad_goToAnchor": "アンカーに移動", "oo_cantMigrate": "この表はアップロードの最大のサイズを超えているため、移行することができません。", "footer_roadmap": "ロードマップ", - "settings_deleteSubscription": "サブスクリプションを管理", + "settings_deleteSubscription": "定額利用を管理", "broadcast_translations": "翻訳", "admin_broadcastCancel": "メッセージを削除", "admin_broadcastButton": "送信", @@ -863,7 +863,7 @@ "broadcast_end": "終了", "broadcast_start": "開始", "broadcast_preview": "通知をプレビュー", - "team_inviteLinkError": "リンクの作成時にエラーが発生しました。", + "team_inviteLinkError": "リンクの作成中にエラーが発生しました。", "team_inviteLinkSetPassword": "リンクをパスワードで保護(推奨)", "team_inviteLinkNote": "プライベート・メッセージを追加", "contacts_muteInfo": "ミュートしたユーザーからは通知を受け取りません。
    ミュートしたことは相手に通知されません。 ", @@ -1252,7 +1252,7 @@ "owner_removeMeConfirm": "オーナー権を放棄しようとしています。これは取り消せません。よろしいですか?", "requestEdit_confirm": "{1}がパッド「{0}」の編集権を要求しました。編集権を与えますか?", "admin_supportInitHelp": "サーバーはサポートメールボックスを使用するように設定されていません。サポートメールボックスを有効にし、ユーザーからメッセージを受け取るためには、サーバーの管理者に連絡し、「./scripts/generate-admin-keys.js」のスクリプトを実行してもらい、生成された公開鍵を「config.js」に保存して、秘密鍵をあなたに送信してもらうよう依頼する必要があります。", - "feedback_privacy": "私たちはプライバシーを配慮すると同時に、CryptPadを使いやすくしたいと望んでいます。このファイルは、実行されたアクションを特定するパラメーターと共に要求され、ユーザーにとって重要なUI機能を特定するために使用されます。", + "feedback_privacy": "私たちはプライバシーに配慮すると同時に、CryptPadを使いやすくしたいと望んでいます。このファイルは、実行されたアクションを特定するパラメーターと共に要求され、ユーザーにとって重要なUI機能を特定するために使用されます。", "register_warning_note": "暗号化を行うCryptPadの性質上、サービス管理者は、ユーザー名とパスワードを忘れた場合にデータを回復することができません。ユーザー名とパスワードを安全な場所に保管してください。", "history_restoreDriveTitle": "選択したバージョンのCryptDriveを復元", "errorPopupBlocked": "新しいタブを開く許可が必要です。お使いのブラウザのアドレスバーから、ポップアップウィンドウを許可してください。これらのウィンドウが広告の表示に使用されることはありません。", @@ -1308,8 +1308,8 @@ "share_linkPasswordAlert": "この項目はパスワードで保護されています。リンクを受け取った相手はパスワードを入力する必要があります。", "register_notes_title": "重要な注意事項", "teams_table_specificHint": "以前のバージョンの共有フォルダでは、閲覧者は既存のパッドを変更することができます。 これらのフォルダで作成またはコピーしたパッドには、標準の権限が与えられます。", - "broadcast_maintenance": "{0}から{1}の間でメンテナンスを予定しています。その間CryptPadは使用できません。", - "admin_archiveHint": "ドキュメントを完全削除することなく、利用できないよう設定できます。「アーカイブ」フォルダに移動し、数日後に削除します(期間については設定ファイルより設定できます)。", + "broadcast_maintenance": "{0}から{1}の間でメンテナンスを予定しています。その間CryptPadは利用できません。", + "admin_archiveHint": "ドキュメントを破棄することなく、利用できないよう設定できます。「アーカイブ」フォルダに移動し、数日後に削除します(期間については設定ファイルより設定できます)。", "admin_unarchiveHint": "アーカイブされたドキュメントを復元できます", "admin_registrationTitle": "登録を締め切る", "admin_defaultlimitHint": "ユーザー定義のルールが適用されていない際の最大のストレージ容量(ユーザーとチームについて)を設定できます", @@ -1368,5 +1368,15 @@ "reminder_time": "{0}が今日の{1}にあります", "form_answerWarning": "本人であることが確認されていません", "team_leaveOwner": "チームから退出する前に、オーナーの役割から降格してください。チームには最低1人以上のオーナーが必要です。あなたが唯一のオーナーの場合は、別のオーナーを追加してください。", - "form_exportCSV": "CSVにエクスポート" + "form_exportCSV": "CSVにエクスポート", + "fm_link_new": "新しいリンク", + "notification_openLink": "{1}からリンク「 {0}」を受け取りました:", + "fm_link_type": "リンク", + "fm_link_url": "URL", + "fm_link_name": "リンク名", + "form_anonName": "あなたの名前", + "notification_linkShared": "{0}があなたとリンクを共有しました: {1}", + "fm_link_name_placeholder": "あなたのリンク", + "fm_link_warning": "注意:URLが200字を超えています", + "fm_link_invalid": "URLが無効です" } diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 29f415755..ae5f1e0a5 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1278,7 +1278,7 @@ "form_submit": "Submit", "form_update": "Update", "form_reset": "Reset", - "form_sent": "Sent", + "form_sent": "Your response was submitted", "form_delete": "Delete", "form_submitWarning": "Submit anyway", "form_updateWarning": "Update anyway", @@ -1368,5 +1368,16 @@ "admin_purpose_business": "For a business or commercial organization", "admin_instancePurposeHint": "Why do you run this instance? Your answer will be used to inform the development roadmap if your telemetry is enabled.", "team_leaveOwner": "Please demote yourself from the owner role before leaving the team. Note that teams must have at least one owner, please add one before proceeding if you are currently the only owner.", - "form_exportCSV": "Export to CSV" + "form_exportCSV": "Export to CSV", + "notification_openLink": "You've received a link {0} from {1}:", + "fm_link_new": "New Link", + "fm_link_type": "Link", + "fm_link_name": "Link name", + "fm_link_url": "URL", + "fm_link_name_placeholder": "My link", + "notification_linkShared": "{0} has shared a link with you: {1}", + "form_anonName": "Your name", + "form_answerAs": "Answer as", + "fm_link_warning": "Warning: the URL exceeds 200 characters", + "fm_link_invalid": "Invalid URL" } diff --git a/www/common/translations/messages.lt.json b/www/common/translations/messages.lt.json new file mode 100644 index 000000000..9d252e196 --- /dev/null +++ b/www/common/translations/messages.lt.json @@ -0,0 +1,208 @@ +{ + "backgroundButtonTitle": "Pakeiskite pristatymo fono spalvą", + "presentButtonTitle": "Įeikite į pristatymo režimą", + "previewButtonTitle": "Rodyti arba slėpti „Markdown“ peržiūros režimą", + "template_empty": "Šablono nėra", + "template_import": "Importuoti šabloną", + "useTemplateCancel": "Pradėk šviežiai (Esc)", + "useTemplateOK": "Pasirinkite šabloną (Enter)", + "useTemplate": "Pradėti nuo šablono?", + "selectTemplate": "Pasirinkite šabloną arba paspauskite „escape“", + "templateSaved": "Šablonas išsaugotas!", + "saveTemplatePrompt": "Pasirinkite šablono pavadinimą", + "saveTemplateButton": "Išsaugoti kaip šabloną", + "uploadButtonTitle": "Įkelkite naują failą į savo „CryptDrive“", + "uploadFolderButton": "Įkelti aplanką", + "uploadButton": "Įkelti failus", + "newButtonTitle": "Sukurti naują padą", + "newButton": "Naujas", + "userAccountButton": "Tavo paskyra", + "chatButton": "Čiatas", + "userListButton": "Vartotojų sąrašas", + "shareSuccess": "Nukopijuota nuoroda", + "shareButton": "Dalintis", + "movedToTrash": "Šis apdas buvo perkeltas į šiukšliadėžę.
    Pasiekite mano diską ", + "forgetPrompt": "Spustelėjus Gerai, ši trinkelė bus perkelta į šiukšliadėžę. Ar tu tuo tikras?", + "forgetButton": "Ištrinti", + "saveTitle": "Išsaugoti pavadinimą (enter)", + "clickToEdit": "Spauskite redaguoti", + "user_accountName": "Paskyros vardas", + "user_displayName": "Rodomas pavadinimas", + "user_rename": "Keisti rodomą pavadinimą", + "changeNamePrompt": "Pakeiskite savo vardą (palikite tuščią, kad būtų anoniminis): ", + "exportPrompt": "Kaip norėtumėte pavadinti savo failą?", + "exportButtonTitle": "Eksportuokite padą į vietinį failą", + "exportButton": "Eksportuoti", + "importButton": "Importuoti", + "importButtonTitle": "Importuokite padą iš vietinio failo", + "pinLimitDrive": "Pasiekėte saugyklos limitą.
    Negalite sukurti naujų padų.", + "pinLimitNotPinned": "Pasiekėte saugyklos limitą.
    Šis padas nėra saugomas jūsų „CryptDrive“.", + "pinLimitReachedAlertNoAccounts": "Pasiekėte saugyklos limitą", + "pinLimitReachedAlert": "Pasiekėte saugyklos limitą. Nauji padai nebus saugomi jūsų „CryptDrive“.
    Norėdami padidinti savo limitą, galite pašalinti padus iš savo „CryptDrive“ arba užsiprenumeruoti premium sąskaitą.", + "pinLimitReached": "Pasiekėte saugyklos limitą", + "formattedKB": "{0} KB", + "formattedGB": "{0} GB", + "formattedMB": "{0} MB", + "KB": "KB", + "GB": "GB", + "MB": "MB", + "storageStatus": "Saugykla:
    {0} sunaudota iš {1} ", + "upgrade": "Atnaujinti", + "upgradeAccount": "Atnaujinti paskyrą", + "language": "Kalba", + "userlist_offline": "Šiuo metu esate neprisijungę, vartotojų sąrašas nepasiekiamas.", + "editor": "redaktorius", + "viewers": "žiūrovai", + "viewer": "žiūrovas", + "users": "Vartotojai", + "anonymous": "Anoniminis", + "readonly": "Tik skaityti", + "errorState": "Kritinė klaida: {0}", + "forgotten": "Perkeltas į šiukšliadėžę", + "initializing": "Inicijuojama ...", + "typing": "Redaguojama", + "reconnecting": "Jungiamasi iš naujo", + "synchronizing": "Sinchronizuojama", + "disconnected": "Atjungta", + "realtime_unrecoverableError": "Įvyko neatkuriama klaida. Norėdami perkrauti, spustelėkite Gerai.", + "disabledApp": "Ši programa buvo išjungta. Norėdami gauti daugiau informacijos, susisiekite su „CryptPad“ administratoriumi.", + "mustLogin": "Norėdami patekti į šį puslapį, turite būti prisijungę", + "deletedFromServer": "Dokumentas ištrintas", + "deleted": "Ištrinta", + "saved": "Užsaugota", + "error": "Klaida", + "loading": "Kraunasi...", + "newVersionError": "Yra nauja „CryptPad“ versija.
    Perkraukite iš naujo , jei norite naudoti naują versiją, arba paspauskite „Escape“, kad pasiektumėte savo turinį offline režimu .", + "errorRedirectToHome": "Paspauskite Esc , kad būtumėte nukreipti į „CryptDrive“.", + "errorCopy": " Vis dar galite naudoti dabartinę versiją tik skaitymo režimu paspausdami Esc .", + "invalidHashError": "Prašytame dokumente yra neteisingas URL.", + "chainpadError": "Atnaujinant turinį įvyko kritinė klaida. Šis puslapis veikia tik skaitymo režimu, kad neprarastumėte savo darbo.
    Jei norite toliau peržiūrėti šį padą, paspauskite Esc arba įkelkite iš naujo, jei norite bandyti redaguoti dar kartą.", + "inactiveError": "Šis padas buvo ištrintas dėl neveiklumo. Paspauskite Esc, kad sukurtumėte naują padą.", + "deletedError": "Šis dokumentas ištrintas ir jo nebėra.", + "expiredError": "Šis Padas nebegalioja ir yra ištrintas.", + "anonymousStoreDisabled": "Šios instancijos nepalaiko anoniminio vartojimo. Jūs turite prisiregistruoti, kad galėtumete naudoti CryptDrive.", + "padNotPinnedVariable": "Šis padas nustos galioti po {4} neaktyvių dienų, {0}prisijungti{1} arba {2}registruotis{3} kad užsaugoti.", + "padNotPinned": "Nenaudojamas padas nustos galioti po 3mėn, {0}prisijungti{1} arba {2} registruotis{3} kad užsaugoti.", + "onLogout": "Jūs esate prisijungęs, {0}Spausti čia{1}Prisjungti čia
    arba spausti Escape pasiekti savo pad tik skaityti.", + "typeError": "Šis Pad nesuderinama su pasirinkta programa", + "common_connectionLost": " serverio sujungimas prarastas
    Tik skaitymo rėžimas kol sujungimas atsiras.", + "button_newsheet": "Naujas Lapas", + "button_newkanban": "Naujas kanbanas", + "button_newwhiteboard": "Nauja Lenta", + "button_newslide": "Nauja prezentacija", + "button_newpoll": "Nauja apklausa", + "button_newcode": "Naujas Kodas", + "button_newpad": "Naujas Tekstas", + "type": { + "form": "Forma", + "teams": "Komandos", + "sheet": "Lapas", + "contacts": "Kontaktai", + "todo": "Todo", + "media": "Medija", + "file": "Failas", + "whiteboard": "Lenta", + "drive": "CryptDiskas", + "slide": "Prezentacija", + "kanban": "Kanbanas", + "poll": "Apklausa", + "code": "Kodas", + "pad": "Tekstas" + }, + "main_title": "CryptPad: lengvai valdomas, bendradarbiavimas realiuoju laiku", + "filePicker_close": "Uždaryti", + "filePickerButton": "Įterpkite failą, saugomą „CryptDrive“", + "printBackgroundRemove": "Pašalinkite šį foną", + "printBackgroundNoValue": " Foninis vaizdas nerodomas ", + "printBackgroundValue": " Dabartinis darbalaukis: {0} ", + "printBackgroundButton": "Pasirinkti paveiksliuką", + "printBackground": "Naudokite fono vaizdą", + "printTransition": "Įgalinti perėjimo animacijas", + "printCSS": "Stiliaus taisyklės (CSS):", + "printTitle": "Parodykite pado pavadinimą", + "printDate": "Rodyti datą", + "printSlideNumber": "Rodyti skaidrės numerį", + "printOptions": "Išdėstymo parinktys", + "printButtonTitle2": "Atspausdinkite dokumentą arba eksportuokite jį kaip PDF failą", + "printButton": "Atspausdinti (neter)", + "printText": "Atspausdinti", + "propertiesButtonTitle": "Gaukite pado savybes", + "propertiesButton": "Savybės", + "colorButtonTitle": "Pakeiskite teksto spalvą prezentacijos režime", + "poll_create_user": "Pridėti naują vartotoją", + "poll_publish_button": "Publikuoti", + "kanban_addBoard": "Pridėkite lentą", + "kanban_working": "Atliekama", + "kanban_done": "Padaryta", + "kanban_todo": "Atlikti", + "kanban_item": "Elementas {0}", + "kanban_newBoard": "Nauja lenta", + "pad_mediatagOptions": "Vaizdo ypatybės", + "pad_mediatagImport": "Išsaugokite savo „CryptDrive“", + "pad_mediatagPreview": "Peržiūra", + "pad_mediatagBorder": "Kraštinės plotis (px)", + "pad_mediatagRatio": "Išlaikyti santykį", + "pad_mediatagHeight": "Aukštis (px)", + "pad_mediatagWidth": "Plotis (px)", + "pad_mediatagTitle": "„Media-Tag“ nustatymai", + "openLinkInNewTab": "Atidarykite nuorodą naujame skirtuke", + "history_restoreDone": "Dokumentas atkurtas", + "history_restorePrompt": "Ar tikrai norite pakeisti dabartinę dokumento versiją į rodoma versiją?", + "history_restoreTitle": "Atkurkite pasirinktą dokumento versiją", + "history_closeTitle": "Uždarykite istoriją", + "history_loadMore": "Įkelti daugiau istorijos", + "history_prev": "Ankstesnė versija", + "history_next": "Kita versija", + "historyButton": "Rodyti dokumentų istoriją", + "historyText": "Istorija", + "help_button": "Pagalba", + "show_help_button": "Rodyti pagalbą", + "cancelButton": "Baigti (esc)", + "cancel": "Baigti", + "okButton": "Taip (enter)", + "ok": "Taip", + "notifyLeft": "{0} išėjo iš bendradarbiavimo sesijos", + "notifyRenamed": "{0} dabar žinomas kaip {1}", + "notifyJoined": "{0} prisijungė prie bendradarbiavimo sesijos", + "fileEmbedTag": "Tada įdėkite šią medijos žymą visur, kur norite įdėti:", + "fileEmbedScript": "Norėdami įterpti šį failą, vieną kartą įtraukite šį scenarijų į savo puslapį, kad įkeltumėte medijos žymą:", + "viewEmbedTag": "Norėdami įterpti šį įklotą, įtraukite šį „iframe“ į savo puslapį visur, kur norite. Galite jį stilizuoti naudodami CSS arba HTML atributus.", + "viewShare": "Tik skaityti nuoroda", + "editShare": "Redaguojama nuoroda", + "themeButtonTitle": "Pasirinkite spalvų dizainą, kurią naudosite kodų ir skaidrių redaktoriams", + "themeButton": "Dizainas", + "languageButtonTitle": "Pasirinkite sintaksės paryškinimui naudojamą kalbą", + "languageButton": "Kalba", + "slide_invalidLess": "Netinkamas pasirinktinis stilius", + "slideOptionsTitle": "Tinkinkite skaidres", + "slideOptionsText": "Pasirinkimai", + "tags_noentry": "Negalite pažymėti ištrinto pado!", + "tags_duplicate": "Pasikartojanti žyma: {0}", + "tags_notShared": "Jūsų žymos nebendrinamos su kitais vartotojais", + "tags_add": "Atnaujinkite pažymėtų padų žymas", + "tags_title": "Žymos (tik jums)", + "filePicker_filter": "Filtruokite failus pagal pavadinimą", + "filePicker_description": "Pasirinkite failą iš „CryptDrive“, kad jį įdėtumėte arba įkeltumėte naują", + "oo_cantUpload": "Įkelti negalima, kol dalyvauja kiti vartotojai.", + "oo_reconnect": "Serverio ryšys atkurtas. Spustelėkite Gerai, jei norite iš naujo įkelti ir tęsti leidimą.", + "poll_comment_disabled": "Paskelbkite šią apklausą naudodami mygtuką ✓, kad įgalintumėte komentarus.", + "poll_comment_placeholder": "Jūsų komentaras", + "poll_comment_remove": "Ištrinti šį komentarą", + "poll_comment_submit": "Siųsti", + "poll_comment_add": "Pridėti komentarą", + "poll_comment_list": "Komentarai", + "poll_total": "Bendras", + "poll_bookmarked_col": "Tai yra jūsų pažymėtas stulpelis. Jums visada bus atrakinta ir rodoma pradžioje.", + "poll_bookmark_col": "Pažymėkite šį stulpelį taip, kad jis visada būtų atrakintas ir rodomas pradžioje", + "poll_unlocked": "Atrakinta", + "poll_locked": "Užrakinta", + "poll_edit": "Redaguoti", + "poll_remove": "Pašalinti", + "poll_descriptionHint": "Apibūdinkite apklausą ir, kai baigsite, naudokite mygtuką ✓ (paskelbti).\nAprašymas gali būti parašytas naudojant žymėjimo sintaksę ir galite įdėti laikmenos elementus iš savo „CryptDrive“.\nVisi, turintys nuorodą, gali pakeisti aprašą, tačiau tai nerekomenduojama.", + "poll_removeUser": "Ar tikrai norite pašalinti šį vartotoją?", + "poll_removeOption": "Ar tikrai norite pašalinti šią parinktį?", + "poll_userPlaceholder": "Vardas", + "poll_optionPlaceholder": "Pasirinkimai", + "poll_commit": "Pateikti", + "poll_create_option": "Pridėti naują parinktį" +} diff --git a/www/common/translations/messages.zh.json b/www/common/translations/messages.zh.json index 49331f9a5..ca0b85c73 100644 --- a/www/common/translations/messages.zh.json +++ b/www/common/translations/messages.zh.json @@ -13,7 +13,8 @@ "todo": "待办事项", "teams": "团队", "sheet": "工作表", - "contacts": "联系我们" + "contacts": "联系我们", + "form": "从" }, "button_newpad": "富文件檔案", "button_newcode": "新代碼檔案", diff --git a/www/common/userObject.js b/www/common/userObject.js index ca716f25f..9de3ea918 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -17,6 +17,7 @@ define([ var SHARED_FOLDERS_TEMP = module.SHARED_FOLDERS_TEMP = "sharedFoldersTemp"; // Maybe deleted or new password var FILES_DATA = module.FILES_DATA = Constants.storageKey; var OLD_FILES_DATA = module.OLD_FILES_DATA = Constants.oldStorageKey; + var STATIC_DATA = module.STATIC_DATA = 'static'; // Create untitled documents when no name is given var getLocaleDate = function () { @@ -138,6 +139,7 @@ define([ var NEW_FILE_NAME = Messages.fm_newFile || 'New file'; exp.ROOT = ROOT; + exp.STATIC_DATA = STATIC_DATA; exp.UNSORTED = UNSORTED; exp.TRASH = TRASH; exp.TEMPLATE = TEMPLATE; @@ -236,6 +238,10 @@ define([ return Boolean(data.roHref && !data.href); }; + exp.isStaticFile = function (element) { + return Boolean(files[STATIC_DATA] && files[STATIC_DATA][element]); + }; + var isFolder = exp.isFolder = function (element) { if (isFolderData(element)) { return false; } return typeof(element) === "object" || isSharedFolder(element); @@ -310,6 +316,12 @@ define([ // Get data from AllFiles (Cryptpad_RECENTPADS) var getFileData = exp.getFileData = function (file, editable) { if (!file) { return; } + var link = (files[STATIC_DATA] || {})[file]; + if (link) { + var _link = editable ? link : Util.clone(link); + if (!editable) { _link.static = true; } + return _link; + } var data = files[FILES_DATA][file] || {}; if (!editable) { data = JSON.parse(JSON.stringify(data)); @@ -344,6 +356,7 @@ define([ return '??'; } var data = getFileData(file); + if (data.static) { return data.name; } if (!file || !data || !(data.href || data.roHref)) { error("getTitle called with a non-existing file id: ", file, data); return; @@ -475,6 +488,11 @@ define([ }); return ret; }; + _getFiles[STATIC_DATA] = function () { + var ret = []; + if (!files[STATIC_DATA]) { return ret; } + return Object.keys(files[STATIC_DATA]).map(Number).filter(Boolean); + }; _getFiles[FILES_DATA] = function () { var ret = []; if (!files[FILES_DATA]) { return ret; } @@ -854,6 +872,7 @@ define([ // RENAME exp.rename = function (path, newName, cb) { + cb = cb || function () {}; if (sframeChan) { return void sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "rename", @@ -891,9 +910,15 @@ define([ if (isSharedFolder(element)) { data = files[SHARED_FOLDERS][element]; } else { - data = files[FILES_DATA][element]; + data = files[FILES_DATA][element] || files[STATIC_DATA][element]; } if (!data) { return; } + if (files[STATIC_DATA][element]) { + if (!newName || !newName.trim()) { return void cb(); } + data.name = newName; + cb(); + return; + } if (!newName || newName.trim() === "") { delete data.filename; if (typeof cb === "function") { cb(); } diff --git a/www/convert/inner.js b/www/convert/inner.js index cdcf745ca..ba9870ccf 100644 --- a/www/convert/inner.js +++ b/www/convert/inner.js @@ -192,8 +192,8 @@ define([ }, }; - Messages.convertPage = "Convert"; // XXX - Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterward."; // XXX + Messages.convertPage = "Convert"; // XXX 4.10.0 + Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterward."; // XXX 4.10.0 var createToolbar = function () { var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications']; diff --git a/www/form/app-form.less b/www/form/app-form.less index 95802e18b..ad26863bb 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -247,6 +247,15 @@ .cp-form-anon-answer { text-align: center; margin: 20px 0 30px 0; + .cp-form-anon-answer-input { + margin-top: 20px; + display: flex; + white-space: nowrap; + align-items: center; + input { + margin-left: 10px; + } + } } } @@ -464,6 +473,16 @@ display: flex; flex-flow: column; position: relative; + + #cp-form-response-msg { + background: @cp_form-bg1; + margin-bottom: 20px; + padding: 10px; + p:last-child { + margin-bottom: 0; + } + } + .cp-form-creator-results-controls { margin-bottom: 20px; //background: @cp_form-bg1; @@ -529,6 +548,7 @@ .cp-form-individual { background: @cp_form-bg1; padding: 10px; + margin-bottom: 20px; & > *:not(:last-child) { margin-right: 10px; } @@ -541,6 +561,9 @@ margin-right: 5px; } } + a, a:visited { + color: @cryptpad_color_link; + } } } } @@ -659,6 +682,16 @@ .avatar_main(30px); margin-right: 10px; } + &.cp-clickable { + cursor: pointer; + &:hover { + color: @cryptpad_color_link; + &::after { + font-family: FontAwesome; + content: "\00a0\f06e"; + } + } + } } .cp-poll-time-day { flex-basis: 100px; @@ -677,6 +710,16 @@ -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } + .cp-form-poll-option, .cp-poll-time-day { + flex-flow: column; + text-align: center; + .cp-form-weekday-separator { + display: none; + } + } + .cp-poll-time-day { + height: 50px; + } } &.cp-form-poll-switch { display: flex; @@ -706,12 +749,21 @@ } .cp-form-poll-option, .cp-poll-switch { width: 200px; + .cp-form-weekday-separator { + margin-right: 5px; + margin-left: 5px; + } } .cp-poll-time-day { flex-basis: 40px; border-right: none; border-right: 1px solid @cryptpad_text_col; border-bottom: 0px; + flex-flow: column; + text-align: center; + .cp-form-weekday-separator { + display: none; + } } } .cp-form-poll-choice, .cp-form-poll-answer { diff --git a/www/form/inner.js b/www/form/inner.js index 2a1501ecb..1b555be5d 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -298,6 +298,7 @@ define([ del ]); $(del).click(function () { + var $block = $(el).closest('.cp-form-edit-block'); $(el).remove(); // We've just deleted an item/option so we should be under the MAX limit and // we can show the "add" button again @@ -306,6 +307,13 @@ define([ $add.show(); if (v.type === "time") { $(addMultiple).show(); } } + // decrement the max choices input when there are fewer options than the current maximum + if (maxInput) { + var inputs = $block.find('input').length; + var $maxInput = $(maxInput); + var currentMax = Number($maxInput.val()); + $maxInput.val(Math.min(inputs, currentMax)); + } }); return el; }; @@ -575,7 +583,22 @@ define([ ]; }; - var makePollTable = function (answers, opts) { + var getWeekDays = function (large) { + var baseDate = new Date(2017, 0, 1); // just a Sunday + var weekDays = []; + for(var i = 0; i < 7; i++) { + weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' })); + baseDate.setDate(baseDate.getDate() + 1); + } + if (!large) { + weekDays = weekDays.map(function (day) { return day.slice(0,3); }); + } + return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }); }); + }; + + // "resultsPageObj" is an object with "content" and "answers" + // only available when viewing the Responses page + var makePollTable = function (answers, opts, resultsPageObj) { // Sort date values if (opts.type !== "text") { opts.values.sort(function (a, b) { @@ -583,18 +606,25 @@ define([ }); } // Create first line with options + var allDays = getWeekDays(true); var els = opts.values.map(function (data) { + var _date; if (opts.type === "day") { - var _date = new Date(data); + _date = new Date(data); data = _date.toLocaleDateString(); } if (opts.type === "time") { - var _dateT = new Date(data); - data = Flatpickr.formatDate(_dateT, timeFormat); + _date = new Date(data); + data = Flatpickr.formatDate(_date, timeFormat); } + var day = _date && allDays[_date.getDay()]; return h('div.cp-poll-cell.cp-form-poll-option', { title: Util.fixHTML(data) - }, data); + }, [ + opts.type === 'day' ? h('span.cp-form-weekday', day) : undefined, + opts.type === 'day' ? h('span.cp-form-weekday-separator', ' - ') : undefined, + h('span', data) + ]); }); // Insert axis switch button var switchAxis = h('button.btn.btn-default', [ @@ -611,13 +641,20 @@ define([ opts.values.forEach(function (d) { var date = new Date(d); var day = date.toLocaleDateString(); - _days[day] = _days[day] || 0; - _days[day]++; + _days[day] = { + n: (_days[day] && _days[day].n) || 0, + name: allDays[date.getDay()] + }; + _days[day].n++; }); Object.keys(_days).forEach(function (day) { days.push(h('div.cp-poll-cell.cp-poll-time-day', { - style: 'flex-grow:'+(_days[day]-1)+';' - }, day)); + style: 'flex-grow:'+(_days[day].n - 1)+';' + }, [ + h('span.cp-form-weekday', _days[day].name), + h('span.cp-form-weekday-separator', ' - '), + h('span', day) + ])); }); lines.unshift(h('div', days)); } @@ -640,13 +677,19 @@ define([ }, v); return cell; }); - els.unshift(h('div.cp-poll-cell.cp-poll-answer-name', { + var nameCell; + els.unshift(nameCell = h('div.cp-poll-cell.cp-poll-answer-name', { title: Util.fixHTML(name) }, [ avatar, h('span', name) ])); bodyEls.push(h('div', els)); + if (resultsPageObj && (APP.isEditor || APP.isAuditor)) { + $(nameCell).addClass('cp-clickable').click(function () { + APP.renderResults(resultsPageObj.content, resultsPageObj.answers, answerObj.curve); + }); + } }); } var body = h('div.cp-form-poll-body', bodyEls); @@ -788,6 +831,7 @@ define([ if (filterCurve && user === filterCurve) { return; } try { return { + curve: user, user: answers[user].msg._userdata, results: answers[user].msg[uid] }; @@ -950,7 +994,7 @@ define([ printResults: function (answers, uid) { var results = []; var empty = 0; - Object.keys(answers).forEach(function (author) { + Object.keys(answers).forEach(function (author) { // TODO deduplicate these? var obj = answers[author]; var answer = obj.msg[uid]; if (!answer || !answer.trim()) { return empty++; } @@ -1016,7 +1060,7 @@ define([ printResults: function (answers, uid) { var results = []; var empty = 0; - Object.keys(answers).forEach(function (author) { + Object.keys(answers).forEach(function (author) { // TODO deduplicate these var obj = answers[author]; var answer = obj.msg[uid]; if (!answer || !answer.trim()) { return empty++; } @@ -1090,8 +1134,7 @@ define([ var obj = answers[author]; var answer = obj.msg[uid]; if (!answer || !answer.trim()) { return empty++; } - count[answer] = count[answer] || 0; - count[answer]++; + Util.inc(count, answer); }); Object.keys(count).forEach(function (value) { results.push(h('div.cp-form-results-type-radio-data', [ @@ -1193,8 +1236,7 @@ define([ var c = count[q_uid] = count[q_uid] || {}; var res = answer[q_uid]; if (!res || !res.trim()) { return; } - c[res] = c[res] || 0; - c[res]++; + Util.inc(c, res); }); }); Object.keys(count).forEach(function (q_uid) { @@ -1304,8 +1346,7 @@ define([ var answer = obj.msg[uid]; if (!Array.isArray(answer) || !answer.length) { return empty++; } answer.forEach(function (val) { - count[val] = count[val] || 0; - count[val]++; + Util.inc(count, val); }); }); Object.keys(count).forEach(function (value) { @@ -1420,8 +1461,7 @@ define([ var res = answer[q_uid]; if (!Array.isArray(res) || !res.length) { return; } res.forEach(function (v) { - c[v] = c[v] || 0; - c[v]++; + Util.inc(c, v); }); }); }); @@ -1470,7 +1510,9 @@ define([ if (!Array.isArray(opts.values)) { return; } var map = {}; var invMap = {}; - var els = opts.values.map(function (data, i) { + var sorted = false; + Util.shuffleArray(opts.values); + var els = opts.values.map(function (data) { var uid = Util.uid(); map[uid] = data; invMap[data] = uid; @@ -1479,7 +1521,7 @@ define([ h('i.fa.fa-ellipsis-v'), h('i.fa.fa-ellipsis-v'), ]), - h('span.cp-form-sort-order', (i+1)), + h('span.cp-form-sort-order', '?'), h('span', data) ]); $(div).data('val', data); @@ -1490,10 +1532,11 @@ define([ els ]); var $tag = $(tag); - var reorder = function () { + var reorder = function (reset) { $tag.find('.cp-form-type-sort').each(function (i, el) { - $(el).find('.cp-form-sort-order').text(i+1); + $(el).find('.cp-form-sort-order').text(reset ? '?' : i+1); }); + sorted = !reset; }; var cursorGetter; var setCursorGetter = function (f) { cursorGetter = f; }; @@ -1516,16 +1559,18 @@ define([ return { tag: tag, getValue: function () { + if (!sorted) { return; } return sortable.toArray().map(function (id) { return map[id]; }); }, reset: function () { + Util.shuffleArray(opts.values); var toSort = (opts.values).map(function (val) { return invMap[val]; }); sortable.sort(toSort); - reorder(); + reorder(true); }, edit: function (cb, tmp) { var v = Util.clone(opts); @@ -1554,7 +1599,7 @@ define([ if (!Array.isArray(answer) || !answer.length) { return empty++; } answer.forEach(function (el, i) { var score = l - i; - count[el] = (count[el] || 0) + score; + Util.inc(count, el, score); }); }); var sorted = Object.keys(count).sort(function (a, b) { @@ -1583,7 +1628,7 @@ define([ if (!opts) { opts = TYPES.poll.defaultOpts; } if (!Array.isArray(opts.values)) { return; } - var lines = makePollTable(answers, opts); + var lines = makePollTable(answers, opts, false); // Add form var addLine = opts.values.map(function (data) { @@ -1667,31 +1712,53 @@ define([ }; }, - printResults: function (answers, uid, form) { + printResults: function (answers, uid, form, content) { var opts = form[uid].opts || TYPES.poll.defaultOpts; var _answers = getBlockAnswers(answers, uid); - var lines = makePollTable(_answers, opts); + + // If content is defined, we'll be able to click on a row to display + // all the answers of this user + var lines = makePollTable(_answers, opts, content && { + content: content, + answers: answers + }); var total = makePollTotal(_answers, opts); if (total) { lines.push(h('div', total)); } return h('div.cp-form-type-poll', lines); }, - exportCSV: function (answer) { - if (answer === false) { return; } - if (!answer || !answer.values) { return ['']; } + exportCSV: function (answer, form) { + var opts = form.opts || TYPES.poll.defaultOpts; + var q = form.q || Messages.form_default; + if (answer === false) { + var cols = opts.values.map(function (key) { + return q + ' | ' + key; + }); + cols.unshift(q); + return cols; + } + if (!answer || !answer.values) { + var empty = opts.values.map(function () { return ''; }); + empty.unshift(''); + return empty; + } var str = ''; Object.keys(answer.values).sort().forEach(function (k, i) { if (i !== 0) { str += ';'; } str += k.replace(';', '').replace(':', '') + ':' + answer.values[k]; }); - return [str]; + var res = opts.values.map(function (key) { + return answer.values[key] || ''; + }); + res.unshift(str); + return res; }, icon: h('i.cptools.cptools-form-poll') }, }; - var renderResults = function (content, answers) { + var renderResults = APP.renderResults = function (content, answers, showUser) { var $container = $('div.cp-form-creator-results').empty(); if (!Object.keys(answers || {}).length) { @@ -1699,9 +1766,15 @@ define([ return; } + if (content.answers.msg) { + var description = h('div.cp-form-creator-results-description#cp-form-response-msg'); + var $desc = $(description).appendTo($container); + DiffMd.apply(DiffMd.render(content.answers.msg), $desc, APP.common); + } + var controls = h('div.cp-form-creator-results-controls'); var $controls = $(controls).appendTo($container); - var exportButton = h('button.btn.btn-secondary', Messages.exportButton); // XXX form_exportCSV; + var exportButton = h('button.btn.btn-secondary', Messages.form_exportCSV); var exportCSV = h('div.cp-form-creator-results-export', exportButton); $(exportCSV).appendTo($container); var results = h('div.cp-form-creator-results-content'); @@ -1729,7 +1802,9 @@ define([ var type = block.type; var model = TYPES[type]; if (!model || !model.printResults) { return; } - var print = model.printResults(answers, uid, form); + + // Only use content if we're not viewing individual answers + var print = model.printResults(answers, uid, form, !header && content); var q = h('div.cp-form-block-question', block.q || Messages.form_default); @@ -1823,14 +1898,23 @@ define([ e.preventDefault(); APP.common.openURL(Hash.hashToHref(ud.profile, 'profile')); }); + if (showUser === curve) { + setTimeout(function () { + showUser = undefined; + $(viewButton).click(); + }); + } return div; }); $results.append(els); }); + if (showUser) { + $s.click(); + } }; var addResultsButton = function (framework, content) { - var $res = $(h('button.cp-toolbar-appmenu', [ + var $res = $(h('button.cp-toolbar-appmenu.cp-toolbar-form-button', [ h('i.fa.fa-bar-chart'), h('span.cp-button-name', Messages.form_results) ])); @@ -1872,25 +1956,42 @@ define([ var makeFormControls = function (framework, content, update, evOnChange) { var loggedIn = framework._.sfCommon.isLoggedIn(); var metadataMgr = framework._.cpNfInner.metadataMgr; + var user = metadataMgr.getUserData(); if (!loggedIn && !content.answers.anonymous) { return; } var cbox; + var anonName, $anonName; cbox = UI.createCheckbox('cp-form-anonymous', Messages.form_anonymousBox, true, { mark: { tabindex:1 } }); + var $anonBox = $(cbox).find('input'); if (loggedIn) { if (!content.answers.anonymous || APP.cantAnon) { $(cbox).hide().find('input').attr('disabled', 'disabled').prop('checked', false); } + } else { + anonName = h('div.cp-form-anon-answer-input', [ + Messages.form_answerAs, + h('input', { + value: user.name || '', + placeholder: Messages.form_anonName + }) + ]); + $anonName = $(anonName).hide(); + $anonBox.on('change', function () { + if (Util.isChecked($anonBox)) { $anonName.hide(); } + else { $anonName.show(); } + }); } var send = h('button.cp-open.btn.btn-primary', update ? Messages.form_update : Messages.form_submit); - var reset = h('button.cp-open.btn.btn-danger-alt', Messages.form_reset); + var reset = h('button.cp-open.cp-reset-button.btn.btn-danger-alt', Messages.form_reset); $(reset).click(function () { if (!Array.isArray(APP.formBlocks)) { return; } APP.formBlocks.forEach(function (data) { if (typeof(data.reset) === "function") { data.reset(); } }); + $(reset).attr('disabled', 'disabled'); }); var $send = $(send).click(function () { $send.attr('disabled', 'disabled'); @@ -1898,14 +1999,16 @@ define([ if (!results) { return; } var user = metadataMgr.getUserData(); - if (!Util.isChecked($(cbox).find('input'))) { + if (!Util.isChecked($anonBox)) { results._userdata = loggedIn ? { avatar: user.avatar, name: user.name, notifications: user.notifications, curvePublic: user.curvePublic, profile: user.profile - } : { name: user.name }; + } : { + name: $anonName ? $anonName.find('input').val() : user.name + }; } var sframeChan = framework._.sfCommon.getSframeChannel(); @@ -1985,7 +2088,10 @@ define([ return h('div.cp-form-send-container', [ invalid, - cbox ? h('div.cp-form-anon-answer', cbox) : undefined, + cbox ? h('div.cp-form-anon-answer', [ + cbox, + anonName + ]) : undefined, reset, send ]); }; @@ -1997,6 +2103,18 @@ define([ APP.formBlocks = []; + if (APP.isClosed && content.answers.privateKey && !APP.isEditor) { + var sframeChan = framework._.sfCommon.getSframeChannel(); + sframeChan.query("Q_FORM_FETCH_ANSWERS", content.answers, function (err, obj) { + var answers = obj && obj.results; + if (answers) { APP.answers = answers; } + $('body').addClass('cp-app-form-results'); + $('.cp-toolbar-form-button').remove(); + renderResults(content, answers); + }); + return; + } + var evOnChange = Util.mkEvent(); if (!APP.isEditor) { var _answers = Util.clone(answers || {}); @@ -2004,6 +2122,7 @@ define([ delete _answers._userdata; evOnChange.reg(function (noBeforeUnload, isSave) { if (noBeforeUnload) { return; } + $container.find('.cp-reset-button').removeAttr('disabled'); var results = getFormResults(); if (isSave) { answers = Util.clone(results || {}); @@ -2356,6 +2475,9 @@ define([ // In view mode, add "Submit" and "reset" buttons $container.append(makeFormControls(framework, content, Boolean(answers), evOnChange)); + if (!answers) { + $container.find('.cp-reset-button').attr('disabled', 'disabled'); + } }; var getTempFields = function () { @@ -2443,6 +2565,72 @@ define([ }; refreshPublic(); + var responseMsg = h('div.cp-form-response-msg-container'); + var $responseMsg = $(responseMsg); + var refreshResponse = function () { + if (true) { return; } // XXX 4.10.0 + $responseMsg.empty(); + Messages.form_updateMsg = "Update response message"; // XXX 4.10.0 + Messages.form_addMsg = "Add response message"; // XXX 4.10.0 + Messages.form_responseMsg = "Add a message that will be displayed in the response page."; // XXX 4.10.0 + var text = content.answers.msg ? Messages.form_updateMsg : Messages.form_addMsg; + var btn = h('button.btn.btn-secondary', text); + $(btn).click(function () { + var editor; + if (!APP.responseModal) { + var t = h('textarea'); + var div = h('div', [ + h('p', Messages.form_responseMsg), + t + ]); + var cm = SFCodeMirror.create("gfm", CMeditor, t); + editor = APP.responseEditor = cm.editor; + editor.setOption('lineNumbers', true); + editor.setOption('lineWrapping', true); + editor.setOption('styleActiveLine', true); + editor.setOption('readOnly', false); + setTimeout(function () { + editor.setValue(content.answers.msg || ''); + editor.refresh(); + editor.save(); + editor.focus(); + }); + + var buttons = [{ + className: 'primary', + name: Messages.settings_save, + onClick: function () { + var v = editor.getValue(); + content.answers.msg = v.trim(0, 2000); // XXX 4.10.0 max length? + framework.localChange(); + framework._.cpNfInner.chainpad.onSettle(function () { + UI.log(Messages.saved); + refreshResponse(); + }); + }, + //keys: [] + }, { + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }]; + APP.responseModal = UI.dialog.customModal(div, { buttons: buttons }); + } else { + editor = APP.responseEditor; + setTimeout(function () { + editor.setValue(content.answers.msg || ''); + editor.refresh(); + editor.save(); + editor.focus(); + }); + } + UI.openCustomModal(APP.responseModal); + }); + // $responseMsg.append(btn); // XXX 4.10.0 + }; + //refreshResponse(); + // Allow anonymous answers var privacyContainer = h('div.cp-form-privacy-container'); var $privacy = $(privacyContainer); @@ -2538,11 +2726,13 @@ define([ evOnChange.reg(refreshPublic); evOnChange.reg(refreshPrivacy); evOnChange.reg(refreshEndDate); + //evOnChange.reg(refreshResponse); return [ endDateContainer, privacyContainer, resultsType, + responseMsg ]; }; diff --git a/www/form/main.js b/www/form/main.js index 3b2c160a4..de2c311ef 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -176,7 +176,7 @@ define([ validateKey: keys.secondaryValidateKey, owners: [myKeys.edPublic], crypto: crypto, - //Cache: Utils.Cache // XXX + //Cache: Utils.Cache // XXX 4.10.0 }; var results = {}; config.onError = function (info) { @@ -265,6 +265,7 @@ define([ }, function (obj) { if (obj && obj.error) { return void cb(obj); } var messages = obj.messages; + if (!messages.length) { return void cb(); } var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, { validateKey: data.validateKey, ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate), diff --git a/www/pad/inner.js b/www/pad/inner.js index 304e9abe4..95e7c1bff 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -1326,6 +1326,11 @@ define([ })); $(waitFor()); }).nThen(function(waitFor) { + // TODO this breaks users' ability to tab out of the editor + // but that's a problem in other editors and nobody has complained so far + // so we'll include this as-is for now while we search for a good pattern + // addresses this issue more generally + Ckeditor.config.tabSpaces = 4; Ckeditor.config.toolbarCanCollapse = true; Ckeditor.config.language = Messages._getLanguage(); if (screen.height < 800) { diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index 4226c7f82..18970c47b 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -127,6 +127,7 @@ define([ sframeChan.event("EV_SECURE_ACTION", { type: parsed.type, password: data.password, + static: data.static, href: data.url, name: data.name }); @@ -214,20 +215,21 @@ define([ $container.html(''); Object.keys(list).forEach(function (id) { var data = list[id]; - var name = data.filename || data.title || '?'; + var name = data.filename || data.title || data.name || '?'; if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) { return; } var $span = $('', { 'class': 'cp-filepicker-content-element', - 'title': name, + 'title': Util.fixHTML(name), }).appendTo($container); $span.append(UI.getFileIcon(data)); $('', {'class': 'cp-filepicker-content-element-name'}).text(name) .appendTo($span); + if (data.static) { $span.attr('title', Util.fixHTML(data.href)); } $span.click(function () { if (typeof onFilePicked === "function") { - onFilePicked({url: data.href, name: name, password: data.password}); + onFilePicked({url: data.href, name: name, static: data.static, password: data.password}); } }); diff --git a/www/support/ui.js b/www/support/ui.js index 90fd43fa5..f61ddac4b 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -345,7 +345,7 @@ define([ var senderKey = content.sender && content.sender.edPublic; var fromMe = senderKey === privateData.edPublic; var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1; - var fromPremium = Boolean(content.sender.plan); + var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan'])); var userData = h('div.cp-support-showdata', [ Messages.support_showData, diff --git a/www/teams/main.js b/www/teams/main.js index ea48f72a1..7d12126eb 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -24,7 +24,10 @@ define([ sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) { if (!teamId) { return void cb({error: 'EINVAL'}); } - data.teamId = teamId; + // a teamId of -1 bypasses guards against modifying your drive + // from the team app + if (data.teamId !== -1) { data.teamId = teamId; } + else { delete data.teamId; } Cryptpad.userObjectCommand(data, cb); }); sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {