mirror of https://github.com/xwiki-labs/cryptpad
Merge branch 'form' into form-del
This commit is contained in:
commit
5d350f1c45
|
@ -8,7 +8,6 @@ www/common/onlyoffice/v2*
|
|||
www/common/onlyoffice/v4
|
||||
www/common/onlyoffice/v5
|
||||
|
||||
server.js
|
||||
www/scratch
|
||||
www/accounts
|
||||
www/lib
|
||||
|
|
|
@ -183,6 +183,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.cp-dropdown-content {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-alertify-type-container {
|
||||
overflow: visible !important;
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
border-width: 0 @checkmark-width @checkmark-width 0;
|
||||
border-width: 0 var(--checkmark-width) var(--checkmark-width) 0;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 5px @cp_checkmark-back1;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
button {
|
||||
.fa-caret-down {
|
||||
margin-right: 1em !important;
|
||||
margin-right: 0.5em !important;
|
||||
}
|
||||
* {
|
||||
.tools_unselectable();
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
height: 100%;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
background-color: @cp_buttons-primary;
|
||||
background-color: @cp_buttons-default-color;
|
||||
&.danger, &.btn-danger, &.danger-alt, &.btn-danger-alt {
|
||||
background-color: @cp_buttons-red;
|
||||
}
|
||||
|
@ -327,6 +327,9 @@
|
|||
fill: @cryptpad_text_col;
|
||||
}
|
||||
}
|
||||
.flatpickr-monthDropdown-month {
|
||||
background: @cp_flatpickr-bg;
|
||||
}
|
||||
}
|
||||
.flatpickr-current-month {
|
||||
span.cur-month:hover {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
& {
|
||||
|
||||
each(@colortheme_apps, {
|
||||
button .cp-icon-color-@{key},
|
||||
.cp-icon-color-@{key} { color: @value; }
|
||||
});
|
||||
|
||||
|
|
|
@ -27,14 +27,16 @@
|
|||
color: @cryptpad_color_red;
|
||||
}
|
||||
}
|
||||
.cp-avatar {
|
||||
.avatar_main(30px);
|
||||
padding: 0 5px;
|
||||
.cp-reminder, .cp-avatar {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: @cp_dropdown-bg-hover;
|
||||
}
|
||||
}
|
||||
.cp-avatar {
|
||||
.avatar_main(30px);
|
||||
padding: 0 5px;
|
||||
}
|
||||
.cp-notification-content {
|
||||
flex: 1;
|
||||
align-items: stretch;
|
||||
|
|
|
@ -967,6 +967,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.cp-toolbar-dropdown-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cp-toolbar-bottom {
|
||||
color: @cp_toolbar-bottom-fg;
|
||||
display: inline-flex;
|
||||
|
@ -998,11 +1001,15 @@
|
|||
.fa, .cptools {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.cp-dropdown-button-title .cp-icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: fade(@cp_toolbar-bottom-bg, 70%);
|
||||
}
|
||||
}
|
||||
.cp-toolbar-bottom-left > button,
|
||||
.cp-toolbar-bottom-left > span > button,
|
||||
.cp-toolbar-bottom-mid > button,
|
||||
.cp-toolbar-bottom-right > button,
|
||||
.cp-toolbar-bottom-right > span > button {
|
||||
|
@ -1070,7 +1077,7 @@
|
|||
.cp-toolbar-name, .cp-button-name {
|
||||
display: none;
|
||||
}
|
||||
i {
|
||||
i, span {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ const Core = require("./commands/core");
|
|||
const Quota = require("./commands/quota");
|
||||
const Util = require("./common-util");
|
||||
const Package = require("../package.json");
|
||||
const Path = require("path");
|
||||
|
||||
var canonicalizeOrigin = function (s) {
|
||||
if (typeof(s) === 'undefined') { return; }
|
||||
|
@ -296,7 +297,7 @@ module.exports.create = function (config) {
|
|||
var paths = Env.paths;
|
||||
|
||||
var keyOrDefaultString = function (key, def) {
|
||||
return typeof(config[key]) === 'string'? config[key]: def;
|
||||
return Path.resolve(typeof(config[key]) === 'string'? config[key]: def);
|
||||
};
|
||||
|
||||
Env.incrementBytesWritten = function (n) {
|
||||
|
|
|
@ -405,6 +405,7 @@ Meta.createLineHandler = function (ref, errorHandler) {
|
|||
ref.meta = {};
|
||||
ref.index = 0;
|
||||
ref.logged = {};
|
||||
var overwritten = false;
|
||||
|
||||
return function (err, line) {
|
||||
if (err) {
|
||||
|
@ -449,6 +450,8 @@ Meta.createLineHandler = function (ref, errorHandler) {
|
|||
// Thus, accept both the first and second lines you process as valid initial state
|
||||
// preferring the second if it exists
|
||||
if (index < 2 && line && typeof(line) === 'object') {
|
||||
if (overwritten) { return; } // hack to avoid overwriting metadata a second time
|
||||
overwritten = true;
|
||||
// special case!
|
||||
ref.meta = line;
|
||||
return;
|
||||
|
|
|
@ -1144,8 +1144,8 @@ module.exports.create = function (conf, _cb) {
|
|||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var env = {
|
||||
root: conf.filePath || './datastore',
|
||||
archiveRoot: conf.archivePath || './data/archive',
|
||||
root: Path.resolve(conf.filePath || './datastore'),
|
||||
archiveRoot: Path.resolve(conf.archivePath || './data/archive'),
|
||||
// supply a volumeId if you want a store to archive channels to and from
|
||||
// to its own subpath within the archive directory
|
||||
volumeId: conf.volumeId || 'datastore',
|
||||
|
|
73
server.js
73
server.js
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
globals require console
|
||||
globals process
|
||||
*/
|
||||
var Express = require('express');
|
||||
var Http = require('http');
|
||||
|
@ -8,7 +8,6 @@ var Path = require("path");
|
|||
var nThen = require("nthen");
|
||||
var Util = require("./lib/common-util");
|
||||
var Default = require("./lib/defaults");
|
||||
var Keys = require("./lib/keys");
|
||||
|
||||
var config = require("./lib/load-config");
|
||||
var Env = require("./lib/env").create(config);
|
||||
|
@ -116,16 +115,17 @@ app.head(/^\/common\/feedback\.html/, function (req, res, next) {
|
|||
});
|
||||
}());
|
||||
|
||||
app.use('/blob', function (req, res, next) {
|
||||
if (req.method === 'HEAD') {
|
||||
Express.static(Path.join(__dirname, Env.paths.blob), {
|
||||
setHeaders: function (res, path, stat) {
|
||||
const serveStatic = Express.static(Env.paths.blob, {
|
||||
setHeaders: function (res) {
|
||||
res.set('Access-Control-Allow-Origin', Env.enableEmbedding? '*': Env.permittedEmbedders);
|
||||
res.set('Access-Control-Allow-Headers', 'Content-Length');
|
||||
res.set('Access-Control-Expose-Headers', 'Content-Length');
|
||||
}
|
||||
})(req, res, next);
|
||||
return;
|
||||
});
|
||||
|
||||
app.use('/blob', function (req, res, next) {
|
||||
if (req.method === 'HEAD') {
|
||||
return void serveStatic(req, res, next);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
@ -150,32 +150,33 @@ app.use(function (req, res, next) {
|
|||
|
||||
// serve custom app content from the customize directory
|
||||
// useful for testing pages customized with opengraph data
|
||||
app.use(Express.static(__dirname + '/customize/www'));
|
||||
app.use(Express.static(__dirname + '/www'));
|
||||
app.use(Express.static(Path.resolve('customize/www')));
|
||||
app.use(Express.static(Path.resolve('www')));
|
||||
|
||||
// FIXME I think this is a regression caused by a recent PR
|
||||
// correct this hack without breaking the contributor's intended behaviour.
|
||||
|
||||
var mainPages = config.mainPages || Default.mainPages();
|
||||
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
|
||||
app.get(mainPagePattern, Express.static(__dirname + '/customize'));
|
||||
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
|
||||
app.get(mainPagePattern, Express.static(Path.resolve('customize')));
|
||||
app.get(mainPagePattern, Express.static(Path.resolve('customize.dist')));
|
||||
|
||||
app.use("/blob", Express.static(Path.join(__dirname, Env.paths.blob), {
|
||||
app.use("/blob", Express.static(Env.paths.blob, {
|
||||
maxAge: Env.DEV_MODE? "0d": "365d"
|
||||
}));
|
||||
app.use("/datastore", Express.static(Path.join(__dirname, Env.paths.data), {
|
||||
app.use("/datastore", Express.static(Env.paths.data, {
|
||||
maxAge: "0d"
|
||||
}));
|
||||
app.use("/block", Express.static(Path.join(__dirname, Env.paths.block), {
|
||||
|
||||
app.use("/block", Express.static(Env.paths.block, {
|
||||
maxAge: "0d",
|
||||
}));
|
||||
|
||||
app.use("/customize", Express.static(__dirname + '/customize'));
|
||||
app.use("/customize", Express.static(__dirname + '/customize.dist'));
|
||||
app.use("/customize.dist", Express.static(__dirname + '/customize.dist'));
|
||||
app.use(/^\/[^\/]*$/, Express.static('customize'));
|
||||
app.use(/^\/[^\/]*$/, Express.static('customize.dist'));
|
||||
app.use("/customize", Express.static(Path.resolve('customize')));
|
||||
app.use("/customize", Express.static(Path.resolve('customize.dist')));
|
||||
app.use("/customize.dist", Express.static(Path.resolve('customize.dist')));
|
||||
app.use(/^\/[^\/]*$/, Express.static(Path.resolve('customize')));
|
||||
app.use(/^\/[^\/]*$/, Express.static(Path.resolve('customize.dist')));
|
||||
|
||||
// if dev mode: never cache
|
||||
var cacheString = function () {
|
||||
|
@ -216,7 +217,7 @@ var makeRouteCache = function (template, cacheName) {
|
|||
};
|
||||
};
|
||||
|
||||
var serveConfig = makeRouteCache(function (host) {
|
||||
var serveConfig = makeRouteCache(function () {
|
||||
return [
|
||||
'define(function(){',
|
||||
'return ' + JSON.stringify({
|
||||
|
@ -244,10 +245,10 @@ var serveConfig = makeRouteCache(function (host) {
|
|||
accounts_api: Env.accounts_api,
|
||||
}, null, '\t'),
|
||||
'});'
|
||||
].join(';\n')
|
||||
].join(';\n');
|
||||
}, 'configCache');
|
||||
|
||||
var serveBroadcast = makeRouteCache(function (host) {
|
||||
var serveBroadcast = makeRouteCache(function () {
|
||||
var maintenance = Env.maintenance;
|
||||
if (maintenance && maintenance.end && maintenance.end < (+new Date())) {
|
||||
maintenance = undefined;
|
||||
|
@ -260,21 +261,21 @@ var serveBroadcast = makeRouteCache(function (host) {
|
|||
maintenance: maintenance
|
||||
}, null, '\t'),
|
||||
'});'
|
||||
].join(';\n')
|
||||
].join(';\n');
|
||||
}, 'broadcastCache');
|
||||
|
||||
app.get('/api/config', serveConfig);
|
||||
app.get('/api/broadcast', serveBroadcast);
|
||||
|
||||
var define = function (obj) {
|
||||
var defineBlock = function (obj) {
|
||||
return `define(function (){
|
||||
return ${JSON.stringify(obj, null, '\t')};
|
||||
});`
|
||||
});`;
|
||||
};
|
||||
|
||||
app.get('/api/instance', function (req, res) { // XXX use caching?
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.send(define({
|
||||
res.send(defineBlock({
|
||||
name: Env.instanceName,
|
||||
description: Env.instanceDescription,
|
||||
location: Env.instanceJurisdiction,
|
||||
|
@ -282,10 +283,10 @@ app.get('/api/instance', function (req, res) { // XXX use caching?
|
|||
}));
|
||||
});
|
||||
|
||||
var four04_path = Path.resolve(__dirname + '/customize.dist/404.html');
|
||||
var fivehundred_path = Path.resolve(__dirname + '/customize.dist/500.html');
|
||||
var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
|
||||
var custom_fivehundred_path = Path.resolve(__dirname + '/customize/500.html');
|
||||
var four04_path = Path.resolve('customize.dist/404.html');
|
||||
var fivehundred_path = Path.resolve('customize.dist/500.html');
|
||||
var custom_four04_path = Path.resolve('customize/404.html');
|
||||
var custom_fivehundred_path = Path.resolve('/customize/500.html');
|
||||
|
||||
var send404 = function (res, path) {
|
||||
if (!path && path !== four04_path) { path = four04_path; }
|
||||
|
@ -321,7 +322,7 @@ app.get('/api/updatequota', function (req, res) {
|
|||
});
|
||||
});
|
||||
|
||||
app.get('/api/profiling', function (req, res, next) {
|
||||
app.get('/api/profiling', function (req, res) {
|
||||
if (!Env.enableProfiling) { return void send404(res); }
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.send(JSON.stringify({
|
||||
|
@ -329,13 +330,13 @@ app.get('/api/profiling', function (req, res, next) {
|
|||
}));
|
||||
});
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
app.use(function (req, res) {
|
||||
res.status(404);
|
||||
send404(res, custom_four04_path);
|
||||
});
|
||||
|
||||
// default message for thrown errors in ExpressJS routes
|
||||
app.use(function (err, req, res, next) {
|
||||
app.use(function (err, req, res) {
|
||||
Env.Log.error('EXPRESSJS_ROUTING', {
|
||||
error: err.stack || err,
|
||||
});
|
||||
|
@ -346,7 +347,7 @@ app.use(function (err, req, res, next) {
|
|||
var httpServer = Env.httpServer = Http.createServer(app);
|
||||
|
||||
nThen(function (w) {
|
||||
Fs.exists(__dirname + "/customize", w(function (e) {
|
||||
Fs.exists(Path.resolve("customize"), w(function (e) {
|
||||
if (e) { return; }
|
||||
console.log("CryptPad is customizable, see customize.dist/readme.md for details");
|
||||
}));
|
||||
|
@ -377,7 +378,7 @@ nThen(function (w) {
|
|||
Http.createServer(app).listen(Env.httpSafePort, Env.httpAddress, w());
|
||||
}
|
||||
}).nThen(function () {
|
||||
var wsConfig = { server: httpServer };
|
||||
//var wsConfig = { server: httpServer };
|
||||
|
||||
// Initialize logging then start the API server
|
||||
require("./lib/log").create(config, function (_log) {
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
.cp-small { display: none; }
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
z-index: 100001 !important; // Alertify is 100000
|
||||
}
|
||||
#cp-sidebarlayout-container #cp-sidebarlayout-rightside {
|
||||
padding: 0;
|
||||
& > div {
|
||||
|
@ -101,6 +104,21 @@
|
|||
color: @cryptpad_text_col !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-full-calendar-floating-layer.cp-calendar-popup-flex {
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
.tui-full-calendar-popup {
|
||||
width: 540px !important;
|
||||
}
|
||||
}
|
||||
#tui-full-calendar-popup-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-timegrid-timezone {
|
||||
background-color: @cp_sidebar-right-bg !important;
|
||||
|
@ -116,13 +134,30 @@
|
|||
border-color: @cp_calendar-border !important;
|
||||
|
||||
}
|
||||
.tui-full-calendar-popup {
|
||||
border-radius: @variables_radius_L;
|
||||
}
|
||||
.tui-full-calendar-popup-container {
|
||||
background: @cp_flatpickr-bg;
|
||||
color: @cryptpad_text_col;
|
||||
border-radius: @variables_radius;
|
||||
border-radius: @variables_radius_L;
|
||||
font-weight: normal;
|
||||
.tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox) {
|
||||
display: none;
|
||||
}
|
||||
.tui-full-calendar-popup-detail-item {
|
||||
a {
|
||||
color: @cryptpad_color_link;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-section-button-save {
|
||||
height: 40px;
|
||||
.btn-primary { // Update button
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
li.tui-full-calendar-popup-section-item {
|
||||
padding: 0 6px;
|
||||
|
@ -186,6 +221,9 @@
|
|||
width: 100%;
|
||||
height: 32px;
|
||||
border-radius: @variables_radius;
|
||||
input[type="checkbox"].tui-full-calendar-checkbox-square:checked + span {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABbmlDQ1BpY2MAACiRdZHNK0RRGMZ/Zoh8NIqFNItZDFmMEiVLxsJmkgZlsLn3zpe6c93uvZNkq2wspizExtfCf8BW2VJKkZKs/AG+Npqu97hqJM7t3PfXc87zds5zIJQyjZJbPwAly3PSE8nYXGY+1vhEMy20EyaqGa49NjWV4t/xfkOdqtf9qtf/+/4cLdmca0Bdk/CwYTue8KhwasWzFW8KdxpFLSu8L5xw5IDCF0rXA35UXAj4VbEzkx6HkOoZK/xg/QcbRack3CccL5ll4/s86iatOWt2Wmq3zCguaSZIEkOnzBImHv1SLcnsb9/Al2+SZfEY8rdZxRFHgaJ4E6KWpWtOal70nHwmqyr333m6+aHBoHtrEhoefP+lBxq3oFrx/Y8D368eQvgezqyaf1lyGnkTvVLT4nsQWYeT85qmb8PpBnTd2ZqjfUlhmaF8Hp6PoS0DHVfQvBBk9b3O0S3MrMkTXcLOLvTK/sjiJ6CLZ94KREMsAAAACXBIWXMAAAsSAAALEgHS3X78AAABHUlEQVQoFWNkaP//n4EMwESGHrAW6mtkZvz3G2g03BsBagz/nRUQ7sNp49//TJ+Byv6BlMbrMjCsC2Jg3BbKwOAgB9GMolGAg4GBESIOIrmA+A9I03xvFHGwCrhGkDOe5TAw9LvAFbEn6jEwwTT9BtodvJ7h/4FHYH0MLBCKgaHTgYGBE8jLN2FgYAJae+4FA8NcLwZWkAtAmoLWMTBsuYNwECMsHrVEGBj2RzEwiIEciATANgE1bb6DJAgMNLhTr71hYHBcxsDw+htCAQ5NIAU/4RpBPKjmf2++MfwHaQpZD7cJyEMB3+BORRL+rybE8FOEk4Hj2FO4KMgdrFAMitun2DTCVSMxYAkBFFYg9j94qCIpwsZEil5wyDIDAAXIUsnSKmq7AAAAAElFTkSuQmCC);
|
||||
}
|
||||
.tui-full-calendar-ic-checkbox {
|
||||
margin-left: 5px;
|
||||
border-radius: 2px;
|
||||
|
@ -196,6 +234,7 @@
|
|||
.tui-full-calendar-popup-detail {
|
||||
font: @colortheme_app-font;
|
||||
color: @cryptpad_text_col;
|
||||
box-shadow: @cryptpad_ui_shadow;
|
||||
.tui-full-calendar-popup-container {
|
||||
padding-bottom: 17px;
|
||||
}
|
||||
|
@ -203,28 +242,44 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
.tui-full-calendar-section-button {
|
||||
margin-top: 10px;
|
||||
border: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-popup-top-line {
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
height: 10px;
|
||||
}
|
||||
.tui-full-calendar-popup-vertical-line {
|
||||
visibility: hidden;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-recurrence-label, .cp-notif-label {
|
||||
color: @cryptpad_text_col;
|
||||
margin-right: 1rem;
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-calendar-recurrence-container {
|
||||
margin-top: 1rem;
|
||||
.cp-calendar-rec-translated-str {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-calendar-add-notif {
|
||||
flex-flow: column;
|
||||
align-items: baseline !important;
|
||||
margin: 10px 0;
|
||||
.cp-notif-label {
|
||||
color: @cp_sidebar-hint;
|
||||
margin-right: 20px;
|
||||
}
|
||||
margin: 1rem 0;
|
||||
* {
|
||||
font-size: @colortheme_app-font-size;
|
||||
font-weight: normal;
|
||||
|
@ -234,34 +289,58 @@
|
|||
}
|
||||
.cp-calendar-notif-list-container {
|
||||
margin-bottom: 10px;
|
||||
.cp-notif-label {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
.cp-calendar-notif-list {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.cp-notif-entry {
|
||||
margin-bottom: 2px;
|
||||
border-radius: @variables_radius;
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
padding: 0.25rem;
|
||||
.cp-notif-value {
|
||||
width: 170px;
|
||||
display: inline-flex;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
.cp-before {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
span:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
.btn-danger-outline {
|
||||
margin-right: 0px !important;
|
||||
background-color: transparent;
|
||||
color: @cryptpad_text_col;
|
||||
border-color: @cryptpad_text_col;
|
||||
&:hover {
|
||||
color: @cp_buttons-red-color;
|
||||
background-color: @cp_buttons-red;
|
||||
border-color: @cp_buttons-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-notif-empty {
|
||||
display: none;
|
||||
margin-bottom: 2px;
|
||||
border-radius: @variables_radius;
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
padding: 0.25rem 0.5rem;
|
||||
line-height: 30px;
|
||||
}
|
||||
.cp-calendar-notif-list:empty ~ .cp-notif-empty {
|
||||
display: block;
|
||||
}
|
||||
.cp-calendar-notif-form {
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
// margin-bottom: 20px;
|
||||
input {
|
||||
width: 80px;
|
||||
margin-right: 5px;
|
||||
|
@ -270,13 +349,118 @@
|
|||
}
|
||||
|
||||
.cp-calendar-close {
|
||||
top: 17px;
|
||||
right: 17px;
|
||||
height: auto;
|
||||
margin-right: 0px;
|
||||
line-height: initial;
|
||||
border: 1px solid;
|
||||
&:not(:hover) {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cp-calendar-rec-inline, .cp-calendar-rec-block {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.cp-calendar-rec-inline {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
& > *:not(:first-child) { margin-left: 5px; }
|
||||
.cp-dropdown-container {
|
||||
position: unset;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 80px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.cp-checkmark {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
.cp-calendar-rec-block {
|
||||
.cp-calendar-rec-block-title {
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
.cp-radio {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
input[type="radio"]:not(:checked) ~ .cp-checkmark-label {
|
||||
input {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
.cp-checkmark-label {
|
||||
& > *:not(:first-child) { margin-left: 5px; }
|
||||
width: 100%;
|
||||
//height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > input {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
input {
|
||||
display: inline;
|
||||
height: 24px !important;
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 200px !important;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 80px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
#cp-calendar-rec-monthly-pick ~ .cp-checkmark-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
button.cp-calendar-pick-el {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
div.cp-calendar-weekly-pick {
|
||||
button {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
div.cp-calendar-monthly-pick {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
& > div {
|
||||
display: flex;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
button {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
&.lastday {
|
||||
width: 115px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tui-full-calendar-ic-repeat-b {
|
||||
display: none;
|
||||
& ~ * {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#cp-toolbar .cp-calendar-browse {
|
||||
|
@ -395,6 +579,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: @variables_radius;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
&.cp-active {
|
||||
background-color: @cp_sidebar-left-item-bg;
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
// Calendars will be exported using this format instead of plain text.
|
||||
define([
|
||||
'/customize/pages.js',
|
||||
], function (Pages) {
|
||||
'/common/common-util.js',
|
||||
'/calendar/recurrence.js'
|
||||
], function (Pages, Util, Rec) {
|
||||
var module = {};
|
||||
|
||||
var getICSDate = function (str) {
|
||||
|
@ -57,24 +59,63 @@ define([
|
|||
var data = content[uid];
|
||||
// DTSTAMP: now...
|
||||
// UID: uid
|
||||
var getDT = function (data) {
|
||||
var start, end;
|
||||
if (data.isAllDay && data.startDay && data.endDay) {
|
||||
if (data.isAllDay) {
|
||||
var startDate = new Date(data.start);
|
||||
var endDate = new Date(data.end);
|
||||
data.startDay = data.startDay || (startDate.getFullYear() + '-' + (startDate.getMonth()+1) + '-' + startDate.getDate());
|
||||
data.endDay = data.endDay || (endDate.getFullYear() + '-' + (endDate.getMonth()+1) + '-' + endDate.getDate());
|
||||
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
|
||||
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
|
||||
} else {
|
||||
start = "DTSTART:"+getICSDate(data.start);
|
||||
end = "DTEND:"+getICSDate(data.end);
|
||||
}
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
};
|
||||
|
||||
Array.prototype.push.apply(ICS, [
|
||||
var getRRule = function (data) {
|
||||
if (!data.recurrenceRule || !data.recurrenceRule.freq) { return; }
|
||||
var r = data.recurrenceRule;
|
||||
var rrule = "RRULE:";
|
||||
rrule += "FREQ="+r.freq.toUpperCase();
|
||||
Object.keys(r).forEach(function (k) {
|
||||
if (k === "freq") { return; }
|
||||
if (k === "by") {
|
||||
Object.keys(r.by).forEach(function (_k) {
|
||||
rrule += ";BY"+_k.toUpperCase()+"="+r.by[_k];
|
||||
});
|
||||
return;
|
||||
}
|
||||
rrule += ";"+k.toUpperCase()+"="+r[k];
|
||||
});
|
||||
return rrule;
|
||||
};
|
||||
|
||||
|
||||
|
||||
var addEvent = function (arr, data, recId) {
|
||||
var uid = data.id;
|
||||
var dt = getDT(data);
|
||||
var start = dt.start;
|
||||
var end = dt.end;
|
||||
var rrule = getRRule(data);
|
||||
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTAMP:'+getICSDate(+new Date()),
|
||||
'UID:'+uid,
|
||||
start,
|
||||
end,
|
||||
recId,
|
||||
rrule,
|
||||
'SUMMARY:'+ data.title,
|
||||
'LOCATION:'+ data.location,
|
||||
]);
|
||||
].filter(Boolean));
|
||||
|
||||
if (Array.isArray(data.reminders)) {
|
||||
data.reminders.forEach(function (valueMin) {
|
||||
|
@ -91,7 +132,7 @@ define([
|
|||
if (hours || minutes || seconds) {
|
||||
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
|
||||
}
|
||||
Array.prototype.push.apply(ICS, [
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VALARM',
|
||||
'ACTION:DISPLAY',
|
||||
'DESCRIPTION:This is an event reminder',
|
||||
|
@ -102,15 +143,113 @@ define([
|
|||
}
|
||||
|
||||
if (Array.isArray(data.cp_hidden)) {
|
||||
Array.prototype.push.apply(ICS, data.cp_hidden);
|
||||
Array.prototype.push.apply(arr, data.cp_hidden);
|
||||
}
|
||||
|
||||
ICS.push('END:VEVENT');
|
||||
arr.push('END:VEVENT');
|
||||
};
|
||||
|
||||
|
||||
var applyChanges = function (base, changes) {
|
||||
var applyDiff = function (obj, k) {
|
||||
var diff = obj[k]; // Diff is always compared to origin start/end
|
||||
var d = new Date(base[k]);
|
||||
d.setDate(d.getDate() + diff.d);
|
||||
d.setHours(d.getHours() + diff.h);
|
||||
d.setMinutes(d.getMinutes() + diff.m);
|
||||
base[k] = +d;
|
||||
};
|
||||
Object.keys(changes || {}).forEach(function (k) {
|
||||
if (k === "start" || k === "end") {
|
||||
return applyDiff(changes, k);
|
||||
}
|
||||
base[k] = changes[k];
|
||||
});
|
||||
};
|
||||
|
||||
var prev = data;
|
||||
|
||||
// Check if we have "one-time" or "from date" updates.
|
||||
// "One-time" updates will be added accordingly to the ICS specs
|
||||
// "From date" updates will be added as new events and will add
|
||||
// an "until" value to the initial event's RRULE
|
||||
var toAdd = [];
|
||||
if (data.recurrenceRule && data.recurrenceRule.freq && data.recUpdate) {
|
||||
var ru = data.recUpdate;
|
||||
var _all = {};
|
||||
var duration = data.end - data.start;
|
||||
|
||||
var all = Rec.getAllOccurrences(data); // "false" if infinite
|
||||
|
||||
Object.keys(ru.from || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.from[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].from = ru.from[d];
|
||||
});
|
||||
Object.keys(ru.one || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.one[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].one = ru.one[d];
|
||||
});
|
||||
Object.keys(_all).sort(function (a, b) {
|
||||
return Number(a) - Number(b);
|
||||
}).forEach(function (d) {
|
||||
d = Number(d);
|
||||
var r = prev.recurrenceRule;
|
||||
|
||||
// This rule won't apply if we've reached "until" or "count"
|
||||
var idx = all && all.indexOf(d);
|
||||
if (all && idx === -1) {
|
||||
// Make sure we don't have both count and until
|
||||
if (all.length === r.count) { delete r.until; }
|
||||
else { delete r.count; }
|
||||
return;
|
||||
}
|
||||
|
||||
var ud = _all[d];
|
||||
|
||||
if (ud.from) { // "From" updates are not supported by ICS: make a new event
|
||||
var _new = Util.clone(prev);
|
||||
r.until = getICSDate(d - 1); // Stop previous recursion
|
||||
delete r.count;
|
||||
addEvent(ICS, prev, null); // Add previous event
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
toAdd = [];
|
||||
prev = _new;
|
||||
if (all) { all = all.slice(idx); }
|
||||
|
||||
// if we updated the recurrence rule, count is reset, nothing to do
|
||||
// if we didn't update the recurrence, we need to fix the count
|
||||
var _r = _new.recurrenceRule;
|
||||
if (all && !ud.from.recurrenceRule && _r && _r.count) {
|
||||
_r.count -= idx;
|
||||
}
|
||||
|
||||
prev.start = d;
|
||||
prev.end = d + duration;
|
||||
prev.id = Util.uid();
|
||||
applyChanges(prev, ud.from);
|
||||
duration = prev.end - prev.start;
|
||||
}
|
||||
if (ud.one) { // Add update
|
||||
var _one = Util.clone(prev);
|
||||
_one.start = d;
|
||||
_one.end = d + duration;
|
||||
applyChanges(_one, ud.one);
|
||||
var recId = "RECURRENCE-ID:"+getICSDate(+d);
|
||||
delete _one.recurrenceRule;
|
||||
addEvent(toAdd, _one, recId); // Add updated event
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addEvent(ICS, prev);
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
});
|
||||
|
||||
ICS.push('END:VCALENDAR');
|
||||
|
||||
return new Blob([ ICS.join('\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
return new Blob([ ICS.join('\r\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
};
|
||||
|
||||
module.import = function (content, id, cb) {
|
||||
|
@ -171,7 +310,7 @@ define([
|
|||
}
|
||||
|
||||
// Store other properties
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp'];
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp', 'rrule', 'recurrence-id'];
|
||||
var hidden = [];
|
||||
ev.getAllProperties().forEach(function (p) {
|
||||
if (used.indexOf(p.name) !== -1) { return; }
|
||||
|
@ -192,8 +331,25 @@ define([
|
|||
if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); }
|
||||
});
|
||||
|
||||
// Get recurrence rule
|
||||
var rrule = ev.getFirstPropertyValue('rrule');
|
||||
var rec;
|
||||
if (rrule && rrule.freq) {
|
||||
rec = {};
|
||||
rec.freq = rrule.freq.toLowerCase();
|
||||
if (rrule.interval) { rec.interval = rrule.interval; }
|
||||
if (rrule.count) { rec.count = rrule.count; }
|
||||
if (Object.keys(rrule).includes('wkst')) { rec.wkst = (rrule.wkst + 6) % 7; }
|
||||
if (rrule.until) { rec.until = +new Date(rrule.until); }
|
||||
Object.keys(rrule.parts || {}).forEach(function (k) {
|
||||
rec.by = rec.by || {};
|
||||
var _k = k.toLowerCase().slice(2); // "BYDAY" ==> "day"
|
||||
rec.by[_k] = rrule.parts[k];
|
||||
});
|
||||
}
|
||||
|
||||
// Create event
|
||||
res[uid] = {
|
||||
var obj = {
|
||||
calendarId: id,
|
||||
id: uid,
|
||||
category: 'time',
|
||||
|
@ -203,16 +359,49 @@ define([
|
|||
start: start,
|
||||
end: end,
|
||||
reminders: reminders,
|
||||
cp_hidden: hidden
|
||||
cp_hidden: hidden,
|
||||
};
|
||||
if (rec) { obj.recurrenceRule = rec; }
|
||||
|
||||
if (!hidden.length) { delete res[uid].cp_hidden; }
|
||||
if (!reminders.length) { delete res[uid].reminders; }
|
||||
if (!hidden.length) { delete obj.cp_hidden; }
|
||||
if (!reminders.length) { delete obj.reminders; }
|
||||
|
||||
var recId = ev.getFirstPropertyValue('recurrence-id');
|
||||
if (recId) {
|
||||
setTimeout(function () {
|
||||
if (!res[uid]) { return; }
|
||||
var old = res[uid];
|
||||
var time = +new Date(recId);
|
||||
var diff = {};
|
||||
var from = {};
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
if (JSON.stringify(old[k]) === JSON.stringify(obj[k])) { return; }
|
||||
if (['start','end'].includes(k)) {
|
||||
diff[k] = Rec.diffDate(old[k], obj[k]);
|
||||
return;
|
||||
}
|
||||
if (k === "recurrenceRule") {
|
||||
from[k] = obj[k];
|
||||
return;
|
||||
}
|
||||
diff[k] = obj[k];
|
||||
});
|
||||
old.recUpdate = old.recUpdate || {one:{},from:{}};
|
||||
if (Object.keys(from).length) { old.recUpdate.from[time] = from; }
|
||||
if (Object.keys(diff).length) { old.recUpdate.one[time] = diff; }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res[uid] = obj;
|
||||
});
|
||||
|
||||
// setTimeout to make sure we call back after the "recurrence-id" setTimeout
|
||||
// are called
|
||||
setTimeout(function () {
|
||||
cb(null, res);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return module;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,13 +6,21 @@ define([
|
|||
'/common/sframe-common-outer.js',
|
||||
], function (nThen, ApiConfig, DomReady, SFCommonO) {
|
||||
|
||||
// Loaded in load #2
|
||||
var hash, href;
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
SFCommonO.initIframe(waitFor);
|
||||
var obj = SFCommonO.initIframe(waitFor, true);
|
||||
href = obj.href;
|
||||
hash = obj.hash;
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var addData = function (meta) {
|
||||
var addData = function (meta, Cryptpad, user, Utils) {
|
||||
if (hash) {
|
||||
var parsed = Utils.Hash.parsePadUrl(href);
|
||||
if (parsed.hashData && parsed.hashData.newPadOpts) {
|
||||
meta.calendarOpts = Utils.Hash.decodeDataOptions(parsed.hashData.newPadOpts);
|
||||
}
|
||||
}
|
||||
meta.calendarHash = Boolean(window.location.hash);
|
||||
};
|
||||
SFCommonO.start({
|
||||
|
|
|
@ -0,0 +1,869 @@
|
|||
define([
|
||||
'/common/common-util.js',
|
||||
], function (Util) {
|
||||
var Rec = {};
|
||||
|
||||
var debug = function () {};
|
||||
|
||||
// Get week number with any "WKST" (firts day of the week)
|
||||
// Week 1 is the first week of the year containing at least 4 days in this year
|
||||
// It depends on which day is considered the first day of the week (default Monday)
|
||||
// In our case, wkst is a number matching the JS rule: 0 == Sunday
|
||||
var getWeekNo = Rec.getWeekNo = function (date, wkst) {
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
|
||||
var newYear = new Date(date.getFullYear(),0,1);
|
||||
var day = newYear.getDay() - wkst; //the day of week the year begins on
|
||||
day = (day >= 0 ? day : day + 7);
|
||||
var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1;
|
||||
var weeknum;
|
||||
// Week 1 / week 53
|
||||
if (day < 4) {
|
||||
weeknum = Math.floor((daynum+day-1)/7) + 1;
|
||||
if (weeknum > 52) {
|
||||
var nYear = new Date(date.getFullYear() + 1,0,1);
|
||||
var nday = nYear.getDay() - wkst;
|
||||
nday = nday >= 0 ? nday : nday + 7;
|
||||
weeknum = nday < 4 ? 1 : 53;
|
||||
}
|
||||
}
|
||||
else {
|
||||
weeknum = Math.floor((daynum+day-1)/7);
|
||||
}
|
||||
return weeknum;
|
||||
};
|
||||
|
||||
var getYearDay = function (date) {
|
||||
var start = new Date(date.getFullYear(), 0, 0);
|
||||
var diff = (date - start) +
|
||||
((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000);
|
||||
var oneDay = 1000 * 60 * 60 * 24;
|
||||
return Math.floor(diff / oneDay);
|
||||
};
|
||||
var setYearDay = function (date, day) {
|
||||
if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; }
|
||||
if (day < 0) {
|
||||
var max = getYearDay(new Date(date.getFullYear(), 11, 31));
|
||||
day = max + day + 1;
|
||||
}
|
||||
date.setMonth(0);
|
||||
date.setDate(day);
|
||||
return true;
|
||||
};
|
||||
|
||||
var getEndData = function (s, e) {
|
||||
if (s > e) { return void console.error("Wrong data"); }
|
||||
var days;
|
||||
if (e.getFullYear() === s.getFullYear()) {
|
||||
days = getYearDay(e) - getYearDay(s);
|
||||
} else { // eYear < sYear
|
||||
var tmp = new Date(s.getFullYear(), 11, 31);
|
||||
var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st
|
||||
var de = getYearDay(e);
|
||||
days = d1 + de;
|
||||
while ((tmp.getFullYear()+1) < e.getFullYear()) {
|
||||
tmp.setFullYear(tmp.getFullYear()+1);
|
||||
days += getYearDay(tmp);
|
||||
}
|
||||
}
|
||||
return {
|
||||
h: e.getHours(),
|
||||
m: e.getMinutes(),
|
||||
days: days
|
||||
};
|
||||
};
|
||||
var setEndData = function (s, e, data) {
|
||||
e.setTime(+s);
|
||||
if (!data) { return; }
|
||||
e.setHours(data.h);
|
||||
e.setMinutes(data.m);
|
||||
e.setSeconds(0);
|
||||
e.setDate(s.getDate() + data.days);
|
||||
};
|
||||
|
||||
var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
|
||||
var getDayData = function (str) {
|
||||
var pos = Number(str.slice(0,-2));
|
||||
var day = DAYORDER.indexOf(str.slice(-2));
|
||||
return pos ? [pos, day] : day;
|
||||
};
|
||||
|
||||
var goToFirstWeekDay = function (date, wkst) {
|
||||
var d = date.getDay();
|
||||
wkst = typeof(wkst) === "number" ? wkst : 1;
|
||||
if (d >= wkst) {
|
||||
date.setDate(date.getDate() - (d-wkst));
|
||||
} else {
|
||||
date.setDate(date.getDate() - (7+d-wkst));
|
||||
}
|
||||
};
|
||||
|
||||
var getDateStr = function (date) {
|
||||
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
|
||||
};
|
||||
var FREQ = {};
|
||||
FREQ['daily'] = function (s, i) {
|
||||
s.setDate(s.getDate()+i);
|
||||
};
|
||||
FREQ['weekly'] = function (s,i) {
|
||||
s.setDate(s.getDate()+(i*7));
|
||||
};
|
||||
FREQ['monthly'] = function (s,i) {
|
||||
s.setMonth(s.getMonth()+i);
|
||||
};
|
||||
FREQ['yearly'] = function (s,i) {
|
||||
s.setFullYear(s.getFullYear()+i);
|
||||
};
|
||||
|
||||
// EXPAND is used to create iterations added from a BYxxx rule
|
||||
// dateA is the start date and b is the number or id of the BYxxx rule item
|
||||
var EXPAND = {};
|
||||
EXPAND['month'] = function (dateS, origin, b) {
|
||||
var oS = new Date(origin.start);
|
||||
var a = dateS.getMonth() + 1;
|
||||
var toAdd = (b-a+12)%12;
|
||||
var m = dateS.getMonth() + toAdd;
|
||||
dateS.setMonth(m);
|
||||
dateS.setDate(oS.getDate());
|
||||
if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
EXPAND['weekno'] = function (dateS, origin, week, rule) {
|
||||
var wkst = rule && rule.wkst;
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
var oS = new Date(origin.start);
|
||||
|
||||
var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st
|
||||
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
|
||||
|
||||
var doubleOne = lastW === 1;
|
||||
if (lastW === 1) { lastW = 52; }
|
||||
|
||||
var a = getWeekNo(dateS, wkst);
|
||||
if (!week || week > lastW) { return false; } // Week 53 may not exist this year
|
||||
|
||||
if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive
|
||||
|
||||
var toAdd = week - a;
|
||||
var weekS = new Date(+dateS);
|
||||
// Go to the selected week
|
||||
weekS.setDate(weekS.getDate() + (toAdd * 7));
|
||||
goToFirstWeekDay(weekS, wkst);
|
||||
|
||||
// Then make sure we are in the correct start day
|
||||
var all = 'aaaaaaa'.split('').map(function (o, i) {
|
||||
var date = new Date(+weekS);
|
||||
date.setDate(date.getDate() + i);
|
||||
if (date.getFullYear() !== dateS.getFullYear()) { return; }
|
||||
return date.toLocaleDateString() !== oS.toLocaleDateString() && date;
|
||||
}).filter(Boolean);
|
||||
|
||||
// If we're looking for week 1 and the last week is a week 1, add the days
|
||||
if (week === 1 && doubleOne) {
|
||||
goToFirstWeekDay(lastD, wkst);
|
||||
'aaaaaaa'.split('').some(function (o, i) {
|
||||
var date = new Date(+lastD);
|
||||
date.setDate(date.getDate() + i);
|
||||
if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; }
|
||||
if (date.getFullYear() > dateS.getFullYear()) { return true; }
|
||||
all.push(date);
|
||||
});
|
||||
}
|
||||
|
||||
return all.length ? all : undefined;
|
||||
};
|
||||
EXPAND['yearday'] = function (dateS, origin, b) {
|
||||
var y = dateS.getFullYear();
|
||||
var state = setYearDay(dateS, b);
|
||||
if (!state) { return; } // Invalid day "b"
|
||||
if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year
|
||||
return true;
|
||||
};
|
||||
EXPAND['monthday'] = function (dateS, origin, b, rule) {
|
||||
if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; }
|
||||
|
||||
var setMonthDay = function (date, day) {
|
||||
var m = date.getMonth();
|
||||
if (day < 0) {
|
||||
var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day
|
||||
day = tmp.getDate() + day + 1;
|
||||
}
|
||||
date.setDate(day);
|
||||
return date.getMonth() === m; // Don't push if day 31 moved us to the next month
|
||||
|
||||
};
|
||||
|
||||
// Monthly events
|
||||
if (rule.freq === 'monthly') {
|
||||
return setMonthDay(dateS, b);
|
||||
}
|
||||
|
||||
var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) {
|
||||
var date = new Date(dateS.getFullYear(), i, 1);
|
||||
var ok = setMonthDay(date, b);
|
||||
return ok ? date : undefined;
|
||||
}).filter(Boolean);
|
||||
return all.length ? all : undefined;
|
||||
};
|
||||
EXPAND['day'] = function (dateS, origin, b, rule) {
|
||||
// Here "b" can be a single day ("TU") or a position and a day ("1MO")
|
||||
var day = getDayData(b);
|
||||
var pos;
|
||||
if (Array.isArray(day)) {
|
||||
pos = day[0];
|
||||
day = day[1];
|
||||
}
|
||||
|
||||
var all = [];
|
||||
if (![0,1,2,3,4,5,6].includes(day)) { return false; }
|
||||
|
||||
var filterPos = function (m) {
|
||||
if (!pos) { return; }
|
||||
|
||||
var _all = [];
|
||||
'aaaaaaaaaaaa'.split('').some(function (a, i) {
|
||||
if (typeof(m) !== "undefined" && i !== m) { return; }
|
||||
|
||||
var _pos;
|
||||
var tmp = all.filter(function (d) {
|
||||
return d.getMonth() === i;
|
||||
});
|
||||
if (pos < 0) {
|
||||
_pos = tmp.length + pos;
|
||||
} else {
|
||||
_pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
|
||||
}
|
||||
_all.push(tmp[_pos]);
|
||||
|
||||
return typeof(m) !== "undefined" && i === m;
|
||||
});
|
||||
all = _all.filter(Boolean); // The "5th" {day} won't always exist
|
||||
};
|
||||
|
||||
var tmp;
|
||||
if (rule.freq === 'yearly') {
|
||||
tmp = new Date(+dateS);
|
||||
var y = dateS.getFullYear();
|
||||
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
|
||||
while (tmp.getFullYear() === y) {
|
||||
all.push(new Date(+tmp));
|
||||
tmp.setDate(tmp.getDate()+7);
|
||||
}
|
||||
filterPos();
|
||||
return all;
|
||||
}
|
||||
|
||||
if (rule.freq === 'monthly') {
|
||||
tmp = new Date(+dateS);
|
||||
var m = dateS.getMonth();
|
||||
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
|
||||
while (tmp.getMonth() === m) {
|
||||
all.push(new Date(+tmp));
|
||||
tmp.setDate(tmp.getDate()+7);
|
||||
}
|
||||
filterPos(m);
|
||||
return all;
|
||||
}
|
||||
|
||||
if (rule.freq === 'weekly') {
|
||||
while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var LIMIT = {};
|
||||
LIMIT['month'] = function (events, rule) {
|
||||
return events.filter(function (s) {
|
||||
return rule.includes(s.getMonth()+1);
|
||||
});
|
||||
};
|
||||
LIMIT['weekno'] = function (events, weeks, rules) {
|
||||
return events.filter(function (s) {
|
||||
var wkst = rules && rules.wkst;
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
|
||||
var lastD = new Date(s.getFullYear(), 11, 31); // December 31st
|
||||
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
|
||||
if (lastW === 1) { lastW = 52; }
|
||||
|
||||
var w = getWeekNo(s, wkst);
|
||||
|
||||
return weeks.some(function (week) {
|
||||
if (week > 0) { return week === w; }
|
||||
return w === (lastW + week + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['yearday'] = function (events, days) {
|
||||
return events.filter(function (s) {
|
||||
var d = getYearDay(s);
|
||||
var max = getYearDay(new Date(s.getFullYear(), 11, 31));
|
||||
|
||||
return days.some(function (day) {
|
||||
if (day > 0) { return day === d; }
|
||||
return d === (max + day + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['monthday'] = function (events, rule) {
|
||||
return events.filter(function (s) {
|
||||
var r = Util.clone(rule);
|
||||
// Transform the negative monthdays into positive for this specific month
|
||||
r = r.map(function (b) {
|
||||
if (b < 0) {
|
||||
var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day
|
||||
b = tmp.getDate() + b + 1;
|
||||
}
|
||||
return b;
|
||||
});
|
||||
return r.includes(s.getDate());
|
||||
});
|
||||
};
|
||||
LIMIT['day'] = function (events, days, rules) {
|
||||
return events.filter(function (s) {
|
||||
var dayStr = s.toLocaleDateString();
|
||||
|
||||
// Check how to handle position in BYDAY rules (last day of the month or the year?)
|
||||
var type = 'yearly';
|
||||
if (rules.freq === 'monthly' ||
|
||||
(rules.freq === 'yearly' && rules.by && rules.by.month)) {
|
||||
type = 'monthly';
|
||||
}
|
||||
|
||||
// Check if this event matches one of the allowed days
|
||||
return days.some(function (r) {
|
||||
// rule elements are strings with pos and day
|
||||
var day = getDayData(r);
|
||||
var pos;
|
||||
if (Array.isArray(day)) {
|
||||
pos = day[0];
|
||||
day = day[1];
|
||||
}
|
||||
if (!pos) {
|
||||
return s.getDay() === day;
|
||||
}
|
||||
|
||||
// If we have a position, we can use EXPAND.day to get the nth {day} of the
|
||||
// year/month and compare if it matches with
|
||||
var d = new Date(s.getFullYear(), s.getMonth(), 1);
|
||||
if (type === 'yearly') { d.setMonth(0); }
|
||||
var res = EXPAND["day"](d, {}, r, {freq: type});
|
||||
return res.some(function (date) {
|
||||
return date.toLocaleDateString() === dayStr;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['setpos'] = function (events, rule) {
|
||||
var init = events.slice();
|
||||
var rules = Util.deduplicateString(rule.slice().map(function (n) {
|
||||
if (n > 0) { return (n-1); }
|
||||
if (n === 0) { return; }
|
||||
return init.length + n;
|
||||
}));
|
||||
return events.filter(function (ev) {
|
||||
var idx = init.indexOf(ev);
|
||||
return rules.includes(idx);
|
||||
});
|
||||
};
|
||||
|
||||
var BYORDER = ['month','weekno','yearday','monthday','day'];
|
||||
var BYDAYORDER = ['month','monthday','day'];
|
||||
|
||||
Rec.getMonthId = function (d) {
|
||||
return d.getFullYear() + '-' + d.getMonth();
|
||||
};
|
||||
var cache = window.CP_calendar_cache = {};
|
||||
var recurringAcross = {};
|
||||
Rec.resetCache = function () {
|
||||
cache = window.CP_calendar_cache = {};
|
||||
recurringAcross = {};
|
||||
};
|
||||
|
||||
var iterate = function (rule, _origin, s) {
|
||||
// "origin" is the original event to detect the start of BYxxx
|
||||
var origin = Util.clone(_origin);
|
||||
var oS = new Date(origin.start);
|
||||
|
||||
var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
|
||||
|
||||
// "uid" is used for the cache
|
||||
var uid = s.toLocaleDateString();
|
||||
cache[id] = cache[id] || {};
|
||||
|
||||
var inter = rule.interval || 1;
|
||||
var freq = rule.freq;
|
||||
|
||||
var all = [];
|
||||
var limit = function (byrule, n) {
|
||||
all = LIMIT[byrule](all, n, rule);
|
||||
};
|
||||
var expand = function (byrule) {
|
||||
return function (n) {
|
||||
// Set the start date at the beginning of the current FREQ
|
||||
var _s = new Date(+s);
|
||||
if (rule.freq === 'yearly') {
|
||||
// January 1st
|
||||
_s.setMonth(0);
|
||||
_s.setDate(1);
|
||||
} else if (rule.freq === 'monthly') {
|
||||
_s.setDate(1);
|
||||
} else if (rule.freq === 'weekly') {
|
||||
goToFirstWeekDay(_s, rule.wkst);
|
||||
} else if (rule.freq === 'daily') {
|
||||
// We don't have < byday rules so we can't expand daily rules
|
||||
}
|
||||
|
||||
var add = EXPAND[byrule](_s, origin, n, rule);
|
||||
|
||||
if (!add) { return; }
|
||||
|
||||
if (Array.isArray(add)) {
|
||||
add = add.filter(function (dateS) {
|
||||
return dateS.toLocaleDateString() !== oS.toLocaleDateString();
|
||||
});
|
||||
Array.prototype.push.apply(all, add);
|
||||
} else {
|
||||
if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; }
|
||||
all.push(_s);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Manage interval for the next iteration
|
||||
var it = Util.once(function () {
|
||||
FREQ[freq](s, inter);
|
||||
});
|
||||
var addDefault = function () {
|
||||
if (freq === "monthly") {
|
||||
s.setDate(15);
|
||||
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
|
||||
s.setDate(28);
|
||||
}
|
||||
|
||||
it();
|
||||
|
||||
var _s = new Date(+s);
|
||||
if (freq === "monthly" || freq === "yearly") {
|
||||
_s.setDate(oS.getDate());
|
||||
if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist
|
||||
if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; }
|
||||
|
||||
// FIXME if there is a recUpdate that moves the 31st to the 30th, the event
|
||||
// will still only be displayed on months with 31 days
|
||||
}
|
||||
all.push(_s);
|
||||
};
|
||||
|
||||
if (Array.isArray(cache[id][uid])) {
|
||||
debug('Get cache', id, uid);
|
||||
if (freq === "monthly") {
|
||||
s.setDate(15);
|
||||
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
|
||||
s.setDate(28);
|
||||
}
|
||||
it();
|
||||
return cache[id][uid];
|
||||
}
|
||||
|
||||
if (rule.by && freq === 'yearly') {
|
||||
var order = BYORDER.slice();
|
||||
var monthLimit = false;
|
||||
if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) {
|
||||
order.shift();
|
||||
monthLimit = true;
|
||||
}
|
||||
var first = true;
|
||||
order.forEach(function (_order) {
|
||||
var r = rule.by[_order];
|
||||
if (!r) { return; }
|
||||
if (first) {
|
||||
r.forEach(expand(_order));
|
||||
first = false;
|
||||
} else if (_order === "day") {
|
||||
if (rule.by.yearday || rule.by.monthday || rule.by.weekno) {
|
||||
limit('day', rule.by.day);
|
||||
} else {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
} else {
|
||||
limit(_order, r);
|
||||
}
|
||||
});
|
||||
if (rule.by.month && monthLimit) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'monthly') {
|
||||
// We're going to compute all the entries for the coming month
|
||||
if (!rule.by.monthday && !rule.by.day) {
|
||||
addDefault();
|
||||
} else if (rule.by.monthday) {
|
||||
rule.by.monthday.forEach(expand('monthday'));
|
||||
} else if (rule.by.day) {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
if (rule.by.month) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
if (rule.by.day && rule.by.monthday) {
|
||||
limit('day', rule.by.day);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'weekly') {
|
||||
// We're going to compute all the entries for the coming week
|
||||
if (!rule.by.day) {
|
||||
addDefault();
|
||||
} else {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
if (rule.by.month) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'daily') {
|
||||
addDefault();
|
||||
BYDAYORDER.forEach(function (_order) {
|
||||
var r = rule.by[_order];
|
||||
if (!r) { return; }
|
||||
limit(_order, r);
|
||||
});
|
||||
}
|
||||
|
||||
all.sort(function (a, b) {
|
||||
return a-b;
|
||||
});
|
||||
|
||||
if (rule.by && rule.by.setpos) {
|
||||
limit('setpos', rule.by.setpos);
|
||||
}
|
||||
|
||||
if (!rule.by || !Object.keys(rule.by).length) {
|
||||
addDefault();
|
||||
} else {
|
||||
it();
|
||||
}
|
||||
|
||||
|
||||
var done = [];
|
||||
all = all.filter(function (newS) {
|
||||
var start = new Date(+newS).toLocaleDateString();
|
||||
if (done.includes(start)) { return false; }
|
||||
done.push(start);
|
||||
return true;
|
||||
});
|
||||
|
||||
debug('Set cache', id, uid);
|
||||
cache[id][uid] = all;
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
var getNextRules = function (obj) {
|
||||
if (!obj.recUpdate) { return []; }
|
||||
var _allRules = {};
|
||||
var _obj = obj.recUpdate.from;
|
||||
Object.keys(_obj || {}).forEach(function (d) {
|
||||
var u = _obj[d];
|
||||
if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; }
|
||||
});
|
||||
return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); })
|
||||
.map(function (k) {
|
||||
var r = Util.clone(_allRules[k]);
|
||||
if (!FREQ[r.freq]) { return; }
|
||||
if (r.interval && r.interval < 1) { return; }
|
||||
r._start = Number(k);
|
||||
return r;
|
||||
}).filter(Boolean);
|
||||
};
|
||||
Rec.getRecurring = function (months, events) {
|
||||
if (window.CP_DEV_MODE) { debug = console.warn; }
|
||||
|
||||
var toAdd = [];
|
||||
months.forEach(function (monthId) {
|
||||
// from 1st day of the month at 00:00 to last day at 23:59:59:999
|
||||
var ms = monthId.split('-');
|
||||
var _startMonth = new Date(ms[0], ms[1]);
|
||||
var _endMonth = new Date(+_startMonth);
|
||||
_endMonth.setMonth(_endMonth.getMonth() + 1);
|
||||
_endMonth.setMilliseconds(-1);
|
||||
|
||||
debug('Compute month', _startMonth.toLocaleDateString());
|
||||
|
||||
var rec = events || [];
|
||||
rec.forEach(function (obj) {
|
||||
var _start = new Date(obj.start);
|
||||
var _end = new Date(obj.end);
|
||||
var _origin = obj;
|
||||
var rule = obj.recurrenceRule;
|
||||
if (!rule) { return; }
|
||||
|
||||
var nextRules = getNextRules(obj);
|
||||
var nextRule = nextRules.shift();
|
||||
|
||||
if (_start >= _endMonth) { return; }
|
||||
|
||||
// Check the "until" date of the latest rule we can use and stop now
|
||||
// if the recurrence ends before the current month
|
||||
var until = rule.until;
|
||||
var _nextRules = nextRules.slice();
|
||||
var _nextRule = nextRule;
|
||||
while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) {
|
||||
until = nextRule.until;
|
||||
_nextRule = _nextRules.shift();
|
||||
}
|
||||
if (until < _startMonth) { return; }
|
||||
|
||||
var endData = getEndData(_start, _end);
|
||||
|
||||
if (rule.interval && rule.interval < 1) { return; }
|
||||
if (!FREQ[rule.freq]) { return; }
|
||||
|
||||
/*
|
||||
// Rule examples
|
||||
rule.by = {
|
||||
//month: [1, 4, 5, 8, 12],
|
||||
//weekno: [1, 2, 4, 5, 32, 34, 35, 50],
|
||||
//yearday: [1, 2, 29, 30, -2, -1, 250],
|
||||
//monthday: [1, 2, 3, -3, -2, -1],
|
||||
//day: ["MO", "WE", "FR"],
|
||||
//setpos: [1, 2, -1, -2]
|
||||
};
|
||||
rule.wkst = 0;
|
||||
rule.interval = 2;
|
||||
rule.freq = 'yearly';
|
||||
rule.count = 10;
|
||||
*/
|
||||
debug('Iterate over', obj.title, obj);
|
||||
debug('Use rule', rule);
|
||||
|
||||
var count = rule.count;
|
||||
var c = 1;
|
||||
|
||||
var next = function (start) {
|
||||
var evS = new Date(+start);
|
||||
|
||||
if (count && c >= count) { return; }
|
||||
|
||||
debug('Start iteration', evS.toLocaleDateString());
|
||||
|
||||
var _toAdd = iterate(rule, obj, evS);
|
||||
|
||||
debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();})));
|
||||
|
||||
// Make sure to continue if the current year doesn't provide any result
|
||||
if (!_toAdd.length) {
|
||||
if (evS.getFullYear() < _startMonth.getFullYear() ||
|
||||
evS < _endMonth) {
|
||||
return void next(evS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var stop = false;
|
||||
var newrule = false;
|
||||
_toAdd.some(function (_newS) {
|
||||
// Make event with correct start and end time
|
||||
var _ev = Util.clone(obj);
|
||||
_ev.id = _origin.id + '|' + (+_newS);
|
||||
var _evS = new Date(+_newS);
|
||||
var _evE = new Date(+_newS);
|
||||
setEndData(_evS, _evE, endData);
|
||||
_ev.start = +_evS;
|
||||
_ev.end = +_evE;
|
||||
_ev._count = c;
|
||||
if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
|
||||
if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }
|
||||
|
||||
if (nextRule && _ev.start === nextRule._start) {
|
||||
newrule = true;
|
||||
}
|
||||
|
||||
var useNewRule = function () {
|
||||
if (!newrule) { return; }
|
||||
debug('Use new rule', nextRule);
|
||||
_ev._count = c;
|
||||
count = nextRule.count;
|
||||
c = 1;
|
||||
evS = +_evS;
|
||||
obj = _ev;
|
||||
rule = nextRule;
|
||||
nextRule = nextRules.shift();
|
||||
};
|
||||
|
||||
|
||||
if (c >= count) { // Limit reached
|
||||
debug(_evS.toLocaleDateString(), 'count');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (_evS >= _endMonth) { // Won't affect us anymore
|
||||
debug(_evS.toLocaleDateString(), 'endMonth');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (rule.until && _evS > rule.until) {
|
||||
debug(_evS.toLocaleDateString(), 'until');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (_evS < _start) { // "Expand" rules may create events before the _start
|
||||
debug(_evS.toLocaleDateString(), 'start');
|
||||
return;
|
||||
}
|
||||
c++;
|
||||
if (_evE < _startMonth) { // Ended before the current month
|
||||
// Nothing to display but continue the recurrence
|
||||
debug(_evS.toLocaleDateString(), 'startMonth');
|
||||
if (newrule) { useNewRule(); }
|
||||
return;
|
||||
}
|
||||
// If a recurring event start and end in different months, make sure
|
||||
// it is only added once
|
||||
if ((_evS < _endMonth && _evE >= _endMonth) ||
|
||||
(_evS < _startMonth && _evE >= _startMonth)) {
|
||||
if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) {
|
||||
return;
|
||||
} else {
|
||||
recurringAcross[_ev.id] = recurringAcross[_ev.id] || [];
|
||||
recurringAcross[_ev.id].push(_ev.start);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add this event
|
||||
toAdd.push(_ev);
|
||||
if (newrule) {
|
||||
useNewRule();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!stop) { next(evS); }
|
||||
};
|
||||
next(_start);
|
||||
debug('Added this month (all events)', toAdd.map(function (ev) {
|
||||
return new Date(ev.start).toLocaleDateString();
|
||||
}));
|
||||
});
|
||||
});
|
||||
return toAdd;
|
||||
};
|
||||
Rec.getAllOccurrences = function (ev) {
|
||||
if (!ev.recurrenceRule) { return [ev.start]; }
|
||||
var r = ev.recurrenceRule;
|
||||
// In case of infinite recursion, we can't get all
|
||||
if (!r.until && !r.count) { return false; }
|
||||
var all = [ev.start];
|
||||
var d = new Date(ev.start);
|
||||
d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
|
||||
var toAdd = [];
|
||||
|
||||
var i = 0;
|
||||
var check = function () {
|
||||
return r.count ? (all.length < r.count) : (+d <= r.until);
|
||||
};
|
||||
while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
|
||||
Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
|
||||
d.setMonth(d.getMonth() + 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
Rec.diffDate = function (oldTime, newTime) {
|
||||
var n = new Date(newTime);
|
||||
var o = new Date(oldTime);
|
||||
|
||||
// Diff Days
|
||||
var d = 0;
|
||||
var mult = n < o ? -1 : 1;
|
||||
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
|
||||
n.setDate(n.getDate() - mult);
|
||||
d++;
|
||||
}
|
||||
d = mult * d;
|
||||
|
||||
// Diff hours
|
||||
n = new Date(newTime);
|
||||
var h = n.getHours() - o.getHours();
|
||||
|
||||
// Diff minutes
|
||||
var m = n.getMinutes() - o.getMinutes();
|
||||
|
||||
return {
|
||||
d: d,
|
||||
h: h,
|
||||
m: m
|
||||
};
|
||||
};
|
||||
|
||||
var sortUpdate = function (obj) {
|
||||
return Object.keys(obj).sort(function (d1, d2) {
|
||||
return Number(d1) - Number(d2);
|
||||
});
|
||||
};
|
||||
Rec.applyUpdates = function (events) {
|
||||
events.forEach(function (ev) {
|
||||
ev.raw = {
|
||||
start: ev.start,
|
||||
end: ev.end,
|
||||
};
|
||||
|
||||
if (!ev.recUpdate) { return; }
|
||||
|
||||
var from = ev.recUpdate.from || {};
|
||||
var one = ev.recUpdate.one || {};
|
||||
var s = ev.start;
|
||||
|
||||
// Add "until" date to our recurrenceRule if it has been modified in future occurences
|
||||
var nextRules = getNextRules(ev).filter(function (r) {
|
||||
return r._start > s;
|
||||
});
|
||||
var nextRule = nextRules.shift();
|
||||
|
||||
var applyDiff = function (obj, k) {
|
||||
var diff = obj[k]; // Diff is always compared to origin start/end
|
||||
var d = new Date(ev.raw[k]);
|
||||
d.setDate(d.getDate() + diff.d);
|
||||
d.setHours(d.getHours() + diff.h);
|
||||
d.setMinutes(d.getMinutes() + diff.m);
|
||||
ev[k] = +d;
|
||||
};
|
||||
|
||||
sortUpdate(from).forEach(function (d) {
|
||||
if (s < Number(d)) { return; }
|
||||
Object.keys(from[d]).forEach(function (k) {
|
||||
if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); }
|
||||
if (k === "recurrenceRule" && !from[d][k]) { return; }
|
||||
ev[k] = from[d][k];
|
||||
});
|
||||
});
|
||||
Object.keys(one[s] || {}).forEach(function (k) {
|
||||
if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); }
|
||||
if (k === "recurrenceRule" && !one[s][k]) { return; }
|
||||
ev[k] = one[s][k];
|
||||
});
|
||||
if (ev.deleted) {
|
||||
Object.keys(ev).forEach(function (k) {
|
||||
delete ev[k];
|
||||
});
|
||||
}
|
||||
|
||||
if (nextRule && ev.recurrenceRule) {
|
||||
ev.recurrenceRule._next = nextRule._start - 1;
|
||||
}
|
||||
|
||||
if (ev.reminders) {
|
||||
ev.raw.reminders = ev.reminders;
|
||||
}
|
||||
});
|
||||
return events;
|
||||
};
|
||||
|
||||
|
||||
return Rec;
|
||||
});
|
|
@ -1566,7 +1566,7 @@ define([
|
|||
console.error(err);
|
||||
}
|
||||
|
||||
return h(`div.errorcp-test-status.${obj.type}`, [
|
||||
return h(`div.error.cp-test-status.${obj.type}`, [
|
||||
h('h5', obj.message),
|
||||
h('div.table-container',
|
||||
h('table', [
|
||||
|
|
|
@ -68,6 +68,7 @@ define([
|
|||
'markdown',
|
||||
'gfm',
|
||||
'html',
|
||||
'asciidoc',
|
||||
'htmlembedded',
|
||||
'htmlmixed',
|
||||
'index.html',
|
||||
|
@ -143,6 +144,31 @@ define([
|
|||
previews['htmlmixed'] = function (val, $div, common) {
|
||||
DiffMd.apply(val, $div, common);
|
||||
};
|
||||
previews['asciidoc'] = function (val, $div, common) {
|
||||
require([
|
||||
'asciidoctor',
|
||||
'/lib/highlight/highlight.pack.js',
|
||||
'css!/lib/highlight/styles/' + (window.CryptPad_theme === 'dark' ? 'dark.css' : 'github.css')
|
||||
], function (asciidoctor) {
|
||||
var reg = asciidoctor.Extensions.create();
|
||||
var Highlight = window.hljs;
|
||||
|
||||
reg.inlineMacro('media-tag', function () {
|
||||
var t = this;
|
||||
t.process(function (parent, target) {
|
||||
var d = target.split('|');
|
||||
return t.createInline(parent, 'quoted', `<media-tag src="${d[0]}" data-crypto-key="${d[1]}"></media-tag>`).convert();
|
||||
});
|
||||
});
|
||||
|
||||
var html = asciidoctor.convert(val, { attributes: 'showtitle', extension_registry: reg });
|
||||
|
||||
DiffMd.apply(html, $div, common);
|
||||
$div.find('pre code').each(function (i, el) {
|
||||
Highlight.highlightBlock(el);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var mkPreviewPane = function (editor, CodeMirror, framework, isPresentMode) {
|
||||
var $previewContainer = $('#cp-app-code-preview');
|
||||
|
@ -370,9 +396,17 @@ define([
|
|||
evModeChange.reg(function (mode) {
|
||||
if (MEDIA_TAG_MODES.indexOf(mode) !== -1) {
|
||||
// Embedding is enabled
|
||||
framework.setMediaTagEmbedder(function (mt) {
|
||||
framework.setMediaTagEmbedder(function (mt, d) {
|
||||
editor.focus();
|
||||
editor.replaceSelection($(mt)[0].outerHTML);
|
||||
var txt = $(mt)[0].outerHTML;
|
||||
if (editor.getMode().name === "asciidoc") {
|
||||
if (d.static) {
|
||||
txt = d.href + `[${d.name}]`;
|
||||
} else {
|
||||
txt = `media-tag:${d.src}|${d.key}[]`;
|
||||
}
|
||||
}
|
||||
editor.replaceSelection(txt);
|
||||
});
|
||||
} else {
|
||||
// Embedding is disabled
|
||||
|
|
|
@ -1417,7 +1417,7 @@ define([
|
|||
return /HTML/.test(Object.prototype.toString.call(o)) &&
|
||||
typeof(o.tagName) === 'string';
|
||||
};
|
||||
var allowedTags = ['a', 'p', 'hr', 'div'];
|
||||
var allowedTags = ['a', 'li', 'p', 'hr', 'div'];
|
||||
var isValidOption = function (o) {
|
||||
if (typeof o !== "object") { return false; }
|
||||
if (isElement(o)) { return true; }
|
||||
|
@ -1541,6 +1541,7 @@ define([
|
|||
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
|
||||
if (config.isSelect && value) {
|
||||
// We use JSON.stringify here to escape quotes
|
||||
if (typeof(value) === "object") { value = JSON.stringify(value); }
|
||||
var $val = $innerblock.find('[data-value='+JSON.stringify(value)+']');
|
||||
setActive($val);
|
||||
try {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
};
|
||||
|
||||
Util.clone = function (o) {
|
||||
if (o === undefined || o === null) { return o; }
|
||||
return JSON.parse(JSON.stringify(o));
|
||||
};
|
||||
|
||||
|
|
|
@ -1223,8 +1223,8 @@ define([
|
|||
pad.onMetadataEvent = Util.mkEvent();
|
||||
pad.onChannelDeleted = Util.mkEvent();
|
||||
|
||||
pad.requestAccess = function (data, cb) {
|
||||
postMessage("REQUEST_PAD_ACCESS", data, cb);
|
||||
pad.contactOwner = function (data, cb) {
|
||||
postMessage("CONTACT_PAD_OWNER", data, cb);
|
||||
};
|
||||
pad.giveAccess = function (data, cb) {
|
||||
postMessage("GIVE_PAD_ACCESS", data, cb);
|
||||
|
|
|
@ -78,6 +78,7 @@ define([
|
|||
var TAGS_NAME = Messages.fm_tagsName;
|
||||
var SHARED_FOLDER = 'sf';
|
||||
var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName;
|
||||
var FILTER = "filter";
|
||||
|
||||
// Icons
|
||||
var faFolder = 'cptools-folder';
|
||||
|
@ -1149,8 +1150,12 @@ define([
|
|||
common.getMediaTagPreview(mts, idx);
|
||||
};
|
||||
|
||||
var FILTER_BY = "filterBy";
|
||||
|
||||
var refresh = APP.refresh = function () {
|
||||
APP.displayDirectory(currentPath);
|
||||
var type = APP.store[FILTER_BY];
|
||||
var path = type ? [FILTER, type, currentPath] : currentPath;
|
||||
APP.displayDirectory(path);
|
||||
};
|
||||
|
||||
// `app`: true (force open wiht the app), false (force open in preview),
|
||||
|
@ -2946,66 +2951,140 @@ define([
|
|||
openIn(type, path, APP.team);
|
||||
});
|
||||
};
|
||||
var getNewPadOptions = function (isInRoot) {
|
||||
var options = [];
|
||||
if (isInRoot) {
|
||||
options.push({
|
||||
class: 'cp-app-drive-new-folder',
|
||||
icon: $folderIcon.clone()[0],
|
||||
name: Messages.fm_folder,
|
||||
});
|
||||
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
|
||||
options.push({
|
||||
class: 'cp-app-drive-new-shared-folder',
|
||||
icon: $sharedFolderIcon.clone()[0],
|
||||
name: Messages.fm_sharedFolder,
|
||||
});
|
||||
}
|
||||
options.push({ separator: true });
|
||||
options.push({
|
||||
class: 'cp-app-drive-new-fileupload',
|
||||
icon: getIcon('fileupload')[0],
|
||||
name: Messages.uploadButton,
|
||||
});
|
||||
if (APP.allowFolderUpload) {
|
||||
options.push({
|
||||
class: 'cp-app-drive-new-folderupload',
|
||||
icon: getIcon('folderupload')[0],
|
||||
name: Messages.uploadFolderButton,
|
||||
});
|
||||
}
|
||||
options.push({ separator: true });
|
||||
options.push({
|
||||
class: 'cp-app-drive-new-link',
|
||||
icon: getIcon('link')[0],
|
||||
name: Messages.fm_link_new,
|
||||
});
|
||||
options.push({ separator: true });
|
||||
}
|
||||
getNewPadTypes().forEach(function (type) {
|
||||
var typeClass = 'cp-app-drive-new-doc';
|
||||
|
||||
var premium = common.checkRestrictedApp(type);
|
||||
if (premium < 0) {
|
||||
typeClass += ' cp-app-hidden cp-app-disabled';
|
||||
} else if (premium === 0) {
|
||||
typeClass += ' cp-app-disabled';
|
||||
}
|
||||
|
||||
options.push({
|
||||
class: typeClass,
|
||||
type: type,
|
||||
icon: getIcon(type)[0],
|
||||
name: Messages.type[type],
|
||||
});
|
||||
});
|
||||
|
||||
if (APP.store[FILTER_BY]) {
|
||||
var typeFilter = APP.store[FILTER_BY];
|
||||
options = options.filter((obj) => {
|
||||
if (obj.separator) { return false; }
|
||||
|
||||
if (typeFilter === 'link') {
|
||||
return obj.class.includes('cp-app-drive-new-link');
|
||||
}
|
||||
if (typeFilter === 'file') {
|
||||
return obj.class.includes('cp-app-drive-new-fileupload');
|
||||
}
|
||||
if (getNewPadTypes().indexOf(typeFilter) !== -1) {
|
||||
return typeFilter === obj.type;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
var createNewButton = function (isInRoot, $container) {
|
||||
if (!APP.editable) { return; }
|
||||
if (!APP.loggedIn) { return; } // Anonymous users can use the + menu in the toolbar
|
||||
|
||||
if (!manager.isPathIn(currentPath, [ROOT, 'hrefArray'])) { return; }
|
||||
|
||||
// Create dropdown
|
||||
var options = getNewPadOptions(isInRoot).map(function (obj) {
|
||||
if (obj.separator) { return { tag: 'hr' }; }
|
||||
var newObj = {
|
||||
tag: 'a',
|
||||
attributes: { 'class': obj.class },
|
||||
content: [ obj.icon, obj.name ]
|
||||
};
|
||||
if (obj.type) {
|
||||
newObj.attributes['data-type'] = obj.type;
|
||||
newObj.attributes['href'] = '#';
|
||||
}
|
||||
return newObj;
|
||||
});
|
||||
var dropdownConfig = {
|
||||
buttonContent: [
|
||||
h('i.fa.fa-plus'),
|
||||
h('span.cp-button-name', Messages.fm_newButton),
|
||||
],
|
||||
buttonCls: 'cp-toolbar-dropdown-nowrap',
|
||||
options: options,
|
||||
feedback: 'DRIVE_NEWPAD_LOCALFOLDER',
|
||||
common: common
|
||||
};
|
||||
var $block = UIElements.createDropdown(dropdownConfig);
|
||||
|
||||
// Custom style:
|
||||
$block.find('button').addClass('cp-app-drive-toolbar-new');
|
||||
|
||||
addNewPadHandlers($block, isInRoot);
|
||||
|
||||
$container.append($block);
|
||||
};
|
||||
|
||||
var createFilterButton = function (isTemplate, $container) {
|
||||
if (!APP.loggedIn) { return; }
|
||||
|
||||
// Create dropdown
|
||||
var options = [];
|
||||
if (isInRoot) {
|
||||
if (APP.store[FILTER_BY]) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-app-drive-new-folder pewpew'},
|
||||
attributes: {
|
||||
'class': 'cp-app-drive-rm-filter',
|
||||
},
|
||||
content: [
|
||||
$folderIcon.clone()[0],
|
||||
Messages.fm_folder,
|
||||
],
|
||||
});
|
||||
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-app-drive-new-shared-folder'},
|
||||
content: [
|
||||
$sharedFolderIcon.clone()[0],
|
||||
Messages.fm_sharedFolder,
|
||||
],
|
||||
});
|
||||
}
|
||||
options.push({tag: 'hr'});
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-app-drive-new-fileupload'},
|
||||
content: [
|
||||
getIcon('fileupload')[0],
|
||||
Messages.uploadButton,
|
||||
],
|
||||
});
|
||||
if (APP.allowFolderUpload) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-app-drive-new-folderupload'},
|
||||
content: [
|
||||
getIcon('folderupload')[0],
|
||||
Messages.uploadFolderButton,
|
||||
],
|
||||
});
|
||||
}
|
||||
options.push({tag: 'hr'});
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-app-drive-new-link'},
|
||||
content: [
|
||||
getIcon('link')[0],
|
||||
Messages.fm_link_new,
|
||||
h('i.fa.fa-times'),
|
||||
Messages.fm_rmFilter,
|
||||
],
|
||||
});
|
||||
options.push({tag: 'hr'});
|
||||
}
|
||||
getNewPadTypes().forEach(function (type) {
|
||||
var attributes = {
|
||||
'class': 'cp-app-drive-new-doc',
|
||||
'class': 'cp-app-drive-filter-doc',
|
||||
'data-type': type,
|
||||
'href': '#'
|
||||
};
|
||||
|
@ -3026,21 +3105,72 @@ define([
|
|||
],
|
||||
});
|
||||
});
|
||||
if (!isTemplate) {
|
||||
options.push({tag: 'hr'});
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'class': 'cp-app-drive-filter-doc',
|
||||
'data-type': 'link'
|
||||
},
|
||||
content: [
|
||||
getIcon('link')[0],
|
||||
Messages.fm_link_type,
|
||||
],
|
||||
});
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'class': 'cp-app-drive-filter-doc',
|
||||
'data-type': 'file',
|
||||
'href': '#'
|
||||
},
|
||||
content: [
|
||||
getIcon('file')[0],
|
||||
Messages.type['file'],
|
||||
],
|
||||
});
|
||||
}
|
||||
var dropdownConfig = {
|
||||
buttonContent: [
|
||||
h('span.fa.fa-plus'),
|
||||
h('span', Messages.fm_newButton),
|
||||
h('i.fa.fa-filter'),
|
||||
h('span.cp-button-name', Messages.fm_filterBy),
|
||||
],
|
||||
buttonCls: 'cp-toolbar-dropdown-nowrap',
|
||||
options: options,
|
||||
feedback: 'DRIVE_NEWPAD_LOCALFOLDER',
|
||||
feedback: 'DRIVE_FILTERBY',
|
||||
common: common
|
||||
};
|
||||
if (APP.store[FILTER_BY]) {
|
||||
var type = APP.store[FILTER_BY];
|
||||
var message = type === 'link' ? Messages.fm_link_type : Messages.type[type];
|
||||
dropdownConfig.buttonContent.push(
|
||||
h('span.cp-button-name', ':'),
|
||||
getIcon(type)[0],
|
||||
h('span.cp-button-name', message)
|
||||
);
|
||||
}
|
||||
var $block = UIElements.createDropdown(dropdownConfig);
|
||||
|
||||
// Custom style:
|
||||
$block.find('button').addClass('cp-app-drive-toolbar-new');
|
||||
// Add style
|
||||
if (APP.store[FILTER_BY]) {
|
||||
$block.find('button').addClass('cp-toolbar-button-active');
|
||||
}
|
||||
|
||||
addNewPadHandlers($block, isInRoot);
|
||||
// Add handlers
|
||||
if (APP.store[FILTER_BY]) {
|
||||
$block.find('a.cp-app-drive-rm-filter')
|
||||
.click(function () {
|
||||
APP.store[FILTER_BY] = undefined;
|
||||
APP.displayDirectory(currentPath);
|
||||
});
|
||||
}
|
||||
$block.find('a.cp-app-drive-filter-doc')
|
||||
.click(function () {
|
||||
var type = $(this).attr('data-type') || 'invalid-filter';
|
||||
APP.store[FILTER_BY] = type;
|
||||
APP.displayDirectory([FILTER, type, currentPath]);
|
||||
});
|
||||
|
||||
$container.append($block);
|
||||
};
|
||||
|
@ -3302,65 +3432,38 @@ define([
|
|||
return keys;
|
||||
};
|
||||
|
||||
var filterPads = function (files, type, path, useId) {
|
||||
var root = path && manager.find(path);
|
||||
|
||||
return files
|
||||
.filter(function (e) {
|
||||
return useId ? manager.isFile(e) : (path && manager.isFile(root[e]));
|
||||
})
|
||||
.filter(function (e) {
|
||||
var id = useId ? e : root[e];
|
||||
var data = manager.getFileData(id);
|
||||
if (type === 'link') { return data.static; }
|
||||
var href = data.href || data.roHref;
|
||||
return href ? (href.split('/')[1] === type) : true;
|
||||
// if types are unreachable, display files to avoid misleading the user
|
||||
});
|
||||
};
|
||||
|
||||
// Create the ghost icon to add pads/folders
|
||||
var createNewPadIcons = function ($block, isInRoot) {
|
||||
var $container = $('<div>');
|
||||
if (isInRoot) {
|
||||
// Folder
|
||||
var $element1 = $('<li>', {
|
||||
'class': 'cp-app-drive-new-folder cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend($folderIcon.clone()).appendTo($container);
|
||||
$element1.append($('<span>', { 'class': 'cp-app-drive-new-name' })
|
||||
.text(Messages.fm_folder));
|
||||
// Shared Folder
|
||||
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
|
||||
var $element3 = $('<li>', {
|
||||
'class': 'cp-app-drive-new-shared-folder cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend($sharedFolderIcon.clone()).appendTo($container);
|
||||
$element3.append($('<span>', { 'class': 'cp-app-drive-new-name' })
|
||||
.text(Messages.fm_sharedFolder));
|
||||
}
|
||||
// Upload file
|
||||
var $elementFileUpload = $('<li>', {
|
||||
'class': 'cp-app-drive-new-fileupload cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend(getIcon('fileupload')).appendTo($container);
|
||||
$elementFileUpload.append($('<span>', {'class': 'cp-app-drive-new-name'})
|
||||
.text(Messages.uploadButton));
|
||||
// Upload folder
|
||||
if (APP.allowFolderUpload) {
|
||||
var $elementFolderUpload = $('<li>', {
|
||||
'class': 'cp-app-drive-new-folderupload cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend(getIcon('folderupload')).appendTo($container);
|
||||
$elementFolderUpload.append($('<span>', {'class': 'cp-app-drive-new-name'})
|
||||
.text(Messages.uploadFolderButton));
|
||||
}
|
||||
// Link
|
||||
var $elementLink = $('<li>', {
|
||||
'class': 'cp-app-drive-new-link cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend(getIcon('link')).appendTo($container);
|
||||
$elementLink.append($('<span>', {'class': 'cp-app-drive-new-name'})
|
||||
.text(Messages.fm_link_type));
|
||||
}
|
||||
// Pads
|
||||
getNewPadTypes().forEach(function (type) {
|
||||
var $element = $('<li>', {
|
||||
'class': 'cp-app-drive-new-doc cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend(getIcon(type)).appendTo($container);
|
||||
$element.append($('<span>', {'class': 'cp-app-drive-new-name'})
|
||||
.text(Messages.type[type]));
|
||||
$element.attr('data-type', type);
|
||||
getNewPadOptions(isInRoot).forEach(function (obj) {
|
||||
if (obj.separator) { return; }
|
||||
|
||||
var premium = common.checkRestrictedApp(type);
|
||||
if (premium < 0) {
|
||||
$element.addClass('cp-app-hidden cp-app-disabled');
|
||||
} else if (premium === 0) {
|
||||
$element.addClass('cp-app-disabled');
|
||||
var $element = $('<li>', {
|
||||
'class': obj.class + ' cp-app-drive-element-row ' +
|
||||
'cp-app-drive-element-grid'
|
||||
}).prepend(obj.icon).appendTo($container);
|
||||
$element.append($('<span>', { 'class': 'cp-app-drive-new-name' })
|
||||
.text(obj.name));
|
||||
|
||||
if (obj.type) {
|
||||
$element.attr('data-type', obj.type);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3435,7 +3538,7 @@ define([
|
|||
|
||||
// Unsorted element are represented by "href" in an array: they don't have a filename
|
||||
// and they don't hav a hierarchical structure (folder/subfolders)
|
||||
var displayHrefArray = function ($container, rootName, draggable) {
|
||||
var displayHrefArray = function ($container, rootName, draggable, typeFilter) {
|
||||
var unsorted = files[rootName];
|
||||
if (unsorted.length) {
|
||||
var $fileHeader = getFileListHeader(true);
|
||||
|
@ -3445,6 +3548,7 @@ define([
|
|||
var sortBy = APP.store[SORT_FILE_BY];
|
||||
sortBy = sortBy === "" ? sortBy = 'name' : sortBy;
|
||||
var sortedFiles = sortElements(false, [rootName], keys, sortBy, !getSortFileDesc(), true);
|
||||
sortedFiles = typeFilter ? filterPads(sortedFiles, typeFilter, false, true) : sortedFiles;
|
||||
sortedFiles.forEach(function (id) {
|
||||
var file = manager.getFileData(id);
|
||||
if (!file) {
|
||||
|
@ -3526,7 +3630,7 @@ define([
|
|||
createGhostIcon($container);
|
||||
};
|
||||
|
||||
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
|
||||
var displayTrashRoot = function ($list, $folderHeader, $fileHeader, typeFilter) {
|
||||
var filesList = [];
|
||||
var root = files[TRASH];
|
||||
var isEmpty = true;
|
||||
|
@ -3549,14 +3653,25 @@ define([
|
|||
isEmpty = false;
|
||||
});
|
||||
|
||||
var sortedFolders = typeFilter ? [] : sortTrashElements(true, filesList, null, !getSortFolderDesc());
|
||||
var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc);
|
||||
|
||||
if (typeFilter) {
|
||||
var ids = sortedFiles.map(function (obj) { return obj.element; });
|
||||
var idsFilter = filterPads(ids, typeFilter, false, true);
|
||||
sortedFiles = sortedFiles.filter(function (obj) {
|
||||
return (idsFilter.indexOf(obj.element) !== -1);
|
||||
});
|
||||
// prevent trash emptying while filter is active
|
||||
isEmpty = true;
|
||||
}
|
||||
|
||||
if (!isEmpty) {
|
||||
var $empty = createEmptyTrashButton();
|
||||
$content.append($empty);
|
||||
}
|
||||
|
||||
var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc());
|
||||
var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc());
|
||||
if (manager.hasSubfolder(root, true)) { $list.append($folderHeader); }
|
||||
if (!typeFilter && manager.hasSubfolder(root, true)) { $list.append($folderHeader); }
|
||||
sortedFolders.forEach(function (f) {
|
||||
var $element = createElement([TRASH], f.spath, root, true);
|
||||
$list.append($element);
|
||||
|
@ -3728,7 +3843,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var displayRecent = function ($list) {
|
||||
var displayRecent = function ($list, typeFilter) {
|
||||
var filesList = manager.getRecentPads();
|
||||
var limit = 20;
|
||||
|
||||
|
@ -3744,6 +3859,14 @@ define([
|
|||
var i = 0;
|
||||
var channels = [];
|
||||
|
||||
if (typeFilter) {
|
||||
var ids = filesList.map(function (arr) { return arr[0]; });
|
||||
var idsFilter = filterPads(ids, typeFilter, false, true);
|
||||
filesList = filesList.filter(function (arr) {
|
||||
return (idsFilter.indexOf(arr[0]) !== -1);
|
||||
});
|
||||
}
|
||||
|
||||
$list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active1Day)));
|
||||
filesList.some(function (arr) {
|
||||
var id = arr[0];
|
||||
|
@ -3932,6 +4055,17 @@ define([
|
|||
$content.html("");
|
||||
sel.$selectBox = $('<div>', {'class': 'cp-app-drive-content-select-box'})
|
||||
.appendTo($content);
|
||||
|
||||
var typeFilter;
|
||||
var isFilter = path[0] === FILTER;
|
||||
if (isFilter) {
|
||||
if (path.length < 3) { return; }
|
||||
typeFilter = path[1];
|
||||
path = path[2];
|
||||
currentPath = path;
|
||||
} else {
|
||||
APP.store[FILTER_BY] = undefined;
|
||||
}
|
||||
var isInRoot = manager.isPathIn(path, [ROOT]);
|
||||
var inTrash = manager.isPathIn(path, [TRASH]);
|
||||
var isTrashRoot = manager.comparePath(path, [TRASH]);
|
||||
|
@ -3939,6 +4073,8 @@ define([
|
|||
var isAllFiles = manager.comparePath(path, [FILES_DATA]);
|
||||
var isVirtual = virtualCategories.indexOf(path[0]) !== -1;
|
||||
var isSearch = path[0] === SEARCH;
|
||||
var isRecent = path[0] === RECENT;
|
||||
var isOwned = path[0] === OWNED;
|
||||
var isTags = path[0] === TAGS;
|
||||
// ANON_SHARED_FOLDER
|
||||
var isSharedFolder = path[0] === SHARED_FOLDER && APP.newSharedFolder;
|
||||
|
@ -4040,6 +4176,9 @@ define([
|
|||
if (!readOnlyFolder) {
|
||||
createNewButton(isInRoot, APP.toolbar.$bottomL);
|
||||
}
|
||||
if (!isTags && !isSearch) {
|
||||
createFilterButton(isTemplate, APP.toolbar.$bottomL);
|
||||
}
|
||||
|
||||
if (APP.mobile()) {
|
||||
var $context = $('<button>', {
|
||||
|
@ -4075,16 +4214,16 @@ define([
|
|||
var $fileHeader = getFileListHeader(true);
|
||||
|
||||
if (isTemplate) {
|
||||
displayHrefArray($list, path[0], true);
|
||||
displayHrefArray($list, path[0], true, typeFilter);
|
||||
} else if (isAllFiles) {
|
||||
displayAllFiles($list);
|
||||
} else if (isTrashRoot) {
|
||||
displayTrashRoot($list, $folderHeader, $fileHeader);
|
||||
displayTrashRoot($list, $folderHeader, $fileHeader, typeFilter);
|
||||
} else if (isSearch) {
|
||||
displaySearch($list, path[1]);
|
||||
} else if (path[0] === RECENT) {
|
||||
displayRecent($list);
|
||||
} else if (path[0] === OWNED) {
|
||||
} else if (isRecent) {
|
||||
displayRecent($list, typeFilter);
|
||||
} else if (isOwned) {
|
||||
displayOwned($list);
|
||||
} else if (isTags) {
|
||||
displayTags($list);
|
||||
|
@ -4093,11 +4232,12 @@ define([
|
|||
displaySharedFolder($list);
|
||||
} else {
|
||||
if (!inTrash) { $dirContent.contextmenu(openContextMenu('content')); }
|
||||
if (manager.hasSubfolder(root)) { $list.append($folderHeader); }
|
||||
if (!isFilter && manager.hasSubfolder(root)) { $list.append($folderHeader); }
|
||||
// display sub directories
|
||||
var keys = Object.keys(root);
|
||||
var sortedFolders = sortElements(true, path, keys, null, !getSortFolderDesc());
|
||||
var sortedFolders = isFilter ? [] : sortElements(true, path, keys, null, !getSortFolderDesc());
|
||||
var sortedFiles = sortElements(false, path, keys, APP.store[SORT_FILE_BY], !getSortFileDesc());
|
||||
sortedFiles = isFilter ? filterPads(sortedFiles, typeFilter, path) : sortedFiles;
|
||||
sortedFolders.forEach(function (key) {
|
||||
if (manager.isFile(root[key])) { return; }
|
||||
var $element = createElement(path, key, root, true);
|
||||
|
|
|
@ -1061,7 +1061,7 @@ define([
|
|||
var requestBlock = h('p', requestButton);
|
||||
var $requestBlock = $(requestBlock).hide();
|
||||
content.push(requestBlock);
|
||||
sframeChan.query('Q_REQUEST_ACCESS', {
|
||||
sframeChan.query('Q_CONTACT_OWNER', {
|
||||
send: false,
|
||||
metadata: data
|
||||
}, function (err, obj) {
|
||||
|
@ -1072,9 +1072,10 @@ define([
|
|||
$requestBlock.show().find('button').click(function () {
|
||||
if (spinner.getState()) { return; }
|
||||
spinner.spin();
|
||||
sframeChan.query('Q_REQUEST_ACCESS', {
|
||||
sframeChan.query('Q_CONTACT_OWNER', {
|
||||
send: true,
|
||||
metadata: data
|
||||
metadata: data,
|
||||
query: "REQUEST_PAD_ACCESS"
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
UI.log(Messages.requestEdit_sent);
|
||||
|
|
|
@ -12,7 +12,7 @@ define([
|
|||
"Asterisk asterisk",
|
||||
"Brainfuck brainfuck .b",
|
||||
"C text/x-csrc .c",
|
||||
"C text/x-c++src .cpp",
|
||||
"C++ text/x-c++src .cpp",
|
||||
"C-like clike .c",
|
||||
"Clojure clojure .clj",
|
||||
"CMake cmake _", /* no extension */
|
||||
|
@ -50,7 +50,6 @@ define([
|
|||
"HTML htmlmixed .html",
|
||||
"HTTP http _", /* no extension */
|
||||
"IDL idl .idl",
|
||||
"JADE jade .jade",
|
||||
"Java text/x-java .java",
|
||||
"JavaScript javascript .js",
|
||||
"Jinja2 jinja2 .j2",
|
||||
|
|
|
@ -335,6 +335,29 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
handlers['FORM_RESPONSE'] = function(common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
|
||||
// Display the notification
|
||||
var title = Util.fixHTML(msg.content.title || Messages.unknownPad);
|
||||
var href = msg.content.href;
|
||||
|
||||
Messages.form_responseNotification = "New responses have been sent to your form <b>{0}</b>";
|
||||
content.getFormatText = function() {
|
||||
return Messages._getKey('form_responseNotification', [title]);
|
||||
};
|
||||
if (href) {
|
||||
content.handler = function() {
|
||||
common.openURL(href);
|
||||
defaultDismiss(common, data)();
|
||||
};
|
||||
}
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['COMMENT_REPLY'] = function(common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
|
@ -481,6 +504,18 @@ define([
|
|||
var missed = content.msg.missed;
|
||||
var start = msg.start;
|
||||
var title = Util.fixHTML(msg.title);
|
||||
content.handler = function () {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var time = Util.find(data, ['content', 'msg', 'content', 'start']);
|
||||
if (priv.app === "calendar" && window.APP && window.APP.moveToDate) {
|
||||
return void window.APP.moveToDate(time);
|
||||
}
|
||||
var url = Hash.hashToHref('', 'calendar');
|
||||
var optsUrl = Hash.getNewPadURL(url, {
|
||||
time: time
|
||||
});
|
||||
common.openURL(optsUrl);
|
||||
};
|
||||
content.getFormatText = function () {
|
||||
var now = +new Date();
|
||||
|
||||
|
|
|
@ -1947,42 +1947,23 @@ define([
|
|||
}).nThen(cb);
|
||||
};
|
||||
|
||||
// requestPadAccess is used to check if we have a way to contact the owner
|
||||
// of the pad AND to send the request if we want
|
||||
// data.send === false ==> check if we can contact them
|
||||
// data.send === true ==> send the request
|
||||
Store.requestPadAccess = function (clientId, data, cb) {
|
||||
// contactPadOwner is used to send "REQUEST_ACCESS" messages
|
||||
// and to notify form owners when sending a response
|
||||
Store.contactPadOwner = function (clientId, data, cb) {
|
||||
var owner = data.owner;
|
||||
|
||||
// If the owner was not is the pad metadata, check if it is a friend.
|
||||
// We'll contact the first owner for whom we know the mailbox
|
||||
/* // TODO decide whether we want to re-enable this feature for our own contacts
|
||||
// communicate the exception to users that 'muting' won't apply to friends
|
||||
check mailbox in our contacts is not compatible with the new "mute pad" feature
|
||||
var owners = data.owners;
|
||||
if (!owner && Array.isArray(owners)) {
|
||||
var friends = store.proxy.friends || {};
|
||||
// If we have friends, check if an owner is one of them (with a mailbox)
|
||||
if (Object.keys(friends).filter(function (curve) { return curve !== 'me'; }).length) {
|
||||
owners.some(function (edPublic) {
|
||||
return Object.keys(friends).some(function (curve) {
|
||||
if (curve === "me") { return; }
|
||||
if (edPublic === friends[curve].edPublic &&
|
||||
friends[curve].notifications) {
|
||||
owner = friends[curve];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// If send is true, send the request to the owner.
|
||||
if (owner) {
|
||||
if (data.send) {
|
||||
store.mailbox.sendTo('REQUEST_PAD_ACCESS', {
|
||||
channel: data.channel
|
||||
var sendTo = function (query, msg, user, _cb) {
|
||||
if (store.mailbox) {
|
||||
return store.mailbox.sendTo(query, msg, user, _cb);
|
||||
}
|
||||
Mailbox.sendToAnon(store.anon_rpc, query, msg, user, _cb);
|
||||
};
|
||||
sendTo(data.query, {
|
||||
channel: data.channel,
|
||||
data: data.msgData
|
||||
}, {
|
||||
channel: owner.notifications,
|
||||
curvePublic: owner.curvePublic
|
||||
|
|
|
@ -4,12 +4,13 @@ define([
|
|||
'/common/common-constants.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/calendar/recurrence.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'chainpad-listmap',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
|
||||
], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, Crypto, ChainPad) {
|
||||
var Calendar = {};
|
||||
|
||||
var getStore = function (ctx, id) {
|
||||
|
@ -90,7 +91,29 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var updateEventReminders = function (ctx, reminders, _ev, useLastVisit) {
|
||||
var getRecurring = function (ev) {
|
||||
var mid = new Date();
|
||||
var start = new Date(mid.getFullYear(), mid.getMonth()-1, 15);
|
||||
var end = new Date(mid.getFullYear(), mid.getMonth()+1, 15);
|
||||
var startId = Rec.getMonthId(start);
|
||||
var midId = Rec.getMonthId(mid);
|
||||
var endId = Rec.getMonthId(end);
|
||||
|
||||
var toAdd = Rec.getRecurring([startId, midId, endId], [ev]);
|
||||
|
||||
var all = [ev];
|
||||
Array.prototype.push.apply(all, toAdd);
|
||||
return Rec.applyUpdates(all);
|
||||
};
|
||||
var clearDismissed = function (ctx, uid) {
|
||||
var h = Util.find(ctx, ['store', 'proxy', 'hideReminders']) || {};
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(uid) === 0;
|
||||
}).forEach(function (id) {
|
||||
delete h[id];
|
||||
});
|
||||
};
|
||||
var _updateEventReminders = function (ctx, reminders, _ev, useLastVisit) {
|
||||
var now = +new Date();
|
||||
var ev = Util.clone(_ev);
|
||||
var uid = ev.id;
|
||||
|
@ -101,6 +124,10 @@ define([
|
|||
}
|
||||
reminders[uid] = [];
|
||||
|
||||
if (_ev.deleted) { return; }
|
||||
|
||||
var d = Util.find(ctx, ['store', 'proxy', 'hideReminders', uid]) || []; // dismissed
|
||||
|
||||
var last = ctx.store.data.lastVisit;
|
||||
|
||||
if (ev.isAllDay) {
|
||||
|
@ -119,10 +146,11 @@ define([
|
|||
if (ev.end <= now && !missed) {
|
||||
// No reminder for past events
|
||||
delete reminders[uid];
|
||||
clearDismissed(ctx, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
var send = function () {
|
||||
var send = function (d) {
|
||||
var hide = Util.find(ctx, ['store', 'proxy', 'settings', 'general', 'calendar', 'hideNotif']);
|
||||
if (hide) { return; }
|
||||
var ctime = ev.start <= now ? ev.start : +new Date(); // Correct order for past events
|
||||
|
@ -133,11 +161,18 @@ define([
|
|||
missed: Boolean(missed),
|
||||
content: ev
|
||||
},
|
||||
hash: 'REMINDER|'+uid
|
||||
hash: 'REMINDER|'+uid+'-'+d
|
||||
}, null, function () {
|
||||
});
|
||||
};
|
||||
var sendNotif = function () { ctx.Store.onReadyEvt.reg(send); };
|
||||
var sent = false;
|
||||
var sendNotif = function (delay) {
|
||||
sent = true;
|
||||
|
||||
ctx.Store.onReadyEvt.reg(function () {
|
||||
send(delay);
|
||||
});
|
||||
};
|
||||
|
||||
var notifs = ev.reminders || [];
|
||||
notifs.sort(function (a, b) {
|
||||
|
@ -148,6 +183,10 @@ define([
|
|||
var delay = delayMinutes * 60000;
|
||||
var time = now + delay;
|
||||
|
||||
if (d.some(function (minutes) {
|
||||
return delayMinutes >= minutes;
|
||||
})) { return; }
|
||||
|
||||
// setTimeout only work with 32bit timeout values. If the event is too far away,
|
||||
// ignore this event for now
|
||||
// FIXME: call this function again in xxx days to reload these missing timeout?
|
||||
|
@ -156,18 +195,35 @@ define([
|
|||
// If we're too late to send a notification, send it instantly and ignore
|
||||
// all notifications that were supposed to be sent even earlier
|
||||
if (ev.start <= time) {
|
||||
sendNotif();
|
||||
sendNotif(delayMinutes);
|
||||
return true;
|
||||
}
|
||||
|
||||
// It starts in more than "delay": prepare the notification
|
||||
reminders[uid].push(setTimeout(function () {
|
||||
sendNotif();
|
||||
sendNotif(delayMinutes);
|
||||
}, (ev.start - time)));
|
||||
});
|
||||
|
||||
if (!sent) {
|
||||
// Remone any existing notification from the UI
|
||||
ctx.Store.onReadyEvt.reg(function () {
|
||||
ctx.store.mailbox.hideMessage('reminders', {
|
||||
hash: 'REMINDER|'+uid
|
||||
}, null, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
var updateEventReminders = function (ctx, reminders, ev, useLastVisit) {
|
||||
var all = getRecurring(Util.clone(ev));
|
||||
all.forEach(function (_ev) {
|
||||
_updateEventReminders(ctx, reminders, _ev, useLastVisit);
|
||||
});
|
||||
};
|
||||
var addReminders = function (ctx, id, ev) {
|
||||
var calendar = ctx.calendars[id];
|
||||
if (!ev) { return; } // XXX deleted event remote: delete reminders
|
||||
if (!calendar || !calendar.reminders) { return; }
|
||||
if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; }
|
||||
|
||||
|
@ -352,10 +408,20 @@ define([
|
|||
c.lm = lm;
|
||||
var proxy = c.proxy = lm.proxy;
|
||||
|
||||
var _updateCalled = false;
|
||||
var _update = function () {
|
||||
if (_updateCalled) { return; }
|
||||
_updateCalled = true;
|
||||
setTimeout(function () {
|
||||
_updateCalled = false;
|
||||
update();
|
||||
});
|
||||
};
|
||||
|
||||
lm.proxy.on('cacheready', function () {
|
||||
if (!proxy.metadata) { return; }
|
||||
c.cacheready = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('ready', function (info) {
|
||||
|
@ -372,24 +438,39 @@ define([
|
|||
title: data.title
|
||||
};
|
||||
}
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('change', [], function () {
|
||||
if (!c.ready) { return; }
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('change', ['content'], function (o, n, p) {
|
||||
if (p.length === 2 && n && !o) { // New event
|
||||
addReminders(ctx, channel, n);
|
||||
return void addReminders(ctx, channel, n);
|
||||
}
|
||||
if (p.length === 2 && !n && o) { // Deleted event
|
||||
addReminders(ctx, channel, {
|
||||
return void addReminders(ctx, channel, {
|
||||
id: p[1],
|
||||
start: 0
|
||||
});
|
||||
}
|
||||
if (p.length === 3 && n && o && p[2] === 'start') { // Update event start
|
||||
setTimeout(function () {
|
||||
if (p.length >= 3 && ['start','reminders','isAllDay'].includes(p[2])) {
|
||||
// Updated event
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
if (p.length >= 6 && ['start','reminders','isAllDay'].includes(p[5])) {
|
||||
// Updated recurring event
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
}).on('remove', ['content'], function (x, p) {
|
||||
_update();
|
||||
if ((p.length >= 3 && p[2] === 'reminders') ||
|
||||
(p.length >= 6 && p[5] === 'reminders')) {
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
|
@ -400,10 +481,10 @@ define([
|
|||
updateLocalCalendars(ctx, c, md);
|
||||
}).on('disconnect', function () {
|
||||
c.offline = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('reconnect', function () {
|
||||
c.offline = false;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('error', function (info) {
|
||||
if (!info || !info.error) { return; }
|
||||
if (info.error === "EDELETED" ) {
|
||||
|
@ -411,7 +492,7 @@ define([
|
|||
}
|
||||
if (info.error === "ERESTRICTED" ) {
|
||||
c.restricted = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}
|
||||
cb(info);
|
||||
});
|
||||
|
@ -760,8 +841,11 @@ define([
|
|||
var ev = c.proxy.content[data.ev.id];
|
||||
if (!ev) { return void cb({error: "EINVAL"}); }
|
||||
|
||||
data.rawData = data.rawData || {};
|
||||
|
||||
// update the event
|
||||
var changes = data.changes || {};
|
||||
var type = data.type || {};
|
||||
|
||||
var newC;
|
||||
if (changes.calendarId) {
|
||||
|
@ -770,7 +854,122 @@ define([
|
|||
newC.proxy.content = newC.proxy.content || {};
|
||||
}
|
||||
|
||||
var RECUPDATE = {
|
||||
one: {},
|
||||
from: {}
|
||||
};
|
||||
if (['one','from','all'].includes(type.which)) {
|
||||
ev.recUpdate = ev.recUpdate || RECUPDATE;
|
||||
if (!ev.recUpdate.one) { ev.recUpdate.one = {}; }
|
||||
if (!ev.recUpdate.from) { ev.recUpdate.from = {}; }
|
||||
}
|
||||
var update = ev.recUpdate;
|
||||
var alwaysAll = ['calendarId'];
|
||||
var keys = Object.keys(changes).filter(function (s) {
|
||||
// we can only change the calendar or recurrence rule on the origin
|
||||
return !alwaysAll.includes(s);
|
||||
});
|
||||
|
||||
// Delete (future) affected keys
|
||||
var cleanAfter = function (time) {
|
||||
[update.from, update.one].forEach(function (obj) {
|
||||
Object.keys(obj).forEach(function (d) {
|
||||
if (Number(d) < time) { return; }
|
||||
delete obj[d];
|
||||
});
|
||||
});
|
||||
};
|
||||
var cleanKeys = function (obj, when) {
|
||||
Object.keys(obj).forEach(function (d) {
|
||||
if (when && Number(d) < when) { return; }
|
||||
keys.forEach(function (k) {
|
||||
delete obj[d][k];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Update recurrence rule. We may create a new event here
|
||||
var dontSendUpdate = false;
|
||||
if (typeof(changes.recurrenceRule) !== "undefined") {
|
||||
if (['one','from'].includes(type.which) && !data.rawData.isOrigin) {
|
||||
cleanAfter(type.when);
|
||||
} else {
|
||||
update = ev.recUpdate = RECUPDATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.which === "one") {
|
||||
update.one[type.when] = update.one[type.when] || {};
|
||||
// Nothing to delete
|
||||
} else if (type.which === "from") {
|
||||
update.from[type.when] = update.from[type.when] || {};
|
||||
// Delete all "single/from" updates (affected keys only) after this "from" date
|
||||
cleanKeys(update.from, type.when);
|
||||
cleanKeys(update.one, type.when);
|
||||
} else if (type.which === "all") {
|
||||
// Delete all "single/from" updates (affected keys only) after
|
||||
cleanKeys(update.from);
|
||||
cleanKeys(update.one);
|
||||
}
|
||||
|
||||
if (changes.start && (!type.which || type.which === "all")) {
|
||||
var diff = changes.start - ev.start;
|
||||
var newOne = {};
|
||||
var newFrom = {};
|
||||
Object.keys(update.one).forEach(function (time) {
|
||||
newOne[Number(time)+diff] = update.one[time];
|
||||
});
|
||||
Object.keys(update.from).forEach(function (time) {
|
||||
newFrom[Number(time)+diff] = update.from[time];
|
||||
});
|
||||
update.one = newOne;
|
||||
update.from = newFrom;
|
||||
}
|
||||
|
||||
|
||||
// Clear the "dismissed" reminders when the user is updating reminders
|
||||
var h = Util.find(ctx, ['store', 'proxy', 'hideReminders']) || {};
|
||||
if (changes.reminders) {
|
||||
if (type.which === 'one') {
|
||||
if (!type.when || type.when === ev.start) { delete h[data.ev.id]; }
|
||||
else { delete h[data.ev.id +'|'+ type.when]; }
|
||||
} else if (type.which === "from") {
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(data.ev.id) === 0;
|
||||
}).forEach(function (id) {
|
||||
var time = Number(id.split('|')[1]);
|
||||
if (!time) { return; }
|
||||
if (time < type.when) { return; }
|
||||
delete h[id];
|
||||
});
|
||||
} else {
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(data.ev.id) === 0;
|
||||
}).forEach(function (id) {
|
||||
delete h[id];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the changes
|
||||
Object.keys(changes).forEach(function (key) {
|
||||
if (!alwaysAll.includes(key) && type.which === "one") {
|
||||
if (key === "recurrenceRule") {
|
||||
if (data.rawData && data.rawData.isOrigin) {
|
||||
return (ev[key] = changes[key]);
|
||||
}
|
||||
// Always "from", never "one" for recurrence rules
|
||||
update.from[type.when] = update.from[type.when] || {};
|
||||
return (update.from[type.when][key] = changes[key]);
|
||||
}
|
||||
update.one[type.when][key] = changes[key];
|
||||
return;
|
||||
}
|
||||
if (!alwaysAll.includes(key) && type.which === "from") {
|
||||
update.from[type.when][key] = changes[key];
|
||||
return;
|
||||
}
|
||||
ev[key] = changes[key];
|
||||
});
|
||||
|
||||
|
@ -790,6 +989,7 @@ define([
|
|||
delete c.proxy.content[data.ev.id];
|
||||
}
|
||||
|
||||
|
||||
nThen(function (waitFor) {
|
||||
Realtime.whenRealtimeSyncs(c.lm.realtime, waitFor());
|
||||
if (newC) { Realtime.whenRealtimeSyncs(newC.lm.realtime, waitFor()); }
|
||||
|
@ -806,8 +1006,8 @@ define([
|
|||
addReminders(ctx, id, ev);
|
||||
}
|
||||
|
||||
sendUpdate(ctx, c);
|
||||
if (newC) { sendUpdate(ctx, newC); }
|
||||
if (!dontSendUpdate || newC) { sendUpdate(ctx, c); }
|
||||
if (newC && !dontSendUpdate) { sendUpdate(ctx, newC); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
@ -816,7 +1016,22 @@ define([
|
|||
var c = ctx.calendars[id];
|
||||
if (!c) { return void cb({error: "ENOENT"}); }
|
||||
c.proxy.content = c.proxy.content || {};
|
||||
var evId = data.id.split('|')[0];
|
||||
if (data.id === evId) {
|
||||
delete c.proxy.content[data.id];
|
||||
} else {
|
||||
var ev = c.proxy.content[evId];
|
||||
var s = data.raw && data.raw.start;
|
||||
if (s) {
|
||||
ev.recUpdate = ev.recUpdate || {
|
||||
one: {},
|
||||
from: {}
|
||||
};
|
||||
ev.recUpdate.one[s] = {
|
||||
deleted: true
|
||||
};
|
||||
}
|
||||
}
|
||||
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
||||
addReminders(ctx, id, {
|
||||
id: data.id,
|
||||
|
@ -867,6 +1082,20 @@ define([
|
|||
openChannels(ctx);
|
||||
}));
|
||||
|
||||
ctx.store.proxy.on('change', ['hideReminders'], function (o,n,p) {
|
||||
var uid = p[1].split('|')[0];
|
||||
Object.keys(ctx.calendars).some(function (calId) {
|
||||
var c = ctx.calendars[calId];
|
||||
if (!c || !c.proxy || !c.proxy.content) { return; }
|
||||
if (c.proxy.content[uid]) {
|
||||
setTimeout(function () {
|
||||
addReminders(ctx, calId, c.proxy.content[uid]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
calendar.closeTeam = function (teamId) {
|
||||
Object.keys(ctx.calendars).forEach(function (id) {
|
||||
var ctxCal = ctx.calendars[id];
|
||||
|
|
|
@ -575,6 +575,59 @@ define([
|
|||
cb();
|
||||
};
|
||||
|
||||
// Hide duplicates when receiving a form notification:
|
||||
// Keep only one notification per channel
|
||||
var formNotifs = {};
|
||||
handlers['FORM_RESPONSE'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var hash = data.hash;
|
||||
var content = msg.content;
|
||||
|
||||
var channel = content.channel;
|
||||
if (!channel) { return void cb(true); }
|
||||
|
||||
var title, href;
|
||||
ctx.Store.getAllStores().some(function (s) {
|
||||
var res = s.manager.findChannel(channel);
|
||||
// Check if the pad is in our drive
|
||||
return res.some(function (obj) {
|
||||
if (!obj.data) { return; }
|
||||
if (href && !obj.data.href) { return; } // We already have the VIEW url, we need EDIT
|
||||
href = obj.data.href || obj.data.roHref;
|
||||
title = obj.data.filename || obj.data.title;
|
||||
if (obj.data.href) { return true; } // Abort only if we have the EDIT url
|
||||
});
|
||||
});
|
||||
|
||||
// If we don't have the edit url, ignore this notification
|
||||
if (!href) { return void cb(true); }
|
||||
|
||||
// Add the title
|
||||
content.href = href;
|
||||
content.title = title;
|
||||
|
||||
// Remove duplicates
|
||||
var old = formNotifs[channel];
|
||||
var toRemove = old ? old.data : undefined;
|
||||
|
||||
// Update the data
|
||||
formNotifs[channel] = {
|
||||
data: {
|
||||
type: box.type,
|
||||
hash: hash
|
||||
}
|
||||
};
|
||||
|
||||
cb(false, toRemove);
|
||||
};
|
||||
removeHandlers['FORM_RESPONSE'] = function (ctx, box, data, hash) {
|
||||
var content = data.content;
|
||||
var channel = content.channel;
|
||||
var old = formNotifs[channel];
|
||||
if (old && old.data && old.data.hash === hash) {
|
||||
delete formNotifs[channel];
|
||||
}
|
||||
};
|
||||
// Hide duplicates when receiving a SHARE_PAD notification:
|
||||
// Keep only one notification per channel: the stronger and more recent one
|
||||
var comments = {};
|
||||
|
|
|
@ -164,6 +164,22 @@ proxy.mailboxes = {
|
|||
});
|
||||
});
|
||||
};
|
||||
Mailbox.sendToAnon = function (anonRpc, type, msg, user, cb) {
|
||||
var Nacl = Crypto.Nacl;
|
||||
var curveSeed = Nacl.randomBytes(32);
|
||||
var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed));
|
||||
var curvePrivate = Nacl.util.encodeBase64(curvePair.secretKey);
|
||||
var curvePublic = Nacl.util.encodeBase64(curvePair.publicKey);
|
||||
sendTo({
|
||||
store: {
|
||||
anon_rpc: anonRpc,
|
||||
proxy: {
|
||||
curvePrivate: curvePrivate,
|
||||
curvePublic: curvePublic
|
||||
}
|
||||
}
|
||||
}, type, msg, user, cb);
|
||||
};
|
||||
|
||||
// Mark a message as read
|
||||
var dismiss = function (ctx, data, cId, cb) {
|
||||
|
@ -177,6 +193,15 @@ proxy.mailboxes = {
|
|||
hideMessage(ctx, type, hash, ctx.clients.filter(function (clientId) {
|
||||
return clientId !== cId;
|
||||
}));
|
||||
|
||||
var uid = hash.slice(9).split('-')[0];
|
||||
var d = Util.find(ctx, ['store', 'proxy', 'hideReminders', uid]);
|
||||
if (!d) {
|
||||
var h = ctx.store.proxy.hideReminders = ctx.store.proxy.hideReminders || {};
|
||||
d = h[uid] = h[uid] || [];
|
||||
}
|
||||
var delay = hash.split('-')[1];
|
||||
if (delay && !d.includes(delay)) { d.push(Number(delay)); }
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -590,6 +615,9 @@ proxy.mailboxes = {
|
|||
});
|
||||
};
|
||||
|
||||
mailbox.hideMessage = function (type, msg) {
|
||||
hideMessage(ctx, type, msg.hash, ctx.clients);
|
||||
};
|
||||
mailbox.showMessage = function (type, msg, cId, cb) {
|
||||
if (type === "reminders" && msg) {
|
||||
ctx.boxes.reminders.content[msg.hash] = msg.msg;
|
||||
|
|
|
@ -79,7 +79,7 @@ define([
|
|||
GET_HISTORY: Store.getHistory,
|
||||
GET_HISTORY_RANGE: Store.getHistoryRange,
|
||||
IS_NEW_CHANNEL: Store.isNewChannel,
|
||||
REQUEST_PAD_ACCESS: Store.requestPadAccess,
|
||||
CONTACT_PAD_OWNER: Store.contactPadOwner,
|
||||
GIVE_PAD_ACCESS: Store.givePadAccess,
|
||||
BURN_PAD: Store.burnPad,
|
||||
GET_PAD_METADATA: Store.getPadMetadata,
|
||||
|
|
|
@ -20,7 +20,9 @@ define([
|
|||
'netflux-client': '/bower_components/netflux-websocket/netflux-client',
|
||||
'chainpad-netflux': '/bower_components/chainpad-netflux/chainpad-netflux',
|
||||
'chainpad-listmap': '/bower_components/chainpad-listmap/chainpad-listmap',
|
||||
'cm-extra': '/lib/codemirror-extra-modes'
|
||||
'cm-extra': '/lib/codemirror-extra-modes',
|
||||
// asciidoctor same
|
||||
'asciidoctor': '/lib/asciidoctor/asciidoctor.min'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
|
|
|
@ -80,7 +80,7 @@ define([
|
|||
try {
|
||||
var val = JSON.parse(states[idx].getContent().doc);
|
||||
var md = config.extractMetadata(val);
|
||||
var users = Object.keys(md.users).sort();
|
||||
var users = Object.keys(md.users || {}).sort();
|
||||
return users.join();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
|
@ -13,6 +13,7 @@ define([
|
|||
Mailbox.create = function (Common) {
|
||||
var mailbox = Common.mailbox;
|
||||
var sframeChan = Common.getSframeChannel();
|
||||
var priv = Common.getMetadataMgr().getPrivateData();
|
||||
|
||||
var execCommand = function (cmd, data, cb) {
|
||||
sframeChan.query('Q_MAILBOX_COMMAND', {
|
||||
|
@ -67,6 +68,14 @@ define([
|
|||
}
|
||||
} else if (data.type === 'reminders') {
|
||||
avatar = h('i.fa.fa-calendar.cp-broadcast.preview');
|
||||
if (priv.app !== 'calendar') { avatar.classList.add('cp-reminder'); }
|
||||
$(avatar).click(function (e) {
|
||||
e.stopPropagation();
|
||||
if (data.content && data.content.handler) {
|
||||
return void data.content.handler();
|
||||
}
|
||||
Common.openURL(Hash.hashToHref('', 'calendar'));
|
||||
});
|
||||
} else if (userData && typeof(userData) === "object" && userData.profile) {
|
||||
avatar = h('span.cp-avatar');
|
||||
Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name);
|
||||
|
@ -120,7 +129,8 @@ define([
|
|||
|
||||
onViewedHandlers.push(function (data) {
|
||||
var hash = data.hash.replace(/"/g, '\\\"');
|
||||
var $notif = $('.cp-notification[data-hash="'+hash+'"]:not(.cp-app-notification-archived)');
|
||||
if (/^REMINDER\|/.test(hash)) { hash = hash.split('-')[0]; }
|
||||
var $notif = $('.cp-notification[data-hash^="'+hash+'"]:not(.cp-app-notification-archived)');
|
||||
if ($notif.length) {
|
||||
$notif.remove();
|
||||
}
|
||||
|
|
|
@ -1010,9 +1010,9 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
// REQUEST_ACCESS is used both to check IF we can contact an owner (send === false)
|
||||
// CONTACT_OWNER is used both to check IF we can contact an owner (send === false)
|
||||
// AND also to send the request if we want (send === true)
|
||||
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
|
||||
sframeChan.on('Q_CONTACT_OWNER', function (data, cb) {
|
||||
if (readOnly && hashes.editHash) {
|
||||
return void cb({error: 'ALREADYKNOWN'});
|
||||
}
|
||||
|
@ -1030,8 +1030,6 @@ define([
|
|||
var crypto = Crypto.createEncryptor(_secret.keys);
|
||||
nThen(function (waitFor) {
|
||||
// Try to get the owner's mailbox from the pad metadata first.
|
||||
// If it's is an older owned pad, check if the owner is a friend
|
||||
// or an acquaintance (from async-store directly in requestAccess)
|
||||
var todo = function (obj) {
|
||||
owners = obj.owners;
|
||||
|
||||
|
@ -1065,11 +1063,12 @@ define([
|
|||
}));
|
||||
}).nThen(function () {
|
||||
// If we are just checking (send === false) and there is a mailbox field, cb state true
|
||||
// If there is no mailbox, we'll have to check if an owner is a friend in the worker
|
||||
if (!send) { return void cb({state: Boolean(owner)}); }
|
||||
|
||||
Cryptpad.padRpc.requestAccess({
|
||||
Cryptpad.padRpc.contactOwner({
|
||||
send: send,
|
||||
query: data.query,
|
||||
msgData: data.msgData,
|
||||
channel: _secret.channel,
|
||||
owner: owner,
|
||||
owners: owners
|
||||
|
|
|
@ -663,49 +663,6 @@ MessengerUI, Messages, Pages) {
|
|||
return $shareBlock;
|
||||
};
|
||||
|
||||
/*
|
||||
var createRequest = function (toolbar, config) {
|
||||
if (!config.metadataMgr) {
|
||||
throw new Error("You must provide a `metadataMgr` to display the request access button");
|
||||
}
|
||||
|
||||
// We can only requets more access if we're in read-only mode
|
||||
if (config.readOnly !== 1) { return; }
|
||||
|
||||
var $requestBlock = $('<button>', {
|
||||
'class': 'fa fa-lock cp-toolbar-share-button',
|
||||
title: Messages.requestEdit_button
|
||||
}).hide();
|
||||
|
||||
// If we have access to the owner's mailbox, display the button and enable it
|
||||
// false => check if we can contact the owner
|
||||
// true ==> send the request
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:false}, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
var locked = false;
|
||||
$requestBlock.show().click(function () {
|
||||
if (locked) { return; }
|
||||
locked = true;
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:true}, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
UI.log(Messages.requestEdit_sent);
|
||||
$requestBlock.hide();
|
||||
} else {
|
||||
locked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
toolbar.$leftside.append($requestBlock);
|
||||
toolbar.request = $requestBlock;
|
||||
|
||||
return $requestBlock;
|
||||
};
|
||||
*/
|
||||
|
||||
var createTitle = function (toolbar, config) {
|
||||
var $titleContainer = $('<span>', {
|
||||
'class': TITLE_CLS
|
||||
|
|
|
@ -1500,5 +1500,57 @@
|
|||
"og_register": "Registriere einen Account auf {0}",
|
||||
"admin_conflictExplanation": "Es gibt zwei Versionen dieses Dokuments. Wenn du die archivierte Version wiederherstellst, wird die aktuelle Version überschrieben. Wenn du die aktuelle Version archivierst, überschreibst du die archivierte Version. Beide Aktionen können nicht rückgängig gemacht werden.",
|
||||
"admin_note": "Abo-Notiz",
|
||||
"admin_planName": "Abo-Name"
|
||||
"admin_planName": "Abo-Name",
|
||||
"calendar_rec_until_count2": "Ereignissen",
|
||||
"calendar_rec_until_date": "Am",
|
||||
"calendar_rec_until_no": "Nie",
|
||||
"calendar_rec_until": "Wiederholen beenden",
|
||||
"calendar_str_filter_month": "Monate: {0}",
|
||||
"calendar_str_filter_weekno": "Wochen: {0}",
|
||||
"calendar_str_filter_day": "Tage: {0}",
|
||||
"calendar_rec_edit": "Dies ist ein sich wiederholendes Ereignis",
|
||||
"calendar_rec_edit_one": "Nur dieses Ereignis bearbeiten",
|
||||
"calendar_rec_edit_from": "Alle zukünftigen Ereignisse bearbeiten",
|
||||
"calendar_rec_edit_all": "Alle Ereignisse bearbeiten",
|
||||
"calendar_rec_stop": "Nicht mehr wiederholen",
|
||||
"calendar_month_last": "letzter Tag",
|
||||
"calendar_rec_until_count": "Nach",
|
||||
"calendar_rec_freq_yearly": "Jahre",
|
||||
"calendar_rec_freq_monthly": "Monate",
|
||||
"calendar_rec_freq_weekly": "Wochen",
|
||||
"calendar_rec_freq_daily": "Tage",
|
||||
"calendar_str_filter": "Filter:",
|
||||
"calendar_rec_txt": "Wiederholen alle",
|
||||
"calendar_rec_custom": "Benutzerdefiniert",
|
||||
"calendar_rec_weekdays": "Täglich an Wochentagen",
|
||||
"calendar_rec_weekend": "Täglich an Wochenenden",
|
||||
"calendar_rec_weekly": "Wöchentlich am {0}",
|
||||
"calendar_rec_daily": "Täglich",
|
||||
"calendar_rec_no": "Einmalig",
|
||||
"calendar_rec": "Wiederholen",
|
||||
"fm_rmFilter": "Filter entfernen",
|
||||
"fm_filterBy": "Filter",
|
||||
"calendar_rec_yearly": "Jährlich am {2}",
|
||||
"calendar_str_yearly": "{0} Jahr(e)",
|
||||
"calendar_str_monthly": "{0} Monat(e)",
|
||||
"calendar_nth_5": "fünften",
|
||||
"calendar_rec_every_date": "Jeden {0}",
|
||||
"calendar_nth_4": "vierten",
|
||||
"calendar_nth_3": "dritten",
|
||||
"calendar_list": "{0}, {1}",
|
||||
"calendar_nth_2": "zweiten",
|
||||
"calendar_list_end": "{0} oder {1}",
|
||||
"calendar_nth_1": "ersten",
|
||||
"calendar_rec_monthly": "Monatlich, Tag {1}",
|
||||
"calendar_str_for": "für {0} Ereignisse",
|
||||
"calendar_str_until": "bis zum {0}",
|
||||
"calendar_str_monthday": "am {0}",
|
||||
"calendar_rec_monthly_nth": "Jeden {0} {1} im Monat",
|
||||
"calendar_rec_yearly_nth": "Jeden {0} {1} im {2}",
|
||||
"calendar_rec_monthly_pick": "An Tagen",
|
||||
"calendar_str_daily": "{0} Tag(e)",
|
||||
"calendar_str_weekly": "{0} Woche(n)",
|
||||
"calendar_str_nthdayofmonth": "am {0} im {1}",
|
||||
"calendar_str_day": "am {0}",
|
||||
"calendar_nth_last": "letzten"
|
||||
}
|
||||
|
|
|
@ -1500,5 +1500,60 @@
|
|||
"admin_listMyInstanceHint": "Si tu instancia es adecuada para el uso público puedes consentir a que sea enlistada en los directorios de la red. La telemetría del servidor debe estar activada para que esto tenga algún efecto.",
|
||||
"admin_listMyInstanceTitle": "Listar mi instancia en los directorios públicos",
|
||||
"admin_consentToContactLabel": "Consiento",
|
||||
"admin_consentToContactHint": "La telemetría del servidor incluye el correo de contacto del administrador/a para que así los/as desarrolladores/as puedan notificarte de problemas serios con el software o tu configuración. Nunca será compartido, vendido, o usado por razones de marketing. Consiente al contacto si te gustaría estar informado/a de problemas críticos en tu servidor."
|
||||
"admin_consentToContactHint": "La telemetría del servidor incluye el correo de contacto del administrador/a para que así los/as desarrolladores/as puedan notificarte de problemas serios con el software o tu configuración. Nunca será compartido, vendido, o usado por razones de marketing. Consiente al contacto si te gustaría estar informado/a de problemas críticos en tu servidor.",
|
||||
"calendar_nth_5": "quinto",
|
||||
"calendar_nth_last": "último",
|
||||
"calendar_rec_monthly_nth": "Cada {0} {1} del mes",
|
||||
"calendar_rec_yearly_nth": "Cada {0} {1} de {2}",
|
||||
"calendar_rec_every_date": "Cada {0}",
|
||||
"calendar_nth_4": "cuarto",
|
||||
"calendar_month_last": "último día",
|
||||
"calendar_nth_3": "tercero",
|
||||
"calendar_list": "{0}, {1}",
|
||||
"calendar_nth_2": "segundo",
|
||||
"calendar_list_end": "{0} ó {1}",
|
||||
"calendar_nth_1": "primero",
|
||||
"calendar_str_yearly": "{0} año(s)",
|
||||
"calendar_str_monthly": "{0} mes(es)",
|
||||
"calendar_rec_monthly_pick": "Se repite en",
|
||||
"calendar_str_weekly": "{0} semana(s)",
|
||||
"calendar_rec_until_count2": "tiempos",
|
||||
"calendar_rec_until_count": "Después",
|
||||
"calendar_str_daily": "{0} día(s)",
|
||||
"calendar_rec_until_date": "En",
|
||||
"calendar_str_day": "en {0}",
|
||||
"calendar_rec_until_no": "Nunca",
|
||||
"calendar_rec_until": "Dejar de repetir",
|
||||
"calendar_str_monthday": "en el {0}",
|
||||
"calendar_rec_freq_yearly": "años",
|
||||
"calendar_str_nthdayofmonth": "en el {0} de {1}",
|
||||
"calendar_str_for": "por {0} veces",
|
||||
"calendar_rec_freq_monthly": "meses",
|
||||
"calendar_str_until": "hasta {0}",
|
||||
"calendar_rec_freq_weekly": "semanas",
|
||||
"calendar_rec_freq_daily": "días",
|
||||
"calendar_str_filter": "Filtros:",
|
||||
"calendar_rec_txt": "Repetir cada",
|
||||
"calendar_str_filter_month": "Meses: {0}",
|
||||
"calendar_str_filter_weekno": "Semanas: {0}",
|
||||
"calendar_str_filter_yearday": "Días del año: {0}",
|
||||
"calendar_rec_custom": "Personalizado",
|
||||
"calendar_str_filter_monthday": "Días del mes: {0}",
|
||||
"calendar_rec_weekdays": "Diariamente los días de semana",
|
||||
"calendar_str_filter_day": "Días: {0}",
|
||||
"calendar_rec_edit": "Este es un evento repetido",
|
||||
"calendar_rec_weekend": "Diariamente los fines de semana",
|
||||
"calendar_rec_edit_one": "Solo editar este evento",
|
||||
"calendar_rec_yearly": "Anualmente en {2}",
|
||||
"calendar_rec_edit_from": "Editar todos los futuros eventos",
|
||||
"calendar_rec_monthly": "Mensual, día {1}",
|
||||
"calendar_rec_edit_all": "Editar todos los eventos",
|
||||
"calendar_rec_weekly": "Semanal en {0}",
|
||||
"calendar_rec_stop": "Dejar de repetir",
|
||||
"calendar_rec_daily": "Diario",
|
||||
"calendar_rec_updated": "Regla actualizada en {0}",
|
||||
"calendar_rec_no": "Ninguno",
|
||||
"calendar_rec": "Repetir",
|
||||
"fm_rmFilter": "Eliminar filtro",
|
||||
"fm_filterBy": "Filtro"
|
||||
}
|
||||
|
|
|
@ -1500,5 +1500,65 @@
|
|||
"og_features": "{0} Fonctionnalités",
|
||||
"og_encryptedAppType": "Chiffré {0}",
|
||||
"admin_conflictExplanation": "Il existe deux versions de ce document. La restauration de la version archivée va écraser la version courante. L'archivage de la version courante va écraser le document archivé. Aucune de ces actions ne peut être annulée.",
|
||||
"admin_documentConflict": "Archiver/restaurer"
|
||||
"admin_documentConflict": "Archiver/restaurer",
|
||||
"fm_rmFilter": "Désactiver le filtre",
|
||||
"fm_filterBy": "Filtrer",
|
||||
"calendar_nth_2": "deuxième",
|
||||
"calendar_nth_1": "premier",
|
||||
"calendar_rec_monthly_pick": "Certains jours",
|
||||
"calendar_rec_until_count2": "fois",
|
||||
"calendar_rec_until_count": "Après",
|
||||
"calendar_rec_until_date": "Le",
|
||||
"calendar_rec_until_no": "Jamais",
|
||||
"calendar_rec_until": "Se termine",
|
||||
"calendar_rec_freq_yearly": "ans",
|
||||
"calendar_rec_freq_monthly": "mois",
|
||||
"calendar_rec_freq_weekly": "semaines",
|
||||
"calendar_rec_freq_daily": "jours",
|
||||
"calendar_rec_txt": "Répéter tous les",
|
||||
"calendar_rec_custom": "Personnalisé",
|
||||
"calendar_rec_weekdays": "Chaque jour (semaine)",
|
||||
"calendar_rec_weekend": "Chaque jour (week-end)",
|
||||
"calendar_rec_yearly": "Chaque année le {2}",
|
||||
"calendar_rec_monthly": "Chaque {1} du mois",
|
||||
"calendar_rec_weekly": "Chaque semaine le {0}",
|
||||
"calendar_rec_stop": "Ne plus répéter",
|
||||
"calendar_rec_daily": "Chaque jour",
|
||||
"calendar_rec_updated": "Règle mise à jour le {0}",
|
||||
"calendar_rec_no": "Une fois",
|
||||
"calendar_rec": "Répéter",
|
||||
"calendar_rec_monthly_nth": "Chaque {0} {1} du mois",
|
||||
"calendar_rec_yearly_nth": "Chaque {0} {1} de {2}",
|
||||
"calendar_rec_every_date": "Chaque {0}",
|
||||
"calendar_month_last": "dernier jour",
|
||||
"calendar_list": "{0}, {1}",
|
||||
"calendar_list_end": "{0} ou {1}",
|
||||
"calendar_str_yearly": "{0} an(s)",
|
||||
"calendar_str_monthly": "{0} mois",
|
||||
"calendar_str_weekly": "{0} semaine(s)",
|
||||
"calendar_str_daily": "{0} jour(s)",
|
||||
"calendar_str_day": "le {0}",
|
||||
"calendar_str_monthday": "le {0}",
|
||||
"calendar_str_nthdayofmonth": "le {0} {1}",
|
||||
"calendar_str_for": "pour {0} fois",
|
||||
"calendar_str_until": "jusqu'au {0}",
|
||||
"calendar_str_filter": "Filtres :",
|
||||
"calendar_str_filter_month": "Mois : {0}",
|
||||
"calendar_str_filter_weekno": "Semaines : {0}",
|
||||
"calendar_str_filter_yearday": "Jours de l'année : {0}",
|
||||
"calendar_str_filter_monthday": "Jours du mois : {0}",
|
||||
"calendar_str_filter_day": "Jours : {0}",
|
||||
"calendar_rec_edit": "Cet événement se répète",
|
||||
"calendar_rec_edit_one": "Modifier seulement cet événement",
|
||||
"calendar_rec_edit_from": "Modifier les événements futurs",
|
||||
"calendar_rec_edit_all": "Modifier tous les événements",
|
||||
"calendar_nth_last": "dernier",
|
||||
"calendar_nth_5": "cinquième",
|
||||
"calendar_nth_4": "quatrième",
|
||||
"calendar_nth_3": "troisième",
|
||||
"calendar_removeNotification": "Supprimer le rappel",
|
||||
"calendar_rec_warn_updateall": "La règle de répétition de cet événement a été modifiée. Le premier événement sur {0} sera conservé, tous les autres seront remplacés.",
|
||||
"calendar_rec_warn_update": "La règle de répétition de cet événement a été modifiée. Les événements futurs seront remplacés.",
|
||||
"calendar_rec_warn_delall": "Cet événement ne sera plus répété. Le premier événement du {0} sera conservé, tous les autres seront supprimés.",
|
||||
"calendar_rec_warn_del": "Cet événement ne sera plus répété. Les événements futurs seront supprimés."
|
||||
}
|
||||
|
|
|
@ -1495,5 +1495,49 @@
|
|||
"admin_blockKey": "ブロックの公開鍵",
|
||||
"admin_blockMetadataPlaceholder": "ブロックの絶対または相対URL",
|
||||
"admin_restoreReason": "復元の理由を指定し、確認して続行してください",
|
||||
"admin_archiveReason": "アーカイブの理由を指定し、確認して続行してください"
|
||||
"admin_archiveReason": "アーカイブの理由を指定し、確認して続行してください",
|
||||
"calendar_nth_1": "第1",
|
||||
"calendar_list_end": "{0}または{1}",
|
||||
"calendar_nth_2": "第2",
|
||||
"calendar_list": "{0}、{1}",
|
||||
"calendar_nth_3": "第3",
|
||||
"calendar_nth_last": "最終",
|
||||
"calendar_nth_5": "第5",
|
||||
"calendar_rec_every_date": "毎{0}",
|
||||
"calendar_nth_4": "第4",
|
||||
"calendar_month_last": "最終日",
|
||||
"calendar_str_yearly": "{0}年",
|
||||
"calendar_str_weekly": "{0}週",
|
||||
"calendar_str_monthly": "{0}月",
|
||||
"calendar_rec_until_count2": "回",
|
||||
"calendar_str_daily": "{0}日",
|
||||
"calendar_str_filter_month": "月:{0}",
|
||||
"calendar_str_filter_weekno": "週:{0}",
|
||||
"calendar_rec_custom": "ユーザー定義",
|
||||
"calendar_rec_edit": "これは繰り返すイベントです",
|
||||
"calendar_rec_weekend": "毎週末",
|
||||
"calendar_rec_edit_one": "このイベントのみを編集",
|
||||
"calendar_rec_yearly": "毎年{2}",
|
||||
"calendar_rec_edit_from": "全ての未来のイベントを編集",
|
||||
"calendar_rec_monthly": "毎月{1}日",
|
||||
"calendar_rec_edit_all": "全てのイベントを編集",
|
||||
"calendar_rec_weekly": "毎週{0}",
|
||||
"calendar_rec_stop": "繰り返しを停止",
|
||||
"calendar_rec_daily": "毎日",
|
||||
"calendar_rec_until": "繰り返しを停止",
|
||||
"calendar_str_monthday": "{0}に",
|
||||
"calendar_rec_freq_yearly": "年",
|
||||
"calendar_str_for": "{0}回まで",
|
||||
"calendar_rec_freq_monthly": "月",
|
||||
"calendar_str_until": "{0}まで",
|
||||
"calendar_rec_freq_weekly": "週",
|
||||
"calendar_rec_freq_daily": "日",
|
||||
"calendar_str_filter": "フィルター:",
|
||||
"calendar_rec_txt": "繰り返しの頻度",
|
||||
"calendar_rec_no": "なし",
|
||||
"calendar_rec": "繰り返す",
|
||||
"fm_rmFilter": "フィルターを削除",
|
||||
"fm_filterBy": "フィルター",
|
||||
"admin_conflictExplanation": "このドキュメントには2つのバージョンがあります。アーカイブされたバージョンを復元すると、現在のバージョンが上書きされます。現在のバージョンをアーカイブすると、アーカイブ済のバージョンが上書きされます。どちらのアクションも取り消しできません。",
|
||||
"admin_documentConflict": "アーカイブ/復元"
|
||||
}
|
||||
|
|
|
@ -1500,5 +1500,65 @@
|
|||
"ui_jsRequired": "JavaScript must be enabled to perform encryption in your browser",
|
||||
"og_encryptedAppType": "Encrypted {0}",
|
||||
"admin_documentConflict": "Archive/restore",
|
||||
"admin_conflictExplanation": "Two versions of this document exist. Restoring the archived version will overwrite the live version. Archiving the live version will overwrite the archived version. Neither action can be undone."
|
||||
"admin_conflictExplanation": "Two versions of this document exist. Restoring the archived version will overwrite the live version. Archiving the live version will overwrite the archived version. Neither action can be undone.",
|
||||
"fm_filterBy": "Filter",
|
||||
"fm_rmFilter": "Remove filter",
|
||||
"calendar_rec": "Repeat",
|
||||
"calendar_rec_no": "One time",
|
||||
"calendar_rec_updated": "Rule updated on {0}",
|
||||
"calendar_rec_daily": "Daily",
|
||||
"calendar_rec_stop": "Stop repeating",
|
||||
"calendar_rec_weekly": "Weekly on {0}",
|
||||
"calendar_rec_edit_all": "Edit all events",
|
||||
"calendar_rec_monthly": "Monthly, day {1}",
|
||||
"calendar_rec_edit_from": "Edit future events",
|
||||
"calendar_rec_yearly": "Yearly on {2}",
|
||||
"calendar_rec_edit_one": "Edit only this event",
|
||||
"calendar_rec_weekend": "Daily on weekends",
|
||||
"calendar_rec_edit": "This is a repeating event",
|
||||
"calendar_str_filter_day": "Days: {0}",
|
||||
"calendar_rec_weekdays": "Daily on weekdays",
|
||||
"calendar_str_filter_monthday": "Days of month: {0}",
|
||||
"calendar_rec_custom": "Custom",
|
||||
"calendar_str_filter_yearday": "Days of year: {0}",
|
||||
"calendar_str_filter_weekno": "Weeks: {0}",
|
||||
"calendar_str_filter_month": "Months: {0}",
|
||||
"calendar_rec_txt": "Repeat every",
|
||||
"calendar_str_filter": "Filters:",
|
||||
"calendar_rec_freq_daily": "days",
|
||||
"calendar_rec_freq_weekly": "weeks",
|
||||
"calendar_str_until": "until {0}",
|
||||
"calendar_rec_freq_monthly": "months",
|
||||
"calendar_str_for": "for {0} times",
|
||||
"calendar_str_nthdayofmonth": "on the {0} of {1}",
|
||||
"calendar_rec_freq_yearly": "years",
|
||||
"calendar_str_monthday": "on the {0}",
|
||||
"calendar_rec_until": "Stop Repeating",
|
||||
"calendar_rec_until_no": "Never",
|
||||
"calendar_str_day": "on {0}",
|
||||
"calendar_rec_until_date": "On",
|
||||
"calendar_str_daily": "{0} day(s)",
|
||||
"calendar_rec_until_count": "After",
|
||||
"calendar_rec_until_count2": "times",
|
||||
"calendar_str_weekly": "{0} week(s)",
|
||||
"calendar_rec_monthly_pick": "On days",
|
||||
"calendar_str_monthly": "{0} month(s)",
|
||||
"calendar_str_yearly": "{0} year(s)",
|
||||
"calendar_nth_1": "first",
|
||||
"calendar_list_end": "{0} or {1}",
|
||||
"calendar_nth_2": "second",
|
||||
"calendar_list": "{0}, {1}",
|
||||
"calendar_nth_3": "third",
|
||||
"calendar_month_last": "last day",
|
||||
"calendar_nth_4": "fourth",
|
||||
"calendar_rec_every_date": "Every {0}",
|
||||
"calendar_rec_yearly_nth": "Every {0} {1} of {2}",
|
||||
"calendar_rec_monthly_nth": "Every {0} {1} of the month",
|
||||
"calendar_nth_last": "last",
|
||||
"calendar_nth_5": "fifth",
|
||||
"calendar_rec_warn_del": "This event will no longer repeat. Future events will be removed.",
|
||||
"calendar_rec_warn_delall": "This event will no longer repeat. The first event on {0} will be kept, all others will be removed.",
|
||||
"calendar_rec_warn_update": "The rule for repeating this event was modified. Future events will be replaced.",
|
||||
"calendar_rec_warn_updateall": "The rule for repeating this event was modified. The first event on {0} will be kept, all others will be replaced.",
|
||||
"calendar_removeNotification": "Remove reminder"
|
||||
}
|
||||
|
|
|
@ -989,7 +989,7 @@
|
|||
}
|
||||
.cp-poll-cell {
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
@ -13,8 +13,71 @@ define([
|
|||
value += '"' + vv + '"';
|
||||
return value;
|
||||
};
|
||||
Export.results = function (content, answers, TYPES, order, isArray) {
|
||||
|
||||
var exportJSON = function (content, answers, TYPES, order) {
|
||||
var form = content.form;
|
||||
var res = {
|
||||
questions: {},
|
||||
responses: []
|
||||
};
|
||||
var q = res.questions;
|
||||
var r = res.responses;
|
||||
|
||||
// Add questions
|
||||
var i = 1;
|
||||
order.forEach(function (key) {
|
||||
var obj = form[key];
|
||||
if (!obj) { return; }
|
||||
var type = obj.type;
|
||||
if (!TYPES[type]) { return; } // Ignore static types
|
||||
var id = `q${i++}`;
|
||||
if (TYPES[type] && TYPES[type].exportCSV) {
|
||||
var _obj = Util.clone(obj);
|
||||
_obj.q = "tmp";
|
||||
q[id] = {
|
||||
question: obj.q,
|
||||
items: TYPES[type].exportCSV(false, _obj).map(function (str) {
|
||||
return str.slice(6); // Remove "tmp | "
|
||||
})
|
||||
};
|
||||
} else {
|
||||
q[id] = obj.q || Messages.form_default;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(answers || {}).forEach(function (key) {
|
||||
var obj = answers[key];
|
||||
var time = new Date(obj.time).toISOString();
|
||||
var msg = obj.msg || {};
|
||||
var user = msg._userdata || {};
|
||||
var data = {
|
||||
'_time': time,
|
||||
'_name': user.name || Messages.anonymous
|
||||
};
|
||||
|
||||
var i = 1;
|
||||
order.forEach(function (key) {
|
||||
if (!form[key]) { return; }
|
||||
var type = form[key].type;
|
||||
if (!TYPES[type]) { return; } // Ignore static types
|
||||
var id = `q${i++}`;
|
||||
if (TYPES[type].exportCSV) {
|
||||
data[id] = TYPES[type].exportCSV(msg[key], form[key]);
|
||||
return;
|
||||
}
|
||||
data[id] = msg[key];
|
||||
});
|
||||
r.push(data);
|
||||
});
|
||||
|
||||
return JSON.stringify(res, 0, 2);
|
||||
};
|
||||
Export.results = function (content, answers, TYPES, order, format) {
|
||||
if (!content || !content.form) { return; }
|
||||
|
||||
if (format === "json") { return exportJSON(content, answers, TYPES, order); }
|
||||
|
||||
var isArray = format === "array";
|
||||
var csv = "";
|
||||
var array = [];
|
||||
var form = content.form;
|
||||
|
|
|
@ -2334,6 +2334,12 @@ define([
|
|||
}
|
||||
}
|
||||
});
|
||||
var sortNode = function (order) {
|
||||
order.forEach(function (uid) {
|
||||
$tag.append($tag.find('[data-id="'+uid+'"]'));
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
tag: tag,
|
||||
isEmpty: function () { return !this.getValue(); },
|
||||
|
@ -2348,7 +2354,7 @@ define([
|
|||
var toSort = extractValues(opts.values).map(function (val) {
|
||||
return invMap[val];
|
||||
});
|
||||
sortable.sort(toSort);
|
||||
sortNode(toSort);
|
||||
reorder(true);
|
||||
},
|
||||
setEditable: function (state) {
|
||||
|
@ -2365,7 +2371,7 @@ define([
|
|||
var toSort = val.map(function (val) {
|
||||
return invMap[val];
|
||||
});
|
||||
sortable.sort(toSort);
|
||||
sortNode(toSort);
|
||||
reorder();
|
||||
}
|
||||
};
|
||||
|
@ -2658,6 +2664,21 @@ define([
|
|||
}), title);
|
||||
});
|
||||
|
||||
// Export JSON
|
||||
Messages.form_exportJSON = "Export as JSON"; // XXX
|
||||
var exportJSONButton = h('button.btn.btn-primary', [
|
||||
h('i.cptools.cptools-code'),
|
||||
Messages.form_exportJSON
|
||||
]);
|
||||
$(exportJSONButton).appendTo($controls);
|
||||
$(exportJSONButton).click(function () {
|
||||
var arr = Exporter.results(content, answers, TYPES, getFullOrder(content), "json");
|
||||
if (!arr) { return void UI.warn(Messages.error); }
|
||||
window.saveAs(new Blob([arr], {
|
||||
type: 'application/json'
|
||||
}), title+".json");
|
||||
});
|
||||
|
||||
// Export in "sheet"
|
||||
var export2Button = h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-file-excel-o'),
|
||||
|
@ -2665,7 +2686,7 @@ define([
|
|||
]);
|
||||
$(export2Button).appendTo($controls);
|
||||
$(export2Button).click(function () {
|
||||
var arr = Exporter.results(content, answers, TYPES, getFullOrder(content), true);
|
||||
var arr = Exporter.results(content, answers, TYPES, getFullOrder(content), "array");
|
||||
if (!arr) { return void UI.warn(Messages.error); }
|
||||
var sframeChan = framework._.sfCommon.getSframeChannel();
|
||||
var title = framework._.title.title || framework._.title.defaultTitle;
|
||||
|
@ -3222,6 +3243,17 @@ define([
|
|||
if (content.answers.cantEdit) {
|
||||
$(radioContainer).hide();
|
||||
}
|
||||
|
||||
// TODO show the author that they can "mute" the pad
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
sframeChan.query('Q_CONTACT_OWNER', {
|
||||
send: true,
|
||||
query: "FORM_RESPONSE",
|
||||
msgData: { channel: priv.channel }
|
||||
}, function (err, obj) {
|
||||
if (err || !obj || !obj.state) { return console.error('ENOTIFY'); }
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -43,7 +43,14 @@ define([
|
|||
};
|
||||
var getEndDate = function () {
|
||||
setTimeout(function () { $(endPickr.calendarContainer).remove(); });
|
||||
return endPickr.parseDate(e.value);
|
||||
var d = endPickr.parseDate(e.value);
|
||||
|
||||
if (endPickr.config.dateFormat === "Y-m-d") { // All day event
|
||||
// Tui-calendar will remove 1s (1000ms) to the date for an unknown reason...
|
||||
d.setMilliseconds(1000);
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -12,4 +12,5 @@ This file is intended to be used as a log of what third-party source we have ven
|
|||
* [Fabricjs 4.6.0](https://github.com/fabricjs/fabric.js) and [Fabric-history](https://github.com/lyzerk/fabric-history) for the whiteboard app
|
||||
* [Requirejs optional module plugin](https://stackoverflow.com/a/27422370)
|
||||
* [asciidoc.js 2.0.0](https://github.com/asciidoctor/codemirror-asciidoc/releases/tag/2.0.0) with slight changes to match the format of other codemirror modes
|
||||
* [Asciidoctor.js 2.2.6](https://github.com/asciidoctor/asciidoctor.js/releases/tag/v2.2.6) for AsciiDoc rendering
|
||||
|
||||
|
|
Loading…
Reference in New Issue