Merge branch '2026-autumn-test-scalable' into notes-from-autumn-test

This commit is contained in:
yflory 2026-09-09 15:08:36 +02:00
commit 8b54310d60
28 changed files with 559 additions and 668 deletions

View File

@ -6,7 +6,7 @@ name: Trigger E2E tests
on:
push:
branches: [ staging, 2026.9-rc, 2026.9-test ]
branches: [ staging, 2026-autumn-rc, 2026-autumn-test ]
jobs:
trigger-tests:
@ -18,9 +18,9 @@ jobs:
if [ "$branch" = "staging" ]; then
event_type="trigger-e2e-tests-staging"
elif [ "$branch" = "2026.9-rc" ]; then
elif [ "$branch" = "2026-autumn-rc" ]; then
event_type="trigger-e2e-tests-rc"
elif [ "$branch" = "2026.9-test" ]; then
elif [ "$branch" = "2026-autumn-test" ]; then
event_type="trigger-e2e-tests-test"
fi

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Multistage build to reduce image size and increase security
FROM node:lts-alpine AS build
FROM node:lts-slim AS build
# Create folder for CryptPad
RUN mkdir /cryptpad
@ -14,20 +14,25 @@ COPY . /cryptpad
RUN sed -i "s@//httpAddress: 'localhost'@httpAddress: '0.0.0.0'@" /cryptpad/config/config.example.js
RUN sed -i "s@installMethod: 'unspecified'@installMethod: 'docker'@" /cryptpad/config/config.example.js
RUN sed -i 's@host: "localhost"@host: "0.0.0.0"@' /cryptpad/config/infra.example.js
# Install dependencies
RUN npm install --production \
&& npm run install:components
# Create actual CryptPad image
FROM node:lts-alpine
FROM node:lts-slim
ENV DEBIAN_FRONTEND=noninteractive
# Create user and group for CryptPad so it does not run as root
RUN addgroup -S cryptpad -g 4001 && adduser -S cryptpad -G cryptpad --uid 4001 -h /cryptpad
RUN groupadd cryptpad -g 4001 && useradd cryptpad -u 4001 -g 4001 -d /cryptpad
# Install curl for healthcheck
# Install git, rdfind and unzip for install-office.sh
RUN apk add --no-cache ca-certificates git rdfind unzip bash curl
RUN apt-get update && apt-get install --no-install-recommends -y \
bash curl ca-certificates git rdfind unzip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Copy cryptpad with installed modules
COPY --from=build --chown=cryptpad /cryptpad /cryptpad
@ -55,7 +60,7 @@ ENTRYPOINT ["/bin/bash", "/cryptpad/docker-entrypoint.sh"]
HEALTHCHECK --interval=1m CMD curl -f http://localhost:3000/ || exit 1
# Ports
EXPOSE 3000 3003
EXPOSE 3000
# Run cryptpad on startup
CMD ["npm", "start"]

View File

@ -165,6 +165,7 @@ define(req, function(AppConfig, Default, Language) {
Messages.settings_profileShare = 'Share profile';
Messages.copy = 'Copy';
Messages.oo_blockURLImageDrop = 'Dragging images from a browser is not supported.'; // XXX
Messages.form_footerInfo = "This form and your answers are protected with end-to-end encryption<br>Only the form author can read the data<br>To learn more and create your own forms <a href='https://cryptpad.org/apps/form/' target='_blank'>visit the CryptPad website</a>"; // XXX
return Messages;

View File

@ -738,7 +738,8 @@
.cp-toolbar-title-value {
overflow: hidden;
text-overflow: ellipsis;
border-collapse: collapse;
margin: 0;
line-height: 1.5;
&:focus {
margin: 2px;
border-radius: @variables_radius;

View File

@ -82,6 +82,10 @@
}
}
}
&.cp-disabled {
opacity: 0.5;
}
.cp-usergrid-user-avatar {
min-height: 40px;
}

View File

@ -28,7 +28,6 @@ services:
ports:
- "3000:3000"
- "3003:3003"
ulimits:
nofile:

View File

@ -8,6 +8,17 @@
# installation (http server by the Nodejs process). If you are using CryptPad
# in production and require professional support please contact sales@cryptpad.fr
# CryptPad possesses several nodes that can answer websocket requests. The
# following allows nginx to load-balance between them.
# The different `server` corresponds to the location of the different front
# nodes specified in your CryptPad `config/infra.js` file. If you change this
# setting, dont forget to update the following list of servers
# The following works with config/infra.example.js:
upstream fronts {
server localhost:3010;
server localhost:3011;
}
server {
listen 80;
listen [::]:80;
@ -202,7 +213,7 @@ server {
# Websocket traffic still needs to be handled by the main process, which means it needs
# to be hosted on a different port. By default 3003 will be used, though this is configurable
# via config.websocketPort
proxy_pass http://localhost:3003;
proxy_pass http://fronts;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -76,15 +76,4 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
location ^~ /cryptpad_websocket {
proxy_pass http://localhost:3003;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
}

830
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,6 @@
"@mcrowe/minibloom": "^0.2.0",
"@node-saml/node-saml": "^5.1.0",
"alertify.js": "1.0.11",
"body-parser": "^1.20.4",
"bootstrap": "^4.0.0",
"bootstrap-tokenfield": "^0.12.0",
"chainpad": "^5.3.1",
@ -28,10 +27,9 @@
"connect-gzip-static": "^4.2.1",
"cookie-parser": "^1.4.7",
"croppie": "^2.5.0",
"cryptpad-server": "^0.0.1",
"cryptpad-server": "^0.0.3",
"dragula": "3.7.2",
"drawio": "github:cryptpad/drawio-npm#npm-29.6.7+3",
"express": "~4.22.1",
"file-saver": "1.3.1",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",

View File

@ -7,7 +7,7 @@ var Fse = require("fs-extra");
var Path = require("path");
var OS = require("os");
var config = require("../lib/load-config");
const { config, infra } = require("../lib/load-config");
var swap = function (s, o) {
return s
@ -108,7 +108,7 @@ var imagePath = `/customize/images/opengraph_preview/`;
var appImagePath = a => {
var partial = previewExists(`og-${a}.png`) && `og-${a}.png` || `og-default.png`;
return new URL(imagePath + partial, config.httpUnsafeOrigin).href;
return new URL(imagePath + partial, infra.public.origin).href;
};
var buildPath = Path.resolve('./customize');
@ -218,7 +218,7 @@ appIndexesToBuild.forEach(function (app) {
var type = types[app];
var built = processPage(src.replace(patt, (current) => {
return current + swap(ogData, {
url: new URL(`/${app}/`, config.httpUnsafeOrigin).href,
url: new URL(`/${app}/`, infra.public.origin).href,
title: type && `Encrypted ${type}` || 'CryptPad',
image: appImagePath(app),
description: Messages.og_default,
@ -237,7 +237,7 @@ appIndexesToBuild.forEach(function (app) {
var instance;
try {
instance = new URL(config.httpUnsafeOrigin).hostname;
instance = new URL(infra.public.origin).hostname;
} catch (err) {
console.error("Failed to parse instance domain name\nAborting...");
return void process.exit(1);
@ -284,12 +284,12 @@ try {
console.log(`Parsing ${srcPath}`);
var src = Fs.readFileSync(srcPath, 'utf8');
var patt = /<\/title>/;
var href = new URL(obj.url, config.httpUnsafeOrigin).href;
var href = new URL(obj.url, infra.public.origin).href;
var built = processPage(src.replace(patt, (current) => {
return current + swap(ogData, {
url: href,
title: obj.title || "CryptPad",
image: new URL(imagePath + 'og-default.png', config.httpUnsafeOrigin).href,
image: new URL(imagePath + 'og-default.png', infra.public.origin).href,
description: Messages.og_default,
});
}));

View File

@ -1319,10 +1319,10 @@ const factory = (UserObject, ProxyManager,
var list = {};
var types = query.types;
var where = query.where;
var filter = query.filter || {};
var filter = query.filter || {};
var isFiltered = function (type, data) {
var filtered;
var fType = filter.fileType || [];
var fType = filter.fileType || [];
if (type === 'file' && fType.length) {
if (!data.fileType) { return true; }
filtered = !fType.some(function (t) {
@ -1353,6 +1353,22 @@ const factory = (UserObject, ProxyManager,
cb(list);
};
// Get all teams where a given pad is stored
Store.getPadTeams = function (userId, data, cb) {
var channel = data && data.channel;
if (!channel) { return void cb({error: 'EINVAL'}); }
var teamIds = [];
getAllStores().forEach(function (s) {
if (!s.id) { return; } // Skip our own drive, we only want teams here
var chans = s.manager.findChannel(channel, true);
if (chans && chans.length) {
teamIds.push(s.id);
}
});
cb(teamIds);
};
// Get the first pad we can find in any of our drives and return its file data
// NOTE: This is currently only used for template: this won't search inside shared folders
Store.getPadData = function (clientId, id, cb) {

View File

@ -45,6 +45,7 @@ const factory = AStore => {
LIST_ALL_TAGS: Store.listAllTags,
GET_TEMPLATES: Store.getTemplates,
GET_SECURE_FILES_LIST: Store.getSecureFilesList,
GET_PAD_TEAMS: Store.getPadTeams,
GET_PAD_DATA: Store.getPadData,
GET_PAD_DATA_FROM_CHANNEL: Store.getPadDataFromChannel,
GET_STRONGER_HASH: Store.getStrongerHash,

View File

@ -161,9 +161,18 @@ const factory = (Util, Hash, UserObject, nThen) => {
hash = obj[0].hash;
var messages = obj.map(function(data) {
return data.msg;
try {
// Remove txid from received messages
let parsed = JSON.parse(data.msg);
parsed[0] = 0;
return JSON.stringify(parsed);
} catch (e) {
// Invalid message in history: should not happen
return data.msg;
}
});
dataSize = messages.join('\n').length;
// +1 is there for the last new line
dataSize = messages.join('\n').length + 1;
}), true);
}
// Metadata
@ -172,7 +181,8 @@ const factory = (Util, Hash, UserObject, nThen) => {
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
if (!obj || typeof(obj) !== "object") { return; }
metadata = JSON.stringify(obj).length;
// +1 is there for the last new line
metadata = JSON.stringify(obj).length + 1;
if (!obj || !Array.isArray(obj.owners) ||
obj.owners.indexOf(edPublic) === -1) {
waitFor.abort();

View File

@ -147,14 +147,11 @@ define([
'active-pads',
'open-files',
'registered',
'disk-usage',
]
},
'performance' : { // Msg.admin_cat_performance
icon : 'performance',
content : [
'refresh-performance',
'performance-profiling',
'enable-disk-measurements',
'bytes-written',
]
@ -2985,53 +2982,6 @@ define([
cb(pre);
});
// Msg.admin_diskUsageHint, .admin_diskUsageTitle, .admin_diskUsageButton
sidebar.addItem('disk-usage', function(cb){
var button = blocks.button('primary', 'report', Messages.admin_diskUsageButton);
var $button = $(button);
var called = false;
var nav = blocks.nav([button]);
var content = blocks.table([], []);
var form = blocks.form([
content
], nav);
Util.onClickEnter($button, function() {
UI.confirm(Messages.admin_diskUsageWarning, function (yes) {
if (!yes) { return; }
$button.hide();
if (called) { return; }
called = true;
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'DISK_USAGE',
}, function (e, data) {
if (e) { return void console.error(e); }
var obj = data[0];
Object.keys(obj).forEach(function (key) {
var val = obj[key];
var unit = Util.magnitudeOfBytes(val);
if (unit === 'GB') {
obj[key] = Util.bytesToGigabytes(val) + ' GB';
} else if (unit === 'MB') {
obj[key] = Util.bytesToMegabytes(val) + ' MB';
} else {
obj[key] = Util.bytesToKilobytes(val) + ' KB';
}
});
let attr = {'class': 'cp-strong'};
let entries = Object.keys(obj).map(function (k) {
return [
{attr, content: (k === 'total' ? k : '/' + k)},
obj[k]
];
});
content.updateContent(entries);
});
});
});
cb(form);
});
let onRefreshSupportEvt = Util.mkEvent();
let refreshSupport = () => {
let moderators, supportKey;
@ -3836,67 +3786,6 @@ define([
});
var onRefreshPerformance = Util.mkEvent();
sidebar.addItem('refresh-performance', function(cb){
var btn = blocks.button('primary', 'refresh', Messages.oo_refresh);
Util.onClickEnter($(btn), function () {
onRefreshPerformance.fire();
});
cb(btn);
}, {
noTitle: true,
noHint: true
});
// Msg.admin_performanceProfilingHint, .admin_performanceProfilingTitle
sidebar.addItem('performance-profiling', function(cb){
var header = [
Messages.admin_performanceKeyHeading,
Messages.admin_performanceTimeHeading,
Messages.admin_performancePercentHeading
];
var table = blocks.table(header, []);
table.setAttribute('id', 'cp-admin-table');
let div = blocks.block([table]);
div.setAttribute('id', 'cp-admin-table-container');
const onRefresh = function () {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_WORKER_PROFILES',
}, function (e, data) {
if (e || data.error) {
UI.warn(Messages.error);
console.error(e, data);
return;
}
var o = data[0];
var sorted = Object.keys(o).sort(function (a, b) {
if (o[b] - o[a] <= 0) { return -1; }
return 1;
});
var total = 0;
sorted.forEach(function (key) { total += o[key]; });
const newRows = sorted.map(function (key) {
var percent = Math.floor((o[key] / total) * 1000) / 10;
return [key, o[key], percent + '%'];
});
table.updateContent(newRows);
});
};
onRefresh();
onRefreshPerformance.reg(onRefresh);
cb(div);
});
// Msg.admin_enableDiskMeasurementsTitle.admin_enableDiskMeasurementsHint
sidebar.addCheckboxItem({
getState: function () {

View File

@ -522,6 +522,13 @@ define([
mkHelpMenu(framework);
}
window.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p') {
e.preventDefault();
$('.cp-toolbar-icon-print').click();
}
});
var evModeChange = Util.mkEvent();
evModeChange.reg(previewPane.modeChange);
evModeChange.reg(markdownTb.modeChange);

View File

@ -791,6 +791,7 @@ define([
message = dialog.message(msg);
}
input.setAttribute('aria-label', message.textContent || message.innerText);
var ok = dialog.okButton(opt.ok);
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([

View File

@ -184,7 +184,7 @@ define([
el = h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
'data-ed': data.edPublic,
'data-teamid': data.teamId,
'data-teamid': data.id,
'data-curve': data.curvePublic || '',
'data-name': name.toLowerCase(),
'data-order': i,

View File

@ -1246,6 +1246,11 @@ define([
cb(void 0, list);
});
};
common.getPadTeams = function (query, cb) {
postMessage("GET_PAD_TEAMS", query, function (teams) {
cb(teams);
});
};
// Get a template href from its id
common.getPadData = function (id, cb) {
postMessage("GET_PAD_DATA", id, function (data) {

View File

@ -82,13 +82,32 @@ define([
}
if (Object.keys(teams).length) {
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
$div.append(teamsList.div);
common.getPadTeams({channel: config.channel}, function (err, teamIds) {
if (err) { console.error(err); }
teamIds = teamIds || [];
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
teamsList.icons.forEach(function (icon) {
var inTeam = teamIds.some(function (id) {
return String(id) === icon.getAttribute('data-teamid');
});
if (inTeam) {
$(icon).addClass('cp-disabled');
$(icon).on('click', function (e) {
e.preventDefault();
return false;
});
}
});
$div.append(teamsList.div);
});
}
var shareButton = {

View File

@ -3001,13 +3001,13 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
}
};
const loadHistoryCp = function(cp, keepQueue) {
APP.history = true;
APP.stopHistory = false;
loadCheckpoint(cp, keepQueue);
};
var loadTemplate = function (href, password, parsed) {
var loadHistoryCp = function (cp, keepQueue) {
APP.history = true;
APP.stopHistory = false;
loadCheckpoint(cp, keepQueue);
};
APP.history = true;
APP.template = true;
var editor = getEditor();
@ -3316,7 +3316,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
var histConfig = {
onPatch: onPatch,
onPatchBack: onPatchBack,
docType: docType,
docType: () => APP.ooconfig.documentType,
loadCp: loadCheckpoint,
loadHistoryCp: loadHistoryCp,
onCheckpoint: onCheckpoint,

View File

@ -1618,6 +1618,10 @@ define([
cb();
});
});
sframeChan.on('Q_GET_PAD_TEAMS', function (types, cb) {
Cryptpad.getPadTeams(types, cb);
});
};
addCommonRpc(sframeChan, isSafe);

View File

@ -689,6 +689,12 @@ define([
});
};
funcs.getPadTeams = function (query, cb) {
ctx.sframeChan.query('Q_GET_PAD_TEAMS', query, function (err, data) {
cb(err || data?.error, data);
});
};
funcs.getCache = function () {
return ctx.cache;
};

View File

@ -856,16 +856,16 @@ MessengerUI, Messages, Pages, PadTypes, Icons) {
var createPageTitle = function (toolbar, config) {
if (!config.pageTitle) { return; }
var $titleContainer = $('<span>', {
var $titleContainer = $('<div>', {
'class': TITLE_CLS
}).appendTo(toolbar.$top);
toolbar.$top.find('.filler').hide();
var $hoverable = $('<span>', {'class': 'cp-toolbar-title-hoverable'}).appendTo($titleContainer);
var $hoverable = $('<div>', {'class': 'cp-toolbar-title-hoverable'}).appendTo($titleContainer);
// Buttons
var $b = $('<span>', {
var $b = $('<h1>', {
'class': 'cp-toolbar-title-value cp-toolbar-title-value-page'
}).appendTo($hoverable).text(config.pageTitle);

File diff suppressed because one or more lines are too long

View File

@ -213,7 +213,26 @@
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 20px 20px 150px 20px;
.cp-form-view-footer-text {
text-align: center;
line-height: 180%;
margin-bottom: 2rem;
svg.cp-form-footer-icon {
margin: 0 0.3rem 0.1rem 0;
width: 1em;
vertical-align: text-bottom;
color: @cryptpad_color_link;
}
a {
color: @cryptpad_color_link;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
div.cp-form-view-logo {
padding: 10px;
font-size: 40px;
@ -915,7 +934,7 @@
align-items: baseline;
border-radius: @variables_radius;
margin-top: 0.5rem;
padding: 1px;
padding: 1px;
.CodeMirror {
cursor: default;
flex: 1;
@ -1486,10 +1505,10 @@
.cp-form-response-modal {
.cp-form-markdown-editor-wrapper {
border: 1px solid @cp_forms-border;
border-radius: @variables_radius;
border-radius: @variables_radius;
}
.CodeMirror {
border-radius: 0 0 @variables_radius @variables_radius;
border-radius: 0 0 @variables_radius @variables_radius;
}
}
@ -1556,3 +1575,15 @@
.charts_main();
}
@media print {
html,
body {
height: auto !important;
overflow: visible !important;
}
.cp-form-creator-container {
max-height: none !important;
overflow-y: visible !important
}
}

View File

@ -125,6 +125,14 @@ define([
return cancelBlock;
};
window.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p') {
e.preventDefault();
window.print();
}
});
var editTextOptions = function (opts, setCursorGetter, cb) {
var evOnSave = Util.mkEvent();
@ -268,7 +276,7 @@ define([
},
content: Messages['form_poll_'+t]
};
});
});
var dropdownConfig = {
text: '', // Button initial text
options: options, // Entries displayed in the menu
@ -312,7 +320,7 @@ define([
dateFormat: dateFormat,
defaultDate: val ? new Date(val) : undefined
});
}
}
// if this element was active before the remote change, restore cursor
var setCursor = function () {
@ -541,7 +549,7 @@ define([
$(el).find('input').focus();
return;
}
}
$(add).click();
return;
} else {
@ -557,12 +565,12 @@ define([
$(element).find('input').focus();
} else {
$container.find('input')[i].value = selectedDates[i].toLocaleDateString();
}
}
}
} else if ($container.find('input')[0]) {
$container.find('input')[0].value = '';
}
}
}
}
$container.find('input').each(function (i, input) {
if (input._flatpickr) {
@ -1191,7 +1199,7 @@ define([
toggleRow,
block
]);
return [
outerWrapper,
cancelBlock
@ -1884,10 +1892,10 @@ define([
});
return res;
},
reset: function () {
reset: function () {
$(tag).find('input').each(function (i, input) {
var $i = $(input);
if (Util.isChecked($i)) {
if (Util.isChecked($i)) {
$i.prop('checked', false);
}
});
@ -2020,10 +2028,10 @@ define([
});
return res;
},
reset: function () {
reset: function () {
$(tag).find('input').each(function (i, input) {
var $i = $(input);
if (Util.isChecked($i)) {
if (Util.isChecked($i)) {
$i.prop('checked', false);
}
});
@ -2254,7 +2262,7 @@ define([
reset: function () {
$(tag).find('input').each(function (i, input) {
var $i = $(input);
if (Util.isChecked($i)) {
if (Util.isChecked($i)) {
$i.prop('checked', false);
}
});
@ -2408,7 +2416,7 @@ define([
lines.forEach(checkDisabled);
$(tag).find('input').each(function (i, input) {
var $i = $(input);
if (Util.isChecked($i)) {
if (Util.isChecked($i)) {
$i.prop('checked', false);
}
});
@ -2754,7 +2762,7 @@ define([
tag: tag,
isEmpty: function () {
var v = this.getValue();
if (!v || !v.values) { return true; }
if (!v || !v.values) { return true; }
// Poll is considered empty if user hasn't interacted with it
return !hasInteracted;
},
@ -3348,7 +3356,9 @@ define([
$(logo).click(function () {
APP.framework._.sfCommon.gotoURL('/');
});
var footer = h('div.cp-form-view-footer', [logo]);
var content = UI.setHTML(h('div.cp-form-view-footer-text'), Messages.form_footerInfo);
content.insertBefore(Icons.get('lock', {'class': 'cp-form-footer-icon', 'aria-hidden': true}), content.firstChild);
var footer = h('div.cp-form-view-footer', [content, logo]);
return footer;
};
@ -3424,7 +3434,7 @@ define([
});
sorted.forEach(function (uid) {
var answer = answers[uid];
var viewOnly = content.answers.cantEdit || APP.isClosed;
var action = h(viewOnly ? 'button.btn.btn-secondary' : 'button.btn.btn-primary', [
@ -4309,7 +4319,7 @@ define([
};
});
}
content.form[_uid] = {
q: content.form[uid].q,
opts: opts,
@ -4512,7 +4522,7 @@ define([
});
});
var shownContent = [];
var shownPages = [];
pgcontent.forEach(function(page) {
@ -4529,7 +4539,7 @@ define([
};
var pageContainer = h('div.cp-form-page-container');
var $page = $(pageContainer);
_content.push(pageContainer);
var refreshPage = APP.refreshPage = function (current, direction) {
@ -4587,7 +4597,7 @@ define([
var shownContent = checkPages[0];
var shownPages = checkPages[1];
$(state).text(Messages._getKey('form_page', [shownPages.indexOf(_content[current-1])+1, shownContent.length]));
});
if (shownPages.indexOf(_content[current-1])+1 === shownContent.length) { $(right).css('visibility', 'hidden'); }
@ -5140,7 +5150,7 @@ define([
var endDateStr = h('div.cp-form-status');
var $endDate = $(endDateContainer);
var $endDateStr = $(endDateStr);
var refreshEndDate = function () {
$endDate.empty();
@ -5379,7 +5389,7 @@ define([
if (state) {
$container.removeClass('cp-no-drag');
} else {
$container.addClass('cp-no-drag');
$container.addClass('cp-no-drag');
}
};
};
@ -5779,7 +5789,7 @@ define([
]);
var editButtons = h('div.cp-form-edit-buttons-container', [ preview, edit, del ]);
var toggleRow = h('div.cp-markdown-toggle-row');
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', [

View File

@ -430,10 +430,16 @@ define([
]);
UI.openCustomModal(UI.dialog.customModal(div, {buttons: []}));
console.log('Trimming team history', APP.team, size);
const removeModalsTimeout = setTimeout(() => {
UI.removeModals();
Feedback.send(`TRIM_HISTORY_TIMEOUT=${channels.map(obj => obj?.channel).join('|')}`, true);
UI.warn(Messages.trimHistory_error);
}, 120000);
APP.history.execCommand('TRIM_HISTORY', {
channels: channels
}, function(obj) {
if (obj && obj.error) { console.error(obj.error); }
clearTimeout(removeModalsTimeout);
UI.removeModals();
});
});