mirror of https://github.com/xwiki-labs/cryptpad
Merge branch '2024.9-rc' into ooapi
This commit is contained in:
commit
1b04232b55
|
@ -16,7 +16,7 @@ www/accounts
|
|||
www/worker
|
||||
www/todo
|
||||
|
||||
lib/plugins/
|
||||
#lib/plugins/
|
||||
|
||||
www/common/hyperscript.js
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = {
|
|||
4
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'off', // git handles linebreak conversion for us
|
||||
'unix'
|
||||
],
|
||||
'quotes': [
|
||||
|
|
|
@ -89,6 +89,9 @@ body:
|
|||
label: Version
|
||||
description: What version of CryptPad are you running?
|
||||
options:
|
||||
- 2024.6.1
|
||||
- 2024.6.0
|
||||
- 2024.3.1
|
||||
- 2024.3.0
|
||||
- 5.7.0
|
||||
- 5.6.0
|
||||
|
@ -100,8 +103,6 @@ body:
|
|||
- 5.2.0
|
||||
- 5.1.0
|
||||
- 5.0.0
|
||||
- 4.14.1
|
||||
- 4.14.0
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
@ -17,12 +17,11 @@ customize
|
|||
messages.log
|
||||
www/scratch
|
||||
data
|
||||
pins/
|
||||
blob/
|
||||
block/
|
||||
blobstage/
|
||||
block/
|
||||
logs/
|
||||
pins
|
||||
blob
|
||||
block
|
||||
blobstage
|
||||
logs
|
||||
privileged.conf
|
||||
config/config.js
|
||||
config/sso.js
|
||||
|
|
60
.lesshintrc
60
.lesshintrc
|
@ -1,60 +0,0 @@
|
|||
{
|
||||
"fileExtensions": [".less"],
|
||||
|
||||
// These rules are almost certainly crap and will not catch bugs (Caleb)
|
||||
"newlineAfterBlock": { "enabled": false }, // not just a newline but an entire empty line after each block
|
||||
"spaceAroundOperator": { "enabled": false }, // disallow calc(10px+10px);
|
||||
"hexLength": { "enabled": false }, // require long hex color codes or require short where possible
|
||||
"hexNotation": { "enabled": false }, // require hex lowercase
|
||||
"propertyOrdering": { "enabled": false }, // require attributes to be in alphabetical order D:
|
||||
"stringQuotes": { "enabled": false }, // force quoting of strings with ' or " (silly)
|
||||
"importPath": { "enabled": false }, // require imports to not have .less, ridiculous
|
||||
"qualifyingElement": { "enabled": false }, // disallow div.xxx and require .xxx
|
||||
"decimalZero": { "enabled": false }, // disallow .5em
|
||||
"borderZero": { "enabled": false }, // disallow border: none;
|
||||
"selectorNaming": { "enabled": false }, // this would be crap because classes are what they are.
|
||||
"zeroUnit": { "enabled": false },
|
||||
"singleLinePerProperty": { "enabled": false },
|
||||
"_singleLinePerProperty": {
|
||||
"enabled": true,
|
||||
"allowSingleLineRules": true
|
||||
},
|
||||
"spaceAroundComma": { "enabled": false },
|
||||
"importantRule": { "enabled": false },
|
||||
"universalSelector": { "enabled": false },
|
||||
"idSelector": { "enabled": false },
|
||||
"singleLinePerSelector": { "enabled": false },
|
||||
"spaceBetweenParens": { "enabled": false },
|
||||
"maxCharPerLine": { "enabled": false }, // using lesshint flags can cause long lines
|
||||
"comment": { "enabled": false }, // ban multi-line comments ?
|
||||
|
||||
// These rules should be discussed, if they're crap then they should be moved up.
|
||||
"colorVariables": { "enabled": false }, // require all colors to be stored as variables first...
|
||||
"variableValue": { "enabled": false }, // any attribute types which should always be variables ? color?
|
||||
"spaceBeforeBrace": { "enabled": true },//{ "enabled": true, "style": "one_space" },
|
||||
|
||||
// Turn everything else on
|
||||
"spaceAfterPropertyColon": { "enabled": true },
|
||||
"finalNewline": { "enabled": true }, // require an empty line at the end of the file (enabled for now)
|
||||
"attributeQuotes": { "enabled": true },
|
||||
"depthLevel": {
|
||||
"depth": 1 // TODO(cjd) This is obviously not triggering, even with 1
|
||||
},
|
||||
"duplicateProperty": { "enabled": false },
|
||||
"emptyRule": { "enabled": true },
|
||||
"hexValidation": { "enabled": true }, // disallow actual garbage color hex codes (e.g. #ab)
|
||||
"propertyUnits": {
|
||||
"valid": ["rem", "vw", "em", "px", "ch"], // These units are allowed for all properties
|
||||
"invalid": ["pt"], // The 'pt' unit is not allowed under any circumstances
|
||||
"properties": {
|
||||
//"line-height": [] // No units are allowed for line-height
|
||||
}
|
||||
},
|
||||
"spaceAfterPropertyName": { "enabled": true, "style": "no_space" },
|
||||
"spaceAfterPropertyValue": { "enabled": true, "style": "no_space" },
|
||||
"spaceAroundBang": { "enabled": true, "style": "before" },
|
||||
"trailingSemicolon": { "enabled": true },
|
||||
"trailingWhitespace": { "enabled": true },
|
||||
"urlFormat": { "enabled": true, "style": "relative" },
|
||||
"urlQuotes": { "enabled": true }
|
||||
}
|
|
@ -25,10 +25,14 @@ Files: .jshintrc
|
|||
Copyright: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
License: AGPL-3.0-or-later
|
||||
|
||||
Files: .lesshintrc
|
||||
Files: .stylelintrc.js
|
||||
Copyright: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
License: AGPL-3.0-or-later
|
||||
|
||||
Files: scripts/tests/test-data/*
|
||||
Copyright: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
License: AGPL-3.0-or-later
|
||||
|
||||
## Dependencies
|
||||
|
||||
Files: www/common/theme/*
|
||||
|
@ -156,4 +160,4 @@ License: AGPL-3.0-or-later
|
|||
|
||||
Files: www/common/onlyoffice/x2t/*
|
||||
Copyright: Ascensio System Limited 2010-2022
|
||||
License: AGPL-3.0-or-later
|
||||
License: AGPL-3.0-or-later
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
module.exports = {
|
||||
"extends": "stylelint-config-standard-less",
|
||||
"rules": {
|
||||
"no-descending-specificity": null,
|
||||
"length-zero-no-unit": null,
|
||||
"no-duplicate-selectors": null,
|
||||
"declaration-block-no-duplicate-properties": null,
|
||||
|
||||
"comment-empty-line-before": null,
|
||||
"rule-empty-line-before": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"at-rule-empty-line-before": null,
|
||||
"custom-property-empty-line-before": null,
|
||||
|
||||
"font-family-name-quotes": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"shorthand-property-no-redundant-values": null,
|
||||
"declaration-block-no-shorthand-property-overrides": null,
|
||||
|
||||
"comment-whitespace-inside": null,
|
||||
|
||||
"property-no-vendor-prefix": null,
|
||||
"selector-no-vendor-prefix": null,
|
||||
"function-name-case": null,
|
||||
"selector-class-pattern": null,
|
||||
"custom-property-pattern": null,
|
||||
"selector-id-pattern": null,
|
||||
|
||||
"selector-pseudo-element-colon-notation": null,
|
||||
"media-feature-range-notation": null,
|
||||
"selector-not-notation": null,
|
||||
"color-function-notation": null,
|
||||
"alpha-value-notation": null,
|
||||
|
||||
"number-max-precision": null,
|
||||
|
||||
"at-rule-no-unknown": null, // FIXME
|
||||
|
||||
"less/no-duplicate-variables": null,
|
||||
"less/color-no-invalid-hex": null
|
||||
}
|
||||
};
|
868
CHANGELOG.md
868
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -14,7 +14,7 @@ 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
|
||||
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --production \
|
||||
&& npm run install:components
|
||||
|
@ -60,7 +60,7 @@ ENTRYPOINT ["/bin/bash", "/cryptpad/docker-entrypoint.sh"]
|
|||
HEALTHCHECK --interval=1m CMD curl -f http://localhost:3000/ || exit 1
|
||||
|
||||
# Ports
|
||||
EXPOSE 3000 3001 3003
|
||||
EXPOSE 3000 3003
|
||||
|
||||
# Run cryptpad on startup
|
||||
CMD ["npm", "start"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) <year> <owner>.
|
||||
Copyright (c) <year> <owner>.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ Mozilla Public License Version 2.0
|
|||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
|
|
@ -191,7 +191,7 @@ module.exports = {
|
|||
* This archived data still takes up space and so you'll probably still want to
|
||||
* remove these files after a brief period.
|
||||
*
|
||||
* cryptpad/scripts/evict-inactive.js is intended to be run daily
|
||||
* cryptpad/scripts/evict-archived.js is intended to be run daily
|
||||
* from a crontab or similar scheduling service.
|
||||
*
|
||||
* The intent with this feature is to provide a safety net in case of accidental
|
||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
|
||||
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript>
|
||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
|
||||
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript>
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
* If you want to check all the configurable values, you can open the internal configuration file
|
||||
but you should not change it directly (/common/application_config_internal.js)
|
||||
*/
|
||||
|
||||
define(['/common/application_config_internal.js'], function (AppConfig) {
|
||||
|
||||
// Example: If you want to remove the survey link in the menu:
|
||||
// AppConfig.surveyURL = "";
|
||||
|
||||
// To inform users of the support ticket panel which languages your admins speak:
|
||||
//AppConfig.supportLanguages = [ 'en', 'fr' ];
|
||||
|
||||
|
||||
return AppConfig;
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript></noscript>
|
||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript></noscript>
|
||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript></noscript>
|
||||
|
|
|
@ -22,11 +22,13 @@ define([
|
|||
'/common/outer/login-block.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/outer/http-command.js',
|
||||
'/api/config',
|
||||
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
'/components/scrypt-async/scrypt-async.min.js', // better load speed
|
||||
], function ($, Listmap, Crypto, Util, NetConfig, Login, Cred, ChainPad, Realtime, Constants, UI,
|
||||
Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand) {
|
||||
Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand,
|
||||
ApiConfig) {
|
||||
var Exports = {
|
||||
Cred: Cred,
|
||||
Block: Block,
|
||||
|
@ -218,6 +220,11 @@ define([
|
|||
proxy.edPublic = result.edPublic;
|
||||
}
|
||||
|
||||
if (ApiConfig && Array.isArray(ApiConfig.adminKeys) &&
|
||||
ApiConfig.adminKeys.includes(proxy.edPublic)) {
|
||||
localStorage.CP_admin = "1";
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
Realtime.whenRealtimeSyncs(result.realtime, function () {
|
||||
proceed(result);
|
||||
|
|
|
@ -5,15 +5,18 @@
|
|||
(function () {
|
||||
// add your module to this map so it gets used
|
||||
var map = {
|
||||
'ar': 'اَلْعَرَبِيَّةُ',
|
||||
'ca': 'Català',
|
||||
'cs': 'Čeština',
|
||||
'de': 'Deutsch',
|
||||
'el': 'Ελληνικά',
|
||||
//'el': 'Ελληνικά',
|
||||
'es': 'Español',
|
||||
'es_CU': 'Español cubano',
|
||||
'eu': 'Euskara',
|
||||
'fi': 'Suomi',
|
||||
'fr': 'Français',
|
||||
//'hi': 'हिन्दी',
|
||||
'id': 'Bahasa Indonesia',
|
||||
'it': 'Italiano',
|
||||
'ja': '日本語',
|
||||
'nb': 'Norwegian Bokmål',
|
||||
|
@ -21,9 +24,9 @@ var map = {
|
|||
'pl': 'Polski',
|
||||
'pt-br': 'Português do Brasil',
|
||||
'pt-pt': 'Português do Portugal',
|
||||
'ro': 'Română',
|
||||
//'ro': 'Română',
|
||||
'ru': 'Русский',
|
||||
//'sv': 'Svenska',
|
||||
'sv': 'Svenska',
|
||||
//'te': 'తెలుగు',
|
||||
'uk': 'Українська',
|
||||
'zh': '中文(簡體)',
|
||||
|
@ -44,6 +47,7 @@ var getLanguage = Messages._getLanguage = function () {
|
|||
(map[l.split('_')[0]] ? l.split('_')[0] : 'en'));
|
||||
};
|
||||
var language = getLanguage();
|
||||
window.cryptpadLanguage = language;
|
||||
|
||||
// Translations files were migrated from requirejs modules to json.
|
||||
// To avoid asking every administrator to update their customized translation files,
|
||||
|
@ -87,6 +91,9 @@ define(req, function(AppConfig, Default, Language) {
|
|||
});
|
||||
}
|
||||
|
||||
let html = typeof(document) !== "undefined" && document.documentElement;
|
||||
if (html) { html.setAttribute('lang', language); }
|
||||
|
||||
var extend = function (a, b) {
|
||||
for (var k in b) {
|
||||
if (Array.isArray(b[k])) {
|
||||
|
@ -128,7 +135,6 @@ define(req, function(AppConfig, Default, Language) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
return Messages;
|
||||
|
||||
});
|
||||
|
|
|
@ -28,8 +28,9 @@ define([
|
|||
var premiumButton = h('a', {
|
||||
href: accounts.upgradeURL,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
}, h('button.cp-features-register-button', Msg.features_f_subscribe));
|
||||
rel: 'noopener noreferrer',
|
||||
class: 'cp-features-register-button',
|
||||
}, Msg.features_f_subscribe);
|
||||
|
||||
var groupItemTemplate = function (title, content) {
|
||||
return h('li.list-group-item', [
|
||||
|
@ -119,14 +120,15 @@ define([
|
|||
h('div.card-body',[
|
||||
h('div.cp-features-register#cp-features-register', [
|
||||
h('a', {
|
||||
href: '/register/'
|
||||
}, h('button.cp-features-register-button', Msg.features_f_register))
|
||||
href: '/register/',
|
||||
class: 'cp-features-register-button',
|
||||
}, Msg.features_f_register)
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
var premiumFeatures =
|
||||
h('div.col-12.col-sm-4.cp-anon-user',[
|
||||
h('div.col-12.col-sm-4.cp-premium-user',[
|
||||
h('div.card',[
|
||||
h('div.title-card',[
|
||||
h('h3.text-center',Msg.features_premium)
|
||||
|
|
|
@ -15,7 +15,8 @@ define([
|
|||
'/common/outer/local-store.js',
|
||||
'/customize/pages.js',
|
||||
'/common/pad-types.js',
|
||||
], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes) {
|
||||
'/common/extensions.js'
|
||||
], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes, Extensions) {
|
||||
var urlArgs = Config.requireConf.urlArgs;
|
||||
|
||||
var checkEarlyAccess = function (x) {
|
||||
|
@ -152,10 +153,10 @@ define([
|
|||
if (Pages.areSubscriptionsAllowed() && !LocalStore.getPremium()) {
|
||||
var sub = h('div.cp-sub-prompt', [
|
||||
h('span', Msg.home_morestorage),
|
||||
h('a', {href:"/accounts/"}, h('button', [
|
||||
h('a', {href:"/accounts/", class:'subscribe-btn'}, [
|
||||
h('i.fa.fa-ticket'),
|
||||
Msg.features_f_subscribe
|
||||
]))
|
||||
])
|
||||
]);
|
||||
return sub;
|
||||
} else {
|
||||
|
@ -164,9 +165,19 @@ define([
|
|||
};
|
||||
|
||||
|
||||
let popup = h('div.cp-extensions-popups');
|
||||
let utils = { h, Util, Hash };
|
||||
Extensions.getExtensions('HOMEPAGE_POPUP').forEach(ext => {
|
||||
if (typeof(ext.check) === "function" && !ext.check()) { return; }
|
||||
ext.getContent(utils, content => {
|
||||
$(popup).append(h('div.cp-extensions-popup', content));
|
||||
});
|
||||
});
|
||||
|
||||
return [
|
||||
h('div#cp-main', [
|
||||
Pages.infopageTopbar(),
|
||||
popup,
|
||||
notice,
|
||||
h('div.container.cp-container', [
|
||||
h('div.row.cp-home-hero', [
|
||||
|
|
|
@ -18,8 +18,6 @@ define([
|
|||
return;
|
||||
}
|
||||
|
||||
Msg.install_token = "Install token";
|
||||
|
||||
document.title = Msg.install_header;
|
||||
|
||||
var frame = function (content) {
|
||||
|
@ -27,8 +25,7 @@ Msg.install_token = "Install token";
|
|||
h('div#cp-main', [
|
||||
//Pages.infopageTopbar(),
|
||||
h('div.container.cp-container', [
|
||||
//h('div.row.cp-page-title', h('h1', Msg.install_header)),
|
||||
h('div.row.cp-page-title', h('h1', Msg.register_header)),
|
||||
h('div.row.cp-page-title', h('h1', Msg.install_header)),
|
||||
].concat(content)),
|
||||
Pages.infopageFooter(),
|
||||
]),
|
||||
|
@ -39,17 +36,12 @@ Msg.install_token = "Install token";
|
|||
h('div.row.cp-register-det', [
|
||||
h('div#data.hidden.col-md-6', [
|
||||
h('h2', Msg.register_notes_title),
|
||||
//Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
|
||||
Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
|
||||
Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
|
||||
]),
|
||||
h('div.cp-reg-form.col-md-6', [
|
||||
h('div#userForm.form-group.hidden', [
|
||||
h('div.cp-register-instance', [
|
||||
Msg._getKey('register_instance', [ Pages.Instance.name ]),
|
||||
/*h('br'),
|
||||
h('a', {
|
||||
href: '/features.html'
|
||||
}, Msg.register_whyRegister)*/
|
||||
Msg.install_instance,
|
||||
]),
|
||||
h('input.form-control#installtoken', {
|
||||
type: 'text',
|
||||
|
@ -75,7 +67,7 @@ Msg.install_token = "Install token";
|
|||
/*h('div.checkbox-container', [
|
||||
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
|
||||
]),*/
|
||||
h('button#register', Msg.login_register)
|
||||
h('button#register', Msg.install_launch)
|
||||
])
|
||||
]),
|
||||
])
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
@import (reference) "./colortheme-all.less";
|
||||
@import (reference) "./forms.less";
|
||||
@import (reference) './icon-colors.less';
|
||||
|
||||
.admin_main() {
|
||||
--LessLoader_require: LessLoader_currentFile();
|
||||
}
|
||||
& {
|
||||
// Instance accent color presets
|
||||
@palette-colors:
|
||||
#0087FF,
|
||||
#de0064,
|
||||
#8c52bc,
|
||||
#3d7672;
|
||||
|
||||
div.cp-palette-container {
|
||||
|
||||
.cp-palette-nocolor {
|
||||
display: none;
|
||||
}
|
||||
.instance-colors(@palette-colors; @index) when (@index > 0) {
|
||||
// loop through the @colors
|
||||
.instance-colors(@palette-colors; (@index - 1));
|
||||
|
||||
@color: extract(@palette-colors, @index);
|
||||
// make a numbered class selector for each color
|
||||
.cp-palette-color@{index} {
|
||||
background-color: @color !important;
|
||||
color: contrast(@color, @cryptpad_color_grey_800, @cryptpad_color_grey_200) !important;
|
||||
}
|
||||
}
|
||||
.instance-colors(@palette-colors; length(@palette-colors));
|
||||
}
|
||||
|
||||
.cp-admin-customize-apps-grid, .cp-admin-customize-options-grid {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.cp-admin-customize-apps-grid {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
.cp-appblock {
|
||||
padding: 0.5rem;
|
||||
border-radius: @variables_radius;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
.iconColors_main();
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
i.cp-icon {
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
.cp-app-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
.cp-inactive-app {
|
||||
background-color: transparent;
|
||||
opacity: 0.75;
|
||||
.cp-on-enabled {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.cp-active-app {
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
.cp-on-enabled {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-admin-customize-options-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.cp-optionblock {
|
||||
padding: 0.5rem;
|
||||
border-radius: @variables_radius;
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
align-self: start;
|
||||
.cp-checkmark-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cp-option-hint {
|
||||
margin-left: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -198,6 +198,12 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.cp-usergrid-user, textarea, a, .fa-times {
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-alertify-type-container {
|
||||
overflow: visible !important;
|
||||
|
@ -237,6 +243,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
span.alertify-tabs-active {
|
||||
background-color: @cp_alertify-fg !important;
|
||||
|
@ -263,6 +273,10 @@
|
|||
|
||||
input {
|
||||
.tools_placeholder-color();
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
|
||||
span.cp-password-container {
|
||||
|
|
|
@ -128,9 +128,9 @@
|
|||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 5px @cp_checkmark-back1;
|
||||
outline: none;
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,9 +216,9 @@
|
|||
height: @checkmark-dim1;
|
||||
height: var(--checkmark-dim1);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 5px @cp_checkmark-back1;
|
||||
outline: none;
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
.cp-unselectable {
|
||||
.tools_unselectable();
|
||||
}
|
||||
.btn-primary{
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
|
||||
/* local mixins */
|
||||
@drive_icon-margin: 10px;
|
||||
|
@ -329,11 +334,10 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.leftside-menu-category_main();
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
margin: 0 0 2px -5px;
|
||||
padding-left: 5px;
|
||||
.fa, .cptools {
|
||||
display: inline-block;
|
||||
|
@ -348,6 +352,9 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +380,9 @@
|
|||
padding-left: 20px;
|
||||
.leftside-menu-category_main();
|
||||
margin: 0;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -399,6 +409,10 @@
|
|||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
border-radius: @variables_radius_S;
|
||||
}
|
||||
}
|
||||
.cp-app-drive-tree-docs {
|
||||
box-shadow: @cryptpad_ui_shadow;
|
||||
|
@ -953,6 +967,9 @@
|
|||
li, li .fa, li .cptools {
|
||||
cursor: pointer;
|
||||
border-radius: @variables_radius;
|
||||
&:focus {
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
}
|
||||
}
|
||||
&> p {
|
||||
display: flex;
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
}
|
||||
&.tui-full-calendar-content {
|
||||
font-size: @colortheme_app-font-size;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
&[readonly] {
|
||||
//margin-top:1rem;
|
||||
|
@ -119,13 +122,37 @@
|
|||
}
|
||||
}
|
||||
|
||||
// The following palette container is just for the UI components
|
||||
// The specific colors you want to show have to be defined in your app
|
||||
// using the classes .cp-palette-nocolor .cp-palette-color1 .cp-palette-color2 etc.
|
||||
div.cp-palette-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.cp-palette-color {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: @cp_kanban-fg;
|
||||
border: 1px solid fade(@cp_kanban-fg, 40%);
|
||||
&.fa-check { // tick on selected color
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.btn {
|
||||
background-color: @cp_buttons-cancel;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
line-height: 36px;
|
||||
line-height: 20px;
|
||||
padding: 8px 6px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
@ -232,11 +259,9 @@
|
|||
}
|
||||
|
||||
|
||||
|
||||
&:focus {
|
||||
//border: 1px dotted @alertify-base;
|
||||
box-shadow: 0px 0px 5px @cp_buttons-primary !important;
|
||||
outline: none;
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
color: @cp_drive-fg;
|
||||
}
|
||||
&.cp-icons-element-selected {
|
||||
background: @cp_drive-icon-hover;
|
||||
color: @cp_drive-fg;
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
}
|
||||
.fa, .cptools {
|
||||
display: block;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
@import (reference) "./colortheme-all.less";
|
||||
@import (reference) "./font.less";
|
||||
@import (reference) "./variables.less";
|
||||
|
||||
@infopages-radius: 5px;
|
||||
@infopages-radius-L: 10px;
|
||||
|
@ -46,6 +47,10 @@ body.html {
|
|||
a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
a:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
border-radius: @variables_radius;
|
||||
}
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -231,6 +236,9 @@ body.html {
|
|||
border: 0px;
|
||||
border-radius: @infopages-radius;
|
||||
padding: 0.5em 0.7em;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-footer-version {
|
||||
|
@ -293,6 +301,9 @@ body.html {
|
|||
&:hover {
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
align-items: center;
|
||||
.leftside-menu-category_main();
|
||||
box-shadow: @cryptpad_ui_shadow;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.cp-leftside-narrow {
|
||||
|
|
|
@ -80,6 +80,10 @@
|
|||
text-overflow: ellipsis;
|
||||
padding-left: 4px;
|
||||
vertical-align: middle;
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
.close {
|
||||
opacity: 1;
|
||||
|
|
|
@ -29,35 +29,6 @@
|
|||
font: @colortheme_app-font;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 870px) {
|
||||
flex-flow: column;
|
||||
.cp-toolbar-history-actions {
|
||||
width: 100%;
|
||||
.cp-history-actions-first {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: ~"calc(100% - 20px)";
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 500px) {
|
||||
padding-top: 0px;
|
||||
.cp-history-timeline-line {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.cp-history-timeline-actions {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.cp-history-init {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
|
@ -102,8 +73,8 @@
|
|||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.fa:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
i:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
&:hover {
|
||||
background-color: fade(@cp_toolbar-fg, 30%);
|
||||
|
@ -293,6 +264,39 @@
|
|||
left: ~"calc(50% - 6px)";
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 870px) {
|
||||
flex-flow: column;
|
||||
.cp-toolbar-history-actions {
|
||||
width: 100%;
|
||||
margin: 0.6rem 0;
|
||||
height: auto;
|
||||
.cp-history-actions-first {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
button {
|
||||
margin: 0.1rem;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: ~"calc(100% - 20px)";
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 500px) {
|
||||
padding-top: 0px;
|
||||
.cp-history-timeline-line {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.cp-history-timeline-actions {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,8 +77,8 @@
|
|||
&:hover {
|
||||
background-color: contrast(@cp_toolbar-bg, darken(@cp_toolbar-bg, 5%), lighten(@cp_toolbar-bg, 5%));
|
||||
}
|
||||
&:focus {
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
button:nth-of-type(1) {
|
||||
|
@ -373,7 +373,7 @@
|
|||
* {
|
||||
outline-style: none;
|
||||
&:focus-visible {
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,6 +676,10 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border-collapse: collapse;
|
||||
&:focus {
|
||||
margin: 2px;
|
||||
border-radius: @variables_radius;
|
||||
}
|
||||
}
|
||||
input {
|
||||
color: @cryptpad_text_col;
|
||||
|
@ -780,7 +784,7 @@
|
|||
padding: 10px;
|
||||
color: @toolbar-bg-color;
|
||||
color: var(--toolbar-bg-color);
|
||||
border-radius: 5px;
|
||||
border-radius: @variables_radius;
|
||||
|
||||
span {
|
||||
font-size: 45px;
|
||||
|
@ -983,11 +987,8 @@
|
|||
height: @toolbar_line-height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.fa, .cptools {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.cp-dropdown-button-title .cp-icon {
|
||||
margin-left: 5px;
|
||||
i ~ span {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
&:hover {
|
||||
background-color: fade(@cp_toolbar-bottom-bg, 70%);
|
||||
|
@ -1109,15 +1110,6 @@
|
|||
}
|
||||
}
|
||||
.cp-toolbar-bottom-left {
|
||||
.cp-toolbar-appmenu, .cp-toolbar-file {
|
||||
button {
|
||||
&::before, .fa {
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-toolbar-file {
|
||||
order: 1;
|
||||
}
|
||||
|
|
|
@ -115,7 +115,8 @@
|
|||
}
|
||||
}
|
||||
.fa-times {
|
||||
padding-left: 5px;
|
||||
border-radius: @variables_radius;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
line-height: 25px;
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
@variables_shadow: 0 8px 32px 0 @cp_shadow-color;
|
||||
|
||||
// Rounded corners
|
||||
@variables_radius_S: 3px;
|
||||
@variables_radius: 5px;
|
||||
@variables_radius_L: 10px;
|
||||
|
||||
@variables_focus_style: @cryptpad_color_brand solid 2px;
|
||||
|
|
|
@ -15,12 +15,19 @@
|
|||
padding: 20px;
|
||||
}
|
||||
.cp-features-register-button {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background-color: @cp_buttons-primary;
|
||||
color: @cryptpad_text_col;
|
||||
font-size: 20px;
|
||||
color: @cryptpad_color_white;
|
||||
background: @cryptpad_color_brand;
|
||||
border-radius: 0;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
margin: 10px 0;
|
||||
border-radius: @infopages-radius;
|
||||
padding: 10px 20px;
|
||||
&:hover, &:focus {
|
||||
background-color: contrast(@cp_buttons-primary-text, darken(@cp_buttons-primary, 10%), lighten(@cp_buttons-primary, 10%));
|
||||
}
|
||||
&:visited {
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
}
|
||||
.cp-features-web {
|
||||
|
@ -114,5 +121,10 @@
|
|||
margin-top: 3em;
|
||||
}
|
||||
}
|
||||
.cp-premium-user {
|
||||
@media (max-width:575px) {
|
||||
margin-top: 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -255,9 +255,10 @@
|
|||
* {
|
||||
display: block;
|
||||
}
|
||||
button {
|
||||
background-color: @cryptpad_color_link;
|
||||
color: @cryptpad_text_col_inv;
|
||||
.subscribe-btn {
|
||||
text-align: center;
|
||||
background-color: @cp_buttons-primary;
|
||||
color: @cryptpad_text_col;
|
||||
font-size: 1.4rem;
|
||||
margin: 10px 0;
|
||||
border-radius: @infopages-radius-L;
|
||||
|
@ -267,8 +268,7 @@
|
|||
margin-right: 10px;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
background-color: @cp_static-card-bg;
|
||||
color: @cryptpad_text_col;
|
||||
background-color: contrast(@cp_buttons-primary-text, darken(@cp_buttons-primary, 10%), lighten(@cp_buttons-primary, 10%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -297,6 +297,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
.cp-extensions-popups {
|
||||
width: 100%;
|
||||
.cp-extensions-popup {
|
||||
background-color: @cp_alertify-bg;
|
||||
border-radius: @infopages-radius-L;
|
||||
padding: 10px;
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
color: @cryptpad_text_col;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.cp-extensions-popups {
|
||||
max-width: 90%;
|
||||
.cp-extensions-popup {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) and (max-width: 767px) {
|
||||
.container {
|
||||
padding-left: 0;
|
||||
|
|
|
@ -43,13 +43,10 @@
|
|||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
nav .btn-danger {
|
||||
line-height: inherit;
|
||||
align-items: flex-end;
|
||||
.btn-confirm {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,13 +68,10 @@
|
|||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
nav .btn-danger {
|
||||
line-height: inherit;
|
||||
align-items: flex-end;
|
||||
.btn-confirm {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,3 +8,8 @@
|
|||
margin: 3cm;
|
||||
size: A4 portrait;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
background: white !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript></noscript>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/*
|
||||
* You can override the translation text using this file.
|
||||
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
|
||||
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
|
||||
* If you want to check all the existing translation keys, you can open the internal language file
|
||||
but you should not change it directly (/common/translations/messages.{LANG}.js)
|
||||
*/
|
||||
define(['/common/translations/messages.ar.js'], function (Messages) {
|
||||
// Replace the existing keys in your copied file here:
|
||||
// Messages.button_newpad = "New Rich Text Document";
|
||||
|
||||
return Messages;
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/*
|
||||
* You can override the translation text using this file.
|
||||
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
|
||||
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
|
||||
* If you want to check all the existing translation keys, you can open the internal language file
|
||||
but you should not change it directly (/common/translations/messages.{LANG}.js)
|
||||
*/
|
||||
define(['/common/translations/messages.es_CU.js'], function (Messages) {
|
||||
// Replace the existing keys in your copied file here:
|
||||
// Messages.button_newpad = "New Rich Text Document";
|
||||
|
||||
return Messages;
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/*
|
||||
* You can override the translation text using this file.
|
||||
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
|
||||
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
|
||||
* If you want to check all the existing translation keys, you can open the internal language file
|
||||
but you should not change it directly (/common/translations/messages.{LANG}.js)
|
||||
*/
|
||||
define(['/common/translations/messages.id.js'], function (Messages) {
|
||||
// Replace the existing keys in your copied file here:
|
||||
// Messages.button_newpad = "New Rich Text Document";
|
||||
|
||||
return Messages;
|
||||
});
|
||||
|
|
@ -3,11 +3,9 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
---
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
cryptpad:
|
||||
image: "cryptpad/cryptpad:version-2024.3.0"
|
||||
image: "cryptpad/cryptpad:version-2024.6.1"
|
||||
hostname: cryptpad
|
||||
|
||||
environment:
|
||||
|
@ -30,7 +28,6 @@ services:
|
|||
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
- "3003:3003"
|
||||
|
||||
ulimits:
|
||||
|
|
|
@ -22,16 +22,16 @@ if [ ! -f "$CPAD_CONF" ]; then
|
|||
eg: docker run -v /path/to/config.js:/cryptpad/config/config.js \n\
|
||||
#################################################################### \n"
|
||||
|
||||
cp "$CPAD_HOME"/config/config.example.js "$CPAD_CONF"
|
||||
cp "$CPAD_HOME"/config/config.example.js "$CPAD_CONF"
|
||||
|
||||
sed -i -e "s@\(httpUnsafeOrigin:\).*[^,]@\1 '$CPAD_MAIN_DOMAIN'@" \
|
||||
sed -i -e "s@\(httpUnsafeOrigin:\).*[^,]@\1 '$CPAD_MAIN_DOMAIN'@" \
|
||||
-e "s@\(^ *\).*\(httpSafeOrigin:\).*[^,]@\1\2 '$CPAD_SANDBOX_DOMAIN'@" "$CPAD_CONF"
|
||||
fi
|
||||
|
||||
cd $CPAD_HOME
|
||||
|
||||
if [ "$CPAD_INSTALL_ONLYOFFICE" == "yes" ]; then
|
||||
./install-onlyoffice.sh --accept-license
|
||||
./install-onlyoffice.sh --accept-license --trust-repository
|
||||
fi
|
||||
|
||||
npm run build
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
# Community configuration files
|
||||
This folder is listing community-contributed configuration files. They **aren't** supported and are provided as-is, without any warranty.
|
||||
|
||||
If you are using CryptPad in production and require professional support please contact sales@cryptpad.org
|
|
@ -0,0 +1,245 @@
|
|||
# SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# This file is included strictly as an example of how Caddy can be configured
|
||||
# to work with CryptPad. This example WILL NOT WORK AS IS. For best results,
|
||||
# compare the sections of this configuration file against a working CryptPad
|
||||
# installation (http server by the Nodejs process). If you are using CryptPad
|
||||
# in production and require professional support please contact sales@cryptpad.org
|
||||
|
||||
(trustedProxies) {
|
||||
# Force Caddy to accept `X-Forwarded-For` and other origin headers.
|
||||
# Modify the line below if you want to restrict the scope of direct downstream sending these headers.
|
||||
trusted_proxies 0.0.0.0/0 ::/0
|
||||
}
|
||||
|
||||
# Caddy does not have variables for server names, so domains need to be hardcoded.
|
||||
# You can bulk replace "your-main-domain.com" and "your-sandbox-domain.com" safely.
|
||||
your-main-domain.com:443,
|
||||
your-sandbox-domain.com:443 {
|
||||
# Define your certificates below.
|
||||
# No need to adjust TLS configurations, as the defaults in Caddy are already secure.
|
||||
tls /path/to/fullchain/publicKey.pem /path/to/certificate/privateKey.pem
|
||||
|
||||
# Enable HSTS.
|
||||
# Do not enable this line when configuring over mixnet, e.g. Tor.
|
||||
header Strict-Transport-Security "max-age=63072000; includeSubDomains"
|
||||
|
||||
# Security headers
|
||||
header X-XSS-Protection "1; mode=block"
|
||||
header X-Content-Type-Options "nosniff"
|
||||
header Access-Control-Allow-Credentials "true"
|
||||
#header X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
# OnlyOffice fonts may be loaded from both domains.
|
||||
@onlyOfficeFonts {
|
||||
path_regexp "^\\/common\\/onlyoffice\\/.*\\/fonts\\/.*$"
|
||||
}
|
||||
header Access-Control-Allow-Origin "*"
|
||||
|
||||
# By default CryptPad forbids remote domains from embedding CryptPad documents in iframes.
|
||||
# The sandbox domain must always be permitted in order for the platform to function.
|
||||
# If you wish to enable remote embedding you may change the value below to "*"
|
||||
# as per the commented value.
|
||||
header ?Access-Control-Allow-Origin "https://your-sandbox-domain.com"
|
||||
#header ?Access-Control-Allow-Origin "*"
|
||||
|
||||
# Opt out of Google's FLoC Network
|
||||
header Permissions-Policy "interest-cohort=()"
|
||||
|
||||
# Enable SharedArrayBuffer in Firefox (for .xlsx export)
|
||||
header ?Cross-Origin-Resource-Policy "cross-origin"
|
||||
header ?Cross-Origin-Embedder-Policy "require-corp"
|
||||
|
||||
# Specify the relative path to root of your custom error page.
|
||||
# This error page won't only be served for 404 errors.
|
||||
handle_errors {
|
||||
rewrite * /error.htm
|
||||
header Cache-Control "no-cache, no-store"
|
||||
file_server
|
||||
templates
|
||||
}
|
||||
|
||||
# Insert the path to your CryptPad repository root here
|
||||
root /home/cryptpad/cryptpad
|
||||
|
||||
# Any static assets loaded with "vers=" in their URL will be cached for a year
|
||||
@staticAssets {
|
||||
query "ver=*"
|
||||
}
|
||||
header @staticAssets Cache-Control "max-age=31536000"
|
||||
|
||||
vars {
|
||||
# CSS can be dynamically set inline, loaded from the same domain, or from your main domain.
|
||||
styleSrc "'unsafe-inline' 'self' https://your-main-domain.com"
|
||||
|
||||
# connect-src restricts URLs which can be loaded using script interfaces.
|
||||
# If you have configured your instance to use a dedicated file delivery domain or API domain,
|
||||
# you will need to add them below.
|
||||
connectSrc "'self' https://your-main-domain.com blob: wss://api.your-main-domain.com https://your-sandbox-domain.com"
|
||||
|
||||
# Fonts can be loaded from data-URLs or the main domain.
|
||||
fontSrc "'self' data: https://your-main-domain.com"
|
||||
|
||||
# Images can be loaded from anywhere, though we'd like to deprecate this as it allows
|
||||
# the use of images for tracking.
|
||||
imgSrc "'self' data: blob: https://your-main-domain.com"
|
||||
|
||||
# frame-src specifies valid sources for nested browsing contexts.
|
||||
# This prevents loading any iframes from anywhere other than the sandbox domain.
|
||||
frameSrc "'self' https://your-sandbox-domain.com blob:"
|
||||
|
||||
# media-src specifies valid sources for loading media using video or audio.
|
||||
mediaSrc "blob:"
|
||||
|
||||
# child-src defines valid sources for webworkers and nested browser contexts.
|
||||
# It is deprecated in favour of worker-src and frame-src.
|
||||
childSrc "https://your-main-domain.com"
|
||||
|
||||
# worker-src valid sources for Worker, Shared Worker, or Service Worker scripts.
|
||||
# Supercedes child-src, but is unfortunately not yet universally supported.
|
||||
workerSrc "'self'"
|
||||
|
||||
# script-src specifies valid sources for JavaScript, including inline handlers.
|
||||
scriptSrc "'self' resource: https://your-main-domain.com"
|
||||
|
||||
# frame-ancestors specifies which origins can embed your CryptPad instance.
|
||||
# This must include 'self' and your main domain (over HTTPS) in order for CryptPad to work,
|
||||
# if you have enabled remote embedding via the admin panel, then this must be more permissive.
|
||||
# Note: cryptpad.fr permits web pages served via https: and vector: (element desktop app)
|
||||
frameAncestors "'self' https://your-main-domain.com"
|
||||
#frameAncestors "'self' https: vector:"
|
||||
|
||||
# A few assets are loaded via the sandbox domain.
|
||||
# They unfortunately still require exceptions to the sandboxing to work correctly.
|
||||
# Everything except the sandbox domain is a privileged scope, as they might be used to handle keys.
|
||||
# Unsafe iframes are exceptions. Office file formats are converted outside of the sandboxed scope,
|
||||
# because of bugs in Chromium-based browsers that incorrectly ignore headers supposed to enable
|
||||
# the use of some modern APIs, that are required when JavaScript is run in a cross-origin context.
|
||||
# We've applied other sandboxing techniques to mitigate the risk of running WebAssembly
|
||||
# in this privileged scope.
|
||||
# Privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied.
|
||||
scriptSrcUnsafe "'self' 'unsafe-eval' 'unsafe-inline' resource: https://your-main-domain.com"
|
||||
}
|
||||
|
||||
# Finally, set all the security rules you have composed above.
|
||||
@privilegedScope1 {
|
||||
host "your-sandbox-domain.com"
|
||||
path_regexp "^\\/(sheet|doc|presentation)\\/inner.html.*$"
|
||||
}
|
||||
@privilegedScope2 {
|
||||
host "your-sandbox-domain.com"
|
||||
path_regexp "^\\/common\\/onlyoffice\\/.*\\/.*\\.html.*$"
|
||||
}
|
||||
@privilegedScope3 {
|
||||
host "your-sandbox-domain.com"
|
||||
path_regexp "^\\/unsafeiframe\\/inner\\.html.*$"
|
||||
}
|
||||
header @privilegedScope1 Content-Security-Policy "default-src 'none'; child-src {vars.childSrc}; worker-src {vars.workerSrc}; media-src {vars.mediaSrc}; style-src {vars.styleSrc}; script-src {vars.scriptSrcUnsafe}; connect-src {vars.connectSrc}; font-src {vars.fontSrc}; img-src {vars.imgSrc}; frame-src {vars.frameSrc}; frame-ancestors {vars.frameAncestors}"
|
||||
header @privilegedScope2 Content-Security-Policy "default-src 'none'; child-src {vars.childSrc}; worker-src {vars.workerSrc}; media-src {vars.mediaSrc}; style-src {vars.styleSrc}; script-src {vars.scriptSrcUnsafe}; connect-src {vars.connectSrc}; font-src {vars.fontSrc}; img-src {vars.imgSrc}; frame-src {vars.frameSrc}; frame-ancestors {vars.frameAncestors}"
|
||||
header @privilegedScope3 Content-Security-Policy "default-src 'none'; child-src {vars.childSrc}; worker-src {vars.workerSrc}; media-src {vars.mediaSrc}; style-src {vars.styleSrc}; script-src {vars.scriptSrcUnsafe}; connect-src {vars.connectSrc}; font-src {vars.fontSrc}; img-src {vars.imgSrc}; frame-src {vars.frameSrc}; frame-ancestors {vars.frameAncestors}"
|
||||
header ?Content-Security-Policy "default-src 'none'; child-src {vars.childSrc}; worker-src {vars.workerSrc}; media-src {vars.mediaSrc}; style-src {vars.styleSrc}; script-src {vars.scriptSrc}; connect-src {vars.connectSrc}; font-src {vars.fontSrc}; img-src {vars.imgSrc}; frame-src {vars.frameSrc}; frame-ancestors {vars.frameAncestors}"
|
||||
|
||||
# Add support for .mjs files used by pdfjs
|
||||
@fileModuleJS {
|
||||
path "*.mjs"
|
||||
}
|
||||
header @fileModuleJS Content-Type "application/javascript"
|
||||
|
||||
# The Node.js process can handle all traffic, whether accessed over websocket or as static assets.
|
||||
# We prefer to serve static content from Caddy directly, and to leave the API server to handle the
|
||||
# the dynamic content that only it can manage. This is primarily for optimization.
|
||||
handle /cryptpad_websocket/* {
|
||||
reverse_proxy * {
|
||||
to 127.0.0.1:3003
|
||||
header_up Host "{host}"
|
||||
header_up X-Real-IP "{remote_host}"
|
||||
|
||||
# Caddy supports WebSockets directly. No additional headers are needed.
|
||||
|
||||
import trustedProxies
|
||||
}
|
||||
}
|
||||
|
||||
handle_path /customize.dist/* {
|
||||
# This is needed in order to prevent infinite recursion between /customize/ and the root.
|
||||
}
|
||||
|
||||
# Try to load customizeable content via /customize/ and fall back to the default content located
|
||||
# at /customize.dist/ .
|
||||
# This is what allows you to override behaviour.
|
||||
handle_path /customize/* {
|
||||
try_files /customize/{path} /customize.dist/{path}
|
||||
file_server {
|
||||
index index.html index.htm default.html default.htm
|
||||
}
|
||||
}
|
||||
|
||||
# /api/config is loaded once per page load, and is used to retrieve the caching variable,
|
||||
# which is applied to every other resource loaded during that session.
|
||||
@sharedReverseProxy {
|
||||
path /api/*
|
||||
path /extensions.js
|
||||
}
|
||||
handle @sharedReverseProxy {
|
||||
reverse_proxy * {
|
||||
to 127.0.0.1:3000
|
||||
header_up Host "{host}"
|
||||
header_up X-Real-IP "{remote_host}"
|
||||
|
||||
# These settings prevent both Caddy and the API server from setting duplicate headers.
|
||||
header_down Cross-Origin-Resource-Policy cross-origin
|
||||
header_down Cross-Origin-Embedder-Policy require-corp
|
||||
|
||||
import trustedProxies
|
||||
}
|
||||
}
|
||||
|
||||
# Requests for blobs and blocks are now proxied to the API server.
|
||||
# This simplifies Caddy path configuration, in the event they are being hosted in a non-standard location
|
||||
# or with odd unexpected permissions. Serving blobs in this manner also means that it will be possible to
|
||||
# enforce access control for them, though this is not yet implemented.
|
||||
# Access control (via TOTP 2FA) has been added to blocks, so they can be handled with the same directives.
|
||||
@blobsAndBlocks {
|
||||
path /blob/*
|
||||
path /block/*
|
||||
}
|
||||
handle @blobsAndBlocks {
|
||||
@corsPreflight {
|
||||
method OPTIONS
|
||||
}
|
||||
handle @corsPreflight {
|
||||
header Access-Control-Allow-Origin "https://your-sandbox-domain.com"
|
||||
header Access-Control-Allow-Credentials "true"
|
||||
header Access-Control-Allow-Methods "GET, POST, OPTIONS"
|
||||
header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range"
|
||||
header Access-Control-Max-Age "1728000"
|
||||
header Content-Type "application/octet-stream; charset=utf-8"
|
||||
header Content-Length "0"
|
||||
respond 204
|
||||
}
|
||||
reverse_proxy * {
|
||||
to 127.0.0.1:3000
|
||||
# Preventing these headers from getting duplicated, since we are proxying to the API server.
|
||||
header_down -X-Content-Type-Options
|
||||
header_down -Access-Control-Allow-Origin
|
||||
header_down -Permissions-Policy
|
||||
header_down -X-XSS-Protection
|
||||
header_down -Cross-Origin-Resource-Policy
|
||||
header_down -Cross-Origin-Embedder-Policy
|
||||
}
|
||||
}
|
||||
|
||||
# The Node.JS server has some built-in forwarding rulesets to prevent URLs not suffixed with a slash
|
||||
# from resulting in a 404 error. This simply adds a trailing slash to a variety of applications.
|
||||
@preventNotFound {
|
||||
path_regexp "^/(register|login|recovery|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams|calendar|presentation|doc|form|report|convert|checkup|diagram)$"
|
||||
}
|
||||
redir @preventNotFound "{path}/"
|
||||
|
||||
# Enable file serving
|
||||
file_server {
|
||||
index index.html index.htm default.html default.htm
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
# SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# This file is included strictly as an example of how Caddy can be configured
|
||||
# to work with CryptPad. This example WILL NOT WORK AS IS. For best results,
|
||||
# compare the sections of this configuration file against a working CryptPad
|
||||
# installation (http server by the Nodejs process). If you are using CryptPad
|
||||
# in production and require professional support please contact sales@cryptpad.org
|
||||
|
||||
(trustedProxies) {
|
||||
# Force Caddy to accept `X-Forwarded-For` and other origin headers.
|
||||
# Modify the line below if you want to restrict the scope of direct downstream sending these headers.
|
||||
trusted_proxies 0.0.0.0/0 ::/0
|
||||
}
|
||||
|
||||
# Caddy does not have variables for server names, so domains need to be hardcoded.
|
||||
# You can bulk replace "your-main-domain.com" and "your-sandbox-domain.com" safely.
|
||||
your-main-domain.com:443,
|
||||
your-sandbox-domain.com:443 {
|
||||
# Define your certificates below.
|
||||
# No need to adjust TLS configurations, as the defaults in Caddy are already secure.
|
||||
tls /path/to/fullchain/publicKey.pem /path/to/certificate/privateKey.pem
|
||||
|
||||
# Enable HSTS.
|
||||
# Do not enable this line when configuring over mixnet, e.g. Tor.
|
||||
header Strict-Transport-Security "max-age=63072000; includeSubDomains"
|
||||
|
||||
# Specify the relative path to root of your custom error page.
|
||||
# This error page won't only be served for 404 errors.
|
||||
handle_errors {
|
||||
rewrite * /error.htm
|
||||
header Cache-Control "no-cache, no-store"
|
||||
file_server
|
||||
templates
|
||||
}
|
||||
|
||||
# The Node.js process can handle all traffic, whether accessed over websocket or as static assets.
|
||||
reverse_proxy /cryptpad_websocket/* {
|
||||
to 127.0.0.1:3003
|
||||
header_up Host "{host}"
|
||||
header_up X-Real-IP "{remote_host}"
|
||||
|
||||
# Caddy supports WebSockets directly. No additional headers are needed.
|
||||
|
||||
import trustedProxies
|
||||
}
|
||||
reverse_proxy * {
|
||||
to 127.0.0.1:3000
|
||||
header_up Host "{host}"
|
||||
header_up X-Real-IP "{remote_host}"
|
||||
|
||||
# These settings prevent both Caddy and the API server from setting duplicate headers.
|
||||
header_down Cross-Origin-Resource-Policy cross-origin
|
||||
header_down Cross-Origin-Embedder-Policy require-corp
|
||||
|
||||
import trustedProxies
|
||||
}
|
||||
|
||||
# Enable file serving
|
||||
file_server {
|
||||
index index.html index.htm default.html default.htm
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
# This file is included strictly as an example of how Apache httpd can be
|
||||
# configured to work with CryptPad. If you are using CryptPad in production
|
||||
# and require professional support please contact sales@cryptpad.fr
|
||||
# and require professional support please contact sales@cryptpad.org
|
||||
|
||||
# This configuration requires mod_ssl, mod_socache_shmcb, mod_proxy,
|
||||
# mod_proxy_http and mod_headers
|
||||
|
@ -33,8 +33,14 @@ SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
|
|||
nokeepalive ssl-unclean-shutdown \
|
||||
downgrade-1.0 force-response-1.0
|
||||
Protocols h2 http/1.1
|
||||
LimitRequestBody 157286400
|
||||
AddType application/javascript mjs
|
||||
ProxyPass / http://localhost:3000/ upgrade=websocket
|
||||
ProxyPassReverse / http://localhost:3000/
|
||||
<Location "/">
|
||||
LimitRequestBody 157286400
|
||||
ProxyPass http://localhost:3000/ upgrade=websocket
|
||||
ProxyPassReverse http://localhost:3000/
|
||||
</Location>
|
||||
<Location "/cryptpad_websocket">
|
||||
ProxyPass http://localhost:3003/ upgrade=websocket
|
||||
ProxyPassReverse http://localhost:3003/
|
||||
</Location>
|
||||
</VirtualHost>
|
|
@ -8,6 +8,21 @@
|
|||
# installation (http server by the Nodejs process). If you are using CryptPad
|
||||
# in production and require professional support please contact sales@cryptpad.fr
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name your-main-domain.com your-sandbox-domain.com;
|
||||
|
||||
access_log /dev/null;
|
||||
error_log /dev/null emerg;
|
||||
|
||||
# Let's Encrypt webroot
|
||||
include letsencrypt-webroot;
|
||||
|
||||
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
@ -234,6 +249,20 @@ server {
|
|||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
}
|
||||
|
||||
location ~ ^/extensions.js {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# These settings prevent both NGINX and the API server
|
||||
# from setting the same headers and creating duplicates
|
||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||
add_header Cross-Origin-Resource-Policy cross-origin;
|
||||
proxy_hide_header Cross-Origin-Embedder-Policy;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
}
|
||||
|
||||
# Requests for blobs and blocks are now proxied to the API server
|
||||
# This simplifies NGINX path configuration in the event they are being hosted in a non-standard location
|
||||
# or with odd unexpected permissions. Serving blobs in this manner also means that it will be possible to
|
||||
|
|
|
@ -8,6 +8,21 @@
|
|||
# installation (http server by the Nodejs process). If you are using CryptPad
|
||||
# in production and require professional support please contact sales@cryptpad.fr
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name your-main-domain.com your-sandbox-domain.com;
|
||||
|
||||
access_log /dev/null;
|
||||
error_log /dev/null emerg;
|
||||
|
||||
# Let's Encrypt webroot
|
||||
include letsencrypt-webroot;
|
||||
|
||||
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
@ -71,4 +86,15 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ main() {
|
|||
install_version v4 6ebc6938
|
||||
install_version v5 88a356f0
|
||||
install_version v6 abd8a309
|
||||
install_version v7 ba82142f
|
||||
install_version v7 e1267803
|
||||
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
|
||||
|
||||
rm -rf "$BUILDS_DIR"
|
||||
|
@ -69,6 +69,10 @@ parse_arguments() {
|
|||
ACCEPT_LICENSE="1"
|
||||
shift
|
||||
;;
|
||||
-t | --trust-repository)
|
||||
TRUST_REPOSITORY="1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
show_help
|
||||
shift
|
||||
|
@ -110,17 +114,26 @@ OPTIONS:
|
|||
Accept the license of OnlyOffice and do not ask when running this
|
||||
script. Read and accept this before using this option:
|
||||
https://github.com/ONLYOFFICE/web-apps/blob/master/LICENSE.txt
|
||||
|
||||
-t, --trust-repository
|
||||
Automatically configure the cloned onlyoffice-builds repository
|
||||
as a safe.directory.
|
||||
https://git-scm.com/docs/git-config/#Documentation/git-config.txt-safedirectory
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_oo_is_downloaded() {
|
||||
ensure_command_available git
|
||||
ensure_command_available git
|
||||
|
||||
if ! [ -d "$BUILDS_DIR" ]; then
|
||||
echo "Downloading OnlyOffice..."
|
||||
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
|
||||
fi
|
||||
if ! [ -d "$BUILDS_DIR" ]; then
|
||||
echo "Downloading OnlyOffice..."
|
||||
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
|
||||
fi
|
||||
if [ ${TRUST_REPOSITORY+x} ] || [ "${PROPS[trust_repository]:-no}" == yes ]; then
|
||||
git config --global --add safe.directory /cryptpad/onlyoffice-conf/onlyoffice-builds.git
|
||||
fi
|
||||
}
|
||||
|
||||
install_version() {
|
||||
|
|
113
lib/api.js
113
lib/api.js
|
@ -8,8 +8,10 @@ const Decrees = require("./decrees");
|
|||
|
||||
const nThen = require("nthen");
|
||||
const Fs = require("fs");
|
||||
const Fse = require("fs-extra");
|
||||
const Path = require("path");
|
||||
const Nacl = require("tweetnacl/nacl-fast");
|
||||
const Hash = require('./common-hash');
|
||||
|
||||
module.exports.create = function (Env) {
|
||||
var log = Env.Log;
|
||||
|
@ -25,6 +27,41 @@ nThen(function (w) {
|
|||
console.error(err);
|
||||
}
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
let admins = Env.admins || [];
|
||||
|
||||
// If we don't have any admin on this instance, print an onboarding link
|
||||
if (Array.isArray(admins) && admins.length) { return; }
|
||||
let token = Env.installToken;
|
||||
let printLink = () => {
|
||||
let url = `${Env.httpUnsafeOrigin}/install/#${token}`;
|
||||
console.log('=============================');
|
||||
console.log('Create your first admin account and customize your instance by visiting');
|
||||
console.log(url);
|
||||
console.log('=============================');
|
||||
|
||||
};
|
||||
|
||||
// If we already have a token, print it
|
||||
if (token) { return void printLink(); }
|
||||
|
||||
// Otherwise create a new token
|
||||
let decreeName = Path.join(Env.paths.decree, 'decree.ndjson');
|
||||
token = Hash.createChannelId() + Hash.createChannelId();
|
||||
let decree = ["ADD_INSTALL_TOKEN",[token],"",+new Date()];
|
||||
Fs.appendFile(decreeName, JSON.stringify(decree) + '\n', w(function (err) {
|
||||
if (err) { console.log(err); return; }
|
||||
Env.installToken = token;
|
||||
Env.envUpdated.fire();
|
||||
printLink();
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (!Env.admins.length) {
|
||||
Env.Log.info('NO_ADMIN_CONFIGURED', {
|
||||
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
|
||||
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
|
||||
});
|
||||
}
|
||||
}).nThen(function (w) {
|
||||
// we assume the server has generated a secret used to validate JWT tokens
|
||||
if (typeof(Env.bearerSecret) === 'string') { return; }
|
||||
|
@ -40,9 +77,19 @@ nThen(function (w) {
|
|||
], w(function (err) {
|
||||
if (err) { throw err; }
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
Fse.mkdirp(Env.paths.block, w(function (err) {
|
||||
if (err) {
|
||||
log.error("BLOCK_FOLDER_CREATE_FAILED", err);
|
||||
}
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
var fullPath = Path.join(Env.paths.block, 'placeholder.txt');
|
||||
Fs.writeFile(fullPath, 'PLACEHOLDER\n', w());
|
||||
Fs.writeFile(fullPath, 'PLACEHOLDER\n', w(function (err) {
|
||||
if (err) {
|
||||
log.error('BLOCK_PLACEHOLDER_CREATE_FAILED', err);
|
||||
}
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// asynchronously create a historyKeeper and RPC together
|
||||
require('./historyKeeper.js').create(Env, function (err, historyKeeper) {
|
||||
|
@ -61,7 +108,7 @@ nThen(function (w) {
|
|||
};
|
||||
|
||||
// spawn ws server and attach netflux event handlers
|
||||
NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
|
||||
let Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
|
||||
.on('channelClose', historyKeeper.channelClose)
|
||||
.on('channelMessage', historyKeeper.channelMessage)
|
||||
.on('channelOpen', historyKeeper.channelOpen)
|
||||
|
@ -90,6 +137,68 @@ nThen(function (w) {
|
|||
});
|
||||
})
|
||||
.register(historyKeeper.id, historyKeeper.directMessage);
|
||||
// Store max active WS during the last day (reset when sending ping if enabled)
|
||||
setInterval(() => {
|
||||
try {
|
||||
// Concurrent usage data
|
||||
let oldWs = Env.maxConcurrentWs || 0;
|
||||
let oldUniqueWs = Env.maxConcurrentUniqueWs || 0;
|
||||
let oldChans = Env.maxActiveChannels || 0;
|
||||
let oldUsers = Env.maxConcurrentRegUsers || 0;
|
||||
let stats = Server.getSessionStats();
|
||||
let chans = Server.getActiveChannelCount();
|
||||
let reg = 0;
|
||||
let regKeys = [];
|
||||
Object.keys(Env.netfluxUsers).forEach(id => {
|
||||
let keys = Env.netfluxUsers[id];
|
||||
let key = Object.keys(keys || {})[0];
|
||||
if (!key) { return; }
|
||||
if (regKeys.includes(key)) { return; }
|
||||
reg++;
|
||||
regKeys.push(key);
|
||||
});
|
||||
Env.maxConcurrentWs = Math.max(oldWs, stats.total);
|
||||
Env.maxConcurrentUniqueWs = Math.max(oldUniqueWs, stats.unique);
|
||||
Env.maxConcurrentRegUsers = Math.max(oldUsers, reg);
|
||||
Env.maxActiveChannels = Math.max(oldChans, chans);
|
||||
} catch (e) {}
|
||||
}, 10000);
|
||||
// Clean up active registered users and channels (possible memory leak)
|
||||
setInterval(() => {
|
||||
try {
|
||||
let users = Env.netfluxUsers || {};
|
||||
let online = Server.getOnlineUsers() || [];
|
||||
let removed = 0;
|
||||
Object.keys(users).forEach(id => {
|
||||
if (!online.includes(id)) {
|
||||
delete users[id];
|
||||
removed++;
|
||||
}
|
||||
});
|
||||
if (removed) {
|
||||
Env.Log.info("CLEANED_ACTIVE_USERS_MAP", {removed});
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
let HK = require('./hk-utils');
|
||||
let chans = Env.channel_cache || {};
|
||||
let active = Server.getActiveChannels() || [];
|
||||
let removed = 0;
|
||||
Object.keys(chans).forEach(id => {
|
||||
if (!active.includes(id)) {
|
||||
HK.dropChannel(Env, id);
|
||||
removed++;
|
||||
}
|
||||
});
|
||||
if (Env.store) {
|
||||
Env.store.closeInactiveChannels(active);
|
||||
}
|
||||
if (removed) {
|
||||
Env.Log.info("CLEANED_ACTIVE_CHANNELS_MAP", {removed});
|
||||
}
|
||||
} catch (e) {}
|
||||
}, 30000);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ var shutdown = function (Env, Server, cb) {
|
|||
// and allow system functionality to restart the server
|
||||
};
|
||||
|
||||
var getRegisteredUsers = function (Env, Server, cb) {
|
||||
var getRegisteredUsers = Admin.getRegisteredUsers = function (Env, Server, cb) {
|
||||
Env.batchRegisteredUsers('', cb, function (done) {
|
||||
var dir = Env.paths.pin;
|
||||
var folders;
|
||||
|
@ -119,11 +119,15 @@ var getRegisteredUsers = function (Env, Server, cb) {
|
|||
var dir = Env.paths.pin + '/' + f;
|
||||
Fs.readdir(dir, waitFor(function (err, list) {
|
||||
if (err) { return; }
|
||||
// Don't count placeholders
|
||||
list = list.filter(name => {
|
||||
return !/\.placeholder$/.test(name);
|
||||
});
|
||||
users += list.length;
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
done(void 0, users);
|
||||
done(void 0, {users});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -466,6 +470,8 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
|
|||
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
|
||||
var instanceStatus = function (Env, Server, cb) {
|
||||
cb(void 0, {
|
||||
|
||||
appsToDisable: Env.appsToDisable,
|
||||
restrictRegistration: Env.restrictRegistration,
|
||||
restrictSsoRegistration: Env.restrictSsoRegistration,
|
||||
dontStoreSSOUsers: Env.dontStoreSSOUsers,
|
||||
|
|
|
@ -10,6 +10,8 @@ const Https = require("https");
|
|||
const Http = require("http");
|
||||
const Util = require("../common-util");
|
||||
const Stats = require("../stats");
|
||||
const Admin = require("./admin-rpc.js");
|
||||
const nThen = require('nthen');
|
||||
|
||||
var validLimitFields = ['limit', 'plan', 'note', 'users', 'origin'];
|
||||
|
||||
|
@ -111,45 +113,83 @@ var queryAccountServer = function (Env, cb) {
|
|||
var done = Util.once(Util.mkAsync(cb));
|
||||
|
||||
var rawBody = Stats.instanceData(Env);
|
||||
Env.Log.info("SERVER_TELEMETRY", rawBody);
|
||||
var body = JSON.stringify(rawBody);
|
||||
|
||||
var options = {
|
||||
host: 'accounts.cryptpad.fr',
|
||||
path: '/api/getauthorized',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(body)
|
||||
}
|
||||
let send = () => {
|
||||
Env.Log.info("SERVER_TELEMETRY", rawBody);
|
||||
var body = JSON.stringify(rawBody);
|
||||
|
||||
var options = {
|
||||
host: 'accounts.cryptpad.fr',
|
||||
path: '/api/getauthorized',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(body)
|
||||
}
|
||||
};
|
||||
|
||||
var req = Https.request(options, function (response) {
|
||||
if (!('' + response.statusCode).match(/^2\d\d$/)) {
|
||||
return void cb('SERVER ERROR ' + response.statusCode);
|
||||
}
|
||||
var str = '';
|
||||
|
||||
response.on('data', function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
|
||||
response.on('end', function () {
|
||||
try {
|
||||
var json = JSON.parse(str);
|
||||
checkUpdateAvailability(Env, json);
|
||||
done(void 0, json);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
req.end(body);
|
||||
};
|
||||
|
||||
var req = Https.request(options, function (response) {
|
||||
if (!('' + response.statusCode).match(/^2\d\d$/)) {
|
||||
return void cb('SERVER ERROR ' + response.statusCode);
|
||||
}
|
||||
var str = '';
|
||||
|
||||
response.on('data', function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
|
||||
response.on('end', function () {
|
||||
try {
|
||||
var json = JSON.parse(str);
|
||||
checkUpdateAvailability(Env, json);
|
||||
done(void 0, json);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
if (Env.provideAggregateStatistics) {
|
||||
let stats = {};
|
||||
nThen(waitFor => {
|
||||
Admin.getRegisteredUsers(Env, null, waitFor((err, data) => {
|
||||
if (err) { return; }
|
||||
stats.registered = data.users;
|
||||
if (Env.lastPingRegisteredUsers) {
|
||||
stats.usersDiff = stats.registered - Env.lastPingRegisteredUsers;
|
||||
}
|
||||
Env.lastPingRegisteredUsers = stats.registered;
|
||||
}));
|
||||
}).nThen(() => {
|
||||
if (Env.maxConcurrentWs) {
|
||||
stats.maxConcurrentWs = Env.maxConcurrentWs;
|
||||
Env.maxConcurrentWs = 0;
|
||||
}
|
||||
if (Env.maxConcurrentUniqueWs) {
|
||||
stats.maxConcurrentUniqueIPs = Env.maxConcurrentUniqueWs;
|
||||
Env.maxConcurrentUniqueWs = 0;
|
||||
}
|
||||
if (Env.maxConcurrentRegUsers) {
|
||||
stats.maxConcurrentRegUsers = Env.maxConcurrentRegUsers;
|
||||
Env.maxConcurrentRegUsers = 0;
|
||||
}
|
||||
if (Env.maxActiveChannels) {
|
||||
stats.maxConcurrentChannels = Env.maxActiveChannels;
|
||||
Env.maxActiveChannels = 0;
|
||||
}
|
||||
rawBody.statistics = stats;
|
||||
send();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
req.end(body);
|
||||
return;
|
||||
}
|
||||
send();
|
||||
};
|
||||
Quota.shouldContactServer = function (Env) {
|
||||
return !(Env.blockDailyCheck === true ||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
module.exports = require("../www/common/common-hash");
|
||||
|
|
@ -220,6 +220,15 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
|
|||
return args_isString(args) && Core.isValidPublicKey(args[0]);
|
||||
});
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_KEYS', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA=", "Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
|
||||
|
||||
|
||||
commands.DISABLE_APPS = function (Env, args) {
|
||||
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
|
||||
if (JSON.stringify(args) === JSON.stringify(Env.appsToDisable)) { return false; }
|
||||
Env.appsToDisable = args;
|
||||
return true;
|
||||
};
|
||||
|
||||
commands.SET_SUPPORT_KEYS = function (Env, args) {
|
||||
const curvePublic = args[0]; // Support mailbox key
|
||||
const edPublic = args[1]; // Support pin log
|
||||
|
@ -232,7 +241,7 @@ commands.SET_SUPPORT_KEYS = function (Env, args) {
|
|||
Env.supportMailboxKey = curvePublic;
|
||||
Env.supportPinKey = edPublic;
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
|
||||
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
|
||||
|
@ -290,7 +299,7 @@ var args_isMaintenance = function (args) {
|
|||
// whenever that happens we can relax validation a bit to support more formats
|
||||
var makeBroadcastSetter = function (attr, validation) {
|
||||
return function (Env, args) {
|
||||
if ((validation && !validation(args)) && !args_isString(args)) {
|
||||
if ((validation && !validation(args)) && !args_isString(args)) {
|
||||
throw new Error('INVALID_ARGS');
|
||||
}
|
||||
var str = args[0];
|
||||
|
|
|
@ -146,6 +146,7 @@ const dropChannel = HK.dropChannel = function (Env, chanName) {
|
|||
expireChannel(Env, chanName);
|
||||
}, TEMPORARY_CHANNEL_LIFETIME);
|
||||
}
|
||||
if (Env.store) { Env.store.closeChannel(chanName, function () {}); }
|
||||
};
|
||||
|
||||
/* checkExpired
|
||||
|
|
|
@ -595,6 +595,7 @@ var serveConfig = makeRouteCache(function () {
|
|||
maxUploadSize: Env.maxUploadSize,
|
||||
premiumUploadSize: Env.premiumUploadSize,
|
||||
restrictRegistration: Env.restrictRegistration,
|
||||
appsToDisable: Env.appsToDisable,
|
||||
restrictSsoRegistration: Env.restrictSsoRegistration,
|
||||
httpSafeOrigin: Env.httpSafeOrigin,
|
||||
enableEmbedding: Env.enableEmbedding,
|
||||
|
@ -630,6 +631,35 @@ var serveBroadcast = makeRouteCache(function () {
|
|||
app.get('/api/config', serveConfig);
|
||||
app.get('/api/broadcast', serveBroadcast);
|
||||
|
||||
(function () {
|
||||
let extensions = plugins._extensions;
|
||||
let styles = plugins._styles;
|
||||
let str = JSON.stringify(extensions);
|
||||
let str2 = JSON.stringify(styles);
|
||||
let js = `let extensions = ${str};
|
||||
let styles = ${str2};
|
||||
let lang = window.cryptpadLanguage;
|
||||
let paths = [];
|
||||
extensions.forEach(name => {
|
||||
paths.push(\`optional!/\${name}/extensions.js\`);
|
||||
paths.push(\`optional!json!/\${name}/translations/messages.json\`);
|
||||
paths.push(\`optional!json!/\${name}/translations/messages.\${lang}.json\`);
|
||||
});
|
||||
styles.forEach(name => {
|
||||
paths.push(\`optional!less!/\${name}/style.less\`);
|
||||
});
|
||||
define(paths, function () {
|
||||
let args = Array.prototype.slice.apply(arguments);
|
||||
return args;
|
||||
}, function () {
|
||||
// ignore missing files
|
||||
});`;
|
||||
app.get('/extensions.js', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.send(js);
|
||||
});
|
||||
})();
|
||||
|
||||
var Define = function (obj) {
|
||||
return `define(function (){
|
||||
return ${JSON.stringify(obj, null, '\t')};
|
||||
|
@ -723,7 +753,7 @@ app.post('/api/auth', function (req, res, next) {
|
|||
});
|
||||
|
||||
app.use(function (req, res /*, next */) {
|
||||
if (/^(\/favicon\.ico\/|.*\.js\.map)$/.test(req.url)) {
|
||||
if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) {
|
||||
// ignore common 404s
|
||||
} else {
|
||||
Log.info('HTTP_404', req.url);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
const fs = require('node:fs');
|
||||
const plugins = {};
|
||||
const extensions = plugins._extensions = [];
|
||||
const styles = plugins._styles = [];
|
||||
|
||||
try {
|
||||
let pluginsDir = fs.readdirSync(__dirname + '/plugins');
|
||||
|
@ -12,6 +14,18 @@ try {
|
|||
try {
|
||||
let plugin = require(`./plugins/${name}/index`);
|
||||
plugins[plugin.name] = plugin.modules;
|
||||
try {
|
||||
let hasExt = fs.existsSync(`lib/plugins/${name}/client/extensions.js`);
|
||||
if (hasExt) {
|
||||
extensions.push(plugin.name.toLowerCase());
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
let hasStyle = fs.existsSync(`lib/plugins/${name}/client/style.less`);
|
||||
if (hasStyle) {
|
||||
styles.push(plugin.name.toLowerCase());
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
|
|
@ -81,8 +81,8 @@ Stats.instanceData = function (Env) {
|
|||
if (Env.provideAggregateStatistics) {
|
||||
// check how many instances provide stats before we put more work into it
|
||||
data.providesAggregateStatistics = true;
|
||||
data.statistics = {}; // Filled in lib/commands/quota.js because of async calls
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
|
@ -286,6 +286,16 @@ var closeChannel = function (env, channelName, cb) {
|
|||
}
|
||||
};
|
||||
|
||||
var closeInactiveChannels = function (env, schedule, active) {
|
||||
Object.keys(env.channels).forEach(channelName => {
|
||||
if (!active.includes(channelName)) {
|
||||
schedule.ordered(channelName, function (next) {
|
||||
closeChannel(env, channelName, next);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var clearOffset = function (env, channelId, cb) {
|
||||
var path = mkOffsetPath(env, channelId);
|
||||
// we should always be able to recover from invalid offsets, so failure to delete them
|
||||
|
@ -429,7 +439,27 @@ How to proceed
|
|||
|
||||
*/
|
||||
|
||||
let requiresChannel = true;
|
||||
let all = [];
|
||||
nThen(function (w) {
|
||||
let first = true;
|
||||
getDedicatedMetadata(env, channelId, (err, line) => {
|
||||
if (first && !err) {
|
||||
if (!Array.isArray(line)) {
|
||||
requiresChannel = false;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
all.push({err, line});
|
||||
}, w(function (err) {
|
||||
if (err) {
|
||||
// stream errors?
|
||||
w.abort();
|
||||
return void cb(err);
|
||||
}
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
if (!requiresChannel) { return; }
|
||||
// returns the first line of a channel, parsed...
|
||||
getChannelMetadata(env, channelId, w(function (err, data) {
|
||||
if (err) {
|
||||
|
@ -445,13 +475,10 @@ How to proceed
|
|||
handler(null, data);
|
||||
}));
|
||||
}).nThen(function () {
|
||||
getDedicatedMetadata(env, channelId, handler, function (err) {
|
||||
if (err) {
|
||||
// stream errors?
|
||||
return void cb(err);
|
||||
}
|
||||
cb();
|
||||
all.forEach(({err, line}) => {
|
||||
handler(err, line);
|
||||
});
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1445,6 +1472,9 @@ module.exports.create = function (conf, _cb) {
|
|||
closeChannel(env, channelName, Util.both(cb, next));
|
||||
});
|
||||
},
|
||||
closeInactiveChannels: function (active) {
|
||||
closeInactiveChannels(env, schedule, active);
|
||||
},
|
||||
// write to a log file
|
||||
log: function (channelName, content, cb) {
|
||||
// you probably want the events in your log to be in the correct order.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cryptpad",
|
||||
"description": "realtime collaborative visual editor with zero knowledge server",
|
||||
"version": "2024.3.0",
|
||||
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
|
||||
"version": "2024.9.0",
|
||||
"license": "AGPL-3.0+",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -20,9 +20,9 @@
|
|||
"bootstrap-tokenfield": "^0.12.0",
|
||||
"chainpad": "^5.2.6",
|
||||
"chainpad-crypto": "^0.2.5",
|
||||
"chainpad-listmap": "^1.0.0",
|
||||
"chainpad-netflux": "^1.0.0",
|
||||
"chainpad-server": "^5.2.0",
|
||||
"chainpad-listmap": "^1.1.0",
|
||||
"chainpad-netflux": "^1.2.0",
|
||||
"chainpad-server": "^5.2.2",
|
||||
"ckeditor": "npm:ckeditor4@~4.22.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"croppie": "^2.5.0",
|
||||
"dragula": "3.7.2",
|
||||
"drawio": "github:cryptpad/drawio-npm#npm-21.8.2+5",
|
||||
"express": "~4.19.2",
|
||||
"express": "~4.20.0",
|
||||
"file-saver": "1.3.1",
|
||||
"fs-extra": "^7.0.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"localforage": "^1.5.2",
|
||||
"marked": "^4.3.0",
|
||||
"mathjax": "3.0.5",
|
||||
"netflux-websocket": "^1.0.0",
|
||||
"netflux-websocket": "^1.2.1",
|
||||
"notp": "^2.0.3",
|
||||
"nthen": "0.1.8",
|
||||
"open-sans-fontface": "^1.4.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"prompt-confirm": "^2.0.4",
|
||||
"pull-stream": "^3.6.1",
|
||||
"require-css": "0.1.10",
|
||||
"requirejs": "2.3.5",
|
||||
"requirejs": "2.3.7",
|
||||
"requirejs-plugins": "^1.0.2",
|
||||
"saferphore": "0.0.1",
|
||||
"scrypt-async": "1.2.0",
|
||||
|
@ -63,19 +63,19 @@
|
|||
"thirty-two": "^1.0.2",
|
||||
"tweetnacl": "~0.12.2",
|
||||
"ulimit": "0.0.2",
|
||||
"ws": "^3.3.1",
|
||||
"ws": "^8.17.1",
|
||||
"x2js": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"lesshint": "6.3.7"
|
||||
"stylelint": "^16.6.1",
|
||||
"stylelint-config-standard-less": "^3.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"glob-parent": "5.1.2",
|
||||
"set-value": "4.0.1",
|
||||
"minimist": "~1.2.3",
|
||||
"minimatch": "~3.1.2",
|
||||
"ws": "^8.17.1",
|
||||
"jquery": "3.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -86,9 +86,9 @@
|
|||
"offline": "FRESH=1 OFFLINE=1 node server.js",
|
||||
"offlinedev": "DEV=1 OFFLINE=1 node server.js",
|
||||
"package": "PACKAGE=1 node server.js",
|
||||
"lint": "eslint . && ./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
|
||||
"lint": "eslint . && stylelint \"./customize.dist/src/less2/**/*.less\"",
|
||||
"lint:js": "eslint .",
|
||||
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
|
||||
"lint:less": "stylelint \"./customize.dist/src/less2/**/*.less\"",
|
||||
"lint:translations": "node ./scripts/translations/lint-translations.js",
|
||||
"unused-translations": "node ./scripts/translations/unused-translations.js",
|
||||
"test": "node scripts/TestSelenium.js",
|
||||
|
@ -98,5 +98,7 @@
|
|||
"clear": "node scripts/clear.js",
|
||||
"installtoken": "node scripts/install.js"
|
||||
},
|
||||
"browserslist": ["> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all"]
|
||||
"browserslist": [
|
||||
"> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all"
|
||||
]
|
||||
}
|
||||
|
|
32
readme.md
32
readme.md
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
|
||||
# CryptPad
|
||||
|
||||
CryptPad is a collaboration suite that is end-to-end-encrypted and open-source. It is built to enable collaboration, synchronizing changes to documents in real time. Because all data is encrypted, the service and its administrators have no way of seeing the content being edited and stored.
|
||||
CryptPad is a collaboration suite that is end-to-end-encrypted and open-source. It is built to enable collaboration, synchronizing changes to documents in real time. Because all data are encrypted, in the eventuality of a breach, attackers have no way of seeing the stored content. Moreover, if the administrators don’t alter the code, they and the service also cannot infer any piece of information about the users' content.
|
||||
|
||||
![Drive screenshot](screenshot.png "preview of the CryptDrive")
|
||||
|
||||
|
@ -24,11 +24,11 @@ Configuring CryptPad for production requires a little more work, but the process
|
|||
|
||||
## Current version
|
||||
|
||||
The most recent version and all past release notes can be found [here](https://github.com/cryptpad/cryptpad/releases/).
|
||||
The most recent version and all past release notes can be found on the [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/).
|
||||
|
||||
## Setup using Docker
|
||||
|
||||
You can find `Dockerfile`, `docker-compose.yml` and `docker-entrypoint.sh` files at the root of this repository. We also publish every release on [Docker Hub](https://hub.docker.com/r/cryptpad/cryptpad) as AMD64 & ARM64 official images.
|
||||
You can find `Dockerfile`, `docker-compose.yml` and `docker-entrypoint.sh` files at the root of this repository. We also publish every release on [Docker Hub](https://hub.docker.com/r/cryptpad/cryptpad) as AMD64 & ARM64 official images.
|
||||
|
||||
Previously, Docker images were community maintained, had their own repository and weren't official supported. We changed that with v5.4.0 during July 2023. Thanks to @promasu for all the work on the community images.
|
||||
|
||||
|
@ -36,7 +36,7 @@ Previously, Docker images were community maintained, had their own repository an
|
|||
|
||||
CryptPad offers a variety of collaborative tools that encrypt your data in your browser
|
||||
before it is sent to the server and your collaborators. In the event that the server is
|
||||
compromized the database holds encrypted data that is not of much value to attackers.
|
||||
compromized, the database holds encrypted data that is not of much value to attackers.
|
||||
|
||||
The code which performs the encryption is still loaded from the host server like any
|
||||
other web page, so you still need to trust the administrator to keep their server secure
|
||||
|
@ -44,23 +44,29 @@ and to send you the right code. An expert can download code from the server and
|
|||
that it isn't doing anything malicious like leaking your encryption keys, which is why
|
||||
this is considered an [active attack].
|
||||
|
||||
The platform is designed to minimize what data is exposed to its operators. User registration
|
||||
and account access is based on a cryptographic key that is derived from your username
|
||||
and password so the server never needs to see either and you don't need to worry about
|
||||
whether they are being stored securely. It is impossible to verify whether a server's
|
||||
operators are logging your IP or other activity, so if you consider this information
|
||||
sensitive it is safest to assume it is being recorded and access your preferred instance
|
||||
via [Tor browser].
|
||||
The platform is designed to minimize what data is exposed to its operators. User
|
||||
registration and account access are based on cryptographic keys that are derived from your
|
||||
username and password. Hence, the server never needs to see either, and you don't need to
|
||||
worry about whether they are being stored securely. It is impossible to verify whether a
|
||||
server's operators are logging your IP or other activity, so if you consider this
|
||||
information sensitive it is safest to assume it is being recorded and access your
|
||||
preferred instance via [Tor browser].
|
||||
|
||||
A correctly configured instance has safeguards to prevent collaborators from doing some
|
||||
nasty things like injecting scripts into collaborative documents or uploads. The project
|
||||
is actively maintained and bugs that our safeguards don't catch tend to get fixed quickly.
|
||||
For this reason it is best to only use instances that are running the most recent version,
|
||||
which is currently on a three-week release cycle. It is difficult for a non-expert to
|
||||
which is currently on a three-month release cycle. It is difficult for a non-expert to
|
||||
determine whether an instance is otherwise configured correctly, so we are actively
|
||||
working on allowing administrators to opt in to a public directory of servers that
|
||||
working on allowing administrators to opt in to a [public directory of
|
||||
servers](https://cryptpad.org/instances/) that
|
||||
meet our strict criteria for safety.
|
||||
|
||||
For end users, a [guide](https://blog.cryptpad.org/2024/03/14/Most-Secure-CryptPad-Usage/)
|
||||
is provided in our blog to help understand the security of CryptPad. This blog post
|
||||
also explains and show the best practices when using CryptPad and clarify what end-to-end
|
||||
encryption entails and not.
|
||||
|
||||
# Translations
|
||||
|
||||
CryptPad can be translated with nothing more than a web browser via our
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
for f in ../customize.dist/favicon/*.png; do
|
||||
base="$(basename $f ".png")"
|
||||
magick convert $f -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "$base.ico"
|
||||
base="$(basename $f ".png")"
|
||||
magick convert $f -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "$base.ico"
|
||||
done
|
||||
|
|
|
@ -40,5 +40,4 @@ nThen(function (w) {
|
|||
console.log(token);
|
||||
var url = config.httpUnsafeOrigin + '/install/';
|
||||
console.log(`Please visit ${url} to create your first admin user`);
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{"owners":["TestOwner"],"validateKey":"TestKey","channel":"0","created":1721035117462}
|
||||
[0,"test","MSG","test",1721035117969]
|
||||
[0,"test","MSG","test2",1721035117969]
|
||||
[0,"test","MSG","test3",1721035117969]
|
||||
[0,"test","MSG","test4",1721035117969]
|
||||
[0,"test","MSG","test5",1721035117969]
|
||||
[0,"test","MSG","test6",1721035117969]
|
|
@ -0,0 +1,5 @@
|
|||
["RESTRICT_ACCESS",[true],1721035145090]
|
||||
["ADD_ALLOWED",["NewAllowedKeyNewAllowedKeyNewAllowedKeyNewAl"],1721035148115]
|
||||
["ADD_PENDING_OWNERS",["PendingOwner"],1721035151295]
|
||||
["RESTRICT_ACCESS",[false],1721035155836]
|
||||
["RESTRICT_ACCESS",[true],1721035156728]
|
|
@ -0,0 +1,7 @@
|
|||
{"owners":["TestOwner"],"validateKey":"TestKey","channel":"0","created":1721035117462}
|
||||
[0,"test","MSG","test",1721035117969]
|
||||
[0,"test","MSG","test2",1721035117969]
|
||||
[0,"test","MSG","test3",1721035117969]
|
||||
[0,"test","MSG","test4",1721035117969]
|
||||
[0,"test","MSG","test5",1721035117969]
|
||||
[0,"test","MSG","test6",1721035117969]
|
|
@ -0,0 +1 @@
|
|||
{"owners":["TestOwner"],"validateKey":"TestKey","channel":"0","created":1721035117462}
|
|
@ -0,0 +1,6 @@
|
|||
[0,"test","MSG","test",1721035117969]
|
||||
[0,"test","MSG","test2",1721035117969]
|
||||
[0,"test","MSG","test3",1721035117969]
|
||||
[0,"test","MSG","test4",1721035117969]
|
||||
[0,"test","MSG","test5",1721035117969]
|
||||
[0,"test","MSG","test6",1721035117969]
|
|
@ -0,0 +1,6 @@
|
|||
{"owners":["TestOwner"],"validateKey":"TestKey","channel":"0","created":1721035117462}
|
||||
["RESTRICT_ACCESS",[true],1721035145090]
|
||||
["ADD_ALLOWED",["NewAllowedKeyNewAllowedKeyNewAllowedKeyNewAl"],1721035148115]
|
||||
["ADD_PENDING_OWNERS",["PendingOwner"],1721035151295]
|
||||
["RESTRICT_ACCESS",[false],1721035155836]
|
||||
["RESTRICT_ACCESS",[true],1721035156728]
|
|
@ -0,0 +1,6 @@
|
|||
[0,"test","MSG","test",1721035117969]
|
||||
[0,"test","MSG","test2",1721035117969]
|
||||
[0,"test","MSG","test3",1721035117969]
|
||||
[0,"test","MSG","test4",1721035117969]
|
||||
[0,"test","MSG","test5",1721035117969]
|
||||
[0,"test","MSG","test6",1721035117969]
|
|
@ -0,0 +1,6 @@
|
|||
[0,"test","MSG","test",1721035117969]
|
||||
[0,"test","MSG","test2",1721035117969]
|
||||
[0,"test","MSG","test3",1721035117969]
|
||||
[0,"test","MSG","test4",1721035117969]
|
||||
[0,"test","MSG","test5",1721035117969]
|
||||
[0,"test","MSG","test6",1721035117969]
|
|
@ -0,0 +1,64 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const Store = require("../../lib/storage/file");
|
||||
const Meta = require("../../lib/metadata");
|
||||
const nThen = require('nthen');
|
||||
|
||||
let chanOld = '00000000000000000000000000000000';
|
||||
let chanOldUpdated = '00000000000000000000000000000001';
|
||||
let chanNew = '00000000000000000000000000000002';
|
||||
let chanNewUpdated = '00000000000000000000000000000003';
|
||||
let chanNoMeta = '00000000000000000000000000000004';
|
||||
|
||||
Store.create({
|
||||
filePath: './test-data/'
|
||||
}, (err, store) => {
|
||||
if (err) { return void console.error(err); }
|
||||
|
||||
const readMetadata = (channel, cb) => {
|
||||
const ref = {};
|
||||
const h = Meta.createLineHandler(ref, console.error);
|
||||
store.readChannelMetadata(channel, h, () => {
|
||||
cb(ref && ref.meta);
|
||||
});
|
||||
};
|
||||
|
||||
nThen(w => {
|
||||
readMetadata(chanOld, w(meta => {
|
||||
if (!meta || meta.validateKey !== "TestKey") {
|
||||
console.log('OldChanNoUpdate', meta);
|
||||
throw new Error("Error with old channel without metadata update");
|
||||
}
|
||||
}));
|
||||
readMetadata(chanOldUpdated, w(meta => {
|
||||
if (!meta || meta.validateKey !== "TestKey" || !meta.restricted || !meta.allowed.includes('NewAllowedKeyNewAllowedKeyNewAllowedKeyNewAl')) {
|
||||
console.log('OldChanUpdate', meta);
|
||||
throw new Error("Error with old channel with metadata updates");
|
||||
}
|
||||
}));
|
||||
readMetadata(chanNew, w(meta => {
|
||||
if (!meta || meta.validateKey !== "TestKey") {
|
||||
console.log('NewChanNoUpdate', meta);
|
||||
throw new Error("Error with new channel without metadata update");
|
||||
}
|
||||
}));
|
||||
readMetadata(chanNewUpdated, w(meta => {
|
||||
if (!meta || meta.validateKey !== "TestKey" || !meta.restricted || !meta.allowed.includes('NewAllowedKeyNewAllowedKeyNewAllowedKeyNewAl')) {
|
||||
console.log('NewChanUpdate', meta);
|
||||
throw new Error("Error with new channel with metadata updates");
|
||||
}
|
||||
}));
|
||||
readMetadata(chanNoMeta, w(meta => {
|
||||
if (meta && Object.keys(meta).length) {
|
||||
console.log('NoMetadataChan', meta);
|
||||
throw new Error("Error with channel without metadata");
|
||||
}
|
||||
}));
|
||||
}).nThen(() => {
|
||||
console.log('Success');
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -68,13 +68,6 @@ nThen(function (w) {
|
|||
Env.Log.info("WEBSERVER_LISTENING", {
|
||||
origin: url,
|
||||
});
|
||||
|
||||
if (!Env.admins.length) {
|
||||
Env.Log.info('NO_ADMIN_CONFIGURED', {
|
||||
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
|
||||
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
Env.Log.error("INVALID_ORIGIN", {
|
||||
httpUnsafeOrigin: Env.httpUnsafeOrigin,
|
||||
|
|
|
@ -9,29 +9,77 @@
|
|||
@import (reference) "../../customize/src/less2/include/creation.less";
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/export.less';
|
||||
@import (reference) '../../customize/src/less2/include/admin.less';
|
||||
|
||||
&.cp-app-admin {
|
||||
.framework_min_main();
|
||||
.sidebar-layout_main();
|
||||
.limit-bar_main();
|
||||
.creation_main();
|
||||
.admin_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font: @colortheme_app-font;
|
||||
|
||||
input, textarea, .cp-appblock {
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
}
|
||||
.cp-admin-customize-logo {
|
||||
padding: 1em;
|
||||
img {
|
||||
max-height: 250px;
|
||||
}
|
||||
}
|
||||
#cp-sidebarlayout-container {
|
||||
#cp-sidebarlayout-rightside {
|
||||
.cp-sidebarlayout-element[data-item] {
|
||||
div#cp-admin-table-container {
|
||||
overflow-x: auto;
|
||||
.cp-sidebar-table#cp-admin-table {
|
||||
tr {
|
||||
display: flex;
|
||||
}
|
||||
th, td {
|
||||
flex: 1;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
min-width: 13rem;
|
||||
margin-right: 0px;
|
||||
}
|
||||
@media (max-width: @browser_media-not-small) {
|
||||
width: 100%;
|
||||
tr {
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
margin-right: 0;
|
||||
min-width: 7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-admin-color-current {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: @cryptpad_color_brand;
|
||||
}
|
||||
input.cp-admin-color-picker {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cp-palette-container {
|
||||
display: inline-flex;
|
||||
width: ~"calc(100% - 5rem)";
|
||||
padding-left: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cp-admin-color-preview {
|
||||
& > div {
|
||||
margin-top: @sidebar_base-margin;
|
||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta name="referrer" content="no-referrer" />
|
||||
<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">
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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">
|
||||
<script async data-bootload="/admin/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/admin/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/common/toolbar.js',
|
||||
'/common/pad-types.js',
|
||||
'/components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-interface.js',
|
||||
|
@ -17,11 +18,11 @@ define([
|
|||
'/common/hyperscript.js',
|
||||
'/common/clipboard.js',
|
||||
'json.sortify',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/api/instance',
|
||||
'/lib/datepicker/flatpickr.js',
|
||||
'/common/hyperscript.js',
|
||||
'/install/onboardscreen.js',
|
||||
|
||||
'css!/lib/datepicker/flatpickr.min.css',
|
||||
'css!/components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/components/components-font-awesome/css/font-awesome.min.css',
|
||||
|
@ -29,6 +30,7 @@ define([
|
|||
], function(
|
||||
$,
|
||||
Toolbar,
|
||||
PadTypes,
|
||||
nThen,
|
||||
SFCommon,
|
||||
UI,
|
||||
|
@ -41,11 +43,12 @@ define([
|
|||
h,
|
||||
Clipboard,
|
||||
Sortify,
|
||||
AppConfig,
|
||||
ApiConfig,
|
||||
Instance,
|
||||
Flatpickr
|
||||
Flatpickr,
|
||||
Onboarding,
|
||||
) {
|
||||
|
||||
var APP = window.APP = {};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
|
@ -90,6 +93,12 @@ define([
|
|||
'forcemfa',
|
||||
]
|
||||
},
|
||||
'apps': { // Msg.admin_cat_apps
|
||||
icon: 'fa fa-wrench',
|
||||
content: [
|
||||
'apps',
|
||||
]
|
||||
},
|
||||
'users' : { // Msg.admin_cat_users
|
||||
icon : 'fa fa-address-card-o',
|
||||
content : [
|
||||
|
@ -162,6 +171,22 @@ define([
|
|||
|
||||
const blocks = sidebar.blocks;
|
||||
|
||||
// EXTENSION_POINT:ADMIN_CATEGORY
|
||||
common.getExtensions('ADMIN_CATEGORY').forEach(ext => {
|
||||
if (!ext || !ext.id || !ext.name || !ext.content) {
|
||||
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
|
||||
}
|
||||
if (categories[ext.id]) {
|
||||
return console.error('Extension point ID already used', ext);
|
||||
}
|
||||
console.error(ext);
|
||||
categories[ext.id] = {
|
||||
icon: ext.icon,
|
||||
name: ext.name,
|
||||
content: ext.content
|
||||
};
|
||||
});
|
||||
|
||||
const flushCache = (cb) => {
|
||||
cb = cb || function () {};
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
|
@ -553,23 +578,38 @@ define([
|
|||
return APP.instanceStatus.enforceMFA;
|
||||
},
|
||||
query: function (val, setState) {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['ENFORCE_MFA', [val]]
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
}
|
||||
APP.updateStatus(function () {
|
||||
setState(APP.instanceStatus.enforceMFA);
|
||||
flushCache();
|
||||
var isChecked = APP.instanceStatus.enforceMFA;
|
||||
function showConfirmation(isChecked, setState) {
|
||||
const confirmationContent = isChecked ? Messages.admin_mfa_confirm_disable : Messages.admin_mfa_confirm_enable;
|
||||
UI.confirm(confirmationContent, function (confirmed) {
|
||||
if (!confirmed) {
|
||||
// User canceled their changes, restore the checkbox value
|
||||
setState(isChecked);
|
||||
return;
|
||||
}
|
||||
// User confirmed their changes, call the command and update the state
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['ENFORCE_MFA', [val]]
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
} else {
|
||||
APP.updateStatus(function () {
|
||||
setState(APP.instanceStatus.enforceMFA);
|
||||
flushCache();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
showConfirmation(isChecked, setState);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var getInstanceString = function (attr) {
|
||||
var val = APP.instanceStatus[attr];
|
||||
var type = typeof(val);
|
||||
|
@ -609,7 +649,6 @@ define([
|
|||
UI.log(Messages._getKey('ui_saved', [Messages.admin_emailTitle]));
|
||||
});
|
||||
});
|
||||
|
||||
var nav = blocks.nav([button]);
|
||||
|
||||
var form = blocks.form([
|
||||
|
@ -621,6 +660,35 @@ define([
|
|||
cb(form);
|
||||
});
|
||||
|
||||
sidebar.addItem('apps', function (cb) {
|
||||
const appsToDisable = ApiConfig.appsToDisable || [];
|
||||
const grid = Onboarding.createAppsGrid(appsToDisable);
|
||||
|
||||
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['DISABLE_APPS', appsToDisable]
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
flushCache();
|
||||
done(true);
|
||||
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
|
||||
});
|
||||
});
|
||||
|
||||
let form = blocks.form([
|
||||
grid
|
||||
], blocks.nav([save]));
|
||||
|
||||
cb(form);
|
||||
});
|
||||
|
||||
|
||||
sidebar.addItem('instance-info-notice', function(cb){
|
||||
var key = 'instance-info-notice';
|
||||
var notice = blocks.alert('info', key, [Messages.admin_infoNotice1, ' ', Messages.admin_infoNotice2]);
|
||||
|
@ -790,7 +858,7 @@ define([
|
|||
|
||||
var currentContainer = blocks.block([], 'cp-admin-customize-logo');
|
||||
let redraw = () => {
|
||||
var current = h('img', {src: '/api/logo?'+(+new Date())});
|
||||
var current = h('img', {src: '/api/logo?'+(+new Date()),alt:'Custom logo'}); // XXX
|
||||
$(currentContainer).empty().append(current);
|
||||
};
|
||||
redraw();
|
||||
|
@ -905,7 +973,7 @@ define([
|
|||
setColor(color, done);
|
||||
});
|
||||
|
||||
let $input = $(input).on('change', () => {
|
||||
let onColorPicked = () => {
|
||||
require(['/lib/less.min.js'], (Less) => {
|
||||
let color = $input.val();
|
||||
let lColor = Less.color(color.slice(1));
|
||||
|
@ -925,7 +993,8 @@ define([
|
|||
$preview.find('.cp-admin-color-preview-dark a').attr('style', `color: ${lightColor} !important`);
|
||||
$preview.find('.cp-admin-color-preview-light a').attr('style', `color: ${color} !important`);
|
||||
});
|
||||
});
|
||||
};
|
||||
let $input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
|
||||
|
||||
UI.confirmButton($remove, {
|
||||
classes: 'btn-danger',
|
||||
|
@ -935,9 +1004,18 @@ define([
|
|||
setColor('', () => {});
|
||||
});
|
||||
|
||||
var colors = UIElements.makePalette(4, (color, $color) => {
|
||||
// onselect
|
||||
let rgb = $color.css('background-color');
|
||||
let hex = Util.rgbToHex(rgb);
|
||||
$input.val(hex);
|
||||
onColorPicked();
|
||||
});
|
||||
|
||||
$(label).append(colors);
|
||||
let form = blocks.form([
|
||||
labelCurrent,
|
||||
label
|
||||
label,
|
||||
], blocks.nav([btn, remove, btn.spinner]));
|
||||
|
||||
cb([form, labelPreview]);
|
||||
|
@ -1038,6 +1116,9 @@ define([
|
|||
""
|
||||
];
|
||||
var list = blocks.table(header, []);
|
||||
list.setAttribute('id', 'cp-admin-table');
|
||||
let div = blocks.block([list]);
|
||||
div.setAttribute('id', 'cp-admin-table-container');
|
||||
|
||||
var nav = blocks.nav([button, refreshButton]);
|
||||
var form = blocks.form([
|
||||
|
@ -1138,7 +1219,7 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
cb([form, list]);
|
||||
cb([form, div]);
|
||||
});
|
||||
|
||||
var getBlockId = (val) => {
|
||||
|
@ -1350,6 +1431,9 @@ define([
|
|||
""
|
||||
];
|
||||
var list = blocks.table(header, []);
|
||||
list.setAttribute('id', 'cp-admin-table');
|
||||
let div = blocks.block([list]);
|
||||
div.setAttribute('id', 'cp-admin-table-container');
|
||||
|
||||
var nav = blocks.nav([button, refreshButton]);
|
||||
|
||||
|
@ -1518,7 +1602,7 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
cb([form, list]);
|
||||
cb([form, div]);
|
||||
});
|
||||
|
||||
// Msg.admin_defaultlimitHint, .admin_defaultlimitTitle
|
||||
|
@ -1678,6 +1762,9 @@ define([
|
|||
Messages.admin_note
|
||||
];
|
||||
var table = blocks.table(header, []);
|
||||
table.setAttribute('id', 'cp-admin-table');
|
||||
let div = blocks.block([table]);
|
||||
div.setAttribute('id', 'cp-admin-table-container');
|
||||
let $table = $(table).hide();
|
||||
|
||||
APP.refreshLimits = function () {
|
||||
|
@ -1735,7 +1822,7 @@ define([
|
|||
});
|
||||
};
|
||||
APP.refreshLimits();
|
||||
cb(table);
|
||||
cb(div);
|
||||
});
|
||||
|
||||
// Msg.admin_accountMetadataHint.admin_accountMetadataTitle
|
||||
|
@ -2647,9 +2734,10 @@ define([
|
|||
var onRefresh = function () {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'REGISTERED_USERS',
|
||||
}, function (e, data) {
|
||||
}, function (e, arr) {
|
||||
pre.innerText = '';
|
||||
pre.append(String(data));
|
||||
let data = arr[0];
|
||||
pre.append(String(data.users));
|
||||
});
|
||||
};
|
||||
onRefresh();
|
||||
|
@ -3224,10 +3312,6 @@ define([
|
|||
$active.empty();
|
||||
if (Broadcast && Broadcast.surveyURL) {
|
||||
var a = blocks.link(Messages.admin_surveyActive, Broadcast.surveyURL);
|
||||
$(a).click(function (e) {
|
||||
e.preventDefault();
|
||||
common.openUnsafeURL(Broadcast.surveyURL);
|
||||
});
|
||||
$active.append([a, removeButton]);
|
||||
}
|
||||
});
|
||||
|
@ -3534,6 +3618,9 @@ define([
|
|||
];
|
||||
|
||||
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', {
|
||||
|
@ -3566,7 +3653,7 @@ define([
|
|||
onRefresh();
|
||||
onRefreshPerformance.reg(onRefresh);
|
||||
|
||||
cb(table);
|
||||
cb(div);
|
||||
});
|
||||
|
||||
|
||||
|
@ -3835,6 +3922,32 @@ define([
|
|||
cb(opts);
|
||||
});
|
||||
|
||||
// EXTENSION_POINT:ADMIN_ITEM
|
||||
let utils = {
|
||||
h, Util, Hash
|
||||
};
|
||||
common.getExtensions('ADMIN_ITEM').forEach(ext => {
|
||||
if (!ext || !ext.id || typeof(ext.getContent) !== "function") {
|
||||
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
|
||||
}
|
||||
if (sidebar.hasItem(ext.id)) {
|
||||
return console.error('Extension point ID already used', ext);
|
||||
}
|
||||
|
||||
sidebar.addItem(ext.id, cb => {
|
||||
ext.getContent(common, blocks, utils, content => {
|
||||
cb(content);
|
||||
});
|
||||
}, {
|
||||
noTitle: !ext.title,
|
||||
noHint: !ext.description,
|
||||
title: ext.title,
|
||||
hint: ext.description
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
sidebar.makeLeftside(categories);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
|
||||
<style>
|
||||
.report {
|
||||
|
|
|
@ -9,6 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<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">
|
||||
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript></noscript>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
.tui-full-calendar-confirm {
|
||||
span, i {
|
||||
padding: 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
span {
|
||||
padding-left: 0;
|
||||
|
@ -133,6 +133,13 @@
|
|||
width: 540px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: @variables_radius_L;
|
||||
@media screen and (max-width : @browser_media-medium-screen){
|
||||
position: fixed;
|
||||
top: 5.5rem;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100% - 6rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
#tui-full-calendar-popup-arrow {
|
||||
|
@ -153,10 +160,12 @@
|
|||
border-color: @cp_calendar-border !important;
|
||||
|
||||
}
|
||||
.tui-full-calendar-popup {
|
||||
border-radius: @variables_radius_L;
|
||||
}
|
||||
.tui-full-calendar-popup-container {
|
||||
.tui-full-calendar-section-allday{
|
||||
&:focus {
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
}
|
||||
}
|
||||
min-width: 100%;
|
||||
background: @cp_flatpickr-bg;
|
||||
color: @cryptpad_text_col;
|
||||
|
@ -208,6 +217,9 @@
|
|||
text-overflow: ellipsis;
|
||||
font: @colortheme_app-font;
|
||||
padding: 0 10px;
|
||||
&:focus{
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
}
|
||||
}
|
||||
input { flex: 1; }
|
||||
}
|
||||
|
@ -403,14 +415,16 @@
|
|||
& > input {
|
||||
height: 40px;
|
||||
}
|
||||
.tui-full-calendar-content{
|
||||
&:focus{
|
||||
outline: @cryptpad_color_brand solid 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// margin-bottom: 20px;
|
||||
input {
|
||||
width: 80px;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
.fa-plus{
|
||||
margin-left:0.3rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -633,6 +647,9 @@
|
|||
color: @cp_sidebar-left-active-fg;
|
||||
border: 0px;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta name="referrer" content="no-referrer" />
|
||||
<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">
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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">
|
||||
<script async data-bootload="/calendar/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/calendar/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -2063,6 +2063,15 @@ APP.recurrenceRule = {
|
|||
editor.setOption('readOnly', false);
|
||||
editor.setOption('autoRefresh', true);
|
||||
editor.setOption('gutters', []);
|
||||
editor.on('keydown', function (editor, e) {
|
||||
if (e.which === 27) {
|
||||
let $next = $(e.target).closest('.tui-full-calendar-popup-section').next();
|
||||
if ($next.length) {
|
||||
$next.find('#tui-full-calendar-schedule-start-date').focus();
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
cm.configureTheme(common, function () {});
|
||||
editor.setValue(oldEventBody);
|
||||
|
||||
|
@ -2147,6 +2156,13 @@ APP.recurrenceRule = {
|
|||
$el.find('input').attr('autocomplete', 'off');
|
||||
$el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary');
|
||||
$el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty();
|
||||
$el.find('.tui-full-calendar-section-allday').attr('tabindex', 0);
|
||||
$el.find('.cp-calendar-close').attr('tabindex',-1);
|
||||
$el.find('.tui-full-calendar-section-allday').keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
$(this).click();
|
||||
}
|
||||
});
|
||||
|
||||
var $container = $el.closest('.tui-full-calendar-floating-layer');
|
||||
$container.addClass('cp-calendar-popup-flex');
|
||||
|
@ -2209,6 +2225,7 @@ APP.recurrenceRule = {
|
|||
setFormat(allDay);
|
||||
});
|
||||
});
|
||||
UI.addTabListener(el);
|
||||
};
|
||||
var onCalendarEditPopup = function (el) {
|
||||
var $el = $(el);
|
||||
|
@ -2244,6 +2261,7 @@ APP.recurrenceRule = {
|
|||
var data = ev.schedule || {};
|
||||
var id = data.id;
|
||||
|
||||
UI.addTabListener(el);
|
||||
if (!id) { return; }
|
||||
if (id.indexOf('|') === -1) { return; } // Original event ID doesn't contain |
|
||||
|
||||
|
|
|
@ -15,5 +15,5 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<div id="cp-progress"></div>
|
||||
<iframe-placeholder>
|
||||
<script type="text/javascript" src="/checkup/dependency-warning.js?ver=1.0.1"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<html class="cp-app-noscroll cp-app-print">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/checkup/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/checkup/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body class="cp-app-checkup">
|
||||
</body>
|
||||
|
|
|
@ -574,7 +574,7 @@ define([
|
|||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var support = ApiConfig.supportMailbox;
|
||||
var support = ApiConfig.supportMailboxKey;
|
||||
setWarningClass(msg);
|
||||
msg.appendChild(h('span', [
|
||||
"This instance's encrypted support ticket functionality has not been enabled. This can make it difficult for its users to safely report issues that concern sensitive information. ",
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cp-progress"></div>
|
||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta name="referrer" content="no-referrer" />
|
||||
<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">
|
||||
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
<link id="favicon-ico" type="image/x-icon" rel="icon"
|
||||
data-main-favicon="/customize/favicon/main-favicon-code.ico"
|
||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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">
|
||||
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.7"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
|
|
|
@ -106,7 +106,7 @@ define([
|
|||
framework._.toolbar.$theme.append($showAuthorColors);
|
||||
markers.setButton($showAuthorColors);
|
||||
};
|
||||
var mkPrintButton = function (framework, $content) {
|
||||
var mkPrintButton = function (framework, $content, $print) {
|
||||
var $printButton = framework._.sfCommon.createButton('print', true);
|
||||
$printButton.click(function () {
|
||||
$print.html($content.html());
|
||||
|
@ -115,8 +115,8 @@ define([
|
|||
framework.feedback('PRINT_CODE');
|
||||
UI.clearTooltipsDelay();
|
||||
});
|
||||
var $print = UIElements.getEntryFromButton($printButton);
|
||||
framework._.toolbar.$drawer.append($print);
|
||||
var $dropdownEntry = UIElements.getEntryFromButton($printButton);
|
||||
framework._.toolbar.$drawer.append($dropdownEntry);
|
||||
};
|
||||
var mkMarkdownTb = function (editor, framework) {
|
||||
var $codeMirrorContainer = $('#cp-app-code-container');
|
||||
|
|
|
@ -164,12 +164,12 @@ define([
|
|||
|
||||
dialog.okButton = function (content, classString) {
|
||||
var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.btn.ok.primary';
|
||||
return h(sel, { tabindex: '2', }, content || Messages.okButton);
|
||||
return h(sel, content || Messages.okButton);
|
||||
};
|
||||
|
||||
dialog.cancelButton = function (content, classString) {
|
||||
var sel = typeof(classString) === 'string'? 'button.' + classString:'button.btn.cancel';
|
||||
return h(sel, { tabindex: '1'}, content || Messages.cancelButton);
|
||||
return h(sel, content || Messages.cancelButton);
|
||||
};
|
||||
|
||||
dialog.message = function (text) {
|
||||
|
@ -240,11 +240,15 @@ define([
|
|||
if (!(tab.content || tab.disabled) || !tab.title) { return; }
|
||||
var content = h('div.alertify-tabs-content', tab.content);
|
||||
var title = h('span.alertify-tabs-title'+ (tab.disabled ? '.disabled' : ''), h('span.tab-title-text',{id: 'cp-tab-' + tab.title.toLowerCase(), 'aria-hidden':"true"}, tab.title));
|
||||
$(title).attr('tabindex', '0');
|
||||
if (tab.icon) {
|
||||
var icon = h('i', {class: tab.icon, 'aria-labelledby': 'cp-tab-' + tab.title.toLowerCase()});
|
||||
$(title).prepend(' ').prepend(icon);
|
||||
}
|
||||
$(title).click(function () {
|
||||
|
||||
Util.onClickEnter($(title), function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (tab.disabled) { return; }
|
||||
var old = tabs[active];
|
||||
if (old.onHide) { old.onHide(); }
|
||||
|
@ -300,14 +304,17 @@ define([
|
|||
var $root = $t.parent();
|
||||
|
||||
var $input = $root.find('.token-input');
|
||||
$input.attr('tabindex', 0);
|
||||
|
||||
var $button = $(h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-plus'),
|
||||
h('span', Messages.tag_add)
|
||||
]));
|
||||
|
||||
|
||||
$button.click(function () {
|
||||
Util.onClickEnter($button, function (e) {
|
||||
$t.tokenfield('createToken', $input.val());
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
var $container = $(h('span.cp-tokenfield-container'));
|
||||
|
@ -325,27 +332,47 @@ define([
|
|||
if (!$tokens.length) {
|
||||
$container.prepend(h('span.tokenfield-empty', Messages.kanban_noTags));
|
||||
}
|
||||
$tokens.find('.close').attr('tabindex', 0).on('keydown', e => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$tokens.find('.token-label').attr('tabindex', 0).on('keydown', function (e) {
|
||||
if (e.which === 13 || e.which === 32) {
|
||||
$(this).dblclick();
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
$form.append($input);
|
||||
$form.append($button);
|
||||
if (isEdit) { $button.find('span').text(Messages.tag_edit); }
|
||||
else { $button.find('span').text(Messages.add); }
|
||||
$container.append($form);
|
||||
$input.focus();
|
||||
isEdit = false;
|
||||
called = false;
|
||||
});
|
||||
};
|
||||
resetUI();
|
||||
|
||||
const focusInput = () => {
|
||||
let active = document.activeElement;
|
||||
if ($.contains($container[0], active)) {
|
||||
setTimeout(() => {
|
||||
$input.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$t.on('tokenfield:removedtoken', function () {
|
||||
resetUI();
|
||||
focusInput();
|
||||
});
|
||||
$t.on('tokenfield:editedtoken', function () {
|
||||
resetUI();
|
||||
focusInput();
|
||||
});
|
||||
$t.on('tokenfield:createdtoken', function () {
|
||||
$input.val('');
|
||||
resetUI();
|
||||
focusInput();
|
||||
});
|
||||
$t.on('tokenfield:edittoken', function () {
|
||||
isEdit = true;
|
||||
|
@ -486,7 +513,7 @@ define([
|
|||
var navs = [];
|
||||
buttons.forEach(function (b) {
|
||||
if (!b.name || !b.onClick) { return; }
|
||||
var button = h('button', { tabindex: '1', 'class': b.className || '' }, [
|
||||
var button = h('button', { 'class': b.className || '' }, [
|
||||
b.iconClass ? h('i' + b.iconClass) : undefined,
|
||||
b.name
|
||||
]);
|
||||
|
@ -509,7 +536,8 @@ define([
|
|||
divClasses: 'left'
|
||||
}, todo);
|
||||
} else {
|
||||
$(button).click(function () {
|
||||
Util.onClickEnter($(button), function (e) {
|
||||
e.stopPropagation();
|
||||
todo();
|
||||
});
|
||||
}
|
||||
|
@ -548,6 +576,40 @@ define([
|
|||
if (opt.forefront) { $(frame).addClass('forefront'); }
|
||||
return frame;
|
||||
};
|
||||
|
||||
let addTabListener = UI.addTabListener = frame => {
|
||||
// find focusable elements
|
||||
let modalElements = $(frame).find('a, button, input, [tabindex]:not([tabindex="-1"]), textarea').filter(':visible').filter(':not(:disabled)');
|
||||
|
||||
if (modalElements.length === 0) {
|
||||
// there are no focusable elements -> nothing to do for us here
|
||||
return;
|
||||
}
|
||||
|
||||
// intialize with focus on first element
|
||||
modalElements[0].focus();
|
||||
|
||||
$(frame).on('keydown', function (e) {
|
||||
modalElements = $(frame).find('a, button, input, [tabindex]:not([tabindex="-1"]), textarea').filter(':visible').filter(':not(:disabled)'); // for modals with dynamic content
|
||||
|
||||
if (e.which === 9) { // Tab
|
||||
if (e.shiftKey) {
|
||||
// On the first element, shift+tab goes to last
|
||||
if (document.activeElement === modalElements[0]) {
|
||||
e.preventDefault();
|
||||
modalElements[modalElements.length - 1].focus();
|
||||
}
|
||||
} else {
|
||||
// On the last element, tab goes to first
|
||||
if (document.activeElement === modalElements[modalElements.length - 1]) {
|
||||
e.preventDefault();
|
||||
modalElements[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
UI.openCustomModal = function (content, opt) {
|
||||
var frame = dialog.frame([
|
||||
content
|
||||
|
@ -564,6 +626,9 @@ define([
|
|||
setTimeout(function () {
|
||||
Notifier.notify();
|
||||
});
|
||||
|
||||
addTabListener(frame);
|
||||
|
||||
return frame;
|
||||
};
|
||||
|
||||
|
@ -605,6 +670,7 @@ define([
|
|||
$modal: $blockContainer,
|
||||
show: function () {
|
||||
$blockContainer.css('display', 'flex');
|
||||
addTabListener($blockContainer);
|
||||
},
|
||||
hide: hide
|
||||
};
|
||||
|
@ -654,6 +720,7 @@ define([
|
|||
Notifier.notify();
|
||||
});
|
||||
|
||||
addTabListener(frame);
|
||||
return {
|
||||
element: frame,
|
||||
delete: close
|
||||
|
@ -705,7 +772,7 @@ define([
|
|||
|
||||
document.body.appendChild(frame);
|
||||
setTimeout(function () {
|
||||
$(input).select().focus();
|
||||
addTabListener(frame);
|
||||
Notifier.notify();
|
||||
});
|
||||
};
|
||||
|
@ -713,7 +780,6 @@ define([
|
|||
UI.confirm = function (msg, cb, opt, force) {
|
||||
cb = cb || function () {};
|
||||
opt = opt || {};
|
||||
|
||||
var message;
|
||||
if (typeof(msg) === 'string') {
|
||||
if (!force) { msg = Util.fixHTML(msg); }
|
||||
|
@ -742,13 +808,20 @@ define([
|
|||
var $ok = $(ok).click(function (ev) { close(true, ev); });
|
||||
var $cancel = $(cancel).click(function (ev) { close(false, ev); });
|
||||
|
||||
document.body.appendChild(frame);
|
||||
|
||||
addTabListener(frame);
|
||||
|
||||
listener = listenForKeys(function () {
|
||||
// Only trigger OK if cancel is not focused
|
||||
if (document.activeElement === $cancel[0]) {
|
||||
return void $cancel.click();
|
||||
}
|
||||
$ok.click();
|
||||
}, function () {
|
||||
$cancel.click();
|
||||
}, frame);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
setTimeout(function () {
|
||||
Notifier.notify();
|
||||
$(frame).find('.ok').focus();
|
||||
|
@ -815,15 +888,19 @@ define([
|
|||
};
|
||||
|
||||
var newCls2 = config.new ? 'new' : '';
|
||||
$(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).click(function (e) {
|
||||
e.stopPropagation();
|
||||
// If we have a validation function, continue only if it's true
|
||||
if (config.validate && !config.validate()) { return; }
|
||||
i = 1;
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
$(originalBtn).hide().after(content);
|
||||
$(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).on('click keydown', function (e) {
|
||||
if (e.type === 'click' || (e.type === 'keydown' && e.key === 'Enter')) {
|
||||
e.stopPropagation();
|
||||
// If we have a validation function, continue only if it's true
|
||||
if (config.validate && !config.validate()) { return; }
|
||||
i = 1;
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
$(originalBtn).hide().after(content);
|
||||
$(button).focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
reset: function () {
|
||||
done(false);
|
||||
|
@ -877,12 +954,14 @@ define([
|
|||
opts = opts || {};
|
||||
var attributes = merge({
|
||||
type: 'password',
|
||||
tabindex: '1',
|
||||
tabindex: '0',
|
||||
autocomplete: 'one-time-code', // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values
|
||||
}, opts);
|
||||
|
||||
var input = h('input.cp-password-input', attributes);
|
||||
var eye = h('span.fa.fa-eye.cp-password-reveal');
|
||||
var eye = h('span.fa.fa-eye.cp-password-reveal', {
|
||||
tabindex: 0
|
||||
});
|
||||
|
||||
var $eye = $(eye);
|
||||
var $input = $(input);
|
||||
|
@ -899,7 +978,8 @@ define([
|
|||
$input.focus();
|
||||
});
|
||||
} else {
|
||||
$eye.click(function () {
|
||||
Util.onClickEnter($eye, function (e) {
|
||||
e.stopPropagation();
|
||||
if ($eye.hasClass('fa-eye')) {
|
||||
$input.prop('type', 'text');
|
||||
$input.focus();
|
||||
|
@ -925,7 +1005,8 @@ define([
|
|||
title: text,
|
||||
href: href,
|
||||
target: "_blank",
|
||||
'data-tippy-placement': "right"
|
||||
'data-tippy-placement': "right",
|
||||
'aria-label': Messages.help_genericMore //TBC XXX
|
||||
});
|
||||
return q;
|
||||
};
|
||||
|
@ -1064,6 +1145,21 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
UI.getNewIcon = function (type) {
|
||||
var icon = h('i.fa.fa-file-text-o');
|
||||
|
||||
if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
|
||||
icon = AppConfig.applicationsIcon[type];
|
||||
var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa';
|
||||
if (type === 'fileupload') { type = 'file'; }
|
||||
if (type === 'folderupload') { type = 'file'; }
|
||||
if (type === 'link') { type = 'drive'; }
|
||||
var appClass = ' cp-icon cp-icon-color-'+type;
|
||||
icon = h('i', {'class': font + ' ' + icon + appClass});
|
||||
}
|
||||
|
||||
return icon;
|
||||
};
|
||||
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
|
||||
UI.getIcon = function (type) {
|
||||
var $icon = $defaultIcon.clone();
|
||||
|
@ -1205,18 +1301,18 @@ define([
|
|||
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
|
||||
|
||||
// Mark properties
|
||||
var markOpts = { tabindex: 0 };
|
||||
var markOpts = { tabindex: 0, role: 'checkbox', 'aria-checked': checked, 'aria-labelledby': inputOpts.id + '-label' };
|
||||
$.extend(markOpts, opts.mark || {});
|
||||
|
||||
var input = h('input', inputOpts);
|
||||
var $input = $(input);
|
||||
var mark = h('span.cp-checkmark-mark', markOpts);
|
||||
var $mark = $(mark);
|
||||
var label = h('span.cp-checkmark-label', labelTxt);
|
||||
var label = h('span.cp-checkmark-label', {id: inputOpts.id + '-label'}, labelTxt);
|
||||
|
||||
$mark.keydown(function (e) {
|
||||
if ($input.is(':disabled')) { return; }
|
||||
if (e.which === 32) {
|
||||
if (e.which === 32 || e.which === 13){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$input.prop('checked', !$input.is(':checked'));
|
||||
|
@ -1228,8 +1324,10 @@ define([
|
|||
if (!opts.labelAlt) { return; }
|
||||
if ($input.is(':checked') !== checked) {
|
||||
$(label).text(opts.labelAlt);
|
||||
$mark.attr('aria-checked', 'true');
|
||||
} else {
|
||||
$(label).text(labelTxt);
|
||||
$mark.attr('aria-checked', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1267,7 +1365,7 @@ define([
|
|||
|
||||
$(mark).keydown(function (e) {
|
||||
if ($input.is(':disabled')) { return; }
|
||||
if (e.which === 32) {
|
||||
if (e.which === 13 || e.which === 32) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if ($input.is(':checked')) { return; }
|
||||
|
|
|
@ -173,8 +173,12 @@ define([
|
|||
var removeBtn, el;
|
||||
if (config.remove) {
|
||||
removeBtn = h('span.fa.fa-times');
|
||||
$(removeBtn).click(function () {
|
||||
config.remove(el);
|
||||
$(removeBtn).attr('tabindex', '0');
|
||||
$(removeBtn).on('click keydown', function(event) {
|
||||
if (event.type === 'click' || (event.type === 'keydown' && event.key === 'Enter')) {
|
||||
event.preventDefault();
|
||||
config.remove(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -184,6 +188,7 @@ define([
|
|||
'data-curve': data.curvePublic || '',
|
||||
'data-name': name.toLowerCase(),
|
||||
'data-order': i,
|
||||
'tabindex': config.noSelect ? '-1' : '0',
|
||||
style: 'order:'+i+';'
|
||||
},[
|
||||
avatar,
|
||||
|
@ -231,6 +236,13 @@ define([
|
|||
}
|
||||
onSelect();
|
||||
});
|
||||
$div.on('keydown', '.cp-usergrid-user', function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(this).trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -611,13 +623,22 @@ define([
|
|||
var appType = (common.getMetadataMgr().getMetadata().type || 'pad').toUpperCase();
|
||||
data = data || {};
|
||||
if (!callback && data.callback) { callback = data.callback; }
|
||||
|
||||
let makeButton = function(iconClasses, buttonClasses, title, text) {
|
||||
const ariaLabel = title || text || '';
|
||||
return $(h('button', {
|
||||
class: buttonClasses,
|
||||
title: title,
|
||||
'aria-label': ariaLabel
|
||||
}, [
|
||||
iconClasses ? h('i', { class: iconClasses }) : null,
|
||||
text ? h('span', { class: 'cp-toolbar-drawer-element' }, text) : null
|
||||
]));
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'export':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-download cp-toolbar-icon-export',
|
||||
title: Messages.exportButtonTitle,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.exportButton));
|
||||
|
||||
button = makeButton('fa fa-download', 'cp-toolbar-icon-export', Messages.exportButtonTitle, Messages.exportButton);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(UI.clearTooltipsDelay);
|
||||
|
@ -626,34 +647,28 @@ define([
|
|||
}
|
||||
break;
|
||||
case 'import':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-upload cp-toolbar-icon-import',
|
||||
title: Messages.importButtonTitle,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.importButton));
|
||||
var importer = importContent((data && data.binary) ? 'application/octet-stream' : 'text/plain', callback, {
|
||||
accept: data ? data.accept : undefined,
|
||||
binary: data ? data.binary : undefined
|
||||
});
|
||||
button = makeButton('fa fa-upload', 'cp-toolbar-icon-import', Messages.importButtonTitle, Messages.importButton);
|
||||
var importer = importContent((data && data.binary) ? 'application/octet-stream' : 'text/plain', callback, {
|
||||
accept: data ? data.accept : undefined,
|
||||
binary: data ? data.binary : undefined
|
||||
});
|
||||
|
||||
var handler = data.first? function () {
|
||||
data.first(function () {
|
||||
importer(); // Make sure we don't pass arguments to importer
|
||||
});
|
||||
}: importer; //importContent;
|
||||
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
handler();
|
||||
UI.clearTooltipsDelay();
|
||||
var handler = data.first? function () {
|
||||
data.first(function () {
|
||||
importer(); // Make sure we don't pass arguments to importer
|
||||
});
|
||||
}: importer; //importContent;
|
||||
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
handler();
|
||||
UI.clearTooltipsDelay();
|
||||
});
|
||||
//}
|
||||
break;
|
||||
case 'upload':
|
||||
button = $('<button>', {
|
||||
'class': 'btn btn-primary new',
|
||||
title: Messages.uploadButtonTitle,
|
||||
}).append($('<span>', {'class':'fa fa-upload'})).append(' '+Messages.uploadButton);
|
||||
button = makeButton('fa fa-upload', 'btn btn-primary new', Messages.uploadButtonTitle, Messages.uploadButton);
|
||||
if (!data.FM) { return; }
|
||||
var $input = $('<input>', {
|
||||
'type': 'file',
|
||||
|
@ -685,9 +700,7 @@ define([
|
|||
});
|
||||
break;
|
||||
case 'copy':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-files-o cp-toolbar-icon-import',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.makeACopy));
|
||||
button = makeButton('fa fa-files-o', 'cp-toolbar-icon-import', '', Messages.makeACopy);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
|
@ -698,9 +711,7 @@ define([
|
|||
case 'importtemplate':
|
||||
if (!AppConfig.enableTemplates) { return; }
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-upload cp-toolbar-icon-import',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.template_import));
|
||||
button = makeButton('fa fa-upload', 'cp-toolbar-icon-import', '', Messages.template_import);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
|
@ -712,9 +723,7 @@ define([
|
|||
case 'template':
|
||||
if (!AppConfig.enableTemplates) { return; }
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
button = $('<button>', {
|
||||
'class': 'cptools cptools-new-template cp-toolbar-icon-template',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.saveTemplateButton));
|
||||
button = makeButton('cptools cptools-new-template', 'cp-toolbar-icon-template', '', Messages.saveTemplateButton);
|
||||
if (data.rt || data.callback) {
|
||||
button
|
||||
.click(function () {
|
||||
|
@ -762,9 +771,7 @@ define([
|
|||
}
|
||||
break;
|
||||
case 'forget':
|
||||
button = $('<button>', {
|
||||
'class': "fa fa-trash cp-toolbar-icon-forget"
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.fc_delete));
|
||||
button = makeButton('fa fa-trash', 'cp-toolbar-icon-forget', '', Messages.fc_delete);
|
||||
callback = typeof callback === "function" ? callback : function () {};
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
|
@ -825,25 +832,16 @@ define([
|
|||
])).click(common.prepareFeedback(type));
|
||||
break;
|
||||
case 'print':
|
||||
button = $('<button>', {
|
||||
title: Messages.printButtonTitle2,
|
||||
'class': "fa fa-print cp-toolbar-icon-print",
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.printText));
|
||||
button = makeButton('fa fa-print', 'cp-toolbar-icon-print', Messages.printButtonTitle2, Messages.printText);
|
||||
break;
|
||||
case 'history':
|
||||
if (!AppConfig.enableHistory) {
|
||||
button = $('<span>');
|
||||
break;
|
||||
}
|
||||
button = $('<button>', {
|
||||
title: Messages.historyButton,
|
||||
'aria-label': Messages.historyButton,
|
||||
'class': "fa fa-history cp-toolbar-icon-history",
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.historyText));
|
||||
button = makeButton('fa fa-history', 'cp-toolbar-icon-history', Messages.historyButton, Messages.historyText, Messages.historyButton);
|
||||
if (data.histConfig) {
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.on('click', function () {
|
||||
button.click(common.prepareFeedback(type)).on('click', function () {
|
||||
common.getHistory(data.histConfig);
|
||||
UI.clearTooltipsDelay();
|
||||
});
|
||||
|
@ -867,9 +865,10 @@ define([
|
|||
if (callback) { button.click(callback); }
|
||||
break;
|
||||
case 'storeindrive':
|
||||
button = $(h('button.cp-toolbar-storeindrive.fa.fa-hdd-o', {
|
||||
button = $(h('button.cp-toolbar-storeindrive', {
|
||||
style: 'display:none;'
|
||||
}, [
|
||||
h('i.fa.fa-hdd-o'),
|
||||
h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_storeInDrive)
|
||||
])).click(common.prepareFeedback(type)).click(function () {
|
||||
$(button).hide();
|
||||
|
@ -890,10 +889,7 @@ define([
|
|||
});
|
||||
break;
|
||||
case 'hashtag':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-hashtag cp-toolbar-icon-hashtag',
|
||||
title: Messages.tags_title,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.fc_hashtag));
|
||||
button = makeButton('fa fa-hashtag', 'cp-toolbar-icon-hashtag', Messages.tags_title, Messages.fc_hashtag);
|
||||
button.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
common.isPadStored(function (err, data) {
|
||||
|
@ -938,11 +934,8 @@ define([
|
|||
//updateIcon(data.element.is(':visible'));
|
||||
break;
|
||||
case 'properties':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-info-circle cp-toolbar-icon-properties',
|
||||
title: Messages.propertiesButtonTitle,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
|
||||
.text(Messages.propertiesButton))
|
||||
button = makeButton('fa fa-info-circle', 'cp-toolbar-icon-properties', Messages.propertiesButtonTitle, Messages.propertiesButton);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
var isTop;
|
||||
|
@ -958,11 +951,8 @@ define([
|
|||
});
|
||||
break;
|
||||
case 'save': // OnlyOffice save
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-save',
|
||||
title: Messages.settings_save,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
|
||||
.text(Messages.settings_save))
|
||||
button = makeButton('fa fa-save', '', Messages.settings_save, Messages.settings_save);
|
||||
button
|
||||
.click(function() {
|
||||
common.prepareFeedback(type);
|
||||
UI.clearTooltipsDelay();
|
||||
|
@ -970,10 +960,7 @@ define([
|
|||
if (callback) { button.click(callback); }
|
||||
break;
|
||||
case 'newpad':
|
||||
button = $('<button>', {
|
||||
title: Messages.newButtonTitle,
|
||||
'class': 'fa fa-plus cp-toolbar-icon-newpad',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.newButton));
|
||||
button = makeButton('fa fa-plus', 'cp-toolbar-icon-newpad', Messages.newButtonTitle, Messages.newButton);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
|
@ -982,10 +969,7 @@ define([
|
|||
});
|
||||
break;
|
||||
case 'snapshots':
|
||||
button = $('<button>', {
|
||||
title: Messages.snapshots_button,
|
||||
'class': 'fa fa-camera cp-toolbar-icon-snapshots',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.snapshots_button));
|
||||
button = makeButton('fa fa-camera', 'cp-toolbar-icon-snapshots', Messages.snapshots_button,Messages.snapshots_button);
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
|
@ -1268,6 +1252,7 @@ define([
|
|||
kanban: 'kanban',
|
||||
form: 'form',
|
||||
whiteboard: 'whiteboard',
|
||||
diagram: 'diagram',
|
||||
};
|
||||
|
||||
var href = "https://docs.cryptpad.org/en/user_guide/applications.html";
|
||||
|
@ -1595,10 +1580,10 @@ define([
|
|||
]));
|
||||
|
||||
if (config.caretDown) {
|
||||
$button.append(h('i.fa.fa-caret-down'));
|
||||
$button.prepend(h('i.fa.fa-caret-down'));
|
||||
}
|
||||
if (config.angleDown) {
|
||||
$button.append(h('i.fa.fa-angle-down'));
|
||||
$button.prepend(h('i.fa.fa-angle-down'));
|
||||
}
|
||||
|
||||
// Menu
|
||||
|
@ -2498,10 +2483,21 @@ define([
|
|||
});
|
||||
|
||||
var selected = -1;
|
||||
var previous = function () {
|
||||
selected = (selected === 0 ? types.length : selected) - 1;
|
||||
$container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected');
|
||||
let element = $container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
|
||||
if (element.hasClass('cp-app-disabled')) {
|
||||
previous();
|
||||
}
|
||||
};
|
||||
var next = function () {
|
||||
selected = ++selected % types.length;
|
||||
$container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected');
|
||||
$container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
|
||||
let element = $container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
|
||||
if (element.hasClass('cp-app-disabled')) {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
$modal.off('keydown');
|
||||
|
@ -2509,7 +2505,11 @@ define([
|
|||
if (e.which === 9) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
next();
|
||||
if (e.shiftKey) {
|
||||
previous();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.which === 13) {
|
||||
|
@ -2645,7 +2645,7 @@ define([
|
|||
var urlArgs = (Config.requireConf && Config.requireConf.urlArgs) || '';
|
||||
|
||||
var logo = h('img', { src: '/customize/CryptPad_logo.svg?' + urlArgs });
|
||||
var fill1 = h('div.cp-creation-fill.cp-creation-logo', logo);
|
||||
var fill1 = h('div.cp-creation-fill.cp-creation-logo',{ role: 'presentation' }, logo);
|
||||
var fill2 = h('div.cp-creation-fill');
|
||||
var $creation = $('<div>', { id: 'cp-creation', tabindex:1 });
|
||||
$creationContainer.append([fill1, $creation, fill2]);
|
||||
|
@ -3547,7 +3547,7 @@ define([
|
|||
$(link).click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var obj = { pw: msg.content.password || '' };
|
||||
var obj = { pw: msg.content.password || '', f: 1 };
|
||||
common.openURL(Hash.getNewPadURL(msg.content.href, obj));
|
||||
});
|
||||
|
||||
|
@ -3603,7 +3603,7 @@ define([
|
|||
|
||||
// Add the pad to your drive
|
||||
// This command will also add your mailbox to the metadata log
|
||||
// The callback is called when the pad is stored, independantly of the metadata command
|
||||
// The callback is called when the pad is stored, independently of the metadata command
|
||||
if (data.calendar) {
|
||||
var calendarModule = common.makeUniversal('calendar');
|
||||
var calendarData = data.calendar;
|
||||
|
@ -4285,5 +4285,76 @@ define([
|
|||
return UI.errorLoadingScreen(msg, false, false);
|
||||
};
|
||||
|
||||
UIElements.makePalette = (maxColors, onSelect) => {
|
||||
let palette = [''];
|
||||
for (var i=1; i<=maxColors; i++) { palette.push('color'+i); }
|
||||
|
||||
let offline = false;
|
||||
let selectedColor = '';
|
||||
let container = h('div.cp-palette-container');
|
||||
let $container = $(container);
|
||||
|
||||
var all = [];
|
||||
palette.forEach(function (color, i) {
|
||||
var $color = $(h('button.cp-palette-color.fa'));
|
||||
all.push($color);
|
||||
$color.addClass('cp-palette-'+(color || 'nocolor'));
|
||||
$color.keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$color.click();
|
||||
}
|
||||
});
|
||||
$color.click(function () {
|
||||
if (offline) { return; }
|
||||
if (color === selectedColor) { return; }
|
||||
selectedColor = color;
|
||||
$container.find('.cp-palette-color').removeClass('fa-check');
|
||||
$color.addClass('fa-check');
|
||||
onSelect(color, $color);
|
||||
}).appendTo($container);
|
||||
$color.keydown(e => {
|
||||
if (e.which === 37) {
|
||||
e.preventDefault();
|
||||
if (i === 0) {
|
||||
all[all.length - 1].focus();
|
||||
} else {
|
||||
all[i - 1].focus();
|
||||
}
|
||||
}
|
||||
if (e.which === 39) {
|
||||
e.preventDefault();
|
||||
if (i === (all.length - 1)) {
|
||||
all[0].focus();
|
||||
} else {
|
||||
all[i + 1].focus();
|
||||
}
|
||||
}
|
||||
if (e.which === 9) {
|
||||
if (e.shiftKey) {
|
||||
all[0].focus();
|
||||
return;
|
||||
}
|
||||
all[all.length - 1].focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
container.disable = state => {
|
||||
offline = !!state;
|
||||
};
|
||||
container.getValue = () => {
|
||||
return selectedColor;
|
||||
};
|
||||
container.setValue = color => {
|
||||
$container.find('.cp-palette-color').removeClass('fa-check');
|
||||
let $color = $container.find('.cp-palette-'+(color || 'nocolor'));
|
||||
$color.addClass('fa-check');
|
||||
selectedColor = color;
|
||||
};
|
||||
return container;
|
||||
};
|
||||
|
||||
return UIElements;
|
||||
});
|
||||
|
|
|
@ -606,6 +606,9 @@
|
|||
parseInt(h.slice(4,6), 16),
|
||||
];
|
||||
};
|
||||
Util.rgbToHex = function (rgb) {
|
||||
return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
|
||||
};
|
||||
|
||||
Util.isSmallScreen = function () {
|
||||
return window.innerHeight < 800 || window.innerWidth < 800;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue