Merge branch 'staging' into worker-loading

This commit is contained in:
yflory 2025-03-17 16:09:04 +01:00
commit 93f8ea2d07
40 changed files with 682 additions and 246 deletions

View File

@ -117,7 +117,6 @@ define(req, function(AppConfig, Default, Language) {
Messages._languages = map;
Messages._languageUsed = language;
// Get keys with parameters
Messages._getKey = function (key, argArray) {
if (!Messages[key]) { return '?'; }
@ -135,6 +134,14 @@ define(req, function(AppConfig, Default, Language) {
}
};
Messages.addItemBottom = 'Add item to bottom of the board'; // XXX or 'Add card..'
Messages.addItemTop = 'Add item to top of the board'; // XXX or 'Add card..'
Messages.admin_logoSize_error = "The logo size is too large"; // XXX
Messages.limit_error = "Please enter a valid number"; // XXX
Messages.positiveNumber_error = "Please enter a positive number"; // XXX
Messages.answerType_error = "Please select how to answer the form"; // XXX
// XXX
Messages.admin_cat_admins = "Administrators";
Messages.admin_admin = "Admin";
@ -144,6 +151,8 @@ define(req, function(AppConfig, Default, Language) {
Messages.admin_addAdminsHint = "Add administrators from their public key or from your contacts list";
Messages.admin_addAdminsAdd = "Promote a contact to admin";
Messages.admin_addKeyLabel = "Add an admin using their public key";
Messages.admin_errorAddKeyLabel = "Add a valid public key";
Messages.admin_errorAddAdmins = "Pick a contact to promote to admin";
Messages.admin_listName = "Admin name";
Messages.admin_listKey = "Admin key";
@ -152,6 +161,9 @@ define(req, function(AppConfig, Default, Language) {
Messages.admin_listHardcoded = "Admin added into config.js. Can only be removed by editing the config file.";
Messages.admin_listConfirm = "Are you sure you want to remove the admin rights of this user?";
Messages.fm_link_invalid = "Please provide a valid URL"; // XXX
Messages.skipLink = "Skip to main content"; // XXX
return Messages;
});

View File

@ -445,6 +445,23 @@
display: none !important;
}
.cp-toolbar-skip-link {
position: absolute;
top: -100px;
left: 45%;
background-color: @cp_buttons-primary;
color: @cp_buttons-primary-text;
padding: 0.3rem 0.5rem;
font-size: 1rem;
text-decoration: none;
border-radius: @variables_radius;
z-index: 1000;
transition: top 0.3s ease;
}
.cp-toolbar-skip-link:focus {
top: 10px;
}
@media screen and (max-width: @browser_media-medium-screen),
screen and (max-height: 500px) {
flex-wrap: wrap;

View File

@ -70,6 +70,7 @@ module.exports = [{
"linebreak-style": ["off", "unix"],
quotes: ["off", "single"],
semi: ["error", "always"],
eqeqeq: ["error", "always"],
"no-irregular-whitespace": ["off"],
"no-self-assign": ["off"],
"no-empty": ["off"],

View File

@ -37,7 +37,17 @@ main() {
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
rm -rf "$BUILDS_DIR"
if command -v rdfind &>/dev/null; then
if [ "${RDFIND+x}" != "x" ]; then
if command -v rdfind &>/dev/null; then
RDFIND="1"
else
RDFIND="0"
fi
fi
if [ "$RDFIND" = "1" ]; then
ensure_command_available rdfind
rdfind -makehardlinks true -makeresultsfile false $OO_DIR/v*
fi
}
@ -73,6 +83,18 @@ parse_arguments() {
TRUST_REPOSITORY="1"
shift
;;
--check)
CHECK="1"
shift
;;
--rdfind)
RDFIND="1"
shift
;;
--no-rdfind)
RDFIND="0"
shift
;;
*)
show_help
shift
@ -103,9 +125,6 @@ show_help() {
cat <<EOF
install-onlyoffice installs or upgrades OnlyOffice.
NOTE: When you have rdfind installed, it will be used to save ~650MB of disk
space.
OPTIONS:
-h, --help
Show this help.
@ -120,6 +139,18 @@ OPTIONS:
as a safe.directory.
https://git-scm.com/docs/git-config/#Documentation/git-config.txt-safedirectory
--check
Do not install OnlyOffice, only check if the existing installation
is up to date. Exits 0 if it is up to date, nonzero otherwise.
--rdfind
Run rdfind to save ~650MB of disk space.
If neither '--rdfind' nor '--no-rdfind' is specified, then rdfind
will only run if rdfind is installed.
--no-rdfind
Do not run rdfind, even if it is installed.
EOF
exit 1
}
@ -143,7 +174,17 @@ install_version() {
local LAST_DIR
LAST_DIR=$(pwd)
if [ ! -e "$FULL_DIR"/.commit ] || [ "$(cat "$FULL_DIR"/.commit)" != "$COMMIT" ]; then
local ACTUAL_COMMIT="not installed"
if [ -e "$FULL_DIR"/.commit ]; then
ACTUAL_COMMIT="$(cat "$FULL_DIR"/.commit)"
fi
if [ "$ACTUAL_COMMIT" != "$COMMIT" ]; then
if [ ${CHECK+x} ]; then
echo "Wrong commit of $FULL_DIR found. Expected: $COMMIT. Actual: $ACTUAL_COMMIT"
exit 1
fi
ensure_oo_is_downloaded
rm -rf "$FULL_DIR"
@ -166,22 +207,31 @@ install_version() {
}
install_x2t() {
ensure_command_available curl
ensure_command_available sha512sum
ensure_command_available unzip
local VERSION=$1
local HASH=$2
local LAST_DIR
LAST_DIR=$(pwd)
local X2T_DIR=$OO_DIR/x2t
local ACTUAL_VERSION="not installed"
if [ -e "$X2T_DIR"/.version ]; then
ACTUAL_VERSION="$(cat "$X2T_DIR"/.version)"
fi
if [ ! -e "$X2T_DIR"/.version ] || [ "$(cat "$X2T_DIR"/.version)" != "$VERSION" ]; then
if [ ${CHECK+x} ]; then
echo "Wrong version of x2t found. Expected: $VERSION. Actual: $ACTUAL_VERSION"
exit 1
fi
rm -rf "$X2T_DIR"
mkdir -p "$X2T_DIR"
cd "$X2T_DIR"
ensure_command_available curl
ensure_command_available sha512sum
ensure_command_available unzip
curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/$VERSION/x2t.zip" --location --output x2t.zip
# curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/v7.3%2B1/x2t.zip" --location --output x2t.zip
echo "$HASH x2t.zip" >x2t.zip.sha512

View File

@ -242,9 +242,14 @@ module.exports.create = function (config) {
sso: plugins?.SSO?.config || {},
enforceMFA: config.enforceMFA,
onlyOffice: {
availableVersions: getInstalledOOVersions()
},
...(getInstalledOOVersions().length > 0
? {
onlyOffice: {
availableVersions: getInstalledOOVersions(),
},
}
: {}),
// initialized as undefined
bearerSecret: void 0,

7
package-lock.json generated
View File

@ -6404,9 +6404,10 @@
}
},
"node_modules/xml-crypto": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz",
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.1.tgz",
"integrity": "sha512-0GUNbPtQt+PLMsC5HoZRONX+K6NBJEqpXe/lsvrFj0EqfpGPpVfJKGE7a5jCg8s2+Wkrf/2U1G41kIH+zC9eyQ==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"xpath": "0.0.32"

View File

@ -513,6 +513,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
if (e) { return void cb({error: e}); }
store.rpc = call;
store.onRpcReadyEvt.fire();
Store.getPinLimit(null, null, function (obj) {

View File

@ -342,27 +342,6 @@ define([
}
}
});
const $keyBtn = $(keyButton);
Util.onClickEnter($keyBtn, () => {
let val = $keyInput.val().trim();
let key = Keys.canonicalize(val);
if (!key) { return; }
// We have a valid key
let name = Messages.admin_admin;
try {
let parsed = Keys.parseUser(val);
name = parsed.user;
} catch (e) {}
$keyBtn.prop('disabled', 'disabled');
addAdmin({ ed:key, name }, (err) => {
$keyBtn.prop('disabled', false);
if (!err) { $keyInput.val(''); }
// refresh
APP.updateStatus(function () {
evRefreshAdmins.fire();
});
});
});
const drawContacts = () => {
$div.empty();
@ -388,6 +367,9 @@ define([
let addBtn = blocks.button('primary', 'fa-plus', Messages.tag_add);
Util.onClickEnter($(addBtn), () => {
var $sel = $(contactsGrid.div).find('.cp-usergrid-user.cp-selected');
if (!$sel.length) {
return UI.warn(Messages.admin_errorAddAdmins);
}
nThen((waitFor) => {
$sel.each((i, el) => {
const $el = $(el);
@ -402,7 +384,6 @@ define([
}).nThen(() => {
APP.updateStatus(function () {
evRefreshAdmins.fire();
drawContacts();
});
});
});
@ -410,6 +391,30 @@ define([
drawContacts();
});
const $keyBtn = $(keyButton);
Util.onClickEnter($keyBtn, () => {
let val = $keyInput.val().trim();
let key = Keys.canonicalize(val);
if (!key) {
return UI.warn(Messages.admin_errorAddKeyLabel);
}
// We have a valid key
let name = Messages.admin_admin;
try {
let parsed = Keys.parseUser(val);
name = parsed.user;
} catch (e) {}
$keyBtn.prop('disabled', 'disabled');
addAdmin({ ed:key, name }, (err) => {
$keyBtn.prop('disabled', false);
if (!err) { $keyInput.val(''); }
// refresh
APP.updateStatus(function () {
evRefreshAdmins.fire();
});
});
});
const list = blocks.form([
//currentList.div,
contactsGrid.div,
@ -1086,7 +1091,12 @@ define([
sframeCommand('UPLOAD_LOGO', {dataURL}, (err, response) => {
$button.removeAttr('disabled');
if (err) {
UI.warn(Messages.error);
if(err === 'E_TOO_LARGE') {
UI.warn(Messages.admin_logoSize_error);
}
else{
UI.warn(Messages.error);
}
$(input).val('');
console.error(err, response);
spinner.hide();
@ -1828,7 +1838,9 @@ define([
multiple: true,
validate: function () {
var l = parseInt($(newLimit).val());
if (isNaN(l)) { return false; }
if (isNaN(l)) {
return UI.warn(Messages.limit_error);
}
return true;
}
}, function () {
@ -1845,6 +1857,7 @@ define([
}
var limit = getPrettySize(l);
$(text).text(Messages._getKey('admin_limit', [limit]));
UI.log(Messages.saved);
});
});
@ -3902,12 +3915,16 @@ define([
multiple: true,
validate: function () {
var l = parseInt($(newDuration).val());
if (isNaN(l)) { return false; }
if (isNaN(l)) {
return void UI.warn(Messages.limit_error);
}
return true;
}
}, function () {
var d = parseInt($(newDuration).val());
if (!isPositiveInteger(d)) { return void UI.warn(Messages.error); }
if (!isPositiveInteger(d)) {
return void UI.warn(Messages.positiveNumber_error);
}
var data = [d];
sFrameChan.query('Q_ADMIN_RPC', {
@ -3919,6 +3936,7 @@ define([
return void console.error(e, response);
}
$(form).find('.cp-admin-bytes-written-duration').text(Messages._getKey('admin_bytesWrittenDuration', [d]));
UI.log(Messages.saved);
});
});
cb(form);
@ -4171,6 +4189,7 @@ define([
$container: APP.$toolbar,
pageTitle: Messages.adminPage || 'Admin',
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-sidebarlayout-container'
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();

View File

@ -176,6 +176,7 @@
}
.tui-full-calendar-icon {
text-align:center;
flex-shrink: 0;
}
.tui-full-calendar-popup-detail-item {
a {
@ -193,7 +194,7 @@
}
li.tui-full-calendar-popup-section-item {
padding: 0 6px;
height: 32px;
min-height: 32px;
}
.tui-full-calendar-popup-section-item {
height: auto;
@ -215,6 +216,8 @@
}
.tui-full-calendar-content {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font: @colortheme_app-font;
padding: 0 10px;
&:focus{

View File

@ -670,7 +670,6 @@ define([
tag: 'a',
attributes: {
'data-value': '.ics',
'href': '#'
},
content: '.ics'
});
@ -1303,7 +1302,6 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v
attributes: {
'class': 'cp-calendar-view',
'data-value': k,
'href': '#',
},
content: Messages['calendar_'+k]
// Messages.calendar_day
@ -1564,7 +1562,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-recurrence',
'data-value': '',
'href': '#',
},
content: Messages.calendar_rec_no
}];
@ -1577,7 +1574,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-recurrence',
'data-value': basicStr[rec],
'href': '#',
},
content: Messages._getKey('calendar_rec_' + rec, [
getWeekDays(true)[date.getDay()],
@ -1598,7 +1594,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-recurrence',
'data-value': basicStr.days,
'href': '#',
},
content: Messages['calendar_rec_' + (isWeekend ? 'weekend' : 'weekdays')]
});
@ -1608,7 +1603,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-recurrence',
'data-value': 'custom',
'href': '#',
},
content: Messages.calendar_rec_custom
});
@ -1690,7 +1684,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-recurrence-freq',
'data-value': rec,
'href': '#',
},
content: Messages['calendar_rec_freq_' + rec]
});
@ -2000,7 +1993,6 @@ APP.recurrenceRule = {
attributes: {
'class': 'cp-calendar-reminder',
'data-value': k,
'href': '#',
},
content: Messages['calendar_'+k]
// Messages.calendar_minutes
@ -2145,6 +2137,7 @@ APP.recurrenceRule = {
$container: APP.$toolbar,
pageTitle: Messages.calendar,
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-sidebarlayout-container'
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
@ -2228,6 +2221,7 @@ APP.recurrenceRule = {
$el.find('.tui-full-calendar-dropdown-menu li').each(function (i, li) {
var $li = $(li);
var id = $li.attr('data-calendar-id');
$li.attr('tabindex', 0);
var c = calendars[id];
if (!c || c.readOnly) {
return void $li.remove();
@ -2235,6 +2229,86 @@ APP.recurrenceRule = {
// If at least one calendar is editable, show the popup
show = true;
});
let calendarDropdown = function ($el) {
let $dropdownButton = $el.find('.tui-full-calendar-dropdown-button');
let $dropdownMenu = $el.find('.tui-full-calendar-dropdown-menu');
let toggleAriaExpanded = function (isOpen) {
$dropdownButton.attr('aria-expanded', isOpen ? 'true' : 'false');
isOpen ? $dropdownMenu.show() : $dropdownMenu.hide();
};
let calendarDropdownNavigation = function (event) {
let $focusedItem = $dropdownMenu.find('li:focus');
switch (event.key) {
case ' ':
case 'Enter':
event.preventDefault();
$focusedItem.click();
toggleAriaExpanded(false);
$el.find('#tui-full-calendar-schedule-title').focus();
break;
case 'Tab':
event.preventDefault();
toggleAriaExpanded(false);
$el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open');
if (event.shiftKey) {
$el.find('.tui-full-calendar-popup-save').focus();
} else {
$el.find('#tui-full-calendar-schedule-title').focus();
}
break;
case 'ArrowDown':
event.preventDefault();
var $next = $focusedItem.next('li');
if ($next.length) {
$next.focus();
} else {
$dropdownMenu.find('li').first().focus();
}
break;
case 'ArrowUp':
event.preventDefault();
var $prev = $focusedItem.prev('li');
if ($prev.length) {
$prev.focus();
} else {
$dropdownMenu.find('li').last().focus();
}
break;
case 'Escape':
event.preventDefault();
event.stopPropagation();
toggleAriaExpanded(false);
$dropdownButton.focus();
break;
}
};
$dropdownButton.on('click keydown', function (event) {
if (event.type !== 'click' && event.key !== 'Enter' && event.key !== ' ' && event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
return;
}
let isOpen = $el.find('.tui-full-calendar-open').length > 0;
toggleAriaExpanded(!isOpen);
if (!isOpen && event.key !== 'ArrowUp') {
$dropdownMenu.find('li').first().focus();
}
else if(!isOpen){
$dropdownMenu.find('li').last().focus();
}
});
// click outside the dropdown button => closes the dropdown => aria-expanded is false
$(document).on('click', function (event) {
if (!$(event.target).closest($dropdownButton).length) {
toggleAriaExpanded(false);
}
});
$dropdownMenu.on('keydown', calendarDropdownNavigation);
};
calendarDropdown($el);
if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length || !show) {
$el.hide();
UI.warn(Messages.calendar_errorNoCalendar);

View File

@ -423,34 +423,39 @@ define([
});
});
const ooEnabled = ApiConfig.onlyOffice && ApiConfig.onlyOffice.availableVersions.includes(
OOCurrentVersion.currentVersion,
);
var sheetURL = `/common/onlyoffice/dist/${OOCurrentVersion.currentVersion}/web-apps/apps/spreadsheeteditor/main/index.html`;
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
};
Tools.common_xhr(sheetURL, function (xhr) {
var result = !Object.keys(expect).some(function (k) {
var response = xhr.getResponseHeader(k);
if (response !== expect[k]) {
msg.appendChild(h('span', [
'A value of ',
code(expect[k]),
' was expected for the ',
code(k),
' HTTP header, but instead a value of "',
code(response),
'" was received.',
]));
return true; // returning true indicates that a value is incorrect
}
if (ooEnabled) {
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
};
Tools.common_xhr(sheetURL, function (xhr) {
var result = !Object.keys(expect).some(function (k) {
var response = xhr.getResponseHeader(k);
if (response !== expect[k]) {
msg.appendChild(h('span', [
'A value of ',
code(expect[k]),
' was expected for the ',
code(k),
' HTTP header, but instead a value of "',
code(response),
'" was received.',
]));
return true; // returning true indicates that a value is incorrect
}
});
cb(result || xhr.getAllResponseHeaders());
});
cb(result || xhr.getAllResponseHeaders());
});
});
}
assert(function (cb, msg) {
setWarningClass(msg);
@ -719,20 +724,21 @@ define([
});
});
assert(function (cb, msg) { // FIXME possibly superseded by more advanced CSP tests?
var url = `/common/onlyoffice/dist/${OOCurrentVersion.currentVersion}/web-apps/apps/spreadsheeteditor/main/index.html`;
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers);
if (ooEnabled) {
assert(function (cb, msg) { // FIXME possibly superseded by more advanced CSP tests?
msg.appendChild(CSP_WARNING(sheetURL));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: sheetURL,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers);
});
});
});
}
/*
assert(function (cb, msg) {

View File

@ -610,6 +610,7 @@ define([
Framework.create({
toolbarContainer: '#cme_toolbox',
contentContainer: '#cp-app-code-editor',
skipLink: '.CodeMirror',
thumbnail: {
getContainer: getThumbnailContainer,
filter: function (el, before) {

View File

@ -32,7 +32,7 @@ const factory = function (AppConfig = {}) {
MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxOwnedTeams || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
// Apps
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin
earlyAccessApps: ['doc', 'presentation']
earlyAccessApps: []
};
};

View File

@ -1517,6 +1517,9 @@ define([
});
Util.onClickEnter(entry, function(e) {
if ($(e.target).attr('href') === '#') {
e.preventDefault();
}
if (config.isSelect) { return; }
e.stopPropagation();
if (typeof(config.action) === "function") {
@ -2407,7 +2410,6 @@ define([
attributes: {
'class': 'cp-language-value',
'data-value': l,
'href': '#',
},
content: [ // supplying content as an array ensures it's a text node, not parsed HTML
languages[l] // Pretty name of the language value
@ -3548,14 +3550,15 @@ define([
var text = Messages._getKey('owner_add', [name, title]);
var obj = { pw: msg.content.password || '', f: 1 };
let newHref = Hash.getNewPadURL(msg.content.href, obj);
var link = h('a', {
href: '#'
href: newHref
}, Messages.requestEdit_viewPad);
$(link).click(function (e) {
e.preventDefault();
e.stopPropagation();
var obj = { pw: msg.content.password || '', f: 1 };
common.openURL(Hash.getNewPadURL(msg.content.href, obj));
common.openURL(newHref);
});
var div = h('div', [

View File

@ -86,6 +86,12 @@
Util.mkEvent = function (once) {
var handlers = [];
var fired = false;
let promiseResolve;
const promise = new Promise(resolve => {
promiseResolve = resolve;
});
return {
reg: function (cb) {
if (once && fired) { return void setTimeout(cb); }
@ -99,10 +105,13 @@
},
fire: function () {
if (once && fired) { return; }
fired = true;
var args = Array.prototype.slice.call(arguments);
if (!fired) { promiseResolve.apply(null, args); }
fired = true;
handlers.forEach(function (h) { h.apply(null, args); });
}
},
// Since a promise can only resolve once only the 1st call to fire() is reflected here. Even is `once` is `false`.
promise
};
};

View File

@ -2919,9 +2919,9 @@ define([
]);
var content = h('p', [
h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder, tabindex:'1'}),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder}),
h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url,tabindex:'1'}),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url}),
warning,
]);
@ -2936,9 +2936,13 @@ define([
};
var $warning = $(warning).hide();
var $url = $(url).on('change keypress keyup keydown', function () {
var $url = $(url).on('change keypress keydown', function () {
var v = $url.val().trim();
$url.toggleClass('cp-input-invalid', !Util.isValidURL(v));
if (Util.isValidURL(v)) {
$url.removeClass('cp-input-invalid');
} else {
$url.addClass('cp-input-invalid');
}
if (v.length > 200) {
$warning.show();
return;
@ -2961,8 +2965,7 @@ define([
var $name = $(name);
var n = $name.val().trim() || $name.attr('placeholder');
var u = $url.val().trim();
if (!n || !u) { return true; }
if (!Util.isValidURL(u)) {
if (!n || !u || !Util.isValidURL(u)) {
UI.warn(Messages.fm_link_invalid);
return true;
}
@ -3137,7 +3140,7 @@ define([
var newObj = {
tag: 'a',
attributes: { 'class': obj.class, href: '#' },
attributes: { 'class': obj.class },
content: [obj.icon, obj.name]
};
@ -3179,7 +3182,6 @@ define([
tag: 'a',
attributes: {
'class': 'cp-app-drive-rm-filter',
'href': '#'
},
content: [
h('i.fa.fa-times'),
@ -3192,7 +3194,6 @@ define([
var attributes = {
'class': 'cp-app-drive-filter-doc',
'data-type': type,
'href': '#'
};
var premium = common.checkRestrictedApp(type);
@ -3218,7 +3219,6 @@ define([
attributes: {
'class': 'cp-app-drive-filter-doc',
'data-type': 'link',
'href': '#'
},
content: [
getIcon('link')[0],
@ -3230,7 +3230,6 @@ define([
attributes: {
'class': 'cp-app-drive-filter-doc',
'data-type': 'file',
'href': '#'
},
content: [
getIcon('file')[0],
@ -3453,6 +3452,58 @@ define([
return $fihElement;
};
var lexicographicCompare = function(a, b) {
if (!Array.isArray(a)) {
a = [a];
}
if (!Array.isArray(b)) {
b = [b];
}
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 {
// This means `a[0] == b[0]`. Chop off the first elements and compare the rest.
return lexicographicCompare(a.slice(1), b.slice(1));
}
};
var splitStringToTextAndNumbers = function(s) {
var textOrDigitsRe = /(?<text>\D+)?(?<digits>\d+)?/g;
var split = [];
for (var match of s.matchAll(textOrDigitsRe)) {
if (match.groups.text !== undefined) {
split.push(match.groups.text);
}
if (match.groups.digits !== undefined) {
split.push(parseInt(match.groups.digits));
}
}
return split;
};
var naturalSort = function(a, b) {
if (typeof(a) === "string") {
a = splitStringToTextAndNumbers(a);
}
if (typeof(b) === "string") {
b = splitStringToTextAndNumbers(b);
}
var comp = lexicographicCompare(a, b);
return comp;
};
var sortElements = function (folder, path, oldkeys, prop, asc, useId) {
var root = path && manager.find(path);
if (path[0] === SHARED_FOLDER) {
@ -3497,9 +3548,8 @@ define([
keys.sort(function(a, b) {
var _a = props[(a && a.uid) || a];
var _b = props[(b && b.uid) || b];
if (_a < _b) { return mult * -1; }
if (_b < _a) { return mult; }
return 0;
return mult * naturalSort(_a, _b);
});
return keys;
};
@ -4091,7 +4141,8 @@ define([
];
sortedTags.forEach(function (tag) {
var tagLink = h('a', { href: '#' }, '#' + tag);
$(tagLink).click(function () {
$(tagLink).click(function (e) {
e.preventDefault();
if (displayedCategories.indexOf(SEARCH) !== -1) {
APP.displayDirectory([SEARCH, '#' + tag]);
}
@ -4529,8 +4580,7 @@ define([
manager.getSharedFolderData(root[a]).title : a;
var newB = manager.isSharedFolder(root[b]) ?
manager.getSharedFolderData(root[b]).title : b;
return newA < newB ? -1 :
(newA === newB ? 0 : 1);
return naturalSort(newA, newB);
});
keys.forEach(function (key) {
// Do not display files in the menu
@ -4758,6 +4808,7 @@ define([
data.sharedFolderId = sfId;
data.name = Util.fixFileName(folderName);
data.folderName = Util.fixFileName(folderName) + '.zip';
data.common = common;
var uo = manager.user.userObject;
if (sfId && manager.folders[sfId]) {
@ -4939,7 +4990,7 @@ define([
else if ($this.hasClass('cp-app-drive-context-download')) {
if (paths.length !== 1) { return; }
var path = paths[0];
el = manager.find(path.path);
el = $(path.element).data('element') || manager.find(path.path);
// folder
if (manager.isFolder(el)) {
// folder

View File

@ -64,7 +64,7 @@ define([
let prefix = icon.slice(0, icon.indexOf('-'));
cls = `.${prefix}.${icon}`;
}
return h(`i${cls}`);
return h(`i${cls}`, { 'aria-hidden': 'true' });
};
blocks.button = (type, icon, text) => {
type = type || 'primary';

View File

@ -357,11 +357,22 @@ define([
max: 0,
done: 0,
cache: cache,
sframeChan: sframeChan
sframeChan: sframeChan,
common: data.common,
};
var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData;
var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static;
if (ctx.common && !ctx.common.isLoggedIn()) {
// Anonymous Drive
ctx.data.root = {};
let index = 0;
Object.keys(ctx.data.filesData).forEach(file => {
ctx.data.root[index] = file;
index += 1;
});
}
progress('reading', -1); // Msg.settings_export_reading
nThen(function (waitFor) {
ctx.waitFor = waitFor;

View File

@ -2271,7 +2271,6 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
tag: 'a',
attributes: {
'data-value': val,
href: '#'
},
content: val
};
@ -2690,7 +2689,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
(content.version <= 3 ? 'v2b/' : CURRENT_VERSION+'/');
var s = h('script', {
type:'text/javascript',
src: '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js'
src: ApiConfig.httpSafeOrigin + '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js'
});
$('#cp-app-oo-editor').empty().append(h('div#cp-app-oo-placeholder-a')).append(s);
@ -2726,7 +2725,8 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
},
sfCommon: common,
$container: $bar,
$contentContainer: $('#cp-app-oo-container')
$contentContainer: $('#cp-app-oo-container'),
skipLink: 'iframe[name="frameEditor"]|#editor_sdk'
};
toolbar = APP.toolbar = Toolbar.create(configTb);
toolbar.showColors();
@ -3132,7 +3132,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
var s = h('script', {
type:'text/javascript',
src: '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js'
src: ApiConfig.httpSafeOrigin + '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js'
});
$('#cp-app-oo-editor').append(s);

View File

@ -723,7 +723,6 @@ define([
tag: 'a',
attributes: {
'data-value': _ext,
'href': '#'
},
content: _ext
});
@ -734,7 +733,6 @@ define([
tag: 'a',
attributes: {
'data-value': ext,
'href': '#'
},
content: ext
});
@ -743,7 +741,6 @@ define([
tag: 'a',
attributes: {
'data-value': '',
'href': '#'
},
content: ' ',
});
@ -978,7 +975,8 @@ define([
realtime: cpNfInner.chainpad,
sfCommon: common,
$container: $(toolbarContainer),
$contentContainer: $(contentContainer)
$contentContainer: $(contentContainer),
skipLink: options.skipLink,
};
toolbar = Toolbar.create(configTb);
title.setToolbar(toolbar);

View File

@ -365,7 +365,6 @@ define([
tag: 'a',
attributes: {
'data-value': l.mode,
'href': '#',
},
content: [l.language] // Pretty name of the language value
});
@ -431,7 +430,6 @@ define([
tag: 'a',
attributes: {
'data-value': l.name,
'href': '#',
},
content: [l.name] // Pretty name of the language value
});

View File

@ -2463,4 +2463,3 @@ define([
return common;
});

View File

@ -1057,6 +1057,11 @@ define([
Mailbox.create(funcs);
// automatically configure all relative links in the inner iframe
// to point to the outer domain by adding a 'base' element to iframe's <head>
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
document.head.appendChild(h('base', { href: ApiConfig.httpUnsafeOrigin }));
cb(funcs);
});
} };

View File

@ -863,6 +863,38 @@ MessengerUI, Messages, Pages, PadTypes) {
};
};
Bar.createSkipLink = function (toolbar, config) {
if (config.readOnly === 1) {return;}
const targetId = config.skipLink;
const $skipLink = $('<a>', {
'class': 'cp-toolbar-skip-link',
'href': targetId,
'tabindex': 0,
'text': Messages.skipLink
});
toolbar.$top.append($skipLink);
$skipLink.on('click', function (event) {
event.preventDefault();
let split = targetId.split('|'); // split for iframes
let $container = $('body');
split.some(selector => {
let $targetElement = $container.find(selector);
if ($targetElement.is('iframe')) {
$container = $targetElement.contents();
return;
}
const $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]').first();
if ($firstFocusable.length) {
$firstFocusable.trigger('focus');
} else {
$skipLink.hide();
}
return true;
});
});
return $skipLink;
};
var createLinkToMain = function (toolbar, config) {
var $linkContainer = $('<span>', {
'class': LINK_CLS
@ -1457,6 +1489,7 @@ MessengerUI, Messages, Pages, PadTypes) {
toolbar['linkToMain'] = createLinkToMain(toolbar, config);
toolbar['skipLink'] = Bar.createSkipLink(toolbar, config);
if (!config.realtime) { toolbar.connected = true; }

View File

@ -248,7 +248,7 @@ const factory = (Util, Hash,
var isFolder = exp.isFolder = function (element) {
if (isFolderData(element)) { return false; }
return typeof(element) === "object" || isSharedFolder(element);
return (typeof(element) === "object" && !element.channel) || isSharedFolder(element);
};
exp.isFolderEmpty = function (element) {
if (!isFolder(element)) { return false; }

View File

@ -7,28 +7,6 @@ define([
], function (
DiagramUtil
) {
const parseDrawioStyle = (styleAttrValue) => {
if (!styleAttrValue) {
return;
}
const result = {};
for (const part of styleAttrValue.split(';')) {
const s = part.split(/=(.*)/);
result[s[0]] = s[1];
}
return result;
};
const stringifyDrawioStyle = (styleAttrValue) => {
const parts = [];
for (const [key, value] of Object.entries(styleAttrValue)) {
parts.push(`${key}=${value}`);
}
return parts.join(';');
};
const blobToImage = (blob) => {
return new Promise((resolve) => {
const reader = new FileReader();
@ -44,28 +22,18 @@ define([
};
const loadCryptPadImages = (doc) => {
return Array.from(doc .querySelectorAll('mxCell'))
.map((element) => [element, parseDrawioStyle(element.getAttribute('style'))])
.filter(([, style]) => style && style.image && style.image.startsWith('cryptpad://'))
return Array.from(doc.querySelectorAll('mxCell'))
.map((element) => [element, DiagramUtil.parseDrawioStyle(element.getAttribute('style'))])
.filter(([, style]) => style.image && style.image.startsWith('cryptpad://'))
.map(([element, style]) => {
return loadImage(style.image)
.then((dataUrl) => {
style.image = dataUrl.replace(';base64', ''); // ';' breaks draw.ios style format
element.setAttribute('style', stringifyDrawioStyle(style));
element.setAttribute('style', DiagramUtil.stringifyDrawioStyle(style));
});
});
};
const parseXML = (xmlStr) => {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlStr, "application/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
throw Error("error while parsing " + errorNode);
}
return doc;
};
return {
main: function(userDoc, cb) {
delete userDoc.metadata;
@ -74,7 +42,7 @@ define([
let doc;
try {
doc = parseXML(xml);
doc = DiagramUtil.parseXML(xml);
} catch(e) {
console.error(e);
return;

65
www/diagram/import.js Normal file
View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'/diagram/util.js',
], function (
DiagramUtil,
) {
const Nacl = window.nacl;
const splitAt = function (str, char) {
const pos = str.indexOf(char);
if (pos <= 0) {
return [str, ''];
}
return [str.substring(0, pos), str.substring(pos + 1)];
};
const parseDataUrl = function (url) {
const [prefix, data] = splitAt(url, ',');
const [, metadata] = splitAt(prefix, ':');
const [mimeType, ] = splitAt(metadata, ';');
const u8 = Nacl.util.decodeBase64(data);
return new Blob([u8], { type: mimeType });
};
const saveImagesToCryptPad = async (fileManager, doc) => {
const images = Array.from(doc.querySelectorAll('mxCell'))
.map((element) => ({
element,
style: DiagramUtil.parseDrawioStyle(element.getAttribute('style')),
}))
.filter(({ style }) => style.image && style.image.startsWith('data:'));
for(const image of images) {
const blob = parseDataUrl(image.style.image);
const cryptPadUrl = await DiagramUtil.uploadFile(fileManager, blob);
image.style.image = cryptPadUrl;
image.element.setAttribute('style', DiagramUtil.stringifyDrawioStyle(image.style));
}
};
const importDiagram = async (common, content) => {
let doc;
try {
doc = DiagramUtil.parseXML(content);
} catch(e) {
console.error(e);
return;
}
const fileManager = DiagramUtil.createSimpleFileManager(common);
await saveImagesToCryptPad(fileManager, doc);
return DiagramUtil.xmlAsJsonContent(new XMLSerializer().serializeToString(doc));
};
return {
importDiagram
};
});

View File

@ -4,10 +4,10 @@
// This is the initialization loading the CryptPad libraries
define([
'/api/config',
'jquery',
'/common/sframe-app-framework.js',
'/customize/messages.js', // translation keys
'/components/pako/dist/pako.min.js',
'/components/x2js/x2js.js',
'/diagram/util.js',
'/common/common-ui-elements.js',
@ -15,51 +15,16 @@ define([
'less!/diagram/app-diagram.less',
'css!/diagram/drawio.css',
], function (
ApiConfig,
$,
Framework,
Messages,
pako,
X2JS,
DiagramUtil,
UIElements
) {
const Nacl = window.nacl;
const APP = window.APP = {};
// As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/
const decompressDrawioXml = function(xmlDocStr) {
var TEXT_NODE = 3;
var parser = new DOMParser();
var doc = parser.parseFromString(xmlDocStr, "application/xml");
var errorNode = doc.querySelector("parsererror");
if (errorNode) {
console.error("error while parsing", errorNode);
return xmlDocStr;
}
doc.firstChild.removeAttribute('modified');
doc.firstChild.removeAttribute('agent');
doc.firstChild.removeAttribute('etag');
var diagrams = doc.querySelectorAll('diagram');
diagrams.forEach(function(diagram) {
if (diagram.childNodes.length === 1 && diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) {
const innerText = diagram.firstChild.nodeValue;
const bin = Nacl.util.decodeBase64(innerText);
const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'});
const xmlStr = decodeURIComponent(xmlUrlStr);
const diagramDoc = parser.parseFromString(xmlStr, "application/xml");
diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild);
}
});
var result = new XMLSerializer().serializeToString(doc);
return result;
};
const deepEqual = function(o1, o2) {
return JSON.stringify(o1) === JSON.stringify(o2);
@ -105,28 +70,8 @@ define([
});
};
const numbersToNumbers = function(o) {
const type = typeof o;
if (type === "object") {
for (const key in o) {
o[key] = numbersToNumbers(o[key]);
}
return o;
} else if (type === 'string' && o.match(/^[+-]?(0|(([1-9]\d*)(\.\d+)?))$/)) {
return parseFloat(o, 10);
} else {
return o;
}
};
const xmlAsJsonContent = (xml) => {
var decompressedXml = decompressDrawioXml(xml);
return numbersToNumbers(x2js.xml2js(decompressedXml));
};
var onDrawioChange = function(newXml) {
var newJson = xmlAsJsonContent(newXml);
var newJson = DiagramUtil.xmlAsJsonContent(newXml);
if (!deepEqual(lastContent, newJson)) {
lastContent = newJson;
framework.localChange();
@ -150,7 +95,10 @@ define([
return new Promise((resolve) => {
framework.insertImage({}, (imageData) => {
if (imageData.blob) {
resolve(imageData.blob);
const fileManager = DiagramUtil.createSimpleFileManager(framework._.sfCommon);
DiagramUtil.uploadFile(fileManager, imageData.blob)
.then(url => resolve(url))
.catch(e => console.error(e));
} else if (imageData.url) {
resolve(imageData.url);
} else {
@ -179,9 +127,12 @@ define([
framework.setFileImporter(
{accept: ['.drawio', 'application/x-drawio']},
(content) => {
return xmlAsJsonContent(content);
}
(content, file, cb) => {
require(['/diagram/import.js'], (importer) => {
importer.importDiagram(framework._.sfCommon, content, file).then(cb);
});
},
true
);
framework.setFileExporter(
@ -207,7 +158,7 @@ define([
// starting the CryptPad framework
framework.start();
drawioFrame.src = '/components/drawio/src/main/webapp/index.html?'
drawioFrame.src = ApiConfig.httpSafeOrigin + '/components/drawio/src/main/webapp/index.html?'
+ new URLSearchParams({
test: 1,
stealth: 1,
@ -250,6 +201,7 @@ define([
Framework.create({
toolbarContainer: '#cme_toolbox',
contentContainer: '#cp-app-diagram-editor',
skipLink: '#cp-app-diagram-content|body .geSearchSidebar',
// validateContent: validateXml,
}, function (framework) {
onFrameworkReady(framework);

View File

@ -7,11 +7,19 @@ define([
'/file/file-crypto.js',
'/common/outer/cache-store.js',
'/components/x2js/x2js.js',
'/components/pako/dist/pako.min.js',
'/common/common-hash.js',
'/api/config',
'jquery',
], function (
Util,
FileCrypto,
Cache,
X2JS,
pako,
Hash,
ApiConfig,
$,
) {
const Nacl = window.nacl;
const x2js = new X2JS();
@ -49,10 +57,139 @@ define([
return x2js.js2xml(cleaned);
};
const parseXML = (xmlStr) => {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlStr, "application/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
throw Error("error while parsing " + errorNode);
}
return doc;
};
const numbersToNumbers = function(o) {
const type = typeof o;
if (type === "object") {
for (const key in o) {
o[key] = numbersToNumbers(o[key]);
}
return o;
} else if (type === 'string' && o.match(/^[+-]?(0|(([1-9]\d*)(\.\d+)?))$/)) {
return parseFloat(o, 10);
} else {
return o;
}
};
const xmlAsJsonContent = (xml) => {
var decompressedXml = decompressDrawioXml(xml);
return numbersToNumbers(x2js.xml2js(decompressedXml));
};
// As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/
const decompressDrawioXml = function(xmlDocStr) {
var TEXT_NODE = 3;
var parser = new DOMParser();
var doc = parser.parseFromString(xmlDocStr, "application/xml");
var errorNode = doc.querySelector("parsererror");
if (errorNode) {
console.error("error while parsing", errorNode);
return xmlDocStr;
}
doc.firstChild.removeAttribute('modified');
doc.firstChild.removeAttribute('agent');
doc.firstChild.removeAttribute('etag');
var diagrams = doc.querySelectorAll('diagram');
diagrams.forEach(function(diagram) {
if (diagram.childNodes.length === 1 && diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) {
const innerText = diagram.firstChild.nodeValue;
const bin = Nacl.util.decodeBase64(innerText);
const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'});
const xmlStr = decodeURIComponent(xmlUrlStr);
const diagramDoc = parser.parseFromString(xmlStr, "application/xml");
diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild);
}
});
var result = new XMLSerializer().serializeToString(doc);
return result;
};
const parseDrawioStyle = (styleAttrValue) => {
if (!styleAttrValue) {
return {};
}
const result = {};
for (const part of styleAttrValue.split(';')) {
const s = part.split(/=(.*)/);
result[s[0]] = s[1];
}
return result;
};
const stringifyDrawioStyle = (styleAttrValue) => {
const parts = [];
for (const [key, value] of Object.entries(styleAttrValue)) {
parts.push(`${key}=${value}`);
}
return parts.join(';');
};
const getCryptPadUrlForUploadData = (data) => {
const urlHash = data.url.split('#')[1];
const secret = Hash.getSecrets('file', urlHash);
const fileHost = ApiConfig.fileHost || window.location.origin;
const hexFileName = secret.channel;
const src = fileHost + Hash.getBlobPathFromHex(hexFileName);
const key = secret.keys && secret.keys.cryptKey;
const cryptKey = Nacl.util.encodeBase64(key);
return getCryptPadUrl(src, cryptKey, data.fileType);
};
const uploadFile = async (fileManager, blob) => {
return new Promise((resolve) => {
fileManager.handleFile(blob, {
callback: (data) => {
const cryptPadUrl = getCryptPadUrlForUploadData(data);
resolve(cryptPadUrl);
}
});
});
};
const createSimpleFileManager = (common) => {
const fmConfigImages = {
noHandlers: true,
noStore: true,
onUploaded: function (ev, data) {
if (!ev.callback) { return; }
ev.callback(data);
}
};
return common.createFileManager(fmConfigImages);
};
return {
parseCryptPadUrl,
getCryptPadUrl,
jsonContentAsXML,
parseXML,
xmlAsJsonContent,
decompressDrawioXml,
parseDrawioStyle,
stringifyDrawioStyle,
uploadFile,
createSimpleFileManager,
loadImage: function(href) {
return new Promise((resolve, reject) => {

View File

@ -220,7 +220,8 @@ define([
metadataMgr: metadataMgr,
readOnly: privateData.readOnly,
sfCommon: common,
$container: APP.$bar
$container: APP.$bar,
skipLink: '#cp-app-drive-tree'
};
var toolbar = Toolbar.create(configTb);

View File

@ -159,7 +159,6 @@ define([
attributes: {
'class': 'cp-form-type-value',
'data-value': t,
'href': '#',
},
content: Messages['form_text_'+t]
};
@ -262,7 +261,6 @@ define([
attributes: {
'class': 'cp-form-type-value',
'data-value': t,
'href': '#',
},
content: Messages['form_poll_'+t]
};
@ -1295,7 +1293,6 @@ define([
attributes: {
'class': 'cp-form-condition-question',
'data-value': obj.uid,
'href': '#',
},
content: obj.q
};
@ -1316,14 +1313,12 @@ define([
tag: 'a',
attributes: {
'data-value': 1,
'href': '#',
},
content: Messages.form_condition_is
}, {
tag: 'a',
attributes: {
'data-value': 0,
'href': '#',
},
content: Messages.form_condition_isnot
}];
@ -1402,7 +1397,6 @@ define([
attributes: {
'class': 'cp-form-condition-value',
'data-value': str,
'href': '#',
},
content: str
};
@ -1576,7 +1570,6 @@ define([
attributes: {
'class': 'cp-form-condition-question',
'data-value': obj.uid,
'href': '#',
},
content: obj.q
};
@ -1604,7 +1597,6 @@ define([
attributes: {
'class': 'cp-form-condition-value',
'data-value': str,
'href': '#',
},
content: str
};
@ -2988,7 +2980,6 @@ define([
attributes: {
'class': 'cp-form-type-value',
'data-value': t.key,
'href': '#',
},
content: t.str
};
@ -3574,7 +3565,7 @@ define([
});
var $send = $(send).click(function () {
if (!$radio.find('input[type="radio"]:checked').length) {
return UI.warn(Messages.error);
return UI.warn(Messages.answerType_error);
}
var results = getFormResults();
@ -5676,5 +5667,6 @@ define([
Framework.create({
toolbarContainer: '#cp-toolbar',
contentContainer: '#cp-app-form-editor',
skipLink: '#cp-app-form-editor'
}, andThen);
});

View File

@ -1401,6 +1401,7 @@ define([
Framework.create({
toolbarContainer: '#cme_toolbox',
contentContainer: '#cp-app-kanban-editor',
skipLink: '#cp-app-kanban-content'
}, waitFor(function (framework) {
andThen2(framework);
}));

View File

@ -160,12 +160,20 @@ define([
}
function __onAddItemClickHandler(nodeItem) {
nodeItem.addEventListener('click', function (e) {
function handleAddItem(e, item) {
e.preventDefault();
e.stopPropagation();
self.options.addItemClick(this);
if (typeof (this.clickfn) === 'function') {
this.clickfn(this);
self.options.addItemClick(item);
if (typeof (item.clickfn) === 'function') {
item.clickfn(item);
}
}
nodeItem.addEventListener('click', function (e) {
handleAddItem(e,this);
});
nodeItem.addEventListener('keydown', function (e) {
if (e.keyCode === 13) {
handleAddItem(e,this);
}
});
}
@ -719,6 +727,7 @@ define([
var addTopBoardItem = document.createElement('span');
addTopBoardItem.classList.add('kanban-title-button');
$(addTopBoardItem).attr('tabindex', '0');
$(addTopBoardItem).attr('aria-label', Messages.addItemTop);
addTopBoardItem.setAttribute('data-top', "1");
addTopBoardItem.innerHTML = '<i class="cptools cptools-add-top">';
footerBoard.appendChild(addTopBoardItem);
@ -726,6 +735,7 @@ define([
var addBoardItem = document.createElement('span');
addBoardItem.classList.add('kanban-title-button');
$(addBoardItem).attr('tabindex', '0');
$(addBoardItem).attr('aria-label', Messages.addItemBottom);
addBoardItem.innerHTML = '<i class="cptools cptools-add-bottom">';
footerBoard.appendChild(addBoardItem);
__onAddItemClickHandler(addBoardItem);

View File

@ -205,7 +205,7 @@ define([
var active = privateData.category || 'all';
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
var $category = $('<div>', {'class': 'cp-sidebarlayout-category', 'tabindex': 0}).appendTo($categories);
if (key === 'all') { $category.append($('<span>', {'class': 'fa fa-bars'})); }
if (key === 'friends') { $category.append($('<span>', {'class': 'fa fa-user'})); }
if (key === 'pads') { $category.append($('<span>', {'class': 'cptools cptools-richtext'})); }
@ -215,6 +215,11 @@ define([
$category.addClass('cp-leftside-active');
}
$category.keydown(function (e) {
if (e.keyCode === 13) {
$category.click();
}
});
$category.click(function () {
if (!Array.isArray(categories[key]) && categories[key].onClick) {
categories[key].onClick();
@ -240,6 +245,7 @@ define([
$container: APP.$toolbar,
pageTitle: Messages.notificationsPage || 'Notifications',
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-sidebarlayout-container',
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();

View File

@ -1335,6 +1335,7 @@ define([
Framework.create({
toolbarContainer: '#cp-app-pad-toolbar',
contentContainer: '#cp-app-pad-editor',
skipLink: '#cke_1_contents .cke_wysiwyg_frame|html',
patchTransformer: ChainPad.NaiveJSONTransformer,
/*thumbnail: {
getContainer: function () { return $('iframe').contents().find('html')[0]; },

View File

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

View File

@ -902,7 +902,7 @@ define([
var todo = function () {
var val = parseInt($input.val());
if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.error); }
if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.limit_error); }
if (val === oldVal) { return; }
spinner.spin();
common.setAttribute(['general', 'mediatag-size'], val, function (err) {
@ -1196,6 +1196,7 @@ define([
Feedback.send('FULL_DRIVE_EXPORT_START');
var todo = function(data, filename) {
var ui = Backup.createExportUI(privateData.origin);
data.common = common;
var bu = Backup.create(data, common.getPad, privateData.fileHost, function(blob, errors) {
saveAs(blob, filename);
@ -1978,6 +1979,7 @@ define([
$container: APP.$toolbar,
pageTitle: Messages.settings_title,
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-sidebarlayout-leftside'
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();

View File

@ -619,7 +619,8 @@ define([
}
$(el).css('background-color', '');
}
}
},
skipLink: '.CodeMirror',
}, waitFor(function (fw) { framework = fw; }));
nThen(function (waitFor) {

View File

@ -1564,7 +1564,8 @@ define([
metadataMgr: metadataMgr,
readOnly: privateData.readOnly,
sfCommon: common,
$container: $bar
$container: $bar,
skipLink: '#cp-sidebarlayout-leftside'
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
// Update the name in the user menu

View File

@ -636,6 +636,7 @@ define([
patchTransformer: ChainPad.NaiveJSONTransformer,
toolbarContainer: '#cp-toolbar',
contentContainer: '#cp-app-whiteboard-canvas-area',
skipLink: '#cp-app-whiteboard-controls'
}, waitFor(function (framework) {
andThen2(framework);
}));