Merge branch 'staging' into forms-footer-improvements

This commit is contained in:
DianaXWiki 2026-05-14 11:58:37 +02:00
commit 7eda1715cb
74 changed files with 3111 additions and 425 deletions

View File

@ -4,6 +4,81 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and cont
SPDX-License-Identifier: AGPL-3.0-or-later
-->
# 🌷 Spring release (2026.5.0)
## Goals
This release introduces an updated version of the Diagram app, now powered by Drawio 29. The app now defaults to the "sketch" theme, a simple infinite canvas suited to many uses from mind-mapping to freehand drawing. We introduce a theme switcher so that everyone can choose the right level of complexity for their needs. This release also comes with lots of fixes and improvements across CryptPad.
## Features
- Upgrade Diagram app to Drawio 29.6.7 [22fe846](https://github.com/cryptpad/cryptpad/pull/2192/changes/22fe846d75a7cdbc9a98878dd9c131ed5dbf5fe0)
- Button to switch diagram mode [#2192](https://github.com/cryptpad/cryptpad/pull/2192)
- Notifications for private messages [#2133](https://github.com/cryptpad/cryptpad/pull/2133)
## Improvements
- Improve Form accessibility [#2260](https://github.com/cryptpad/cryptpad/pull/2260)
- Enable zh-Hant/zh-Hans locales (#2237) and add alias system for locales [#2254](https://github.com/cryptpad/cryptpad/pull/2254) by @toomore
- Improve crowdfunding banner UI and show logic [#2242](https://github.com/cryptpad/cryptpad/pull/2242)
- Contacts page improvements [#2219](https://github.com/cryptpad/cryptpad/pull/2219)
## Fixes
- fix: set bearer secret in env [#2268](https://github.com/cryptpad/cryptpad/pull/2268) by @ebuildy
- Diagram initialized in read-only mode until document is ready [#2238](https://github.com/cryptpad/cryptpad/pull/2238)
- Fix #2216: Table of contents not clickable in read-only mode [#2229](https://github.com/cryptpad/cryptpad/pull/2229) by @sliortega295-ops
- Enforce immediate access-list lockout and prevent stale content visibility on refresh [#2226](https://github.com/cryptpad/cryptpad/pull/2226)
- Fix app icons and spacing in Drive "Open in" context menu [#2213](https://github.com/cryptpad/cryptpad/pull/2213)
- Fix leftside sidebar buttons' text overflow and prevent icon shrinking [#2212](https://github.com/cryptpad/cryptpad/pull/2212)
- Fix paragraph selection in richtext for mobile [#2208](https://github.com/cryptpad/cryptpad/pull/2208)
- Remove kanban tags when board is deleted [#2188](https://github.com/cryptpad/cryptpad/pull/2188)
- Check for other users before OnlyOffice upload [#2228](https://github.com/cryptpad/cryptpad/pull/2228)
- Update status for trashed OnlyOffice documents [#2183](https://github.com/cryptpad/cryptpad/pull/2183)
## Dependencies
- Upgrades
- chainpad-server: from ^5.2.4 to ^5.3.0
- drawio-npm: from 21.8.2+6 to 29.6.7+3
## Upgrade notes
### SSO plugin
If your instance relies on the SSO plugin for authentication, please upgrade the plugin to [0.5.0](https://github.com/cryptpad/sso/releases/tag/0.5.0) as part of this upgrade.
### CryptPad
If you are upgrading from a version older than `2026.2.2` please read the upgrade notes of all versions between yours and `2026.5.0` to avoid configuration issues.
To upgrade:
1. Stop your server
2. Get the latest code with git
```bash
git fetch --depth 1 origin tag 2026.5.0
git checkout 2026.5.0
npm ci
npm run install:components
./install-onlyoffice.sh
```
3. Restart your server
4. Review your instance's checkup page to ensure that you are passing all tests
## Contributors
Community: @toomore @sliortega295-ops @ebuildy
CryptPad team: @AAAMON @Chouhartem @dariiing @davidbenque @DianaXWiki @wginolas @yflory @zuzanna-maria
# ❄️🩹🩹 Winter fix release 2 (2026.2.2)
## Goals

View File

@ -13,7 +13,8 @@ CKEDITOR.editorConfig = function( config ) {
config.removeButtons= 'Source,Maximize';
// magicline plugin inserts html crap into the document which is not part of the
// document itself and causes problems when it's sent across the wire and reflected back
config.removePlugins= 'resize,elementspath,liststyle';
var isMobile = window.matchMedia ? window.matchMedia('(pointer: coarse)').matches : navigator.maxTouchPoints > 0;
config.removePlugins= 'resize,elementspath,liststyle' + (isMobile ? ',contextmenu,tabletools,tableselection' : '');
config.resize_enabled= false; //bottom-bar
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64,mathjax,wordcount,comments';
config.toolbarGroups= [

View File

@ -49,12 +49,17 @@ define([
};
if (window.location.hash) { setRedirectTo(); }
Exports.ssoRedirectTo = (localData) => {
redirectTo = localData?.redirectTo || redirectTo;
};
Exports.ssoAuth = function (provider, cb) {
var keys = Nacl.sign.keyPair();
var inviteToken = window.location.hash.slice(1);
localStorage.CP_sso_auth = JSON.stringify({
s: Util.encodeBase64(keys.secretKey),
p: Util.encodeBase64(keys.publicKey),
redirectTo,
token: inviteToken
});
ServerCommand(keys, {

View File

@ -29,19 +29,42 @@ var map = {
'sv': 'Svenska',
//'te': 'తెలుగు',
'uk': 'Українська',
'zh': '中文(簡體)',
'zh': '中文(簡體)', // simplified
'zh_Hant': '中文(正體)', // traditional
};
var Messages = {};
var LS_LANG = "CRYPTPAD_LANG";
var getStoredLanguage = function () { return localStorage && localStorage.getItem(LS_LANG); };
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; };
// Normalize browser/localStorage language labels to CryptPad internal keys.
// We keep this centralized to avoid scattered `if (l === 'zh') ...` logic.
var langAliases = {
'zh-cn': 'zh',
'zh-sg': 'zh',
'zh-hans': 'zh',
'zh-tw': 'zh_Hant',
'zh-hk': 'zh_Hant',
'zh-mo': 'zh_Hant',
'zh-hant': 'zh_Hant',
};
var normalizeLanguage = function (l) {
if (!l) { return l; }
// If it already matches a supported internal key, return as-is.
if (map[l]) { return l; }
var lLower = String(l).toLowerCase().replace('_', '-');
return langAliases[lLower] || l;
};
var getLanguage = Messages._getLanguage = function () {
if (window.cryptpadLanguage) { return window.cryptpadLanguage; }
var l = getBrowserLanguage();
try {
l = getStoredLanguage() || getBrowserLanguage();
} catch (e) { console.log(e); }
l = normalizeLanguage(l);
return map[l] ? l :
(map[l.split('-')[0]] ? l.split('-')[0] :
(map[l.split('_')[0]] ? l.split('_')[0] : 'en'));

View File

@ -396,6 +396,14 @@
vertical-align: middle;
}
}
@media screen and (max-width: @browser_media-medium-screen) {
.cp-crowdfunding-modal nav {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
}
}
}
}

View File

@ -33,6 +33,16 @@
li {
padding: 0;
font-size: @colortheme_app-font-size;
a.cp-app-drive-context-openincode,
a.cp-app-drive-context-openinsheet,
a.cp-app-drive-context-openindoc,
a.cp-app-drive-context-openinpresentation {
& > svg.lucide {
margin: 0 0.25rem;
vector-effect: non-scaling-stroke;
stroke-width: 1.7;
}
}
&.dropdown-submenu {
position: relative;
&> a {

View File

@ -976,6 +976,12 @@
stroke-width: 3.5;
vector-effect: non-scaling-stroke;
}
&.cp-app-drive-content-grid {
li.cp-app-drive-element-row > svg.lucide * {
stroke-width: 3.5;
vector-effect: non-scaling-stroke;
}
}
}
#cp-app-drive-new-ghost-dialog.cp-modal-container {

View File

@ -7,6 +7,7 @@
@import (reference) './avatar.less';
@import (reference) './badges.less';
@import (reference) "./colortheme-all.less";
@import (reference) "./browser.less";
.messenger_vars (
@bg-color: @cp_messenger-bg,
@ -89,7 +90,6 @@
background-color: var(--msg-bg-color);
color: @msg-color;
color: var(--msg-color);
overflow-y: auto;
display: flex;
flex-flow: column;
.cp-app-contacts-friend {
@ -100,9 +100,15 @@
margin-bottom: 0;
cursor: pointer;
position: relative;
height: @room-height;
display: flex;
align-items: center;
overflow: hidden;
.cp-avatar {
flex-shrink: 0;
margin-right: 5px;
}
.cp-app-contacts-right-col {
margin-left: 5px;
margin-left: 0;
display: flex;
flex-flow: column;
flex: 1;
@ -112,13 +118,53 @@
overflow: hidden;
text-overflow: ellipsis;
}
.cp-app-contacts-icons {
text-align: right;
& > span:hover {
color: @msg-color-hover;
.cp-app-contacts-bottom-row {
display: flex;
align-items: center;
flex-shrink: 0;
.cp-app-contacts-mute-indicator {
font-size: 0.75em;
display: flex;
align-items: center;
}
svg {
margin-right: 0.1rem;
.cp-app-contacts-icons {
margin-left: auto;
.cp-app-contacts-dropdown-btn {
display: flex;
align-items: center;
background: none;
border: none;
color: inherit;
cursor: pointer;
padding: 2px;
border-radius: @variables_radius;
flex-shrink: 0;
&:hover {
background-color: var(--msg-bg-color-dark);
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
svg {
margin: 0;
}
}
.cp-dropdown-content {
min-width: auto;
a {
display: flex;
align-items: center;
gap: 5px;
}
svg {
margin: 0;
}
li:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
}
}
@ -134,6 +180,10 @@
border-color: var(--msg-bg-color-darker);
}
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
&.cp-app-contacts-notify {
animation: notif 2s ease-in-out infinite;
}
@ -166,7 +216,7 @@
.cp-app-contacts-category-content {
order: 2;
display: flex;
flex-flow: column-reverse;
flex-flow: column;
padding-bottom: 10px;
&:empty {
display: none;
@ -236,6 +286,16 @@
}
}
&.cp-app-contacts-no-chats {
#cp-app-contacts-friendlist {
display: none;
}
#cp-app-contacts-messaging {
display: flex;
flex-direction: column;
}
}
#cp-app-contacts-messaging {
flex: 1;
height: 100%;
@ -245,6 +305,17 @@
.cp-app-contacts-info {
padding: 20px;
a {
color: @msg-color;
color: var(--msg-color);
&:focus-visible {
outline: none;
svg {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
}
.cp-app-contacts-header {
background-color: @msg-bg-color-lighter;
@ -257,19 +328,17 @@
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
min-height: 50px;
svg {
margin-right: 0;
}
.hover () {
cursor: pointer;
height: 100%;
line-height: 30px;
padding: 10px;
border-radius: @variables_radius;
&:hover {
background-color: @msg-bg-color-darker;
background-color: var(--msg-color-darker);
background-color: var(--msg-bg-color-dark);
}
}
@ -277,9 +346,23 @@
.cp-app-contacts-right-col {
flex: 1 1 auto;
text-align: center;
min-width: 0;
.cp-app-contacts-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.cp-app-contacts-right-col {
overflow: hidden;
}
.cp-app-contacts-remove-history {
.hover;
margin-right: 3px;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
.cp-avatar-container {
display: flex;
@ -294,6 +377,10 @@
&.cp-app-contacts-faded {
color: @cryptpad_text_col;
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
.cp-app-contacts-header-title {
@ -320,60 +407,79 @@
height: 100%;
display: flex;
flex-flow: column;
.cp-app-contacts-history-cleared {
font-style: italic;
background-color: fade(@cp_help-bg, 50%);
padding: 2px 10px;
> svg {
flex-shrink: 0;
margin: 0;
}
}
.cp-app-contacts-messages {
padding: 0 20px;
margin: 10px 0;
padding: 0 10px;
margin: 10px 2px;
flex: 1;
overflow-x: auto;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
.cp-app-contacts-message {
display: flex;
flex-wrap: wrap;
& > div {
padding: 0 10px;
flex-direction: column;
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: var(--msg-bg-color-dark);
display: flex;
justify-content: space-between;
border-radius: @variables_radius;
padding: 0 5px;
}
.cp-app-contacts-message-row {
display: flex;
align-items: flex-start;
}
.cp-app-contacts-content {
overflow: hidden;
word-wrap: break-word;
padding: 0 10px;
&> * {
margin: 0;
}
flex: 1;
min-width: 70%;
position: relative;
min-width: 0;
}
.cp-app-contacts-date {
display: none;
font-style: italic;
}
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
width: 100%;
}
.cp-app-contacts-time {
display: none;
display: flex;
font-size: 0.8em;
align-items: center;
align-items: flex-start;
justify-content: flex-end;
color: @msg-color;
font-weight: bold;
position: absolute;
right: 0;
top: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
padding: 0 10px;
padding-right: 5px;
opacity: 0.4;
transition: opacity 0.2s ease;
flex-shrink: 0;
text-align: right;
white-space: nowrap;
}
&:hover {
.cp-app-contacts-time {
display: flex;
opacity: 1;
}
}
}
.cp-app-contacts-system-notification {
align-items: center;
flex-direction: row;
justify-content: center;
color: fade(@cryptpad_text_col, 80%);
}
}
}
.cp-app-contacts-input {
@ -386,7 +492,7 @@
justify-content: center;
padding: 0 5%;
textarea {
margin: 5px 0;
margin: 5px 2px 5px 0;
padding: 5px 10px;
border: none;
height: 54px; // 2 lines (22px height) + 2 margins (5px)
@ -411,6 +517,7 @@
color: @cp_messenger-fg;
background-color: @msg-bg-color-darker;
background-color: var(--msg-bg-color-darker);
border-radius: @variables_radius;
&:hover {
background-color: @msg-bg-color-dark;
background-color: var(--msg-bg-color-dark);
@ -425,4 +532,47 @@
}
}
}
.cp-app-contacts-back {
display: none;
cursor: pointer;
padding: 10px;
&:hover {
background-color: var(--msg-bg-color-dark);
border-radius: @variables_radius;
}
}
@media (max-width: @browser_media-medium-screen) {
#cp-app-contacts-container {
#cp-app-contacts-friendlist {
width: 100%;
flex: 1;
}
#cp-app-contacts-messaging {
display: none;
}
&.cp-app-contacts-chat-open {
#cp-app-contacts-friendlist {
display: none;
}
#cp-app-contacts-messaging {
display: flex;
flex-direction: column;
}
}
&.cp-app-contacts-no-chats {
#cp-app-contacts-friendlist {
display: none;
}
#cp-app-contacts-messaging {
display: flex;
flex-direction: column;
}
}
}
.cp-app-contacts-back {
display: flex;
}
}
}

View File

@ -46,6 +46,14 @@
.cp-sidebarlayout-category {
display: flex;
align-items: center;
white-space: nowrap;
svg {
flex: 0 0 auto;
}
.cp-sidebarlayout-category-name {
overflow: hidden;
text-overflow: ellipsis;
}
.leftside-menu-category_main();
box-shadow: @cryptpad_ui_shadow;
&:focus-visible {

View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2026 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.zh_Hant.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View File

@ -94,6 +94,7 @@ nThen(function (w) {
// if one does not exist, then create one and remember it
// 256 bits
var bearerSecret = Util.encodeBase64(Nacl.randomBytes(32));
Env.bearerSecret = bearerSecret;
Env.Log.info("GENERATING_BEARER_SECRET", {});
Decrees.write(Env, [
'SET_BEARER_SECRET',

View File

@ -7,9 +7,10 @@ const Invitation = module.exports;
const Invite = require('../storage/invite');
const Util = require("../common-util");
const Users = require("./users");
const Crypto = require('node:crypto');
const getUid = () => {
return Util.uid() + Util.uid() + Util.uid();
return Crypto.randomBytes(18).toString('hex');
};
Invitation.getAll = (Env, cb) => {

View File

@ -129,7 +129,7 @@ var handleCommand = function (Env, req, res) {
COMMANDS[command](Env, body, function (err) {
if (err) {
Env.Log.error('CHALLENGE_COMMAND_EXECUTION_ERROR', {
body: body,
command,
error: Util.serializeError(err),
});
// errors returned from commands are passed back to the client
@ -229,16 +229,6 @@ var handleResponse = function (Env, req, res) {
});
}
// garbage collection can clean this up later
Challenge.delete(Env, txid, function (err) {
if (err) {
Env.Log.error("CHALLENGE_DELETION_ERROR", {
txid: txid,
error: Util.serializeError(err),
});
}
});
var json = Util.tryParse(text);
if (!json) {
@ -284,8 +274,7 @@ var handleResponse = function (Env, req, res) {
u8_publicKey = Util.decodeBase64(publicKey);
} catch (err3) {
Env.Log.error('CHALLENGE_RESPONSE_DECODING_ERROR', {
text: text,
sig: sig,
command: json.command,
publicKey: publicKey,
error: Util.serializeError(err3),
});
@ -305,6 +294,16 @@ var handleResponse = function (Env, req, res) {
});
}
// garbage collection can clean this up later
Challenge.delete(Env, txid, function (err) {
if (err) {
Env.Log.error("CHALLENGE_DELETION_ERROR", {
txid: txid,
error: Util.serializeError(err),
});
}
});
// execute the command
action(Env, json, function (err, content) {
if (err) {

View File

@ -258,7 +258,7 @@ app.use('/ssoauth', (req, res, next) => {
Log.error('E_SSO_WRITE_REQ', err);
return res.sendStatus(500);
}
let value = `samltoken="${token}"; SameSite=Strict; HttpOnly`;
let value = `samltoken="${token}"; SameSite=Strict; HttpOnly; Path=/; Secure`;
res.setHeader('Set-Cookie', value);
next();
});
@ -750,22 +750,6 @@ var send500 = function (res, path) {
});
};
app.get('/api/updatequota', function (req, res) {
if (!Env.accounts_api) {
res.status(404);
return void send404(res);
}
sendMessage({
command: 'UPDATE_QUOTA',
}, (err) => {
if (err) {
res.status(500);
return void send500(res);
}
res.send();
});
});
app.get('/api/profiling', function (req, res) {
if (!Env.enableProfiling) { return void send404(res); }
sendMessage({

View File

@ -89,3 +89,7 @@ Basic.restore = function (Env, archivePath, path, cb) {
cb(err);
});
};
Basic.isValidId = id => {
return id && typeof(id) === "string" && /^[a-zA-Z0-9-+=]+$/.test(id);
};

View File

@ -22,7 +22,9 @@ const Challenge = module.exports;
*/
const pathFromId = function (Env, id) {
if (!id || typeof(id) !== 'string') { return void console.error('CHALLENGE_BAD_ID', id); }
if (!Basic.isValidId(id)) {
return void console.error('CHALLENGE_BAD_ID', id);
}
return Path.join(Env.paths.base, "challenges", id.slice(0, 2), id);
};

View File

@ -17,7 +17,9 @@ const Invite = module.exports;
*/
const pathFromId = function (Env, id) {
if (!id || typeof(id) !== 'string') { return void console.error('INVITE_BAD_ID', id); }
if (!Basic.isValidId(id)) {
return void console.error('INVITE_BAD_ID', id);
}
return Path.join(Env.paths.base, "invitations", id.slice(0, 2), id);
};

View File

@ -28,6 +28,7 @@ so that it can be accessed quickly.
var pathFromId = function (Env, id) {
if (!id || typeof(id) !== 'string') { return; }
id = Util.escapeKeyCharacters(id);
if (!Basic.isValidId(id)) { return; }
return Path.join(Env.paths.base, "mfa", id.slice(0, 2), `${id}.json`);
};

View File

@ -14,7 +14,9 @@ const Moderator = module.exports;
*/
const pathFromId = function (Env, id) {
if (!id || typeof(id) !== 'string') { return void console.error('KNWONUSER_BAD_ID', id); }
if (!Basic.isValidId(id)) {
return void console.error('MODERATOR_BAD_ID', id);
}
return Path.join(Env.paths.base, "support", id.slice(0, 2), id);
};

View File

@ -25,6 +25,7 @@ const Sessions = module.exports;
var pathFromId = function (Env, id, ref) {
if (!id || typeof(id) !== 'string') { return; }
if (!Basic.isValidId(ref)) { return; }
id = Util.escapeKeyCharacters(id);
return Path.join(Env.paths.base, "sessions", id.slice(0, 2), id, ref);
};

View File

@ -20,6 +20,7 @@ The path for the user database is based on their persistent identifier (id) from
var pathFromId = function (Env, id, subPath) {
if (!id || typeof(id) !== 'string') { return; }
id = Util.escapeKeyCharacters(id);
if (!Basic.isValidId(id)) { return; }
return Path.join(Env.paths.base, subPath, id.slice(0, 2), `${id}.json`);
};
var reqPathFromId = function (Env, id) {

View File

@ -14,7 +14,9 @@ const User = module.exports;
*/
const pathFromId = function (Env, id) {
if (!id || typeof(id) !== 'string') { return void console.error('KNWONUSER_BAD_ID', id); }
if (!Basic.isValidId(id)) {
return void console.error('USER_BAD_ID', id);
}
return Path.join(Env.paths.base, "users", id.slice(0, 2), id);
};

24
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "cryptpad",
"version": "2026.2.2",
"version": "2026.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cryptpad",
"version": "2026.2.2",
"version": "2026.5.0",
"license": "AGPL-3.0+",
"dependencies": {
"@mcrowe/minibloom": "^0.2.0",
@ -19,14 +19,14 @@
"chainpad-crypto": "^0.3.0",
"chainpad-listmap": "^1.2.0",
"chainpad-netflux": "^1.3.0",
"chainpad-server": "^5.2.4",
"chainpad-server": "^5.3.0",
"ckeditor": "npm:ckeditor4@~4.22.1",
"codemirror": "^5.19.0",
"connect-gzip-static": "^4.2.1",
"cookie-parser": "^1.4.7",
"croppie": "^2.5.0",
"dragula": "3.7.2",
"drawio": "github:cryptpad/drawio-npm#npm-21.8.2+6",
"drawio": "github:cryptpad/drawio-npm#npm-29.6.7+3",
"express": "~4.22.1",
"file-saver": "1.3.1",
"fs-extra": "^7.0.0",
@ -1598,7 +1598,9 @@
}
},
"node_modules/chainpad-server": {
"version": "5.2.4"
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-5.3.1.tgz",
"integrity": "sha512-yDDsN0Oa8ojtNNJRBpqy/AwnRWkettJfGxUxRGZmMf+bbbiKTPWum8ixP9l8UzErTlYR4wzaTZIKmNpWWcyOtg=="
},
"node_modules/ckeditor": {
"name": "ckeditor4",
@ -1900,8 +1902,8 @@
}
},
"node_modules/drawio": {
"version": "21.8.2+6",
"resolved": "git+ssh://git@github.com/cryptpad/drawio-npm.git#61699f777690d23aab727461ff58b60e198ab0e4"
"version": "29.6.7+3",
"resolved": "git+ssh://git@github.com/cryptpad/drawio-npm.git#c3493636b4f3a65d9f068245a968887fd1332326"
},
"node_modules/dunder-proto": {
"version": "1.0.1",
@ -2373,7 +2375,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.0",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"dev": true,
"funding": [
{
@ -3537,7 +3541,9 @@
}
},
"node_modules/netflux-websocket": {
"version": "1.3.0",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/netflux-websocket/-/netflux-websocket-1.3.1.tgz",
"integrity": "sha512-vom3V+3oXf6dnT6UkCkpi76zZLGYNXTaWA0Azg/CsIfMHP66owYynkOFXoZ8N2gaQNoEWMT6msz9xjSOnAH0cw==",
"license": "LGPL-2.1"
},
"node_modules/node-releases": {

View File

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
"version": "2026.2.2",
"version": "2026.5.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
@ -22,14 +22,14 @@
"chainpad-crypto": "^0.3.0",
"chainpad-listmap": "^1.2.0",
"chainpad-netflux": "^1.3.0",
"chainpad-server": "^5.2.4",
"chainpad-server": "^5.3.0",
"ckeditor": "npm:ckeditor4@~4.22.1",
"codemirror": "^5.19.0",
"connect-gzip-static": "^4.2.1",
"cookie-parser": "^1.4.7",
"croppie": "^2.5.0",
"dragula": "3.7.2",
"drawio": "github:cryptpad/drawio-npm#npm-21.8.2+6",
"drawio": "github:cryptpad/drawio-npm#npm-29.6.7+3",
"express": "~4.22.1",
"file-saver": "1.3.1",
"fs-extra": "^7.0.0",

View File

@ -61,7 +61,6 @@ const factory = (Crypto, CPNetflux, Netflux, Util,
nThen(function (waitFor) {
Session.accessKeys.forEach(function (obj) {
Pinpad.create(config.network, obj, waitFor(function (e) {
console.log('done', obj);
if (e) { console.error(e); }
}));
});

View File

@ -209,12 +209,13 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
};
Block.updateSSOBlock = function (data, cb) {
const { blockKeys, oldBlockKeys } = data;
const { blockKeys, oldBlockKeys, hasPassword } = data;
var oldProof = oldBlockKeys && Block.proveAncestor(oldBlockKeys);
ServerCommand(blockKeys.sign, {
command: 'SSO_UPDATE_BLOCK',
ancestorProof: oldProof
ancestorProof: oldProof,
hasPassword
}, cb);
};

View File

@ -859,6 +859,22 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
delete sfDeleted[id];
};
var msgNotif = {};
handlers['SEND_CHAT_MESSAGE'] = function (ctx, box, data, cb) {
var msgSender = data.msg.author;
//Check if sender is in contacts and not muted
if (msgNotif[msgSender] || isMuted(ctx, data) || !ctx.store.proxy.friends[msgSender]) { return void cb(true); }
msgNotif[msgSender] = 1;
cb(false);
};
removeHandlers['SEND_CHAT_MESSAGE'] = function (ctx, box, data) {
var msgSender = data.author;
delete msgNotif[msgSender];
};
// New support
handlers['NEW_TICKET'] = function (ctx, box, data, cb) {
var msg = data.msg;

View File

@ -167,6 +167,7 @@ const _join: Callback = (ctx, clientId, data) => {
}
const isNew = typeof channels[channelId] === "undefined";
const isAnonymousSession = !store.loggedIn;
// Create or get existing channel object
const channel = channels[channelId] ||= {
@ -201,7 +202,7 @@ const _join: Callback = (ctx, clientId, data) => {
}
// Existing pad already loaded: send userlist and history
if (!isNew && channel.wc) {
const sendExistingState = () => {
postMessage(clientId, "PAD_CONNECT", { // Initialize
myID: channel.wc.myID,
id: channel.wc.id,
@ -218,7 +219,29 @@ const _join: Callback = (ctx, clientId, data) => {
});
});
postMessage(clientId, "PAD_READY"); // Ready
return;
};
const preflightAnonAccess = (cb) => {
const allow = () => { cb(true); };
const deny = () => {
Cache?.clearChannel?.(channelId);
channel.bcast("PAD_ERROR", { type: "ERESTRICTED" });
ctx.leavePad(null, data, function () {});
cb(false);
};
if (!isAnonymousSession) { return void allow(); }
if (!store.anon_rpc) { return void allow(); }
_getMetadata(ctx, clientId, { channel: channelId }, md => {
if (md?.rejected) { return void deny(); }
allow();
});
};
if (!isNew) {
return void preflightAnonAccess((ok) => {
if (!ok) { return; }
sendExistingState();
});
}
// chainpad-netflux config
@ -231,7 +254,7 @@ const _join: Callback = (ctx, clientId, data) => {
}
channel.bcast("PAD_ERROR", err);
if (type === "EDELETED" && Cache?.clearChannel) {
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].includes(type) && Cache?.clearChannel) {
Cache.clearChannel(channelId);
}
@ -343,7 +366,10 @@ const _join: Callback = (ctx, clientId, data) => {
});
}
};
channel.cpNf = CpNetflux.start(conf);
preflightAnonAccess((ok) => {
if (!ok) { return; }
channel.cpNf = CpNetflux.start(conf);
});
};
// Send a message to a pad we already joined

View File

@ -62,7 +62,7 @@ const factory = (SRpc, Channel, Util) => {
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
//console.log(data);
}
if (q === "DISCONNECT") {
onClose();
@ -99,7 +99,7 @@ const factory = (SRpc, Channel, Util) => {
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
//console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
@ -112,7 +112,7 @@ const factory = (SRpc, Channel, Util) => {
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
//console.log(data);
}
});

View File

@ -15,6 +15,7 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
var Types = {
message: 'MSG',
cleared: 'CLEARED',
unfriend: 'UNFRIEND',
mapId: 'MAP_ID',
mapIdAck: 'MAP_ID_ACK'
@ -116,7 +117,6 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
var getChannelMessagesSince = function (ctx, channel, data, keys) {
var network = ctx.store.network;
console.log('Fetching [%s] messages since [%s]', channel.id, data.lastKnownHash || '');
if (channel.isPadChat || channel.isTeamChat) {
// We need to use GET_HISTORY_RANGE to make sure we won't get the full history
@ -271,6 +271,11 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
return true;
}
var proxy = ctx.store.proxy;
if (parsedMsg[0] === Types.cleared) {
channel.messages = [];
ctx.emit('CLEAR_CHANNEL', channel.id, channel.clients);
return;
}
if (parsedMsg[0] === Types.unfriend) {
curvePublic = parsedMsg[1];
@ -917,11 +922,21 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
var channel = ctx.channels[id];
if (!channel) { return void cb({error: 'NO_CHANNEL'}); }
if (!ctx.store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var proxy = ctx.store.proxy || {};
ctx.store.rpc.clearOwnedChannel(id, function (err) {
cb({error:err});
if (!err) {
channel.messages = [];
ctx.emit('CLEAR_CHANNEL', id, channel.clients);
var msg = [Types.cleared, proxy.curvePublic, +new Date()];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encrypt(msgStr);
channel.wc.bcast(cryptMsg).then(function () {
// Success (message sent)
}, function (err) {
console.error('Failed to send message:', err);
});
}
});
};

View File

@ -170,7 +170,6 @@ const factory = (Util, Hash, Constants, Realtime,
ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients);
};
profile.execCommand = function (clientId, obj, cb) {
console.log(obj);
var cmd = obj.cmd;
var data = obj.data;
if (cmd === 'SUBSCRIBE') {

View File

@ -28,7 +28,7 @@ define([
"grid": "layout-grid",
"list": "list",
"document-owner": "id-card-lanyard",
"burn-drive": "ban",
"burn-drive": "eraser",
// Teams
"teams": "users-round",
"promote": "chevrons-up",
@ -85,7 +85,7 @@ define([
"share": "share-2",
"download": "hard-drive-download",
"destroy": "shredder",
"donate": "hand-coins",
"donate": "hand-heart",
"send": "send",
"cloud-upload": "cloud-upload",
"print": "printer",
@ -250,7 +250,10 @@ define([
"badge-error": "circle-alert",
// Other
"maintenance": "construction",
"release-notes": "notepad-text"
"release-notes": "notepad-text",
"crowdfunding-donate": "hand-heart",
"crowdfunding-snooze": "timer",
"crowdfunding-donate2": "ticket"
};
Icons.add = (newIcons) => {

View File

@ -213,7 +213,6 @@ define([
opt.channelHex = secret.channel;
}
console.warn(opt);
return opt;
};

View File

@ -3271,6 +3271,10 @@ define([
}
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
sframeChan.event('EV_SHARE_OPEN', {hidden: true});
UI.errorLoadingScreen(msg, false, false);
(cb || function () {})();
return;
} else if (err.type === 'HASH_NOT_FOUND' && priv.isHistoryVersion) {
msg = Messages.oo_deletedVersion;
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
@ -3388,39 +3392,70 @@ define([
UIElements.displayCrowdfunding = function (common, force) {
if (crowdfundingState) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey) { return; }
if (priv.app === 'drive') { return; }
if (!priv.channel) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey) { return; }
var todo = function () {
crowdfundingState = true;
// Display the popup
var text = Messages.crowdfunding_popup_text;
var yes = h('button.cp-corner-primary', [
Icons.get('external-link'),
'OpenCollective'
]);
var no = h('button.cp-corner-cancel', Messages.crowdfunding_popup_no);
var actions = h('div', [no, yes]);
var recordShown = function () {
common.getSframeChannel().query('Q_RECORD_CROWDFUNDING_SHOWN', {}, function () {});
};
var dontShowAgain = function () {
common.setAttribute(['general', 'crowdfunding'], false);
Feedback.send('CROWDFUNDING_NEVER');
};
var modal = UI.cornerPopup(text, actions, '', {
big: true,
alt: true,
dontShowAgain: dontShowAgain
});
$(yes).click(function () {
modal.delete();
common.openURL(priv.accounts.donateURL);
Feedback.send('CROWDFUNDING_YES');
});
$(no).click(function () {
modal.delete();
Feedback.send('CROWDFUNDING_NO');
var content = Messages.crowdfunding_popup_text;
var buttons = [{
name: Messages.dontShowAgain,
className: 'cancel left',
iconClass: 'close',
onClick: function () {
recordShown();
dontShowAgain();
}
}, {
name: Messages.crowdfunding_popup_no,
className: 'cancel',
iconClass: 'crowdfunding-snooze',
onClick: function () {
recordShown();
Feedback.send('CROWDFUNDING_NO');
}
}];
if (!Config.removeDonateButton) {
buttons.push({
name: Messages.crowdfunding_button2,
className: 'primary',
iconClass: 'crowdfunding-donate',
onClick: function () {
recordShown();
common.openURL(priv.accounts.donateURL);
Feedback.send('CROWDFUNDING_YES');
}
});
}
if (Config.accounts_api && common.isLoggedIn()) {
content += ' ' + Messages.crowdfunding_popup_text2;
buttons.push({
name: Messages.features_f_subscribe,
className: 'primary',
iconClass: 'crowdfunding-donate2',
onClick: function () {
recordShown();
common.openURL('/accounts/');
Feedback.send('CROWDFUNDING_SUBSCRIBE');
}
});
}
var modal = UI.dialog.customModal(content, {
force: true,
scrollable: true,
buttons: buttons
});
$(modal).addClass('cp-crowdfunding-modal');
UI.openCustomModal(modal, { wide: true });
};
if (force) {
@ -3430,13 +3465,16 @@ define([
if (AppConfig.disableCrowdfundingMessages) { return; }
if (priv.plan) { return; }
if (Config.removeDonateButton && !Config.accounts_api) { return; }
crowdfundingState = true;
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
if (err || val === false) { return; }
common.getSframeChannel().query('Q_GET_PINNED_USAGE', null, function (err, obj) {
var quotaMb = obj.quota / (1024 * 1024);
if (quotaMb < 10) { return; }
if (err || val === false) { crowdfundingState = false; return; }
common.getSframeChannel().query('Q_CROWDFUNDING_SHOULD_SHOW', null, function (err, result) {
if (err || !result || !result.show) {
crowdfundingState = false;
return;
}
todo();
});
});
@ -3457,7 +3495,7 @@ define([
// This pad will be deleted automatically, it shouldn't be stored
if (priv.burnAfterReading) { return; }
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey && !common.isLoggedIn()) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey && !common.isLoggedIn()) { return; }
var typeMsg = priv.pathname.indexOf('/file/') !== -1 ? Messages.autostore_file :
priv.pathname.indexOf('/drive/') !== -1 ? Messages.autostore_sf :
Messages.autostore_pad;

View File

@ -2010,6 +2010,7 @@ define([
var oldBlockKeys = oldAllocated.blockKeys;
var blockKeys = newAllocated.blockKeys;
var auth = data.auth;
var hasPassword = Boolean(data.newPassword);
nThen(function (waitFor) {
// Check if our drive is already owned
@ -2135,6 +2136,7 @@ define([
// Update "sso_block" data for SSO accounts
Block.updateSSOBlock({
blockKeys: blockKeys,
hasPassword: hasPassword,
oldBlockKeys: oldBlockKeys
}, waitFor(function (err) {
if (err) {

View File

@ -369,8 +369,9 @@ define([
APP.premiumPlan = priv.plan;
var getOpenIn = function (app) {
var icon = AppConfig.applicationsIcon[app];
var html = '<i data-lucide="'+icon+'"></i>' + Messages.type[app];
var icon = AppConfig.applicationsIcon[app] || app;
var iconHtml = Icons.get(icon).outerHTML;
var html = iconHtml + Messages.type[app];
return Messages._getKey('fc_openIn', [html]);
};
var restricted = {};

View File

@ -370,15 +370,17 @@ define([
Object.keys(categories).forEach(function (key, i) {
if (!active && !i) { active = key; }
var category = categories[key];
var name = category.name || Messages[`${app}_cat_${key}`] || key;
var icon;
if (category.icon) { icon = Icons.get(category.icon); }
var item = h('li.cp-sidebarlayout-category', {
'role': 'menuitem',
'tabindex': 0,
'data-category': key
'data-category': key,
'aria-label': name
}, [
icon,
category.name || Messages[`${app}_cat_${key}`] || key,
h('span.cp-sidebarlayout-category-name', name),
]);
var $item = $(item).appendTo(container);
Util.onClickEnter($item, function () {

View File

@ -120,6 +120,7 @@ var factory = function (Util) {
var iframe = document.createElement('iframe');
if (cfg.pdf.viewer) { // PDFJS
var viewerUrl = cfg.pdf.viewer + '?file=' + url;
iframe.setAttribute('sandbox', 'allow-scripts allow-downloads allow-same-origin allow-modals');
iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
iframe.onload = function () {
if (!metadata.name) { return; }
@ -574,7 +575,11 @@ var factory = function (Util) {
var copyAttributes = function (origin, dest) {
Object.keys(origin.attributes).forEach(function (i) {
if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
var name = origin.attributes[i].name.slice(10);
var name = origin.attributes[i].name.slice(10).toLowerCase();
// Ignore attributes filtered out by the sanitizer
if (name === "src") { return; }
if (name === "srcdoc") { return; }
if (/^on/i.test(name)) { return; }
var value = origin.attributes[i].value;
dest.setAttribute(name, value);
});

View File

@ -8,11 +8,14 @@ define([
'/common/common-util.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/visible.js',
'/common/notify.js',
'/common/inner/badges.js',
'/common/hyperscript.js',
'/common/diffMarked.js',
'/common/common-icons.js',
], function ($, Messages, Util, UI, UIElements, Badges, h, DiffMd, Icons) {
'/customize/pages.js',
], function ($, Messages, Util, UI, UIElements, Visible, Notification, Badges, h, DiffMd, Icons, Pages) {
'use strict';
var debug = console.log;
@ -64,7 +67,6 @@ define([
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info3),
h('li', Messages.contacts_info4),
])
])
@ -76,11 +78,11 @@ define([
h('div.cp-app-contacts-category-content')
]),
h('div.cp-app-contacts-friends.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content.cp-contacts-friends'),
h('button.btn.btn-default.cp-app-contacts-muted-button', {tabindex:0},[
Icons.get('mute'),
Messages.contacts_manageMuted
]),
h('div.cp-app-contacts-category-content.cp-contacts-friends')
])
]),
h('div.cp-app-contacts-rooms.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
@ -150,13 +152,14 @@ define([
var channels = Object.keys(state.channels).sort(function (a, b) {
var m1 = state.channels[a].messages.slice(-1)[0];
var m2 = state.channels[b].messages.slice(-1)[0];
if (!m2) { return !m1 ? 0 : 1; }
if (!m1) { return -1; }
return m1.time - m2.time;
if (!m2) { return !m1 ? 0 : -1; }
if (!m1) { return 1; }
return m2.time - m1.time;
});
channels.forEach(function (c, i) {
$userlist.find(dataQuery(c)).css('order', i);
channels.forEach(function (c) {
var $el = $userlist.find(dataQuery(c));
$el.appendTo($el.parent());
});
// Make sure the width is correct even if there is a scrollbar
@ -184,7 +187,8 @@ define([
});
var time = h('div.cp-app-contacts-time', hour);
$d.append(time);
var row = h('div.cp-app-contacts-message-row', [d, time]);
return row;
} catch (e) {
console.error(md);
console.error(e);
@ -239,19 +243,45 @@ define([
};
var clearChannel = function (id) {
$(getChat(id)).find('.cp-app-contacts-messages').html('');
var $chat = $(getChat(id));
if (state.channels && state.channels[id]) {
state.channels[id].messages = [];
}
if ($chat.length) {
$chat.find('.cp-app-contacts-messages').html('');
$chat.find('.cp-app-contacts-message').remove();
}
};
var displaySystemMessage = function (id, message, icon) {
var $messagebox = $(getChat(id)).find('.cp-app-contacts-messages');
if (!$messagebox.length) { return; }
var content = icon ? [Icons.get(icon), h('span', message)] : message;
$messagebox.append(h('div.cp-app-contacts-message.cp-app-contacts-system-notification', content));
normalizeLabels($messagebox);
scrollChatToBottom();
};
var userInfo;
markup.chatbox = function (id, data, curvePublic) {
var moreHistory = h('span', {
class: 'cp-app-contacts-more-history',
tabindex: '0',
role: 'button',
'aria-label': Messages.contacts_fetchHistory,
title: Messages.contacts_fetchHistory
});
moreHistory.append(Icons.get('history', {title: Messages.contacts_fetchHistory}));
moreHistory.append(Icons.get('history'));
var chan = state.channels[id];
var displayName = UI.getDisplayName(chan.name || chan.displayName);
var fetching = false;
var $moreHistory = $(moreHistory).click(function () {
var $moreHistory = $(moreHistory).on('keydown', function (e) {
if (e.which === 13 || e.which === 32) {
e.preventDefault();
$(this).click();
}
}).click(function () {
if (fetching) { return; }
// get oldest known message...
@ -308,11 +338,20 @@ define([
});
var removeHistory = h('span', {
'class': 'cp-app-contacts-remove-history'
'class': 'cp-app-contacts-remove-history',
'tabindex': '0',
'role': 'button',
'aria-label': Messages.contacts_removeHistoryTitle,
'title': Messages.contacts_removeHistoryTitle
});
removeHistory.append(Icons.get('remove-history', {title: Messages.contacts_removeHistoryTitle}));
removeHistory.append(Icons.get('remove-history'));
$(removeHistory).click(function () {
$(removeHistory).on('keydown', function (e) {
if (e.which === 13 || e.which === 32) {
e.preventDefault();
$(this).click();
}
}).click(function () {
UI.confirm(Messages.contacts_confirmRemoveHistory, function (yes) {
if (!yes) { return; }
@ -322,6 +361,7 @@ define([
UI.alert(Messages.contacts_removeHistoryServerError);
return;
}
clearChannel(id);
});
});
});
@ -329,13 +369,23 @@ define([
var avatar = h('div.cp-avatar');
var avatarDiv = h('div.cp-avatar-container', avatar);
var backButton = h('span.cp-app-contacts-back', {
'aria-label': Messages.form_backButton,
title: Messages.form_backButton,
}, Icons.get('arrow-left'));
$(backButton).click(function () {
$container.removeClass('cp-app-contacts-chat-open');
});
var headerContent = [
backButton,
avatarDiv,
moreHistory,
data.isFriendChat ? removeHistory : undefined
];
if (isApp) {
headerContent = [
backButton,
h('div.cp-app-contacts-header-title', Messages.contacts_padTitle),
moreHistory
];
@ -363,6 +413,7 @@ define([
});
var sendButton = h('button.btn.btn-primary', {
title: Messages.contacts_send,
'aria-label': Messages.contacts_send
}, Icons.get('send'));
var rightCol = h('span.cp-app-contacts-right-col', [
@ -396,6 +447,28 @@ define([
// failed to send
return void console.error('failed to send', e);
}
//Send mailbox message if:
//- recipient is a contact
//- recipient is offline
//- no previous messages to recipient sent while current tab is open
var messageSent = {};
execCommand('GET_STATUS', id, function (e, online) {
if (online) {
delete messageSent[id];
} else {
if (friend && !messageSent[id]) {
common.mailbox.sendTo("SEND_CHAT_MESSAGE", {
name: userInfo.displayName,
}, {
channel: contactsData[chan.curvePublic].notifications,
curvePublic: chan.curvePublic
});
messageSent[id] = true;
}
}
});
input.value = '';
sending = false;
debug('sent successfully');
@ -456,6 +529,32 @@ define([
$messages.find('.cp-app-contacts-info').show();
};
var updateInfoMessage = function () {
var chats = Object.keys(state.channels).length;
var $info = $messages.find('.cp-app-contacts-info');
if (chats === 0) {
$info.html([
h('h2', Messages.contacts_noFriends),
h('ul', [
h('li', [
UI.createHelper(Pages.localizeDocsLink(' https://docs.cryptpad.org/en/user_guide/collaboration.html#contacts'), Messages.contacts_noFriendsInfo),
Messages.contacts_noFriendsInfo
]),
])
]);
$container.addClass('cp-app-contacts-no-chats');
} else {
$info.html([
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info4),
])
]);
$container.removeClass('cp-app-contacts-no-chats');
}
};
var updateStatus = function (id) {
if (!state.channels[id]) { return; }
var $status = find.inList(id).find('.cp-app-contacts-status');
@ -491,6 +590,7 @@ define([
setActive(chanId);
unnotify(chanId);
$container.addClass('cp-app-contacts-chat-open');
var $chat = getChat(chanId);
hideInfo();
$messages.find('div.cp-app-contacts-chat[data-key]').hide();
@ -534,6 +634,7 @@ define([
'tabindex': '0',
'data-key': id,
'data-user': room.isFriendChat ? userlist[0].curvePublic : '',
'aria-label': room.isFriendChat ? UI.getDisplayName(room.name) : room.name
});
@ -543,85 +644,146 @@ define([
curve = __channel.curvePublic;
}
var unmute = h('span', Icons.get('notification'), {
class: 'cp-app-contacts-remove cp-unmute-icon',
title: Messages.contacts_unmute || 'unmute',
style: (curve && mutedUsers[curve]) ? undefined : 'display: none;'
var isMuted = curve && mutedUsers[curve];
var friendData = room.isFriendChat ? userlist[0] : {};
var dropdownOptions = [];
var removeOption = {
tag: 'a',
content: [Icons.get('unfriend'), h('span', Messages.contacts_remove)],
action: function () {
var channel = state.channels[id];
if (!channel.isFriendChat) {
UI.warn(Messages.error);
return;
}
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
var name = Util.fixHTML(UI.getDisplayName(friend.name || friend.displayName));
var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('contacts_confirmRemove', [ name ])),
]);
UI.confirm(content, function (yes) {
if (!yes) { return; }
removeFriend(curvePublic);
// TODO remove friend from userlist ui
// FIXME seems to trigger EJOINED from netflux-websocket (from server);
// (tried to join a channel in which you were already present)
});
return true;
}
};
var viewProfileOption = {
tag: 'a',
content: [Icons.get('user-profile'), h('span', Messages.userlist_visitProfile)],
action: function () {
if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); }
return true;
}
};
let $dropdown, muteOption, unmuteOption;
var rebuildDropdown = function () {
if (!$dropdown || !$dropdown.setOptions) { return; }
var opts = [];
if (room.isFriendChat) {
var isCurrentlyMuted = curve && mutedUsers[curve];
opts.push(isCurrentlyMuted ? unmuteOption : muteOption);
if (friendData.profile) {
opts.push(viewProfileOption);
}
opts.push(removeOption);
}
$dropdown.setOptions(opts);
$dropdown.find('.cp-dropdown-content').hide();
};
muteOption = {
tag: 'a',
content: [Icons.get('mute'), h('span', Messages.contacts_mute)],
action: function () {
var channel = state.channels[id];
if (!channel.isFriendChat) { return true; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
muteUser(friend);
rebuildDropdown();
return true;
}
};
unmuteOption = {
tag: 'a',
content: [Icons.get('notification'), h('span', Messages.contacts_unmute || 'Unmute')],
action: function () {
var channel = state.channels[id];
if (!channel.isFriendChat) { return true; }
var curvePublic = channel.curvePublic;
unmuteUser(curvePublic);
rebuildDropdown();
return true;
}
};
if (room.isFriendChat) {
dropdownOptions.push(isMuted ? unmuteOption : muteOption);
if (friendData.profile) {
dropdownOptions.push(viewProfileOption);
}
dropdownOptions.push(removeOption);
}
$dropdown = UIElements.createDropdown({
iconCls: 'settings',
options: dropdownOptions,
buttonCls: 'cp-app-contacts-dropdown-btn',
buttonTitle: Messages.settingsButton
});
var mute = h('span', Icons.get('mute'), {
class: 'cp-app-contacts-remove cp-mute-icon',
title: Messages.contacts_mute || 'mute',
style: (curve && mutedUsers[curve]) ? 'display: none;' : undefined
$dropdown.addClass('cp-app-contacts-icons');
$dropdown.on('click dblclick', function (e) {
e.stopPropagation();
});
var remove = h('span', Icons.get('unfriend', {
class: 'cp-app-contacts-remove',
title: Messages.contacts_remove
}));
var leaveRoom = h('span', Icons.get('logout', {
class: 'cp-app-contacts-remove',
title: Messages.contacts_leaveRoom
}));
var $dropdownMenu = $dropdown.find('.cp-dropdown-content');
$dropdownMenu.css('position', 'fixed');
$dropdown.find('button').on('click', function () {
rebuildDropdown();
var rect = this.getBoundingClientRect();
var menuWidth = $dropdownMenu.outerWidth() || 150;
var viewportWidth = window.innerWidth;
var left = rect.left;
if (left + menuWidth > viewportWidth) {
left = rect.right - menuWidth;
}
if (left < 0) { left = 0; }
$dropdownMenu.css({
top: rect.bottom + 'px',
left: left + 'px',
});
});
var status = h('span.cp-app-contacts-status', {
title: Messages.contacts_online
});
var mute = h('span.cp-app-contacts-mute-indicator', {
title: Messages.contacts_muted,
style: isMuted ? '' : 'display: none;'
}, Icons.get('mute'));
var bottomRow = h('span.cp-app-contacts-bottom-row', [
mute,
$dropdown[0],
]);
var rightCol = h('span.cp-app-contacts-right-col', [
h('span.cp-app-contacts-name', [room.isFriendChat? UI.getDisplayName(room.name): room.name]),
h('span.cp-app-contacts-icons', [
room.isFriendChat ? mute : undefined,
room.isFriendChat ? unmute : undefined,
room.isFriendChat ? remove :
(room.isPadChat || room.isTeamChat) ? undefined : leaveRoom,
])
bottomRow,
]);
var friendData = room.isFriendChat ? userlist[0] : {};
var $room = $(roomEl).on('click keypress', function (event) {
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
display(id);
}
}).dblclick(function () {
if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); }
});
$(unmute).on('click dblclick', function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
$(mute).show();
$(unmute).hide();
unmuteUser(curvePublic);
});
$(mute).on('click dblclick', function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
$(mute).hide();
$(unmute).show();
muteUser(friend);
});
$(remove).click(function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
var name = Util.fixHTML(UI.getDisplayName(friend.name || friend.displayName));
var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('contacts_confirmRemove', [ name ])),
]);
UI.confirm(content, function (yes) {
if (!yes) { return; }
removeFriend(curvePublic);
// TODO remove friend from userlist ui
// FIXME seems to trigger EJOINED from netflux-websocket (from server);
// (tried to join a channel in which you were already present)
});
});
const $avatar = $(h('div.cp-avatar')).appendTo($room);
@ -668,6 +830,11 @@ define([
}
notifyToolbar();
if (!Visible.currently()) {
common.notify();
Notification.create();
}
channel.messages.push(message);
var $chat = $(chat);
@ -678,6 +845,8 @@ define([
var $messagebox = $chat.find('.cp-app-contacts-messages');
var shouldScroll = isBottomedOut($messagebox);
$messagebox.find('.cp-app-contacts-system-notification').remove();
$messagebox.append(el_message);
if (shouldScroll) {
@ -801,7 +970,6 @@ define([
$messagebox.append(el_message);
});
normalizeLabels($messagebox);
var roomEl = markup.room(id, room, list);
var $parentEl;
@ -835,6 +1003,7 @@ define([
if (err) { return void console.error(err); }
debug('rooms: ' + JSON.stringify(rooms));
rooms.forEach(initializeRoom);
updateInfoMessage();
});
};
@ -848,6 +1017,7 @@ define([
if (channel && channel.curvePublic === curvePublic) {
showInfo();
}
updateInfoMessage();
if (!removedByMe) {
// TODO UI.alert if this is triggered by the other guy
}
@ -895,6 +1065,8 @@ define([
.find('.cp-mute-icon').show();
$('.cp-app-contacts-friend[data-user]')
.find('.cp-unmute-icon').hide();
$('.cp-app-contacts-friend[data-user]')
.find('.cp-app-contacts-mute-indicator').hide();
if (!muted || Object.keys(muted).length === 0) {
$button.hide();
return;
@ -905,6 +1077,8 @@ define([
.find('.cp-mute-icon').hide();
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-unmute-icon').show();
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-app-contacts-mute-indicator').show();
var data = muted[curve];
var avatar = h('span.cp-avatar');
var button = h('button.btn', {
@ -915,15 +1089,15 @@ define([
]);
common.displayAvatar($(avatar), data.avatar, data.name, Util.noop, data.uid, data.badge);
$(button).click(function () {
unmuteUser(curve, button);
execCommand('UNMUTE_USER', curve, function (e, data) {
if (e) { return void console.error(e); }
unmuteUser(curve, function () {
$(button).closest('div').remove();
if (!data) { $button.hide(); }
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-unmute-icon').hide();
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-mute-icon').show();
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-app-contacts-mute-indicator').hide();
if ($('.cp-contacts-muted-table').find('.cp-contacts-muted-user').length === 0) {
UI.findOKButton().click();
}
@ -957,6 +1131,7 @@ define([
debug('rooms: ' + JSON.stringify(rooms));
rooms.forEach(initializeRoom);
updateInfoMessage();
});
updateMutedList();
@ -1015,6 +1190,7 @@ define([
}
if (cmd === 'CLEAR_CHANNEL') {
clearChannel(data);
displaySystemMessage(data, Messages.contacts_historyCleared, 'clear-canvas');
return;
}
if (cmd === 'PADCHAT_READY') {
@ -1072,10 +1248,10 @@ define([
});
};
//});
execCommand('GET_MY_INFO', null, function (e, info) {
if (e) { return; }
contactsData[info.curvePublic] = info;
userInfo = info;
});

View File

@ -148,6 +148,25 @@ define([
}
};
// Send chat message
handlers['SEND_CHAT_MESSAGE'] = function(common, data) {
var content = data.content;
var msg = content.msg;
var key = 'sent_chatMessage';
var name = Util.fixHTML(msg.content.name) || Messages.anonymous;
content.getFormatText = function() {
return Messages._getKey(key, [name]);
};
content.handler = function() {
common.openURL('/contacts/');
defaultDismiss(common, data)();
};
if (!content.archived) {
content.dismissHandler = defaultDismiss(common, data);
}
};
// New support message from the admins
handlers['SUPPORT_MESSAGE'] = function(common, data) {
var content = data.content;

View File

@ -2813,14 +2813,21 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
});
};
var importFile = function(content) {
// Abort if there is another real user in the channel (history keeper excluded)
// Abort if there is another real user in the channel (history keeper excluded)
var checkChannelUsers = function () {
var m = metadataMgr.getChannelMembers().slice().filter(function (nId) {
return nId.length === 32;
});
if (m.length > 1) {
UI.removeModals();
return void UI.alert(Messages.oo_cantUpload);
UI.alert(Messages.oo_cantUpload);
return true;
}
};
var importFile = function(content) {
if (checkChannelUsers()) {
return;
}
if (!content) {
UI.removeModals();
@ -2862,6 +2869,10 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
if (!supportsXLSX()) {
return void UI.alert(Messages.oo_invalidFormat);
}
if (checkChannelUsers()) {
return;
}
var div = h('div.cp-oo-x2tXls', [
Icons.get('loading'),
h('span', Messages.oo_importInProgress)
@ -3460,6 +3471,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
var $forgetButton = common.createButton('forget', true, {}, function (err) {
if (err) { return; }
setEditable(false);
toolbar.forgotten();
});
var $forget = UIElements.getEntryFromButton($forgetButton);
toolbar.$drawer.append($forget);
@ -3796,8 +3808,12 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
const integrationHasUnsavedChanges = function(unsavedChanges, cb) {
integrationChannel.query('Q_INTEGRATION_HAS_UNSAVED_CHANGES', unsavedChanges, cb);
};
const onUserlistChange = (list) => {
integrationChannel.event('Q_INTEGRATION_USERLIST_CHANGE', list);
};
var inte = common.createIntegration(integrationSave,
integrationHasUnsavedChanges);
integrationHasUnsavedChanges,
onUserlistChange);
if (inte && cfg.autosave) {
evIntegrationSave.reg(function () {
inte.changed();

View File

@ -151,8 +151,8 @@ define([
sessionStorage.clear();
try {
Object.keys(localStorage || {}).forEach(function (k) {
// Remvoe everything in localStorage except CACHE and FS_hash
if (/^CRYPTPAD_CACHE/.test(k) || /^LESS_CACHE/.test(k) || k === Constants.fileHashKey || /^CRYPTPAD_STORE|colortheme/.test(k)) { return; }
// Remove everything in localStorage except CACHE and FS_hash
if (/^CRYPTPAD_CACHE/.test(k) || /^LESS_CACHE/.test(k) || k === Constants.fileHashKey || /^CRYPTPAD_STORE|colortheme/.test(k) || (!isDeletion && /^cp_crowdfunding_/.test(k))) { return; }
delete localStorage[k];
});
} catch (e) { console.error(e); }

View File

@ -103,7 +103,7 @@ define([
const sendMyID = () => {
const user = Util.clone(privateData?.integrationConfig?.
_?.editorConfig?.user);
_?.editorConfig?.user) || {};
user.readOnly = isView;
execCommand?.('SEND', {
msg: 'MYID',

View File

@ -1028,6 +1028,110 @@ define([
cb({error:e});
});
});
var CROWDFUNDING_PREFIX = 'cp_crowdfunding_';
var CROWDFUNDING_DRIVE_KEY = ['general', 'crowdfunding_metrics'];
// First action (opening or creating a document) count threshold before showing the banner
var CROWDFUNDING_MIN_ACTIONS = 5;
// Additional actions required after each shown banner
var CROWDFUNDING_ACTIONS_INTERVAL = 10;
// Quota usage threshold for quota-based banner display
var CROWDFUNDING_MIN_QUOTA_MB = 50;
// Cooldown between banner displays based on last shown timestamp in milliseconds
var CROWDFUNDING_COOLDOWN_MS = 24 * 60 * 60 * 1000;
var crowdfundingGetLS = function () {
var get = function (suffix) {
var k = CROWDFUNDING_PREFIX + suffix;
var val = localStorage.getItem(k);
if (val !== null && val !== '') { return Number(val) || null; }
return null;
};
return {
visitCount: get('visitCount') || 0,
firstSeen: get('firstSeen') || null,
lastShownAtCount: get('lastShownAtCount') || 0,
lastShownAtTime: get('lastShownAtTime') || 0
};
};
// Read metrics: encrypted drive for logged-in users, localStorage for guests
var crowdfundingReadMetrics = function (cb) {
if (!Utils.LocalStore.isLoggedIn()) { return cb(crowdfundingGetLS()); }
Cryptpad.getAttribute(CROWDFUNDING_DRIVE_KEY, function (e, metrics) {
if (e || !metrics || typeof metrics !== 'object') {
return cb({
visitCount: 0,
firstSeen: null,
lastShownAtCount: 0,
lastShownAtTime: 0
});
}
cb(metrics);
});
};
// Write metrics: encrypted drive for logged-in users, localStorage for guests
var crowdfundingWriteMetrics = function (metrics, cb) {
if (!Utils.LocalStore.isLoggedIn()) {
try {
['visitCount', 'firstSeen', 'lastShownAtCount', 'lastShownAtTime'].forEach(function (k) {
if (metrics[k] !== null && metrics[k] !== undefined) {
localStorage.setItem(CROWDFUNDING_PREFIX + k, String(metrics[k]));
}
});
} catch (e) {}
return cb && cb();
}
Cryptpad.setAttribute(CROWDFUNDING_DRIVE_KEY, metrics, function () {
cb && cb();
});
};
var crowdfundingIncrementAction = function (cb) {
crowdfundingReadMetrics(function (metrics) {
metrics.visitCount = (metrics.visitCount || 0) + 1;
if (!metrics.firstSeen) { metrics.firstSeen = Date.now(); }
crowdfundingWriteMetrics(metrics, cb);
});
};
sframeChan.on('Q_CROWDFUNDING_SHOULD_SHOW', function (data, cb) {
crowdfundingReadMetrics(function (metrics) {
var actionCount = metrics.visitCount || 0;
var lastShownAtCount = metrics.lastShownAtCount || 0;
var lastShownAtTime = metrics.lastShownAtTime || 0;
var now = Date.now();
var nextThreshold = lastShownAtCount === 0 ? CROWDFUNDING_MIN_ACTIONS : lastShownAtCount + CROWDFUNDING_ACTIONS_INTERVAL;
var enoughTimePassed = lastShownAtTime === 0 || (now - lastShownAtTime >= CROWDFUNDING_COOLDOWN_MS);
var showFromActions = actionCount >= nextThreshold && enoughTimePassed;
if (showFromActions) {
return cb({
show: true,
actionCount: actionCount
});
}
if (CROWDFUNDING_MIN_QUOTA_MB <= 0) {
return cb({
show: false,
actionCount: actionCount
});
}
Cryptpad.getPinnedUsage({}, function (e, used) {
var usedMb = (typeof used === 'number') ? (used / (1024 * 1024)) : 0;
cb({
show: !e && usedMb >= CROWDFUNDING_MIN_QUOTA_MB && enoughTimePassed,
actionCount: actionCount
});
});
});
});
sframeChan.on('Q_RECORD_CROWDFUNDING_SHOWN', function (data, cb) {
crowdfundingReadMetrics(function (metrics) {
metrics.lastShownAtCount = (data && typeof data.count === 'number') ? data.count : (metrics.visitCount || 0);
metrics.lastShownAtTime = Date.now();
crowdfundingWriteMetrics(metrics, cb);
});
});
sframeChan.on('Q_CROWDFUNDING_INCREMENT_OPEN', function (data, cb) {
if (readOnly) { return cb && cb(); }
crowdfundingIncrementAction(cb);
});
Cryptpad.mailbox.onEvent.reg(function (data, cb) {
sframeChan.query('EV_MAILBOX_EVENT', data, function (err, obj) {

View File

@ -768,7 +768,7 @@ define([
// Ctrl+E: New pad modal
var priv = ctx.metadataMgr.getPrivateData();
if (e.which === 69 && isApp) {
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey) { return; }
e.preventDefault();
return void funcs.createNewPadModal();
}
@ -1063,10 +1063,16 @@ define([
document.title = title;
});
funcs.isPadStored(function (err, val) {
if (err || !val) { return; }
var showCrowdfunding = function () {
UIElements.displayCrowdfunding(funcs);
});
};
var privateData = ctx.metadataMgr.getPrivateData();
var isDriveContext = privateData.app === 'drive';
var isReadOnlyFormResponse = privateData.app === 'form' && privateData.readOnly && !privateData.form_auditorHash && !privateData.form_auditorKey;
var skipCrowdfunding = privateData.secureIframe === true || privateData.unsafeIframe === true || isReadOnlyFormResponse || isDriveContext;
if (!skipCrowdfunding) {
ctx.sframeChan.query('Q_CROWDFUNDING_INCREMENT_OPEN', {}, showCrowdfunding);
}
ctx.sframeChan.ready();

View File

@ -509,12 +509,15 @@ MessengerUI, Messages, Pages, PadTypes, Icons) {
};
var createCollapse = function (toolbar) {
var icon = Icons.get('chevron-up', {title: Messages.toolbar_collapse});
var icon = Icons.get('chevron-up');
var notif = h('span.cp-collapsed-notif');
var $button = $(h('button.cp-toolbar-collapse',[
icon,
notif
var $button = $(h('button.cp-toolbar-collapse', {
'aria-label': Messages.toolbar_collapse,
'title': Messages.toolbar_collapse
}, [
icon,
notif
]));
toolbar.$bottomR.prepend($button);
$(notif).hide();
@ -530,10 +533,14 @@ MessengerUI, Messages, Pages, PadTypes, Icons) {
$button.toggleClass('cp-toolbar-button-active');
const newIcon = hidden ?
Icons.get('chevron-down', {title: Messages.toolbar_expand}):
Icons.get('chevron-up', {title: Messages.toolbar_collapse});
Icons.get('chevron-down'):
Icons.get('chevron-up');
$button.find('[data-lucide]').replaceWith(newIcon);
$button.attr({
'aria-label': hidden ? Messages.toolbar_expand : Messages.toolbar_collapse,
'title': hidden ? Messages.toolbar_expand : Messages.toolbar_collapse
});
if (!hidden) { $(notif).hide(); }

View File

@ -227,7 +227,7 @@
"contacts_info3": "Auf den Avatar doppelklicken, um das entsprechende Profil anzuzeigen",
"contacts_info4": "Jeder Teilnehmer kann den Chatverlauf endgültig löschen",
"contacts_removeHistoryTitle": "Den Chatverlauf löschen",
"contacts_confirmRemoveHistory": "Bist du sicher, dass du den Chatverlauf endgültig löschen willst? Die Daten sind dann weg",
"contacts_confirmRemoveHistory": "Bist du sicher, dass du den Chatverlauf löschen willst? Die Nachrichten werden für alle gelöscht und können nicht wiederhergestellt werden.",
"contacts_removeHistoryServerError": "Es gab einen Fehler bei dem Löschen des Chatverlaufs. Versuche es später noch einmal",
"contacts_fetchHistory": "Den früheren Verlauf laden",
"contacts_rooms": "Chaträume",
@ -590,7 +590,7 @@
"autostore_forceSave": "Speichere die Datei in deinem CryptDrive",
"autostore_notAvailable": "Du musst dieses Dokument in deinem CryptDrive speichern, bevor du diese Funktion benutzen kannst.",
"crowdfunding_button": "Unterstütze CryptPad",
"crowdfunding_popup_text": "<h3>Wir brauchen deine Hilfe!</h3>Um sicherzustellen, dass CryptPad weiter aktiv entwickelt wird, unterstütze bitte das Projekt über die OpenCollective Seite, wo du unsere <b>Roadmap</b> und <b>Funding-Ziele</b> lesen kannst.",
"crowdfunding_popup_text": "<h3>Wir brauchen deine Hilfe!</h3>Um sicherzustellen, dass CryptPad weiter aktiv entwickelt wird, unterstütze bitte das Projekt mit einer Spende auf OpenCollective.",
"crowdfunding_popup_no": "Nicht jetzt",
"invalidHashError": "Das angeforderte Dokument hat eine ungültige URL.",
"oo_cantUpload": "Das Hochladen von Dateien ist nicht erlaubt, während andere Nutzer anwesend sind.",
@ -1868,5 +1868,18 @@
"support_moreTickets": "{0} Tickets verfügbar",
"oo_rtChannelMissingNoSupport": "Beim Laden deines Dokuments ist ein Fehler aufgetreten. Es ist nun schreibgeschützt, um Beschädigung oder Verlust der Daten zu vermeiden. Bitte sende die folgenden Informationen an den Administrator deiner Instanz.",
"oo_rtChannelMissing": "Beim Laden deines Dokuments ist ein Fehler aufgetreten. Es ist nun schreibgeschützt, um Beschädigung oder Verlust der Daten zu vermeiden. Bitte verwende die folgende Schaltfläche, um die entsprechenden Informationen an das Support-Team zu senden.",
"oo_rtChannelMissingDate": "Nachricht gesendet am {0}"
"oo_rtChannelMissingDate": "Nachricht gesendet am {0}",
"contacts_noFriends": "Deine Kontaktliste ist leer",
"contacts_historyCleared": "Der Chatverlauf wurde für alle gelöscht",
"support_moderatorNotification": "{0} hat dich als Moderator zum Support-Team hinzugefügt",
"crowdfunding_popup_text2": "Alternativ kannst du auf dieser Instanz ein Abonnement abschließen.",
"form_input_ph_text": "Deine Antwort hier",
"form_input_ph_number": "Gib eine Zahl ein",
"form_date_time": "Wähle Datum und Uhrzeit aus",
"contacts_muted": "Stummgeschaltet",
"contacts_noFriendsInfo": "<a>Füge einen Kontakt hinzu</a>, um eine Unterhaltung zu beginnen.",
"diagram_modesOptionLabel": "Zum Theme {0} wechseln, lädt die Anwendung neu",
"diagram_sketchTheme": "Skizze",
"diagram_simpleTheme": "Einfach",
"diagram_classicTheme": "Klassisch"
}

View File

@ -231,7 +231,7 @@
"contacts_info3": "Double-cliquer sur son nom pour voir son profil",
"contacts_info4": "Chaque participant·e peut nettoyer définitivement l'historique d'une discussion",
"contacts_removeHistoryTitle": "Supprimer l'historique du chat",
"contacts_confirmRemoveHistory": "Êtes-vous sûr de vouloir supprimer définitivement l'historique de votre chat ? Les messages ne pourront pas être restaurés",
"contacts_confirmRemoveHistory": "Êtes-vous sûr de vouloir supprimer définitivement l'historique de votre chat ? Les messages seront supprimés pour tout le monde et ne pourront être restaurés.",
"contacts_removeHistoryServerError": "Une erreur est survenue lors de la supprimer de l'historique du chat. Veuillez réessayer plus tard",
"contacts_fetchHistory": "Récupérer l'historique plus ancien",
"contacts_rooms": "Salons",
@ -353,7 +353,7 @@
"settings_restore": "Restaurer",
"settings_backupHint2": "Téléchargez tous les documents dans votre drive. Les documents seront téléchargés dans un format lisible par d'autres applications si un tel format est disponible. Lorsqu'un tel format n'est pas disponible, les documents seront téléchargés dans un format lisible par CryptPad.",
"settings_backup2": "Télécharger mon CryptDrive",
"settings_backup2Confirm": "Vous allez télécharger tous les documents de votre CryptDrive. Si vous souhaitez continuer, choisissez un nom et appuyez sur OK.",
"settings_backup2Confirm": "Vous allez télécharger tous les documents et fichiers de votre CryptDrive. Si vous souhaitez continuer, choisissez un nom et appuyez sur OK",
"settings_exportTitle": "Téléchargement de votre CryptDrive",
"settings_exportDescription": "Veuillez patienter pendant que nous téléchargeons et déchiffrons vos documents. Cette opération peut prendre plusieurs minutes. Fermer l'onglet du navigateur interrompra le processus.",
"settings_exportFailed": "Si un pad nécessite plus d'une minute pour être traité, il ne sera pas inclus dans l'archive. Une liste des documents n'ayant pas été exportés sera disponible à la fin.",
@ -558,8 +558,8 @@
"password_submit": "Valider",
"properties_addPassword": "Ajouter un mot de passe",
"properties_changePassword": "Modifier le mot de passe",
"properties_confirmNew": "Êtes-vous sûr·e ? Ajouter un mot de passe changera l'URL de ce document et supprimera son historique. Les utilisateur·ices ne connaissant pas le nouveau mot de passe perdront l'accès au document.",
"properties_confirmChange": "Êtes-vous sûr·e ? Changer le mot de passe supprimera l'historique de ce document. Les utilisateur·ices ne connaissant pas le nouveau mot de passe perdront l'accès au document.",
"properties_confirmNew": "Êtes-vous sûr·e ? Ajouter un mot de passe changera l'URL de ce document et supprimera son historique. Les utilisateur·ices ne connaissant pas le mot de passe perdront l'accès à ce document",
"properties_confirmChange": "Êtes-vous sûr·e ? Changer le mot de passe supprimera l'historique. Les utilisateur·ices ne connaissant pas le nouveau mot de passe perdront l'accès à ce document",
"properties_passwordSame": "Le nouveau mot de passe doit être différent de celui existant.",
"properties_passwordError": "Une erreur est survenue lors de la modification du mot de passe. Veuillez réessayer.",
"properties_passwordWarning": "Le mot de passe a été modifié avec succès mais nous n'avons pas réussi à mettre à jour votre CryptDrive avec les nouvelles informations. Vous devrez peut-être supprimer manuellement l'ancienne version de ce pad.<br>Appuyez sur OK pour recharger le document et mettre à jour vos droits d'accès.",
@ -595,7 +595,7 @@
"autostore_notAvailable": "Vous devez stocker ce document dans votre CryptDrive avant de pouvoir utiliser cette fonctionnalité.",
"crowdfunding_button": "Soutenir CryptPad",
"crowdfunding_button2": "Faire un don",
"crowdfunding_popup_text": "<h3>Aider CryptPad</h3>Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la page OpenCollective, où vous pouvez trouver notre <b>Roadmap</b> et nos <b>objectifs de financement</b>.",
"crowdfunding_popup_text": "<h3>Nous avons besoin de votre aide !</h3>Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la page OpenCollective.",
"crowdfunding_popup_no": "Pas maintenant",
"survey": "Enquête CryptPad",
"markdown_toc": "Sommaire",
@ -1868,5 +1868,18 @@
"loadAll": "Charger tous les tickets",
"oo_rtChannelMissing": "Une erreur s'est produite lors du chargement de votre document. Celui-ci est désormais en lecture seule afin d'éviter toute corruption ou perte de données. Veuillez utiliser le bouton ci-dessous pour envoyer les informations nécessaires à l'équipe de support.",
"oo_rtChannelMissingDate": "Message envoyé le {0}",
"oo_rtChannelMissingNoSupport": "Une erreur s'est produite lors du chargement de votre document. Celui-ci est désormais en lecture seule afin d'éviter toute corruption ou perte de données. Veuillez transmettre les informations suivantes à l'administrateur·ice de votre instance."
"oo_rtChannelMissingNoSupport": "Une erreur s'est produite lors du chargement de votre document. Celui-ci est désormais en lecture seule afin d'éviter toute corruption ou perte de données. Veuillez transmettre les informations suivantes à l'administrateur·ice de votre instance.",
"support_moderatorNotification": "{0} vous a ajouté à l'équipe de support en tant que modérateur",
"crowdfunding_popup_text2": "Vous pouvez également vous abonner sur cette instance.",
"form_input_ph_text": "Votre réponse ici",
"form_input_ph_number": "Entrez un nombre",
"form_date_time": "Sélectionner la date et l'heure",
"contacts_muted": "Masqué",
"contacts_noFriends": "Votre liste de contacts est vide",
"contacts_noFriendsInfo": "Commencez par <a>ajouter un contact</a> pour débuter une conversation.",
"contacts_historyCleared": "L'historique de conversation a été supprimé pour tout le monde",
"diagram_modesOptionLabel": "Changer le thème pour {0}, réactualise l'application",
"diagram_sketchTheme": "Croquis",
"diagram_simpleTheme": "Simple",
"diagram_classicTheme": "Classique"
}

View File

@ -1,15 +1,15 @@
{
"synchronizing": "Szinkronizálás",
"reconnecting": "Újracsatlakozás",
"main_title": "CryptPad: titkosított, együttműködő, valós idejű szerkesztés",
"main_title": "CryptPad: zéró-tudás, együttműködő valós idejű szerkesztés",
"type": {
"pad": "Formázott szöveg",
"code": "Kód",
"poll": "Szavazás",
"kanban": "Kanban",
"slide": "Markdown prezentáció",
"slide": "Markdown diák",
"drive": "CryptDrive",
"whiteboard": "Rajztábla",
"whiteboard": "Fehér tábla",
"file": "Fájl",
"media": "Média",
"todo": "Teendők",
@ -19,16 +19,16 @@
"form": "Űrlap",
"doc": "Dokumentum",
"presentation": "Prezentáció",
"diagram": "Diagram"
"diagram": "Diagramm"
},
"common_connectionLost": "<b>Megszakadt a kapcsolat a serverrel</b><br>Amíg a kapcsolat helyreáll csak olvasási mód érhető el.",
"onLogout": "Kiléptél, {0}kattints ide{1} a belépéshez<br>vagy nyomj egy Esc-et a csak olvasható módban megnyitáshoz.",
"padNotPinnedVariable": "Ez a dokumentum {4} nap inaktivitás után lejár, {0}lépj be{1} vagy {2}regisztrálj{3} ahhoz, hogy megőrizd.",
"anonymousStoreDisabled": "A CryptPad-példány rendszergazdája letiltotta a vendégek számára a tárolást. Jelentkezz be a CryptDrive használatához.",
"expiredError": "Ez a dokumentum elérte a megsemmisítési időhatárt, és már nem érhető el.",
"inactiveError": "Ez a dokumentum tétlenség miatt törlésre került. Nyomd meg az Esc gombot egy új dokumentum létrehozásához.",
"invalidHashError": "A kért dokumentum webcíme hibás.",
"errorCopy": " Az aktuális változatot még használhatod csak olvasható módban az Esc megnyomásával.",
"onLogout": "Kijelentkezett, bejelentkezéshez {0}kattintson ide{1}<br>vagy nyomjon Esc-et a dokumentum olvasási módban való megnyitásához.",
"padNotPinnedVariable": "Ez a dokumentum {4} nap inaktivitás után lejár, a megtartásához {0}lépjen be{1} vagy {2}regisztráljon{3}.",
"anonymousStoreDisabled": "Ezen CryptPad példány rendszergazdája letiltotta a tárhelyet vendégek számára. Jelentkezzen be, hogy hozzáférjen a saját CryptDrive-jához.",
"expiredError": "Ez a dokumentum elérte a megsemmisítési időpontot, és már nem elérhető.",
"inactiveError": "Ez a dokumentum tétlenség miatt törlésre került. Új dokumentum létrehozásához nyomjon Esc-et.",
"invalidHashError": "A kért dokumentum webcíme érvénytelen.",
"errorCopy": " Az Esc billentyű lenyomásával olvasási módban még mindig használhatja a jelenlegi verziót.",
"errorRedirectToHome": "Nyomd meg az Esc-et hogy visszakerülj a CryptDrive-odba.",
"loading": "Betöltés…",
"error": "Hiba",
@ -60,9 +60,9 @@
"pinLimitDrive": "Elérted a tárolási korlátot.<br>Nem készíthetsz több dokumentumot.",
"importButton": "Importálás",
"typeError": "Ez a dokumentum nem kompatibilis a kiválasztott alkalmazással",
"padNotPinned": "Ez a dokumentum 3 hónap inaktivitás után lejár, {0}lépj be{1} vagy {2}regisztrálj{3} ahhoz, hogy megőrizd.",
"deletedError": "Ez a dokumentum törlésre került, és már nem érhető el.",
"chainpadError": "A frissítés közben kritikus hiba történt. A lap csak olvasható módba került hogy ne veszítsd el a munkádat.<br>nyomd meg az Esc-et a dokumentum további megtekintéséhez, vagy töltsd újra hogy megpróbáld szerkeszteni.",
"padNotPinned": "Ez a dokumentum 3 hónap inaktivitás után lejár, a megőrzéséhez {0}lépjen be{1} vagy {2}regisztráljon{3}.",
"deletedError": "Ez a dokumentum törlésre került, és már nem elérhető.",
"chainpadError": "Kritikus hiba lépett fel a tartalom frissítése során. Az oldal olvasási módra váltott, hogy ne veszítse el munkáját.<br>Nyomjon Esc-et a dokumentum megtekintéséhez, vagy frissítse az oldalt a szerkesztés újrapróbálásához.",
"newVersionError": "A CryptPad új verziója érhető el.<br><a href='#'>Töltsd újra</a> ezt a lapot a használatához vagy nyomj Esc-et hogy <b>offline módban</b> érd el a dokumentumot.",
"disconnected": "Nincs kapcsolat",
"typing": "Szerkesztés",
@ -360,7 +360,7 @@
"fc_color": "Szín megváltoztatása",
"fc_open": "Megnyitás",
"fc_open_ro": "Megnyitás (csak olvasás)",
"fc_expandAll": "Minden kinyitása",
"fc_expandAll": "Összes kinyitása",
"fc_delete": "Kukába helyezés",
"fc_delete_owned": "Megsemmisítés",
"fc_restore": "Visszaállítás",
@ -427,7 +427,7 @@
"fm_noResult": "Keresés eredménytelen",
"settings_cat_style": "Megjelenés",
"logoutButton": "Kijelentkezés",
"login_noSuchUser": "Hibás felhasználónév vagy jelszó",
"login_noSuchUser": "Érvénytelen felhasználónév vagy jelszó",
"login_invalUser": "Felhasználónév szükséges",
"login_unhandledError": "Váratlan hiba történt :(",
"register_passwordsDontMatch": "A jelszavak nem egyeznek!",
@ -450,5 +450,11 @@
"support_cat_all": "Összes",
"support_addAttachment": "Csatolmány hozzáadása",
"oo_refresh": "Frissítés",
"support_formCategoryError": "Hiba: üres kategória"
"support_formCategoryError": "Hiba: üres kategória",
"login_register": "Regisztráció",
"login_confirm": "Jelszó megerősítése",
"login_hashing": "Jelszó hashelése, ez eltarthat egy ideig.",
"login_invalPass": "Jelszó szükséges",
"register_importRecent": "Dokumentumok importálása a vendég munkamenetből",
"import_note": "A bejelentkezés nélkül létrehozott és/vagy tárolt dokumentumok megőrzéséhez pipálja be a \"Dokumentumok importálása a vendég munkamenetből\" opciót."
}

View File

@ -233,7 +233,7 @@
"contacts_info3": "Double-click their icon to view their profile",
"contacts_info4": "Either participant can clear permanently a chat history",
"contacts_removeHistoryTitle": "Clean the chat history",
"contacts_confirmRemoveHistory": "Are you sure you want to permanently remove your chat history? Data cannot be restored",
"contacts_confirmRemoveHistory": "Are you sure you want to delete chat history? Messages will be removed for everyone and cannot be restored.",
"contacts_removeHistoryServerError": "There was an error while removing your chat history. Try again later",
"contacts_fetchHistory": "Retrieve older history",
"contacts_rooms": "Rooms",
@ -615,7 +615,7 @@
"autostore_notAvailable": "You must store this document in your CryptDrive before being able to use this feature.",
"crowdfunding_button": "Support CryptPad",
"crowdfunding_button2": "Donate",
"crowdfunding_popup_text": "<h3>We need your help!</h3>To ensure that CryptPad is actively developed, consider supporting the project via the OpenCollective page, where you can see our <b>Roadmap</b> and <b>Funding goals</b>.",
"crowdfunding_popup_text": "<h3>We need your help!</h3>To ensure that CryptPad is actively developed, consider supporting the project with a donation on OpenCollective.",
"crowdfunding_popup_no": "Not now",
"survey": "CryptPad survey",
"markdown_toc": "Contents",
@ -1868,5 +1868,18 @@
"loadAll": "Load all tickets",
"oo_rtChannelMissing": "An error occured while loading your document. It is now read-only to prevent data being corrupted or lost. Please use the button below to send the relevant information to the support team.",
"oo_rtChannelMissingDate": "Message sent on {0}",
"oo_rtChannelMissingNoSupport": "An error occured while loading your document. It is now read-only to prevent data being corrupted or lost. Please send the following information to your instance administrator."
"oo_rtChannelMissingNoSupport": "An error occured while loading your document. It is now read-only to prevent data being corrupted or lost. Please send the following information to your instance administrator.",
"support_moderatorNotification": "{0} has added you to the support team as a moderator",
"crowdfunding_popup_text2": "Alternatively, you can subscribe on this instance.",
"form_input_ph_text": "Your answer here",
"form_input_ph_number": "Enter a number",
"form_date_time": "Select date and time",
"contacts_muted": "Muted",
"contacts_noFriends": "Your contact list is empty",
"contacts_noFriendsInfo": "Start by <a>adding a contact</a> to begin a conversation.",
"contacts_historyCleared": "The chat history has been deleted for everyone",
"diagram_modesOptionLabel": "Change theme to {0}, will refresh app",
"diagram_sketchTheme": "Sketch",
"diagram_simpleTheme": "Simple",
"diagram_classicTheme": "Classic"
}

View File

@ -169,7 +169,7 @@
"contacts_rooms": "Pokoje",
"contacts_fetchHistory": "Odzyskaj starszą historię",
"contacts_removeHistoryServerError": "Wystąpił błąd podczas usuwania historii czatu. Spróbuj ponownie później",
"contacts_confirmRemoveHistory": "Czy na pewno chcesz trwale usunąć historię czatu? Danych nie można przywrócić",
"contacts_confirmRemoveHistory": "Czy na pewno chcesz usunąć historię czatu? Wiadomości zostaną usunięte dla wszystkich użytkowników i nie będzie można ich przywrócić.",
"contacts_removeHistoryTitle": "Wyczyść historię czatu",
"contacts_info4": "Każdy z uczestników może trwale wyczyścić historię czatu",
"contacts_info3": "Kliknij dwukrotnie na ich ikonę, aby wyświetlić ich profil",
@ -523,7 +523,7 @@
"markdown_toc": "Zawartość",
"survey": "Ankieta CryptPad",
"crowdfunding_popup_no": "Nie teraz",
"crowdfunding_popup_text": "<h3>Potrzebujemy Twojej pomocy!</h3>Aby zapewnić aktywny rozwój pakietu CryptPad, rozważ wsparcie projektu poprzez stronę OpenCollective, gdzie możesz zobaczyć nasz <b>Plan działania</b> i <b>Cele Finansowania</b>.",
"crowdfunding_popup_text": "<h3>Potrzebujemy Twojej pomocy!</h3>Aby zapewnić dalszy rozwój pakietu CryptPad, rozważ wsparcie projektu poprzez darowiznę na platformie OpenCollective.",
"crowdfunding_button2": "Przekaż darowiznę",
"crowdfunding_button": "Wspieraj CryptPad",
"autostore_notAvailable": "Aby móc korzystać z tej funkcji, dokument musi znajdować się na Twoim CryptDrive.",
@ -1863,5 +1863,19 @@
"loading_encrypted": "szyfrowane od końca do końca",
"form_poll_required_hint": "Ta ankieta jest obowiązkowa, należy wybrać co najmniej jedną opcję.",
"fo_trash_move_error": "Zawartości folderów współdzielonych nie można przenieść do kosza. Zamiast tego użyj opcji „Usuń”.",
"fo_sharedFolder_move_error": "Folderów współdzielonych nie można przenosić do innych folderów współdzielonych."
"fo_sharedFolder_move_error": "Folderów współdzielonych nie można przenosić do innych folderów współdzielonych.",
"support_moreTickets": "{0} dostępnych zgłoszeń",
"loadAll": "Załaduj wszystkie zgłoszenia",
"oo_rtChannelMissing": "Podczas ładowania dokumentu wystąpił błąd. Dokument ma teraz status „tylko do odczytu”, aby zapobiec uszkodzeniu lub utracie danych. Prosimy o skorzystanie z poniższego przycisku, aby przesłać odpowiednie informacje do zespołu pomocy technicznej.",
"oo_rtChannelMissingDate": "Wiadomość wysłana {0}",
"oo_rtChannelMissingNoSupport": "Podczas ładowania dokumentu wystąpił błąd. Dokument ma teraz status „tylko do odczytu”, aby zapobiec uszkodzeniu lub utracie danych. Prosimy o przesłanie poniższych informacji do administratora instancji.",
"support_moderatorNotification": "{0} dodał(a) Cię do zespołu wsparcia jako moderatora",
"crowdfunding_popup_text2": "Możesz też zapisać się w tej instancji.",
"form_input_ph_text": "Twoja odpowiedź tutaj",
"form_input_ph_number": "Wpisz liczbę",
"form_date_time": "Wybierz datę i godzinę",
"contacts_muted": "Wyciszone",
"contacts_noFriends": "Twoja lista kontaktów jest pusta",
"contacts_noFriendsInfo": "Zacznij od <a>dodania kontaktu</a>, aby rozpocząć rozmowę.",
"contacts_historyCleared": "Historia czatu została usunięta dla wszystkich"
}

View File

@ -1693,5 +1693,18 @@
"admin_invitationDeleteConfirm": "Tem certeza de que deseja apagar esse convite?",
"admin_usersTitle": "Diretório do usuário",
"admin_usersHint": "Lista de contas conhecidas nesta instância. Selecione abaixo para adicionar contas automaticamente, ou insira as informações manualmente utilizando o formulário.",
"admin_usersAdd": "Adicionar um usuário conhecido"
"admin_usersAdd": "Adicionar um usuário conhecido",
"support_moderatorNotification": "{0} adicionou você como moderador da equipe de suporte",
"crowdfunding_popup_text2": "Alternativamente, você pode se subscrever nessa instância.",
"form_input_ph_text": "Sua resposta aqui",
"form_input_ph_number": "Insira um número",
"form_date_time": "Selecione uma data e horário",
"contacts_muted": "Mudo",
"contacts_noFriends": "Sua lista de contatos está vazia",
"contacts_noFriendsInfo": "Inicie <a>adicionando um contato</a> para iniciar uma conversa.",
"contacts_historyCleared": "O histórico da conversa foi apagado para todos",
"diagram_modesOptionLabel": "Modificar o tema para {0} atualizará o aplicativo",
"diagram_sketchTheme": "Esboço",
"diagram_simpleTheme": "Simples",
"diagram_classicTheme": "Clássico"
}

View File

@ -209,7 +209,7 @@
"contact": "Contact",
"terms": "Temos de Serviço",
"header_logoTitle": "Go to the main page",
"edit": "edit",
"edit": "editar",
"view": "view",
"feedback_about": "Se você está lendo isso, provavelmente está curioso para saber por que o CryptPad está solicitando páginas da web quando você executa certas ações.",
"feedback_privacy": "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.",
@ -444,7 +444,7 @@
"contacts_confirmRemove": "Tem certeza de que quer remover <em>{0}</em> dos seus contactos?",
"contacts_remove": "Remover este contacto",
"contacts_send": "Enviar",
"contacts_request": "<em>{0}</em> quer adiciona-lo como contacto. <b>Aceitar</b>?",
"contacts_request": "<em>{0}</em> quer adicioná-lo como contacto. <b>Aceitar</b>?",
"contacts_rejected": "Convite de contacto rejeitado",
"contacts_added": "Convite de contacto aceite.",
"contacts_title": "Contactos",
@ -1480,5 +1480,97 @@
"admin_planlimit": "Limite de armazenamento",
"calendar_rec_no": "Uma vez",
"calendar_rec_updated": "Regra atualizada a {0}",
"admin_documentCreationTime": "Criado"
"admin_documentCreationTime": "Criado",
"og_pricing": "{0} Preços",
"admin_nameTitle": "Nome da instância",
"footer_source": "Código-fonte",
"info_termsFlavour": "<a>Termos de uso</a> desta instância",
"admin_descriptionTitle": "Descrição da instância",
"info_sourceFlavour": "<a>Código-fonte</a> de CryptPad",
"support_cat_drives": "Drive ou equipa",
"support_cat_document": "Documento",
"fivehundred_internalServerError": "Erro interno do servidor",
"ui_ms": "milissegundos",
"register_instance": "Criar uma nova conta em {0}",
"error_embeddingDisabled": "O embutimento está desativado nesta instância do CryptPad",
"error_embeddingDisabledSpecific": "O embutimento está desativado nesta aplicação do CryptPad.",
"error_incorrectAccess": "Só se pode aceder a esta página através {0}.",
"admin_cat_database": "Base de dados",
"admin_accountMetadataTitle": "Informações sobre a conta",
"admin_documentMetadataTitle": "Informações sobre o documento",
"admin_documentSize": "Tamanho do documento",
"admin_documentMetadata": "Metadados atuais",
"ui_true": "verdadeiro",
"ui_false": "falso",
"home_morestorage": "Para obter mais espaço de armazenamento:",
"ui_none": "nenhum",
"ui_confirm": "Confirmar",
"admin_documentModifiedTime": "Última modificação",
"admin_currentlyOpen": "Atualmente aberto",
"admin_planName": "Nome do plano",
"admin_logoButton": "Carregar novo logotipo",
"admin_supportSetupHint": "Criar ou atualizar as chaves de suporte.",
"admin_cat_customize": "Personalizar",
"admin_logoTitle": "Logotipo personalizado",
"admin_usersAdd": "Adicionar utilizador/a conhecido/a",
"duplicate": "Duplicar",
"dph_account_destroyed": "Esta conta foi eliminada pelo seu/sua dono/a",
"team_nameTooLong": "O nome da equipa é demasiado comprido (máx. 50 caracteres)",
"admin_totpDisableButton": "Desativar",
"goLeft": "Esquerda",
"goRight": "Direita",
"date": "Data",
"calendar_settings": "Configurações do calendário",
"admin_totpDisable": "Desativar a autenticação de dois fatores para esta conta",
"login_notFilledPass": "Inserir uma palavra-passe",
"login_notFilledUser": "Inserir um nome de utilizador",
"admin_totpEnabled": "A autenticação de dois fatores está ativada",
"register_nameTooLong": "O nome de utilizador deve conter menos de {0} caracteres",
"mfa_disable": "Desativar autenticação de dois fatores",
"mfa_enable": "Ativar autenticação de dois fatores",
"mfa_revoke_button": "Confirmar a desativação da autenticação de dois fatores",
"mfa_setup_button": "Iniciar a configuração da autenticação de dois fatores",
"continue": "Continuar",
"form_type_date": "Data",
"settings_mfaTitle": "Autenticação de dois fatores (A2F)",
"settings_otp_code": "Código de verificação",
"contacts_noFriends": "A sua lista de contactos está vazia",
"contacts_historyCleared": "O histórico de conversas foi eliminado para todos",
"diagram_classicTheme": "Clássico",
"diagram_modesOptionLabel": "Mudar o tema para {0} irá recarregar a aplicação",
"crowdfunding_popup_text2": "Em alternativa, pode subscrever nesta instância.",
"diagram_simpleTheme": "Simples",
"help_close_button": "Fechar a notificação de ajuda",
"admin_logoSize_error": "O tamanho do logotipo è demasiado grande",
"admin_onboardingOptionsHint": "Escolha a opção apropriada para a sua instância.<br>Estas configurações podem ser mudadas mais tarde no painel de administração.",
"admin_listAdminsHint": "Ver e remover administradores/as",
"install_header": "Instalação",
"admin_appSelection": "Configuração da aplicação",
"support_legacyDump": "Exportar tudo",
"install_launch": "Configuração da instância",
"admin_listAction": "Remover os direitos de administração",
"error_positiveNumber": "Insira um número positivo",
"admin_addKeyLabel": "Adicionar um/a administrador/a através da chave pública dele/a",
"form_answerType_error": "Escolha como responder ao formulário",
"moveItemLeft": "Mover o elemento para a esquerda",
"admin_addAdminsHint": "Adicionar administradores/as através da chave pública deles/as ou a partir da sua lista de contactos",
"moveItemRight": "Mover o elemento para a direita",
"moveItemUp": "Mover elemento para cima",
"moveItemDown": "Mover elemento para baixo",
"badges_moderator": "Suporte e moderação",
"support_legacyClear": "Eliminar para esta conta",
"support_movePending": "Mover para o arquivo",
"support_pasteUserData": "Colar os dados do/a utilizador/a aqui",
"admin_supportDelete": "Desativar suporte",
"support_cat_open": "Caixa de entrada",
"support_cat_closed": "Fechado",
"support_cat_search": "Procurar",
"support_cat_settings": "Configurações",
"support_pending": "Tickets arquivados:",
"support_pending_tag": "Arquivado",
"support_active_tag": "Caixa de entrada",
"support_notificationsTitle": "Desativar as notificações",
"support_notificationsHint": "Escolha esta opção para desativar as notificações de novos tickets e respostas",
"admin_supportTeamHint": "Adicionar e remover pessoas da equipa de suporte da instância",
"support_team": "A equipa de suporte"
}

View File

@ -334,7 +334,7 @@
"contacts_info3": "Dă dublu-click pe iconița contactului pentru a-i vizualiza profilul",
"contacts_info4": "Ambii participanți pot șterge definitiv istoricul unui chat",
"contacts_removeHistoryTitle": "Șterge istoricul chat-ului",
"contacts_confirmRemoveHistory": "Ești sigur că vrei să ștergi definitiv istoricul chat-ului? Datele nu vor putea fi recuperate",
"contacts_confirmRemoveHistory": "Ești sigur că vrei să ștergi istoricul chat-ului? Mesajele vor fi șterse pentru toată lumea și nu vor putea fi recuperate.",
"contacts_removeHistoryServerError": "A apărut o eroare în timpul ștergerii istoricului chat-ului. Te rugăm să încerci mai târziu",
"contacts_fetchHistory": "Recuperează istoricul mai vechi",
"contacts_rooms": "Camere",
@ -662,7 +662,7 @@
"crowdfunding_button": "Susțineți CryptPad",
"crowdfunding_popup_no": "Nu acum",
"survey": "Sondaj CryptPad",
"crowdfunding_popup_text": "<h3>Avem nevoie de ajutorul vostru!</h3>Pentru a ne asigura că dezvoltăm în mod activ CryptPad, luați în considerare susținerea proiectului prin intermediul paginii OpenCollective, unde puteți vedea <b>foaia noastră de parcurs</b> și <b>obiectivele de finanțare</b>.",
"crowdfunding_popup_text": "<h3>Avem nevoie de ajutorul vostru!</h3>Pentru a ne asigura că dezvoltăm în mod activ CryptPad, luați în considerare susținerea proiectului printr-o donație pe OpenCollective.",
"sharedFolders_share": "Distribuie acest link altor utilizatori înregistrați pentru a le da acces la dosarul distribuit. Odată ce acest link este accesat, dosarul distribuit va fi adăugat la CryptDrive lor.",
"convertFolderToSF_SFParent": "Acest dosar nu poate fi convertit într-un dosar distribuit în locația lui actuală. Mută-l în afara dosarului distribuit în care se află pentru a continua.",
"convertFolderToSF_SFChildren": "Acest dosar nu poate fi convertit într-un dosar distribuit deoarece el deja conține dosare distribuite. Mută dosarele distribuite altundeva pentru a continua.",
@ -1865,5 +1865,21 @@
"fo_trash_move_error": "Conținutul unui dosar distribuit nu poate fi mutat la gunoi. În schimb, folosiți funcția \"Elimină\".",
"fo_sharedFolder_move_error": "Dosarele distribuite nu pot fi mutate în alte dosare distribuite.",
"loadAll": "Încarcă toate tichetele",
"support_moreTickets": "{0} tichete disponibile"
"support_moreTickets": "{0} tichete disponibile",
"diagram_classicTheme": "Clasic",
"oo_rtChannelMissing": "A apărut o eroare la încărcarea documentului. Acesta a fost trecut în modul read-only pentru a preveni coruperea sau pierderea acestuia. Vă rugăm să folosiți butonul de mai jos pentru a trimite informațiile relevante către echipa de suport.",
"oo_rtChannelMissingDate": "Mesaj trimis la {0}",
"oo_rtChannelMissingNoSupport": "A apărut o eroare la încărcarea documentului. Acesta a fost trecut în modul read-only pentru a preveni coruperea sau pierderea acestuia. Vă rugăm să trimiteți următoarele informații către administratorul instanței dumneavoastră.",
"support_moderatorNotification": "{0} te-a adăugat în echipa de suport ca și moderator",
"crowdfunding_popup_text2": "În mod alternativ, puteți să vă abonați pe această instanță.",
"form_input_ph_text": "Răspunsul tău aici",
"form_input_ph_number": "Inserați un număr",
"form_date_time": "Selectați data și ora",
"contacts_muted": "Mut",
"contacts_noFriends": "Lista dumneavoastră de contacte este goală",
"contacts_noFriendsInfo": "Începeți prin <a>adăugarea unui contact</a> pentru a porni o conversație.",
"contacts_historyCleared": "Istoricul chat-ului a fost șters pentru toată lumea",
"diagram_modesOptionLabel": "Schimbă tema cu {0}, va reîmprospăta aplicația",
"diagram_sketchTheme": "Schiță",
"diagram_simpleTheme": "Simplu"
}

View File

@ -260,7 +260,7 @@
"contacts_warning": "Все, что вы вводите здесь, является постоянным и доступно для всех существующих и будущих пользователей этого документа. Будьте осторожны с конфиденциальной информацией!",
"contacts_info2": "Нажмите на значок контакта, чтобы пообщаться с ним",
"contacts_info4": "Любой участник может полностью очистить историю чата",
"contacts_confirmRemoveHistory": "Вы уверены, что хотите навсегда удалить свою историю чата? Данные не могут быть восстановлены",
"contacts_confirmRemoveHistory": "Вы уверены, что хотите удалить историю чата? Сообщения будут удалены для всех и не могут быть восстановлены.",
"contacts_removeHistoryServerError": "При удалении истории чата произошла ошибка. Попробуйте позже еще раз",
"contacts_online": "Другой пользователь из этой комнаты находится онлайн",
"fm_newButtonTitle": "Создать новый документ или папку, импортировать файл в текущую папку.",
@ -938,7 +938,7 @@
"admin_activeSessionsTitle": "Активные сессии",
"admin_authError": "Только администраторы могут получить доступ к этой странице",
"survey": "Опрос CryptPad",
"crowdfunding_popup_text": "<h3>Нам нужна ваша помощь!</h3>Чтобы быть уверенными, что CryptPad активно развивается - рассмотрите возможность поддержки проекта через страницу OpenCollective, где Вы можете увидеть нашу <b>Дорожную карту</b> и <b>Цели финансирования</b>.",
"crowdfunding_popup_text": "<h3>Нам нужна ваша помощь!</h3>Чтобы быть уверенными, что CryptPad активно развивается - рассмотрите возможность поддержки проекта через страницу OpenCollective.",
"crowdfunding_button2": "Помочь деньгами",
"autostore_notAvailable": "Вы должны сохранить этот документ на Вашем CryptDrive, прежде чем сможете использовать эту функцию.",
"autostore_forceSave": "Сохраните файл в Вашем CryptDrive",
@ -1868,5 +1868,18 @@
"loadAll": "Загрузить все обращения",
"oo_rtChannelMissing": "Произошла ошибка при загрузке Вашего документа. Теперь он доступен только для чтения, чтобы предотвратить повреждение или потерю данных. Пожалуйста, используйте кнопку ниже, чтобы отправить соответствующую информацию в службу поддержки.",
"oo_rtChannelMissingDate": "Сообщение отправлено в {0}",
"oo_rtChannelMissingNoSupport": "Произошла ошибка при загрузке Вашего документа. Теперь он доступен только для чтения, чтобы предотвратить повреждение или потерю данных. Пожалуйста, отправьте следующую информацию администратору Вашего экземпляра."
"oo_rtChannelMissingNoSupport": "Произошла ошибка при загрузке Вашего документа. Теперь он доступен только для чтения, чтобы предотвратить повреждение или потерю данных. Пожалуйста, отправьте следующую информацию администратору Вашего экземпляра.",
"support_moderatorNotification": "{0} добавил(а) Вас в команду поддержки в качестве модератора",
"crowdfunding_popup_text2": "В качестве альтернативы Вы можете оформить подписку на этом экземпляре.",
"form_input_ph_text": "Ваш ответ здесь",
"form_input_ph_number": "Введите номер",
"form_date_time": "Выберите дату и время",
"contacts_muted": "Без звука",
"contacts_noFriends": "Ваш список контактов пуст",
"contacts_noFriendsInfo": "Начните с <a>добавления контакта</a>, чтобы начать разговор.",
"contacts_historyCleared": "История чата удалена для всех",
"diagram_modesOptionLabel": "Сменить тему на {0}, приложение перезагрузится",
"diagram_sketchTheme": "Скетч",
"diagram_simpleTheme": "Просто",
"diagram_classicTheme": "Классика"
}

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@
"movedToTrash": "该文档已移至回收站。<br><a>访问我的云盘</a>",
"shareButton": "分享",
"shareSuccess": "链接已复制到剪贴板",
"newButton": "新",
"newButton": "新",
"newButtonTitle": "创建新文档",
"saveTemplateButton": "另存为模板",
"saveTemplatePrompt": "为模板选择标题",
@ -110,7 +110,7 @@
"fm_templateName": "模板",
"fm_searchName": "搜索",
"fm_searchPlaceholder": "搜索…",
"fm_newButton": "新",
"fm_newButton": "新",
"fm_newButtonTitle": "创建新文档或文件夹,在当前文件夹中导入文件。",
"fm_newFolder": "新建文件夹",
"fm_newFile": "新文档",
@ -147,7 +147,7 @@
"fc_restore": "还原",
"fc_remove": "删除",
"fc_empty": "清空回收站",
"fc_prop": "Properties",
"fc_prop": "属性",
"fo_moveUnsortedError": "您无法将文件夹移动到模板列表",
"fo_existingNameError": "名称已在该目录中使用,请选择其他名称。",
"fo_moveFolderToChildError": "您无法将文件夹移至其子文件夹",
@ -265,7 +265,7 @@
"contacts_rooms": "房间",
"contacts_fetchHistory": "检索更早的历史记录",
"contacts_removeHistoryServerError": "移除聊天记录时出错,请稍后重试",
"contacts_confirmRemoveHistory": "是否确定要永久移除您的聊天记录?数据无法恢复",
"contacts_confirmRemoveHistory": "是否确定要删除聊天记录?所有人的消息都将被移除,且无法恢复。",
"contacts_removeHistoryTitle": "清理聊天记录",
"contacts_info4": "任一参与者均可永久清除聊天记录",
"contacts_info3": "双击联系人图标查看其个人资料",
@ -517,7 +517,7 @@
"markdown_toc": "内容",
"survey": "CryptPad调查",
"crowdfunding_popup_no": "以后",
"crowdfunding_popup_text": "<h3>我们需要您的帮助!</h3>为使 CryptPad 能活跃地开发,请通过 OpenCollective 页面支持开发,在那里您可以看到我们的<b>开发路线图</b>和<b>创立目标</b>。",
"crowdfunding_popup_text": "<h3>我们需要您的帮助!</h3>为确保 CryptPad 持续活跃开发,请考虑在 OpenCollective 上捐款支持该项目。",
"crowdfunding_button2": "捐款",
"crowdfunding_button": "支持CryptPad",
"autostore_notAvailable": "在使用这个功能之前您必须将文档保存到您的 CryptDrive 中。",
@ -712,11 +712,11 @@
"team_inviteLinkSetPassword": "使用密码保护链接(推荐)",
"team_inviteLinkTempName": "临时名称(在待处理的邀请列表中可见)",
"team_inviteLinkTitle": "为该团队创建个性化邀请",
"contacts_muteInfo": "您不会收到来自禁言用户的任何通知或消息。<br>他们不会知道您已将他们禁言。 ",
"contacts_mutedUsers": "已禁言账号",
"contacts_manageMuted": "管理禁言",
"contacts_unmute": "取消禁言",
"contacts_mute": "禁言",
"contacts_muteInfo": "您将不会收到来自已静音用户的任何通知或消息。<br>他们不会知道您已将他们静音。 ",
"contacts_mutedUsers": "已静音账号",
"contacts_manageMuted": "管理静音",
"contacts_unmute": "取消静音",
"contacts_mute": "静音",
"share_noContactsNotLoggedIn": "登录或注册以查看您现有的联系人并添加新联系人。",
"share_copyProfileLink": "复制个人资料链接",
"share_noContactsLoggedIn": "您尚未与 CryptPad 上的任何人创建联系。 分享指向您的个人资料的链接,以便人们向您发送联系请求。",
@ -987,7 +987,7 @@
"allow_text": "使用访问列表意味着只有选定的用户和所有者才能访问此文档。",
"logoutEverywhere": "全部登出",
"owner_text": "文档的所有者是唯一有权执行以下操作的用户:添加/删除所有者、使用访问列表限制对文档的访问或删除文档。",
"access_muteRequests": "忽略此文档的访问请求",
"access_muteRequests": "静音此文档的访问请求",
"allow_label": "访问列表:{0}",
"allow_disabled": "禁用",
"allow_enabled": "启用",
@ -1868,5 +1868,18 @@
"loadAll": "加载所有工单",
"oo_rtChannelMissing": "加载文档时出错。为防止数据损坏或丢失,文档现已设为只读。请使用下方按钮将相关信息发送给支持团队。",
"oo_rtChannelMissingDate": "消息已于 {0} 发送",
"oo_rtChannelMissingNoSupport": "加载文档时出错。为防止数据损坏或丢失,文档现已设为只读。请将以下信息发送给您的实例管理员。"
"oo_rtChannelMissingNoSupport": "加载文档时出错。为防止数据损坏或丢失,文档现已设为只读。请将以下信息发送给您的实例管理员。",
"support_moderatorNotification": "{0} 已将您添加为支持团队的审核员",
"crowdfunding_popup_text2": "或者,您也可以在此实例上订阅。",
"form_input_ph_text": "请在此输入您的回答",
"form_input_ph_number": "输入一个数字",
"form_date_time": "选择日期和时间",
"contacts_muted": "已静音",
"contacts_noFriends": "您的联系人列表为空",
"contacts_noFriendsInfo": "请先<a>添加联系人</a>以开始对话。",
"contacts_historyCleared": "所有人的聊天记录已被删除",
"diagram_modesOptionLabel": "将主题更改为 {0},应用将刷新",
"diagram_sketchTheme": "草图",
"diagram_simpleTheme": "简洁",
"diagram_classicTheme": "经典"
}

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2026 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,9 @@
align-items: center;
span:nth-child(2) {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}

View File

@ -53,6 +53,7 @@ define([
$container: $(toolbarElement),
pageTitle: Messages.contacts_title,
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-app-contacts-container'
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();

View File

@ -5,6 +5,8 @@
(function () {
'use strict';
var factory = function (/*Hash*/) {
let devMode = false;
try { devMode = localStorage.CryptPad_dev === "1"; } catch (e) {}
// This API is used to load a CryptPad editor for a provided document in
// an external platform.
@ -48,7 +50,7 @@
var msg = data.msg;
var txid = data.txid;
if (commands[msg.q]) {
console.warn('OUTER RECEIVED QUERY', msg.q, msg.data);
if (devMode) { console.warn('OUTER RECEIVED QUERY', msg.q, msg.data); }
commands[msg.q](msg.data, function (args) {
_sendCb(txid, args);
});
@ -62,7 +64,7 @@
var txid = getTxid();
if (cb) { handlers[txid] = cb; }
console.warn('OUTER SENT QUERY', q, data);
if (devMode) { console.warn('OUTER SENT QUERY', q, data); }
iWindow.postMessage({ msg: {
q: q,
data: data,

View File

@ -152,7 +152,40 @@ define([
}, true
);
var parameters;
var checkDefaultTheme = function(cb) {
var privateData = framework._.cpNfInner.metadataMgr.getPrivateData();
if (!privateData.settings['diagram'] || !privateData.settings['diagram'].mode) {
cb('sketch');
} else {
cb(privateData.settings['diagram'].mode);
}
};
var parameters = new URLSearchParams({
test: 1,
stealth: 1,
embed: 1,
drafts: 0,
p: 'cryptpad',
integrated: framework.isIntegrated() ? 'true' : 'false',
chrome: 0,
dark: window.CryptPad_theme === "dark" ? 1 : 0,
// Hide save and exit buttons
noSaveBtn: 1,
saveAndExit: 0,
noExitBtn: 1,
browser: 0,
pv: 0,
noDevice: 1,
filesupport: 0,
modified: 'unsavedChanges',
proto: 'json',
lang: Messages._languageUsed
});
framework.onEditableChange(function () {
var readOnly = framework.isReadOnly() || framework.isLocked();
@ -163,41 +196,31 @@ define([
parameters.set('chrome', '1');
parameters.set('grid', '1');
}
});
var loadDiagram = function () {
checkDefaultTheme(function(theme) {
var defaultTheme = theme;
parameters.set('ui', defaultTheme);
});
var isReadOnly = framework.isReadOnly() ? 0 : 1;
parameters.set('chrome', isReadOnly);
drawioFrame.src = ApiConfig.httpSafeOrigin + '/components/drawio/src/main/webapp/index.html?'
+ parameters;
});
};
// starting the CryptPad framework
framework.start();
parameters = new URLSearchParams({
test: 1,
stealth: 1,
embed: 1,
drafts: 0,
p: 'cryptpad',
integrated: framework.isIntegrated() ? 'true' : 'false',
chrome: framework.isReadOnly() ? 0 : 1,
dark: window.CryptPad_theme === "dark" ? 1 : 0,
// Hide save and exit buttons
noSaveBtn: 1,
saveAndExit: 0,
noExitBtn: 1,
browser: 0,
noDevice: 1,
filesupport: 0,
modified: 'unsavedChanges',
proto: 'json',
lang: Messages._languageUsed
});
drawioFrame.src = ApiConfig.httpSafeOrigin + '/components/drawio/src/main/webapp/index.html?'
+ parameters;
//wait for metadata to update before checking the theme and loading Drawio UI
var metadataMgr = framework._.sfCommon.getMetadataMgr();
var onChange = function () {
var privateData = metadataMgr.getPrivateData();
if (!privateData.settings.toolbar) { return; }
loadDiagram();
metadataMgr.off('change', onChange);
};
metadataMgr.onChange(onChange);
window.addEventListener("message", (event) => {
if (event.source === drawioFrame.contentWindow) {
@ -209,6 +232,58 @@ define([
}
}
}, false);
var setTheme = function (theme, cb) {
framework._.sfCommon.setAttribute(['diagram', 'mode'], theme, function() {
cb();
});
};
var mkModeButton = function (framework) {
var modes = [Messages.diagram_sketchTheme, Messages.diagram_simpleTheme, Messages.diagram_classicTheme];
var types = [];
modes.forEach(function(mode){
types.push({
tag: 'a',
attributes: {
'data-value': mode,
'aria-label': Messages._getKey('diagram_modesOptionLabel', [mode]),
},
content: mode,
action: function () {
var $self = $('a[data-value="' + mode + '"]');
parameters.set('ui', mode);
drawioFrame.src = ApiConfig.httpSafeOrigin + '/components/drawio/src/main/webapp/index.html?'
+ parameters;
setTheme(mode, function() {
$('.cp-dropdown-content').find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
$self.addClass('cp-dropdown-element-active');
$self.closest('li').focus();
$('.cp-dropdown-content').hide();
} );
},
});
});
checkDefaultTheme(function(theme) {
var $drawer = UIElements.createDropdown({
text: Messages.themeButton,
options: types,
common: framework._.sfCommon,
iconCls: 'color-palette',
initialValue: parameters.get('ui') || theme
});
framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content');
framework._.toolbar.$bottomL.append($drawer);
$drawer.addClass('cp-toolbar-appmenu');
$drawer.on('click', function () {
$('a[data-value="' + parameters.get('ui') || theme + '"]').addClass('cp-dropdown-element-active');
});
});
};
mkModeButton(framework);
};
$('#cp-app-diagram-editor').hide();

View File

@ -41,7 +41,19 @@ define([
for (var k in objRef) { delete objRef[k]; }
$.extend(true, objRef, objToCopy);
};
var updateSharedFolders = function (sframeChan, manager, drive, folders, cb) {
var lockoutAnonSharedFolder = function (common) {
APP.newSharedFolder = null;
APP.closed = true;
var msg = Messages.restrictedError;
if (common && !common.isLoggedIn()) {
msg = UIElements.loginErrorScreenContent(common);
}
setTimeout(function () {
UI.errorLoadingScreen(msg, false, false);
}, 0);
};
var updateSharedFoldersCore = function (common, sframeChan, manager, drive, folders, cb) {
if (!drive || !drive.sharedFolders) {
return void cb();
}
@ -57,6 +69,12 @@ define([
sharedFolder: fId
}, waitFor(function (err, newObj) {
if (!APP.loggedIn && APP.newSharedFolder) {
if (newObj && newObj.restricted) {
lockoutAnonSharedFolder(common);
waitFor.abort();
return;
}
if (err) { return; }
if (!newObj || !Object.keys(newObj).length) {
// Empty anon drive: deleted
var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome;
@ -103,6 +121,11 @@ define([
cb();
});
};
var updateSharedFolders = function (common) {
return function (sframeChan, manager, drive, folders, cb) {
updateSharedFoldersCore(common, sframeChan, manager, drive, folders, cb);
};
};
var updateObject = function (sframeChan, obj, cb) {
sframeChan.query('Q_DRIVE_GETOBJECT', null, function (err, newObj) {
copyObjectValue(obj, newObj);
@ -303,7 +326,7 @@ define([
proxy: proxy,
folders: folders,
updateObject: updateObject,
updateSharedFolders: updateSharedFolders,
updateSharedFolders: updateSharedFolders(common),
history: history,
toolbar: toolbar,
APP: APP

View File

@ -136,7 +136,8 @@ define([
type:"number",
value: opts.maxLength,
min: 100,
max: 5000
max: 5000,
'aria-label': Messages.form_editMaxLength
});
maxLength = h('div.cp-form-edit-max-options', [
h('span', Messages.form_editMaxLength),
@ -244,7 +245,8 @@ define([
type:"number",
value: v.max,
min: 1,
max: v.values.length
max: v.values.length,
'aria-label': Messages.form_editMax
});
maxOptions = h('div.cp-form-edit-max-options', [
h('span', Messages.form_editMax),
@ -291,7 +293,7 @@ define([
var $add, $addItem;
var addMultiple;
var getOption = function (val, placeholder, isItem, uid) {
var input = h('input', {value:val});
var input = h('input', {value:val, 'aria-label': val});
var $input = $(input);
if (placeholder) {
input.placeholder = val;
@ -332,7 +334,7 @@ define([
setCursor();
}
var del = h('button.btn.btn-danger-outline', Icons.get('close'));
var del = h('button.btn.btn-danger-outline',{ 'aria-label': Messages.poll_remove }, Icons.get('close'));
var formHandle;
if (v.type !== 'time') {
formHandle = h('span.cp-form-handle', [
@ -390,6 +392,7 @@ define([
}
$(input).on('input', function () {
$input.attr('aria-label', $(input).val());
evOnSave.fire();
});
@ -1330,7 +1333,8 @@ define([
options: qOptions, // Entries displayed in the menu
isSelect: true,
caretDown: true,
buttonCls: 'btn btn-secondary'
buttonCls: 'btn btn-secondary',
buttonTitle: Messages.form_condition_q
};
qSelect = UIElements.createDropdown(qConfig);
qSelect[0].dropdown = qSelect;
@ -1362,7 +1366,10 @@ define([
$(iSelect).attr('data-drop', 'i').hide();
iSelect.onChange.reg(function () { onChange(); });
var remove = h('button.btn.btn-danger-alt.cp-condition-remove', [
var remove = h('button.btn.btn-danger-alt.cp-condition-remove',{
'title': Messages.poll_remove,
'aria-label': Messages.poll_remove
}, [
Icons.get('close', {'class': 'nomargin'})
]);
$(remove).on('click', function () {
@ -1699,10 +1706,13 @@ define([
var tag = h('input', {
type: opts.type,
step: "any",
placeholder: Messages['form_input_ph_'+opts.type] || ''
'aria-label': Messages['form_input_ph_'+opts.type],
placeholder: Messages['form_input_ph_'+opts.type]
});
var $tag = $(tag);
$tag.on('change keypress keydown', Util.throttle(function () {
let currentVal = $tag.val().trim();
$tag.attr('aria-label', currentVal || Messages['form_input_ph_'+opts.type] || '');
evOnChange.fire();
}, 500));
var cursorGetter;
@ -1774,6 +1784,7 @@ define([
$text.val($text.val().slice(0, opts.maxLength));
l = $text.val().length;
}
$text.attr('aria-label', $text.val() || Messages.form_input_ph_text);
$(charCount).text(Messages._getKey('form_maxLength', [
$text.val().length,
opts.maxLength
@ -2106,13 +2117,18 @@ define([
get: function (opts, a, n, evOnChange) {
opts = Util.clone(TYPES.date.defaultOpts);
var tag = h('input');
var tag = h('input', {'aria-label': Messages.form_date_time});
var picker = Flatpickr(tag, {
disableMobile: true,
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
onChange: function(date) {
if (date) {
$(tag).attr('aria-label', date);
}
}
});
var $tag = $(tag);
@ -2532,7 +2548,7 @@ define([
$(div).data('val', data);
return div;
});
var tag = h('div.cp-form-type-sort-container', { 'role': 'listbox' },[
var tag = h('div.cp-form-type-sort-container', { 'role': 'listbox', 'aria-label': Messages._getKey('form_sort_hint', [els.length]) },[
h('div.cp-form-sort-hint', Messages._getKey('form_sort_hint', [els.length])),
els
]);
@ -3932,6 +3948,7 @@ define([
var btn = h('button.btn.btn-secondary', {
title: full ? '' : Messages['form_type_'+type],
'aria-label': full ? '' : Messages['form_type_'+type],
'data-type': type
}, [
(TYPES[type] || STATIC_TYPES[type]).icon.cloneNode(),
@ -3987,7 +4004,8 @@ define([
var add = h('div', [Icons.get('add')]);
if (!full) {
add = h('button.btn.cp-form-creator-inline-add', {
title: Messages.tag_add
title: Messages.tag_add,
'aria-label': Messages.tag_add
}, [
Icons.get('add', {class: 'add-open'}),
Icons.get('close', {class: 'add-close'}),
@ -4082,7 +4100,7 @@ define([
});
requiredContent.push(infoIcon);
}
requiredTag = h('span.cp-form-required-tag', requiredContent);
requiredTag = h('span.cp-form-required-tag', { 'id': 'cp-required-' + (n-1) }, requiredContent);
}
var dragHandle;
@ -4202,6 +4220,7 @@ define([
}
if (saving && !e) { return; } // Prevent spam Enter
block.q = v.trim();
$inputQ.attr('aria-label', block.q);
framework.localChange();
saving = true;
framework._.cpNfInner.chainpad.onSettle(function () {
@ -4213,6 +4232,7 @@ define([
};
var onCancelQ = function () {
$inputQ.val(block.q || Messages.form_default);
$inputQ.attr('aria-label', block.q || Messages.form_default);
cancel = true;
$inputQ.blur();
$(q).removeClass('editing');
@ -4225,6 +4245,8 @@ define([
$(q).addClass('editing');
});
$inputQ.blur(onSaveQ);
$inputQ.attr('aria-label', block.q || Messages.form_default);
q = h('div.cp-form-input-block', [inputQ]);
// Delete question
@ -4420,11 +4442,16 @@ define([
var editableCls = editable ? ".editable" : "";
var draggable = APP.drag ? '' : '.nodrag';
elements.push(h('fieldset.cp-form-block'+editableCls+draggable, {
var ariaLabelledby = 'cp-question-' + (n-1);
if (requiredTag) {
ariaLabelledby += ' cp-required-' + (n-1);
}
var attributes = {
'data-id':uid,
'data-type':type,
'aria-labelledby': 'cp-question-' + (n-1)
}, [
'aria-labelledby': ariaLabelledby
};
elements.push(h('fieldset.cp-form-block'+editableCls+draggable, attributes, [
h('header', [
APP.isEditor ? dragHandle : undefined,
shiftButtons,
@ -5144,13 +5171,18 @@ define([
return;
}
// Otherwise add it
var datePicker = h('input');
var datePicker = h('input', {'aria-label': Messages.form_date_time});
var picker = Flatpickr(datePicker, {
disableMobile: true,
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
minDate: new Date()
minDate: new Date(),
onChange: function(date) {
if (date) {
$(datePicker).attr('aria-label', date);
}
}
});
var save = h('button.btn.btn-primary', Messages.settings_save);
$(save).click(function () {
@ -5164,7 +5196,7 @@ define([
refreshEndDate();
});
});
var cancel = h('button.btn.btn-danger',Icons.get('close', {'class': 'nomargin'}));
var cancel = h('button.btn.btn-danger',{'aria-label': Messages.poll_remove}, Icons.get('close', {'class': 'nomargin'}));
$(cancel).click(function () {
refreshEndDate();
});
@ -5335,8 +5367,8 @@ define([
var toggleOffclass = 'ontouchstart' in window ? 'cp-toggle-active' : undefined;
var toggleOnclass = 'ontouchstart' in window ? undefined : 'cp-toggle-active';
var toggleDragOff = h(`button#cp-toggle-drag-off.cp-form-view-drag.${toggleOffclass}`, {'title': Messages.toggleArrows, 'tabindex': 0}, Icons.get('select'));
var toggleDragOn = h(`button#cp-toggle-drag-on.cp-form-view-drag.${toggleOnclass}`, {'title': Messages.toggleDrag, 'tabindex': 0}, Icons.get('touch-mode'));
var toggleDragOff = h(`button#cp-toggle-drag-off.cp-form-view-drag.${toggleOffclass}`, {'title': Messages.toggleArrows, 'tabindex': 0, 'aria-label': Messages.toggleArrows}, Icons.get('select'));
var toggleDragOn = h(`button#cp-toggle-drag-on.cp-form-view-drag.${toggleOnclass}`, {'title': Messages.toggleDrag, 'tabindex': 0, 'aria-label': Messages.toggleDrag}, Icons.get('touch-mode'));
const updateDrag = state => {
return function () {
var $container = $('.cp-form-creator-content');

View File

@ -14,7 +14,10 @@ define([
return Math.random().toString(16).replace('0.', '');
};
var init = function () {
console.warn('INIT');
let devMode = false;
try { devMode = localStorage.CryptPad_dev === "1"; } catch (e) {}
if (devMode) { console.warn('INIT'); }
var p = window.parent;
var txid = getTxid();
p.postMessage({ q: 'INTEGRATION_READY', txid: txid }, '*');
@ -86,7 +89,7 @@ define([
http.open('HEAD', url);
http.onreadystatechange = function() {
if (this.readyState === this.DONE) {
console.error(oldKey, this.status);
if (devMode) { console.error(oldKey, this.status); }
if (this.status === 200) {
return cb({state: true});
}
@ -150,7 +153,7 @@ define([
}
if (data.keepOld) { // they provide their own key, we must turn it into a hash
var key = sanitizeKey(data.key) + "000000000000000000000000000000000";
console.warn('KEY', key);
if (devMode) { console.warn('KEY', key); }
let hash = `/2/integration/edit/${key.slice(0,24)}/`;
return void cb({
key: hash,
@ -265,7 +268,7 @@ define([
xhr.responseType = 'blob';
//xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
console.error(this.status);
if (devMode) { console.error(this.status); }
if (this.status === 200) {
cb();
} else {
@ -278,7 +281,7 @@ define([
xhr.send(blob);
};
chan.on('START', function (data, cb) {
console.warn('INNER START', data);
if (devMode) { console.warn('INNER START', data); }
// data.key is a hash
var href = Hash.hashToHref(data.key, data.application);
if (data.editorConfig.lang) {
@ -303,7 +306,7 @@ define([
});
};
console.error(Hash.hrefToHexChannelId(href));
if (devMode) { console.error(Hash.hrefToHexChannelId(href)); }
let startApp = function (blob) {
window.CP_integration_outer = {
pathname: `/${data.application}/`,
@ -336,7 +339,6 @@ define([
path = '/common/onlyoffice/main.js';
}
require([path], function () {
console.warn('SAO REQUIRED');
delete window.CP_integration_outer;
cb();
});

View File

@ -432,6 +432,10 @@ define([
var list = boards.list || [];
var idx = list.indexOf(id);
if (idx !== -1) { list.splice(idx, 1); }
var boardItems = (boards.data || {})[id].item;
boardItems.forEach(function(item) {
delete kanban.options.boards.items[item];
});
delete (boards.data || {})[id];
kanban.removeBoard(id);
return void commit();

View File

@ -313,9 +313,16 @@ define([
if (target.classList.contains('kanban-trash')) {
list.splice(index1, 1);
if (list.indexOf(id) === -1) {
var board = self.options.boards.data[id];
var boardItems = board.item;
boardItems.forEach(function(item) {
delete self.options.boards.items[item];
});
delete self.options.boards.data[id];
}
self.onChange();
self.setBoards(self.options.boards);
return;
}

View File

@ -218,7 +218,8 @@ define([
var active = privateData.category || 'all';
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category', 'tabindex': 0, 'role': 'menuitem'}).appendTo($categories);
var name = Messages['notifications_cat_'+key] || key;
var $category = $('<div>', {'class': 'cp-sidebarlayout-category', 'tabindex': 0, 'role': 'menuitem', 'aria-label': name}).appendTo($categories);
if (key === 'all') { $category.append($(Icons.get('all'))); }
if (key === 'friends') { $category.append($(Icons.get('contacts-book'))); }
if (key === 'pads') { $category.append($(Icons.get('file-pad'))); }
@ -245,7 +246,7 @@ define([
showCategories(categories[key]);
});
$category.append(Messages['notifications_cat_'+key] || key);
$category.append(h('span.cp-sidebarlayout-category-name', name));
});
showCategories(categories[active]);
};

View File

@ -728,13 +728,21 @@ define([
var href = el.getAttribute('href');
if (/^#/.test(href)) {
try {
var foundAnchor = false;
$inner.find('.cke_anchor[data-cke-realelement]').each(function (j, el) {
if (foundAnchor) { return; }
var i = editor.restoreRealElement($(el));
var node = i.$;
if (node.id === href.slice(1)) {
el.scrollIntoView();
foundAnchor = true;
}
});
// Fallback: try to find anchor by ID directly
if (!foundAnchor) {
var anchorsById = $inner.find('#' + href.slice(1));
if (anchorsById.length) { anchorsById[0].scrollIntoView(); }
}
} catch (err) {}
return;
}

View File

@ -2053,7 +2053,7 @@ define([
Messages.settings_cat_notifications = Messages.notificationsPage;
Messages.settings_cat_profile = Messages.profileButton;
var createLeftside = function() {
var $categories = $('<div>', { 'class': 'cp-sidebarlayout-categories' })
var $categories = $('<div>', { 'class': 'cp-sidebarlayout-categories', 'role': 'menu'})
.appendTo(APP.$leftside);
APP.$usage = $('<div>', { 'class': 'usage' }).appendTo(APP.$leftside);
var active = privateData.category || 'account';
@ -2068,11 +2068,13 @@ define([
let name = SIDEBAR_NAMES[key] ||
Messages['settings_cat_' + key] || key;
var $category = $(h('div.cp-sidebarlayout-category', {
'role': 'menuitem',
'tabindex': 0,
'data-category': key
'data-category': key,
'aria-label': name
}, [
icon,
name,
h('span.cp-sidebarlayout-category-name', name),
])).appendTo($categories);

View File

@ -14,10 +14,11 @@ define([
'/common/outer/http-command.js',
'/common/outer/local-store.js',
'/common/outer/login-block.js',
'/customize/login.js',
'/customize/messages.js',
'/common/common-icons.js',
], function (ApiConfig, $, h, Util, Cred, UI, Login, Constants,
ServerCommand, LocalStore, Block, Messages, Icons) {
ServerCommand, LocalStore, Block, CLogin, Messages, Icons) {
if (window.top !== window) { return; }
let ssoAuthCb = function (cb) {
@ -31,6 +32,7 @@ define([
publicKey: Util.decodeBase64(b64Keys.p)
};
var inviteToken = b64Keys.token;
CLogin.ssoRedirectTo(b64Keys);
ServerCommand(keys, {
command: 'SSO_AUTH_CB',
url: window.location.href
@ -63,7 +65,7 @@ define([
return void UI.warn(msg);
}
LocalStore.setSSOSeed(seed.toLowerCase());
window.location.href = '/drive/';
CLogin.redirect();
});
};

View File

@ -296,7 +296,7 @@ define([
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories', 'role': 'menu'})
.appendTo(APP.$leftside);
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
@ -304,10 +304,13 @@ define([
if (!categories[active]) { active = 'tickets'; }
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var name = Messages['support_cat_'+key] || key;
var $category = $('<div>', {
'class': 'cp-sidebarlayout-category',
'data-category': key,
'tabindex': 0
'tabindex': 0,
'role': 'menuitem',
'aria-label': name
}).appendTo($categories);
var iconClass = icons[key];
if (iconClass) {
@ -329,7 +332,7 @@ define([
showCategories(categories[key]);
});
$category.append(Messages['support_cat_'+key] || key);
$category.append(h('span.cp-sidebarlayout-category-name', name));
});
showCategories(categories[active]);
};

View File

@ -106,6 +106,7 @@ define([
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
unsafeIframe: true,
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }