Merge branch 'staging' into profile

This commit is contained in:
yflory 2025-06-23 15:54:05 +02:00
commit 4fe08f760a
38 changed files with 2665 additions and 1540 deletions

View File

@ -18,7 +18,7 @@ Note that every GitHub release page has an RSS compatible feed that you can subs
We do also communicate about this topic on:
- [Our blog](https://blog.cryptpad.org)
- [Our Matrix public space](https://matrix.to/#/#cryptpad:matrix.xwiki.com)
- [Our Mastodon account](https://fosstodon.org/@cryptpad)
- [Our Mastodon account](https://social.xwiki.com/@CryptPad)
## Reporting a Vulnerability

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<head>
<title data-localization="main_title">CryptPad: Collaboration suite, encrypted and open-source</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
<script src="/customize/pre-loading.js?ver=1.1"></script>
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 -256 1850 1850"
id="svg3013"
version="1.1"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
width="100%"
height="100%"
sodipodi:docname="forum.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata3023">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3021" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1403"
id="namedview3019"
showgrid="false"
inkscape:zoom="0.26081081"
inkscape:cx="-486.943"
inkscape:cy="498.4456"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg3013"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<g
transform="matrix(1,0,0,-1,37.966102,1305.4576)"
id="g3015"
style="fill:#0087ff;fill-opacity:1">
<path
d="m 1664,896 q 53,0 90.5,-37.5 Q 1792,821 1792,768 1792,715 1754.5,677.5 1717,640 1664,640 V 256 q 0,-52 -38,-90 -38,-38 -90,-38 -417,347 -812,380 -58,-19 -91,-66 -33,-47 -31,-100.5 2,-53.5 40,-92.5 -20,-33 -23,-65.5 -3,-32.5 6,-58 9,-25.5 33.5,-55 Q 683,41 706.5,20.5 730,0 768,-30 739,-88 656.5,-113 574,-138 488,-124.5 402,-111 356,-69 349,-46 326.5,18.5 304,83 294.5,113 q -9.5,30 -23,89 -13.5,59 -15,101 -1.5,42 3.5,98.5 5,56.5 22,110.5 H 160 Q 94,512 47,559 0,606 0,672 v 192 q 0,66 47,113 47,47 113,47 h 480 q 435,0 896,384 52,0 90,-38 38,-38 38,-90 V 896 z M 1536,292 v 954 Q 1142,944 768,903 V 633 q 377,-42 768,-341 z"
id="path3017"
inkscape:connector-curvature="0"
style="fill:#0087ff;fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: Font Awesome by Dave Gandy
SPDX-License-Identifier: CC-BY-SA-3.0

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_2_);}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="4.7893" y1="12.0669" x2="6.7712" y2="12.0669">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<polygon class="st0" points="4.8,13 6.4,13 6.8,11.2 5.1,11.2 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="0.4529" y1="12" x2="23.5471" y2="12">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<path class="st1" d="M21.6,17.8c0-0.1,0.1-0.1,0.1-0.2c1.2-1.7,1.8-3.8,1.8-6c0-6.2-5.2-11.2-11.5-11.2S0.5,5.4,0.5,11.6
c0,6.2,5.2,11.1,11.6,11.1c1.6,0,3.1-0.3,4.5-0.9c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0.2-0.1,0.4-0.1,0.6-0.1c0.2,0,0.5,0,0.7,0.1
l4.7,1.7l-1.2-4.9C21.4,18.3,21.4,18.1,21.6,17.8z M9.1,11.2H7.5L7.2,13h1.5v0.7H7L6.6,16H5.8l0.5-2.3H4.7L4.2,16H3.5l0.4-2.3H2.5
V13h1.5l0.4-1.8H2.9v-0.7h1.6L5,8.1h0.7l-0.4,2.3h1.6l0.5-2.3h0.7l-0.5,2.3h1.4V11.2z M11.2,16h-0.9v-5.9h0.9V16z M11.1,8.9
C11,9,10.9,9.1,10.7,9.1c-0.2,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0.1-0.4,0.2-0.4c0.1-0.1,0.2-0.1,0.4-0.1
c0.1,0,0.3,0,0.4,0.1s0.2,0.2,0.2,0.4S11.2,8.9,11.1,8.9z M16.3,10.9c-0.2-0.1-0.5-0.1-0.6-0.1c-0.5,0-0.9,0.2-1.2,0.6
c-0.3,0.4-0.5,0.9-0.5,1.4V16h-0.9v-5.9h0.7l0.1,1.1h0c0.2-0.4,0.5-0.7,0.8-0.9c0.3-0.2,0.6-0.3,1-0.3c0.3,0,0.5,0,0.7,0.1
L16.3,10.9z M17.9,15.3c-0.5-0.5-0.7-1.3-0.7-2.2c0-1,0.2-1.7,0.7-2.3s1.2-0.8,2-0.8c0.3,0,0.6,0,0.8,0.1c0.3,0.1,0.5,0.1,0.7,0.2
l-0.3,0.8c-0.2-0.1-0.4-0.1-0.6-0.2c-0.2-0.1-0.4-0.1-0.6-0.1c-1.2,0-1.8,0.8-1.8,2.3c0,0.7,0.1,1.3,0.4,1.7s0.7,0.6,1.3,0.6
c0.5,0,1-0.1,1.5-0.3v0.8c-0.4,0.2-0.9,0.3-1.5,0.3C19.1,16.1,18.4,15.8,17.9,15.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

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

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#Shape_2_);}
</style>
<g id="Octicons_1_">
<g id="issue-opened">
<linearGradient id="Shape_2_" gradientUnits="userSpaceOnUse" x1="1.1619" y1="12" x2="22.8381" y2="12">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<path id="Shape_1_" class="st0" d="M12,3.2c4.9,0,8.8,4,8.8,8.8s-4,8.8-8.8,8.8s-8.8-4-8.8-8.8S7.1,3.2,12,3.2L12,3.2z M12,1.2
C6,1.2,1.2,6,1.2,12S6,22.8,12,22.8S22.8,18,22.8,12S18,1.2,12,1.2L12,1.2z M13.5,5.8h-3.1v7.7h3.1V5.8L13.5,5.8z M13.5,15.1h-3.1
v3.1h3.1V15.1L13.5,15.1z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View File

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

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0.1 (1.0.1+r73)"
sodipodi:docname="twiitter.svg"
xml:space="preserve"
style="enable-background:new 0 0 24 24;"
viewBox="0 0 24 24"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata1416"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs1414" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="0"
inkscape:window-y="10"
inkscape:window-x="10"
inkscape:cy="14.107333"
inkscape:cx="-5.7258526"
inkscape:zoom="17.645834"
showgrid="false"
id="namedview1412"
inkscape:window-height="1036"
inkscape:window-width="1900"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff"
inkscape:document-rotation="0" />
<style
id="style1402"
type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<linearGradient
y2="12"
x2="23.6009"
y1="12"
x1="0.3991"
gradientUnits="userSpaceOnUse"
id="SVGID_1_">
<stop
id="stop1404"
style="stop-color:#4592C4"
offset="0" />
<stop
id="stop1406"
style="stop-color:#545ACD"
offset="1" />
</linearGradient>
<path
style="fill:#0087ff;fill-opacity:1"
id="path1409"
d="M23.6,4.8c-0.9,0.4-1.8,0.6-2.7,0.8C21.9,5,22.6,4,23,2.9c-0.9,0.5-1.9,0.9-3,1.2c-0.9-0.9-2.1-1.5-3.5-1.5 c-2.6,0-4.8,2.1-4.8,4.8c0,0.4,0,0.7,0.1,1.1c-4-0.2-7.5-2.1-9.8-5C1.6,4.1,1.4,5,1.4,5.8c0,1.7,0.8,3.1,2.1,4 C2.7,9.8,2,9.6,1.3,9.2c0,0,0,0,0,0.1c0,2.3,1.6,4.2,3.8,4.7c-0.4,0.1-0.8,0.2-1.3,0.2c-0.3,0-0.6,0-0.9-0.1 c0.6,1.9,2.4,3.3,4.4,3.3c-1.6,1.3-3.7,2-5.9,2c-0.4,0-0.8,0-1.1-0.1c2.1,1.4,4.6,2.2,7.3,2.2c8.8,0,13.6-7.3,13.6-13.6 c0-0.2,0-0.4,0-0.6C22.2,6.6,23,5.8,23.6,4.8z"
class="st0" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

@ -145,7 +145,11 @@ define(req, function(AppConfig, Default, Language) {
Messages.hide_password = "Hide password";
Messages.next_templateList = "Next template list";
Messages.previous_templateList = "Previous template list";
Messages.contact_mastodon = 'Mastodon'; // XXX
Messages.contact_forum = 'Forum'; // XXX
Messages.toolbar_text_tools = "Text tools";
Messages.toolbar_show_text_tools = "Show text tools";
Messages.toolbar_hide_text_tools = "Hide text tools";
Messages.settings_profileLinkLabel = "Edit your profile link";
Messages.settings_profileDescLabel = "Edit your profile description";
Messages.settings_profileAvatarLabel = "Profile picture";

View File

@ -19,10 +19,38 @@ define([
].indexOf(Config.adminEmail) === -1;
var adminMailbox = Config.supportMailbox && LocalStore.isLoggedIn();
let contacts = [
{
name: Msg.contact_chat || "Chat",
image: "/customize/images/sayhi.svg",
href: "https://matrix.to/#/#cryptpad:matrix.xwiki.com",
},
{
name: Msg.contact_mastodon || "Mastodon",
image: "/customize/images/mastodon.svg",
href: "https://social.xwiki.com/@CryptPad",
},
{
name: Msg.contact_bug || "Bug report",
image: "/customize/images/github.svg",
href: "https://github.com/cryptpad/cryptpad/issues/",
},
{
name: Msg.contact_forum || "Forum",
image: "/customize/images/forum.svg",
href: "https://forum.cryptpad.org/",
},
{
name: Msg.contact_email || "Email",
image: "/customize/images/email.svg",
href: 'mailto:' + developerEmail,
},
];
return h('div#cp-main', [
Pages.infopageTopbar(),
h('div.container.cp-container', [
h('div.row.cp-page-title',[
h('div.row.cp-page-title', [
h('div.col-12.text-center', h('h1', Msg.contact)),
]),
(adminEmail || adminMailbox) ? h('div.row.cp-iconCont.align-items-center', [
@ -64,76 +92,22 @@ define([
Pages.setHTML(h('h2.text-center'), Msg.contact_dev),
h('p.center', Msg.contact_devHint)
),
].concat(contacts.map(item =>
h('div',
h('a.card-small', {href : "https://matrix.to/#/#cryptpad:matrix.xwiki.com"},
h('a.card-small', { href: item.href },
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/sayhi.svg',
src: item.image,
alt: '',
'aria-hidden': 'true'
}),
Msg.contact_chat || 'Chat'
item.name
])
)
)
),
h('div',
h('a.card-small', {href : "https://fosstodon.org/@cryptpad"},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/mastodon.svg',
alt: '',
'aria-hidden': 'true'
}),
'Mastodon'
])
)
)
),
h('div',
h('a.card-small', {href : "https://twitter.com/cryptpad"},
h('div.card-body',
h('p', [
// this is not a typo. adblock plus blocks images with src *twitter* apparently
h('img', {
src: '/customize/images/twiitter.svg',
alt: '',
'aria-hidden': 'true'}),
'Twitter'
])
)
)
),
h('div',
h('a.card-small', {href : "https://github.com/cryptpad/cryptpad/issues/"},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/github.svg',
alt: '',
'aria-hidden': 'true'}),
Msg.contact_bug || 'Bug report'
])
)
)
),
h('div',
h('a.card-small', {href : "mailto:" + developerEmail},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/email.svg',
alt: '',
'aria-hidden': 'true'
}),
Msg.contact_email || 'Email'
])
)
)
),
]),
)
))),
]),
Pages.infopageFooter(),
]);

View File

@ -138,6 +138,12 @@
display: none;
}
}
#cp-app-drive-content-container {
.cp-splitter {
display: none;
}
}
}
}
@ -493,7 +499,7 @@
}
}
}
/* CONTENT */
#cp-app-drive-content-container {
display: flex;
@ -512,6 +518,13 @@
display: flex;
align-items: center;
justify-content: flex-end;
touch-action: none;
-ms-touch-action: none;
-webkit-touch-action: none;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
&:hover {
border-left: 1px solid @cryptpad_text_col;
}

View File

@ -289,3 +289,32 @@
}
}
//reusable styling for the markdown toggle button across all apps
.markdown-toolbar-button-style() {
padding: 3px 5px;
height: fit-content;
margin: 0;
line-height: 1;
border-radius: @variables_radius;
display: inline-flex;
background-color: @cp_toolbar-bottom-bg;
color: @cp_toolbar-bottom-fg;
&:hover {
background-color: fade(@cryptpad_text_col, 20%);
border-color: @cryptpad_text_col;
color: @cryptpad_text_col;
}
&.cp-toolbar-button-active {
background-color: @cp_toolbar-bg;
border-color: @cp_toolbar-fg;
color: @cp_toolbar-fg;
&:hover {
background-color: fade(@cryptpad_text_col, 20%);
}
}
}

View File

@ -64,7 +64,10 @@
}
.cp-markdown-toolbar {
height: @toolbar_line-height;
min-height: @toolbar_line-height;
padding: 0.1rem 0;
height: auto;
flex-wrap: wrap;
background-color: @cp_toolbar-bg;
color: @cp_toolbar-fg;
display: none;
@ -74,7 +77,10 @@
border-radius: @variables_radius;
color: @toolbar-color;
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
.fa {
font: normal normal normal 14px/1 FontAwesome;
font-family: FontAwesome;
}
&:hover {
background-color: contrast(@cp_toolbar-bg, darken(@cp_toolbar-bg, 5%), lighten(@cp_toolbar-bg, 5%));
}
@ -85,6 +91,11 @@
button:nth-of-type(1) {
margin-left: 0.3rem;
}
@media screen and (max-width: @browser_media-medium-screen) {
button:nth-of-type(1) {
margin-left: 0;
}
}
}
.cp-toolbar-container {
@ -400,7 +411,6 @@
.fa {
font: normal normal normal 14px/1 FontAwesome;
font-family: FontAwesome;
}
.tools_unselectable();

View File

@ -86,4 +86,28 @@ removeBlock.complete = function (Env, body, cb) {
Block.removeLoginBlock(Env, publicKey, reason, edPublic, cb);
};
// Get an upload cookie
// Get a cookie allowing you to upload to the blobstage of your user
const uploadCookie = Commands.UPLOAD_COOKIE = function (Env, body, cb) {
const { publicKey } = body;
// they must provide a valid public key
if (publicKey && typeof(publicKey) === "string"
&& publicKey.length === 44) {
return cb();
}
cb("INVALID_KEY");
};
uploadCookie.complete = function (Env, body, cb) {
const { publicKey } = body;
const safeKey = Util.escapeKeyCharacters(publicKey);
Env.blobStore.uploadCookie(safeKey, (err, cookie) => {
if (err) { return void cb(err); }
cb(void 0, {cookie});
});
};

View File

@ -75,7 +75,7 @@ Upload.status = function (Env, safeKey, filesize, _cb) { // FIXME FILES
};
Upload.upload = function (Env, safeKey, chunk, cb) {
Env.blobStore.upload(safeKey, chunk, cb);
Env.blobStore.uploadWs(safeKey, chunk, cb);
};
Upload.cancel = function (Env, safeKey, arg, cb) {

View File

@ -69,6 +69,7 @@ const NOAUTH = require("./challenge-commands/base.js");
COMMANDS.MFA_CHECK = NOAUTH.MFA_CHECK;
COMMANDS.WRITE_BLOCK = NOAUTH.WRITE_BLOCK; // Account creation + password change
COMMANDS.REMOVE_BLOCK = NOAUTH.REMOVE_BLOCK;
COMMANDS.UPLOAD_COOKIE = NOAUTH.UPLOAD_COOKIE;
const TOTP = require("./challenge-commands/totp.js");
COMMANDS.TOTP_SETUP = TOTP.TOTP_SETUP;

View File

@ -19,6 +19,7 @@ const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const plugins = require("./plugin-manager");
const gzipStatic = require('connect-gzip-static');
const CPCrypto = require('./crypto');
const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;
@ -26,6 +27,8 @@ const PID = process.pid;
let SSOUtils = plugins.SSO && plugins.SSO.utils;
var Env = JSON.parse(process.env.Env);
let blobStore;
let cpcrypto;
Env.plugins = plugins;
const response = Util.response(function (errLabel, info) {
if (!Env.Log) { return; }
@ -70,6 +73,7 @@ const EVENTS = {};
EVENTS.ENV_UPDATE = function (data /*, cb */) {
try {
Env = JSON.parse(data);
Env.blobStore = blobStore;
Env.Log = Log;
Env.plugins = plugins;
Env.sendMessage = sendMessage;
@ -263,7 +267,6 @@ app.use('/ssoauth', (req, res, next) => {
next();
});
app.use('/blob', function (req, res, next) {
/* Head requests are used to check the size of a blob.
Clients can configure a maximum size to download automatically,
@ -774,6 +777,51 @@ app.get('/api/logo', function (req, res) {
});
});
app.post('/upload-blob', Express.json({limit:"500kb"}), (req, res) => {
const { chunk, sig, edPublic } = req.body;
if (!cpcrypto) {
return void res.status(500).send({error: 'NOCRYPTO'});
}
const forbidden = reason => {
return void res.status(403).send({error: reason});
};
try {
// Check signature
const sigu8 = Util.decodeBase64(sig);
const vkey = Util.decodeBase64(edPublic);
const ok = cpcrypto.open(sigu8, vkey);
if (!ok) { return forbidden('INVALID_KEY'); }
const cookie = Util.encodeUTF8(sigu8.subarray(64));
// Check cookie
const safeKey = Util.escapeKeyCharacters(edPublic);
Env.blobStore.checkUploadCookie(safeKey, value => {
if (value !== cookie) {
return forbidden('INVALID_COOKIE');
}
// Upload chunk
Env.blobStore.upload(safeKey, chunk, (err) => {
if (err) {
return res.status(500).send({error: err});
}
// Get new cookie
Env.blobStore.uploadCookie(safeKey, (err, _c) => {
if (err) {
return res.status(500).send({error: err});
}
res.status(200).send({
cookie: _c
});
});
});
});
} catch (e) {
return void res.status(500).send({error: e.message});
}
});
// This endpoint handles authenticated RPCs over HTTP
// via an interactive challenge-response protocol
app.use(Express.json());
@ -781,6 +829,7 @@ app.post('/api/auth', function (req, res, next) {
AuthCommands.handle(Env, req, res, next);
});
app.use(function (req, res /*, next */) {
if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) {
// ignore common 404s
@ -824,7 +873,10 @@ nThen(function (w) {
getSession: function () {},
}, w(function (err, blob) {
if (err) { return; }
Env.blobStore = blob;
Env.blobStore = blobStore = blob;
}));
CPCrypto.init(w(function (err, crypto) {
cpcrypto = crypto;
}));
}).nThen(function () {
// TODO inform the parent process that this worker is ready

View File

@ -10,6 +10,8 @@ var BlobStore = module.exports;
var nThen = require("nthen");
var Semaphore = require("saferphore");
var Util = require("../common-util");
const Crypto = require('crypto');
const PERMISSIVE = 511;
const readFileBin = require("../stream-file").readFileBin;
@ -270,15 +272,17 @@ var readBlobMetadata = function (env, blobId, handler, _cb) {
/********** METHODS **************/
var upload = function (Env, safeKey, content, cb) {
var uploadWs = function (Env, safeKey, content, cb) {
var dec;
try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }
var len = dec.length;
var session = Env.getSession(safeKey);
/*
if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.pendingUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading?
@ -289,6 +293,7 @@ var upload = function (Env, safeKey, content, cb) {
if (session.currentUploadSize > session.pendingUploadSize) {
return cb('E_OVER_LIMIT');
}
*/
var stagePath = makeStagePath(Env, safeKey);
@ -300,15 +305,50 @@ var upload = function (Env, safeKey, content, cb) {
blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
});
} else {
session.blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
}
};
var upload = function (Env, safeKey, content, cb) {
var dec;
try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }
var path = makeStagePath(Env, safeKey);
Fs.appendFile(path, dec, cb);
};
const getRandomCookie = function () {
return Crypto.randomBytes(16).toString('hex');
};
var uploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';
const cookie = getRandomCookie();
Fse.mkdirp(Path.dirname(cookiePath), PERMISSIVE, function (err) {
if (err && err.code !== 'EEXIST') { return void cb(err); }
Fs.writeFile(cookiePath, cookie, err => {
cb(err, cookie);
});
});
};
var checkUploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';
Fs.readFile(cookiePath, function (err, content) {
if (err) { return void cb(); }
let expireTime = +new Date() - (5*60*1000);
Fs.stat(cookiePath, function (err, stats) {
if (stats.mtime < expireTime) { return void cb(); }
cb(content.toString('utf8'));
});
});
};
var closeBlobstage = function (Env, safeKey) {
var session = Env.getSession(safeKey);
@ -370,6 +410,10 @@ var upload_complete = function (Env, safeKey, id, cb) {
// FIXME we could just move and handle the EEXISTS instead of the above block
Fse.move(oldPath, newPath, function (e) {
if (e) { return void cb('RENAME_ERR'); }
// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});
cb(void 0, id);
});
});
@ -448,6 +492,9 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
// otherwise it worked...
}));
}).nThen(function () {
// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});
// clean up their session when you're done
// call back with the blob id...
cb(void 0, id);
@ -734,11 +781,26 @@ BlobStore.create = function (config, _cb) {
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
isFile(makeStagePath(Env, safeKey), cb);
},
uploadWs: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadWs(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
upload: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
upload(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
uploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},
checkUploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
checkUploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},
cancel: function (safeKey, fileSize, _cb) {
var cb = Util.once(Util.mkAsync(_cb));

3221
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,7 @@ More information about this can be found in [our translation guide](/customize.d
The best places to reach the development team and the community are the [CryptPad Forum](https://forum.cryptpad.org) and the [Matrix chat](https://matrix.to/#/#cryptpad:matrix.xwiki.com)
The team is also on the fediverse: [@cryptpad@fosstodon.org](https://fosstodon.org/@cryptpad)
The team is also on the fediverse: [@cryptpad@xwiki.com](https://social.xwiki.com/@CryptPad)
# Team

View File

@ -423,7 +423,7 @@ const factory = (Sortify, UserObject, ProxyManager,
var initTempRpc = (clientId, cb) => {
if (store.rpc) { return void cb(store.rpc); }
var kp = Crypto.Nacl.sign.keyPair();
var keys = {
var keys = store.tempKeys = {
edPublic: Util.encodeBase64(kp.publicKey),
edPrivate: Util.encodeBase64(kp.secretKey)
};
@ -2715,6 +2715,10 @@ const factory = (Sortify, UserObject, ProxyManager,
if (obj?.error) {
Feedback.send("NO_DRIVE_ERROR", true);
}
if (!!data.neverDrive) {
// Send temp RPC keys to the browser to allow upload
obj.tempKeys = store?.tempKeys;
}
cb(obj);
}, !!data.neverDrive);
}

View File

@ -578,6 +578,13 @@ define([
framework.setCursorGetter(CodeMirror.getCursor);
editor.on('cursorActivity', updateCursor);
editor.setOption("extraKeys", {
"Esc": function(cm, event) {
event.preventDefault();
document.body.focus();
}
});
framework.onEditableChange(function () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
});

View File

@ -1256,6 +1256,10 @@ define([
arrow: true,
maxWidth: '200px',
flip: true,
onShow: () => {
// Hide other tooltips
$('body').find('.tippy-popper').hide();
},
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }

View File

@ -905,7 +905,8 @@ define([
case 'toggle':
button = $(h('button.cp-toolbar-tools', {
//title: data.title || '', // TODO display if the label text is collapsed
'aria-label': data.text || Messages.toolbar_tools // Fallback
'aria-label': data.text || Messages.toolbar_tools, // Fallback
'aria-pressed': false
}, [
h('i.fa.' + (data.icon || 'fa-wrench')),
h('span.cp-toolbar-name', data.text || Messages.toolbar_tools)
@ -923,6 +924,7 @@ define([
button.click(function (e) {
data.element.toggle();
var isVisible = data.element.is(':visible');
button.attr('aria-pressed', isVisible ? 'true' : 'false');
if (callback) { callback(isVisible); }
if (isVisible) {
button.addClass('cp-toolbar-button-active');
@ -1157,20 +1159,40 @@ define([
};
for (var k in actions) {
let $b = $('<button>', {
'data-notippy':1,
'data-type': k,
'class': 'pure-button fa ' + actions[k].icon,
title: Messages['mdToolbar_' + k] || k
}).click(onClick);
'class': 'pure-button cp-markdown-' + k,
'title': Messages['mdToolbar_' + k] || k,
'aria-label': Messages['mdToolbar_' + k] || k
}).append(
$('<i>', {
'class': 'fa ' + actions[k].icon,
'aria-hidden': 'true'
})).click(onClick);
if (k === "embed") { $toolbar.prepend($b); }
else { $toolbar.append($b); }
}
$('<button>', {
'class': 'pure-button fa fa-question cp-markdown-help',
title: Messages.mdToolbar_help
}).click(function () {
'data-notippy':1,
'class': 'pure-button cp-markdown-help',
'title': Messages.mdToolbar_help,
'aria-label': Messages.mdToolbar_help
}).append(
$('<i>', {
'class': 'fa fa-question',
'aria-hidden': 'true'
})).click(function () {
var href = Messages.mdToolbar_tutorial;
common.openUnsafeURL(href);
}).appendTo($toolbar);
$toolbar.on('keydown', function (e) {
if (e.key === 'Escape' || e.keyCode === 27) {
editor.focus();
e.preventDefault();
}
});
return $toolbar;
};
UIElements.createMarkdownToolbar = function (common, editor, opts) {
@ -1229,6 +1251,71 @@ define([
$toolbarButton.show();
};
function isSmallScreen() {
return window.innerHeight < 530 || window.innerWidth < 530;
}
var toolbarVisibleOnSmallScreen = false;
var $toolbarToggleButton = null;
var appType = common.getMetadataMgr().getPrivateData().app;
function updateToolbarVisibility() {
var $wrapper = $(opts && opts.wrapper ? opts.wrapper : $toolbar.parent());
if (isSmallScreen()) {
if (!$toolbarToggleButton) {
$toolbarToggleButton = $(h('button.btn.cp-markdown-toggle-button', {
'aria-label': Messages.toolbar_show_text_tools,
'aria-pressed': 'false',
'data-notippy': 1,
'type': 'button',
'title': Messages.toolbar_show_text_tools
})).append([
h('i.fa.fa-wrench', { 'aria-hidden': 'true' }),
h('span.cp-toolbar-label', {}, Messages.toolbar_text_tools)
]).click(function () {
var isExpanded = $toolbar.is(':visible');
$toolbar.toggle();
$(this).toggleClass('cp-toolbar-button-active', !isExpanded)
.attr('aria-pressed', String(!isExpanded))
.attr('title', !isExpanded ? Messages.toolbar_hide_text_tools : Messages.toolbar_show_text_tools)
.attr('aria-label', !isExpanded ? Messages.toolbar_hide_text_tools : Messages.toolbar_show_text_tools);
toolbarVisibleOnSmallScreen = !isExpanded;
}).on('keydown keyup', e => {
// don't close modals when pressing Enter
// on the button
e.stopPropagation();
});
$wrapper.append($toolbarToggleButton);
}
if (toolbarVisibleOnSmallScreen) {
$toolbar.show();
$toolbarToggleButton.addClass('cp-toolbar-button-active')
.attr('aria-pressed', 'true');
} else {
$toolbar.hide();
$toolbarToggleButton.removeClass('cp-toolbar-button-active')
.attr('aria-pressed', 'false');
}
} else {
if ($toolbarToggleButton) {
$toolbarToggleButton.remove();
$toolbarToggleButton = null;
}
$toolbar.show();
}
}
if (appType !== 'code' && appType !== 'slide') {
$(window).on('resize', updateToolbarVisibility);
updateToolbarVisibility();
// Small delay to ensure the toolbar layout has rendered before checking for wrapping (ex: profile medium screen size)
setTimeout(() => {
updateToolbarVisibility();
}, 10);
}
return {
toolbar: $toolbar,
button: $toolbarButton,
@ -1318,7 +1405,6 @@ define([
text: text
};
};
/* Create a usage bar which keeps track of how much storage space is used
by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls,
so we throttle its usage. Clients will not update more than once per

View File

@ -48,6 +48,8 @@ define([
}
};
const Env = {};
// Upgrade and donate URLs duplicated in pages.js
var origin = encodeURIComponent(window.location.hostname);
var common = window.Cryptpad = {
@ -79,6 +81,12 @@ define([
common.getAccessKeys = function (cb) {
var keys = [];
nThen(function (waitFor) {
// Not logged in? check for temp RPC keys
if (!LocalStore.isLoggedIn() && Env?.returned?.tempKeys) {
keys.push(Env.returned.tempKeys);
return;
}
// Push account keys
postMessage("GET", {
key: ['edPrivate'],
@ -631,7 +639,7 @@ define([
common.uploadChunk = function (teamId, data, cb) {
postMessage("UPLOAD_CHUNK", {teamId: teamId, chunk: data}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
@ -2737,6 +2745,7 @@ define([
console.log('Posting CONNECT');
postMessage('CONNECT', cfg, function (data) {
Env.returned = data;
// FIXME data should always exist
// this indicates a false condition in sharedWorker
// got here via a reference error:

View File

@ -660,25 +660,30 @@ define([
var $trashContextMenu = $("#cp-app-drive-context-trash");
$content.attr("tabindex", "0");
var splitter = h('div.cp-splitter', [
h('i.fa.fa-ellipsis-v')
]);
$contentContainer.append(splitter);
APP.$splitter = $(splitter).on('mousedown', function (e) {
e.preventDefault();
var x = e.pageX;
var w = $tree.width();
var handler = function (evt) {
if (evt.type === 'mouseup') {
$(window).off('mouseup mousemove', handler);
return;
}
$tree.css('width', (w - x + evt.pageX) + 'px');
};
$(window).off('mouseup mousemove', handler);
$(window).on('mouseup mousemove', handler);
});
if (APP.loggedIn) {
var splitter = h('div.cp-splitter', [
h('i.fa.fa-ellipsis-v')
]);
$contentContainer.append(splitter);
APP.$splitter = $(splitter).on('mousedown touchstart', function (e) {
e.preventDefault();
var x = e.type === 'touchstart' ? e.originalEvent.touches[0].pageX : e.pageX;
var w = $tree.width();
var handler = function (evt) {
if (evt.type === 'mouseup' || evt.type === 'touchend') {
$(window).off('mouseup mousemove touchend touchmove', handler);
return;
}
var pageX = evt.type === 'touchmove'
? evt.originalEvent.touches[0].pageX
: evt.pageX;
$tree.css('width', (w - x + pageX) + 'px');
};
$(window).off('mouseup mousemove touchend touchmove', handler, { passive: false });
$(window).on('mouseup mousemove touchend touchmove', handler);
});
}
// TOOLBAR
// DRIVE
@ -3462,19 +3467,23 @@ define([
b = [b];
}
if(a.length === 0 && b.length === 0) {
if (a.length === 0 && b.length === 0) {
return 0;
} else if (a.length === 0) {
return -1;
} else if (b.length === 0) {
return 1;
} else if(a[0] < b[0]) {
return -1;
} else if(a[0] > b[0]) {
return 1;
} else if (typeof (a[0]) !== typeof (b[0])) {
return String(a[0]) < String(b[0]) ? -1 : 1;
} else {
// This means `a[0] == b[0]`. Chop off the first elements and compare the rest.
return lexicographicCompare(a.slice(1), b.slice(1));
if (a[0] < b[0]) {
return -1;
} else if (a[0] > b[0]) {
return 1;
} else {
// This means `a[0] == b[0]`. Chop off the first elements and compare the rest.
return lexicographicCompare(a.slice(1), b.slice(1));
}
}
};
@ -3792,8 +3801,6 @@ define([
});
$element.contextmenu(openContextMenu('default'));
$element.data('context', 'default');
var $fileMenu = $('<li>').append($fileMenuIcon);
$element.append($fileMenu);
$container.append($element);
});
createGhostIcon($container);

View File

@ -1090,6 +1090,7 @@ define([
if (content.version <= 2) {
setEditable(false, true);
}
console.log("OO loading");
};
@ -2124,6 +2125,7 @@ define([
}];
common.checkTrimHistory(channels);
}
console.log("OO ready");
};
const copy = (a, b) => {

View File

@ -3,15 +3,19 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'/api/config',
'/file/file-crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/cache-store.js',
'/common/outer/http-command.js',
'/components/chainpad-crypto/crypto.js',
'/components/nthen/index.js',
], function (FileCrypto, Hash, Util, Cache, nThen) {
], function (ApiConfig, FileCrypto, Hash, Util, Cache, ServerCommand, Crypto, nThen) {
var module = {};
const USE_WS = false;
module.uploadU8 =function (common, data, cb) {
module.uploadU8 = function (common, data, cb) {
var teamId = data.teamId;
var u8 = data.u8;
var metadata = data.metadata;
@ -27,13 +31,53 @@ define([
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var sendChunk = function (box, cb) {
var sendChunkWs = function (box, cb) {
var enc = Util.encodeBase64(box);
common.uploadChunk(teamId, enc, function (e, msg) {
cb(e, msg);
});
};
let uploadUrl = '/upload-blob';
let size = 0;
if (ApiConfig.fileHost) {
const origin = new URL(ApiConfig.fileHost).origin;
uploadUrl = origin + uploadUrl;
}
var sendChunk = function (box, cb) {
const enc = Util.encodeBase64(box);
size += enc.length;
const c = Util.decodeUTF8(cookie);
const sig_str = window.nacl.sign(c, keys.secretKey);
const sig = Util.encodeBase64(sig_str);
const body = {
chunk: enc,
sig: sig,
edPublic: keys.edPublic
};
fetch(uploadUrl, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}).then(res => res.json())
.then(json => {
if (json?.error) {
return cb(json.error);
}
cookie = json.cookie;
cb();
}).catch(cb);
};
let keys, cookie;
var actual = 0;
var encryptedArr = [];
var again = function (err, box) {
@ -45,7 +89,11 @@ define([
progressValue = Math.min(progressValue, 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
let send = sendChunk;
if (USE_WS) {
send = sendChunkWs;
}
return void send(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
@ -69,6 +117,30 @@ define([
});
};
const startUpload = () => {
if (USE_WS) {
return void next(again);
}
common.getAccessKeys(arr => {
const myKeys = arr.find(obj => {
return (!obj.id && !teamId) || +obj.id === +teamId;
});
if (!myKeys) { return void onError('NO_KEYS'); }
keys = {
edPublic: myKeys.edPublic,
publicKey: Util.decodeBase64(myKeys.edPublic),
secretKey: Util.decodeBase64(myKeys.edPrivate)
};
ServerCommand(keys, {
command: 'UPLOAD_COOKIE',
}, (err, data) => {
cookie = data?.cookie;
if (err || !cookie) { return void onError(err || 'NOCOOKIE'); }
next(again);
});
});
};
common.uploadStatus(teamId, estimate, function (e, pending) {
if (e) {
console.error(e);
@ -83,11 +155,11 @@ define([
if (e) {
return void console.error(e);
}
next(again);
startUpload();
});
});
}
next(again);
startUpload();
});
};

File diff suppressed because one or more lines are too long

View File

@ -176,6 +176,16 @@
flex: 1;
justify-content: center;
min-width: 300px;
.cp-markdown-toggle-row {
margin: 0.5rem 0;
position: relative;
.cp-markdown-toggle-button {
.markdown-toolbar-button-style() !important;
&.cp-toolbar-button-active {
background-color: transparent !important;
}
}
}
div.timeline-container {
width: 100%;
@ -381,6 +391,12 @@
}
}
}
div.cp-form-response-msg-container {
.cp-markdown-toolbar {
background-color: transparent;
border-radius: @variables_radius @variables_radius 0 0;
}
}
}
div.cp-form-creator-results {
.cp-form-block {
@ -431,6 +447,14 @@
}
}
.cp-editor-wrapper {
.cp-form-edit-options-block {
padding: 1px;
overflow: hidden;
border: 1px solid @cp_forms-border;
}
}
.cp-form-creator-add-inline {
display: flex;
flex-flow: row;
@ -532,7 +556,6 @@
padding-bottom: 75px;
button {
white-space: initial;
line-height: 25px;
padding: 5.5px 6px;
}
.cp-form-response-msg-hint {
@ -819,6 +842,9 @@
display: flex;
flex-wrap: wrap;
align-items: baseline;
border-radius: @variables_radius;
margin-top: 0.5rem;
padding: 1px;
.CodeMirror {
cursor: default;
flex: 1;
@ -827,13 +853,16 @@
width: 80%;
min-height: 200px;
height: 200px;
border: 1px solid @cp_forms-border;
.CodeMirror-placeholder {
color: #777;
}
}
.cp-markdown-toolbar {
display: block;
padding: 0.1rem 0;
border-radius: @variables_radius 0;
width: 100%;
background-color: transparent;
}
}
.cp-form-edit-block {
@ -1358,8 +1387,12 @@
}
}
.cp-form-response-modal {
.CodeMirror {
.cp-form-markdown-editor-wrapper {
border: 1px solid @cp_forms-border;
border-radius: @variables_radius;
}
.CodeMirror {
border-radius: 0 0 @variables_radius @variables_radius;
}
}

View File

@ -1098,6 +1098,7 @@ define([
editor.setOption("extraKeys", {
"Esc": function () {
editor.display.input.blur();
$(cancelBlock).focus();
},
});
editor.setOption('lineNumbers', true);
@ -1115,18 +1116,25 @@ define([
}
editor.refresh();
editor.save();
editor.focus();
var firstBtn = $(markdownTb.toolbar).find('button').get(0);
if (firstBtn) {
firstBtn.focus();
} else {
editor.focus(); // fallback
}
});
var toggleRow = h('div.cp-markdown-toggle-row');
if (APP.common && !(tmp && tmp.block) && cm) {
var markdownTb = APP.common.createMarkdownToolbar(editor, {
embed: function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML);
}
},
wrapper: toggleRow
});
$(block).prepend(markdownTb.toolbar);
$(markdownTb.toolbar).show();
$(block).prepend(toggleRow);
cm.configureTheme(APP.common, function () {});
}
@ -1159,8 +1167,13 @@ define([
};
};
var outerWrapper = h('div.cp-editor-wrapper', [
toggleRow,
block
]);
return [
block,
outerWrapper,
cancelBlock
];
},
@ -2113,7 +2126,10 @@ define([
Object.keys(answers).forEach(function (author) {
var obj = answers[author];
var answer = Flatpickr.formatDate(new Date(obj.msg[uid]), dateFormat);
var answer;
if (obj.msg[uid]) {
answer = Flatpickr.formatDate(new Date(obj.msg[uid]), dateFormat);
}
if (isEmpty(answer)) { return empty++; }
results.push(h('div.cp-charts-row', h('span.cp-value', answer)));
});
@ -5558,30 +5574,39 @@ define([
]);
var editButtons = h('div.cp-form-edit-buttons-container', [ preview, edit, del ]);
var toggleRow = h('div.cp-markdown-toggle-row');
var markdownWrapper = h('div.cp-form-markdown-editor-wrapper', t);
var editDiv, previewDiv;
var div = h('div.cp-form-block.editable.nodrag.cp-form-submit-message', [
h('div.cp-form-block-content', [
p,
editDiv = h('div.cp-form-response-modal', t),
editDiv = h('div.cp-form-response-modal', toggleRow, markdownWrapper),
previewDiv = h('div.cp-form-response-preview#cp-response-preview'),
editButtons
]),
]);
var cm = APP.responseCM = SFCodeMirror.create("gfm", CMeditor, t);
var editor = APP.responseEditor = cm.editor;
editor.setOption("extraKeys", {
"Esc": function() {
editor.display.input.blur();
$(preview).focus();
}
});
var markdownTb = APP.common.createMarkdownToolbar(editor, {
embed: function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML);
}
},
wrapper: toggleRow
});
var $tb = $(markdownTb.toolbar).insertAfter($(p));
$(markdownWrapper).prepend(markdownTb.toolbar);
var $edit = $(editDiv);
var $preview = $(previewDiv);
var $p = $(preview);
var $e = $(edit);
var $tb = $(markdownTb);
var previewState = true;
var updatePreview = function () {
@ -5598,6 +5623,14 @@ define([
$preview.hide();
editor.refresh();
$tb.show();
setTimeout(function () {
var firstBtn = $(markdownTb.toolbar).find('button').get(0);
if (firstBtn) {
firstBtn.focus();
} else {
editor.focus(); // fallback
}
}, 0);
});
APP.$e = $e;
APP.$p = $p;

View File

@ -120,9 +120,31 @@
}
margin-bottom: 5px;
}
.cp-markdown-label-row {
margin-bottom: 0.5rem;
position: relative;
}
.cp-markdown-label-row label {
display: inline-block;
margin: 0;
vertical-align: middle;
}
.cp-markdown-toggle-button {
.markdown-toolbar-button-style();
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
&.cp-toolbar-button-active {
background-color: transparent;
}
}
#cp-kanban-edit-body {
border: 1px solid @cp_forms-border;
border-radius: @variables_radius;
overflow: hidden;
.CodeMirror {
height: 105px;
resize: vertical;
@ -132,7 +154,7 @@
box-sizing: content-box;
}
.cp-markdown-toolbar {
background-color: @cp_kanban-conflict-bg;
background-color: transparent;
color: @cp_kanban-fg;
border-radius: @variables_radius @variables_radius 0px 0px;
button {
@ -141,6 +163,20 @@
color: @cp_kanban-conflict-bg;
}
}
.btn {
outline: 0;
border-radius: @variables_radius;
.fa {
font: normal normal normal 14px/1 FontAwesome;
font-family: FontAwesome;
}
&:hover {
background-color: contrast(@cp_toolbar-bg, darken(@cp_toolbar-bg, 5%), lighten(@cp_toolbar-bg, 5%));
}
&:focus-visible {
outline: @variables_focus_style;
}
}
}
margin-bottom: 15px;
}
@ -241,9 +277,8 @@
background: @cp_kanban-item-bg;
box-shadow: @cryptpad_ui_shadow;
.tools_unselectable();
touch-action: none;
&:not(.no-drag) {
cursor: move;
touch-action: none;
cursor: grab;
}

View File

@ -174,7 +174,6 @@ define([
}
_updateBoardsThrottle(framework, kanban, boards);
};
var editModal;
var PROPERTIES = ['title', 'body', 'tags', 'color'];
var BOARD_PROPERTIES = ['title', 'color'];
@ -200,6 +199,10 @@ define([
commit();
});
var markdownEditorWrapper = h('div.cp-markdown-label-row', [
h('label', { for: 'cp-kanban-edit-body' }, Messages.kanban_body)
]);
var conflicts, conflictContainer, titleInput, tagsDiv, text;
var content = h('div', [
conflictContainer = h('div#cp-kanban-edit-conflicts', [
@ -208,7 +211,7 @@ define([
]),
h('label', {for:'cp-kanban-edit-title'}, Messages.kanban_title),
titleInput = h('input#cp-kanban-edit-title'),
h('label', {for:'cp-kanban-edit-body'}, Messages.kanban_body),
markdownEditorWrapper,
h('div#cp-kanban-edit-body', [
text = h('textarea')
]),
@ -280,10 +283,20 @@ define([
embed: function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML);
},
wrapper: markdownEditorWrapper
});
$(markdownTb.toolbar).on('keydown', function (e) {
if (e.which === 27) { // Escape key
e.preventDefault();
e.stopPropagation();
editor.focus(); // Focus the editor instead of closing the modal
}
else if (e.which === 13 || e.which === 9) { // "Enter" or "Tab" key should not close modal
e.stopPropagation();
}
});
$(text).before(markdownTb.toolbar);
$(markdownTb.toolbar).show();
editor.refresh();
var body = {
getValue: function () {

View File

@ -90,6 +90,12 @@
margin: 5px !important;
}
}
.cp-markdown-toggle-row {
margin-bottom: 0.5rem;
.btn.cp-markdown-toggle-button {
.markdown-toolbar-button-style() !important;
}
}
.cp-app-profile-resizer {
text-align: center;
@ -184,9 +190,9 @@
.cp-app-profile-description-code {
display: none;
.cp-markdown-toolbar {
display: flex;
height: auto;
flex-wrap: wrap;
max-width: 31rem;
background-color: @cp_toolbar-bg;
border-radius: @variables_radius @variables_radius 0 0;
}
}
#cp-app-profile-description-info {
@ -209,6 +215,7 @@
font-size: 16px;
line-height: initial;
max-width: 31rem;
border-radius: 0 0 @variables_radius @variables_radius;
pre {
margin: 0;
font-family: inherit;

View File

@ -517,19 +517,28 @@ define([
cm.configureTheme(common, function () {});
editor.setOption("extraKeys", {
"Esc": function () {
cm.getInputField().blur();
editor.getInputField().blur();
$(save).focus();
}
});
var markdownTb = common.createMarkdownToolbar(editor);
var toggleRow = h('div.cp-markdown-toggle-row');
var markdownTb = common.createMarkdownToolbar(editor, {
wrapper: toggleRow
});
$(code).prepend(markdownTb.toolbar);
$(markdownTb.toolbar).show();
$(code).prepend(toggleRow);
if(!Util.isSmallScreen()) {
$(markdownTb.toolbar).show();
}
$(button).click(function () {
$(code).show();
APP.editor.refresh();
$(button).hide();
var firstBtn = $(markdownTb.toolbar).find('button').get(0);
if (firstBtn) {
firstBtn.focus();
}
});
$(save).click(function () {
$(save).hide();

View File

@ -208,14 +208,21 @@ define([
var addEvent = function () {
console.log($modal);
var icon_to;
$modal.mousemove(function () {
var mousemove = function () {
var $buttons = $modal.find('.cp-app-slide-modal-button');
$buttons.show();
if (icon_to) { window.clearTimeout(icon_to); }
icon_to = window.setTimeout(function() {
$buttons.fadeOut();
}, 1000);
});
};
$modal.mousemove(mousemove);
$modal.on('touchstart', mousemove);
$modal.on('touchend', mousemove);
$modal.on('touchcancel', mousemove);
$modal.on('touchmove', mousemove);
$modal.find('#cp-app-slide-modal-exit').click(function () {
var ev = $.Event("keyup");
ev.which = 27;