mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Merge branch 'master' into conferencing
This commit is contained in:
commit
80a9f264c5
@ -34,4 +34,4 @@ customize/
|
||||
www/debug/chainpad.dist.js
|
||||
|
||||
www/pad/mathjax/
|
||||
www/code/mermaid.js
|
||||
www/code/mermaid*.js
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@ -1,3 +1,50 @@
|
||||
# PigFootedBandicoot release (3.15.0)
|
||||
|
||||
## Goals
|
||||
|
||||
Our plan for this release was to allow our server's code to stabilize after a prologued period of major changes. The massive surge of new users on cryptpad.fr forced us to change our plans and focus instead on increasing performance and scalability of our serverside code and its supporting infrastructure. Most of this release's changes have been thoroughly tested as they've been deployed to our instance on an ongoing basis, however, we're still looking forward to stabilizing as planned.
|
||||
|
||||
We also ended up making significant improvements to our clientside code, since the increased load on the server seemed to exacerbate a few race conditions which occurred less frequently under the previous circumstances.
|
||||
|
||||
## Update notes
|
||||
|
||||
Updating from version 3.14.0 should follow the usual process:
|
||||
|
||||
1. stop your server
|
||||
2. fetch the latest code with git
|
||||
3. install clientside dependencies with `bower update`
|
||||
4. install serverside dependencies with `npm i`
|
||||
5. start your server
|
||||
|
||||
You may notice that the server now launches a number of child processes named `crypto-worker.js` and `db-worker.js`. These worker processes make use of however many cores your server has available to perform more CPU-intensive tasks in parallel.
|
||||
|
||||
## Features
|
||||
|
||||
* As noted above, the server uses an multi-process architecture and parallelizes more routines. This improvement will be the most noticeable when the server is run on ARM processors which validate cryptographic signatures particularly slowly.
|
||||
* The admin panel available to instance administrators now displays a list of "Open files". We added this to help us diagnose a "file descriptor leak" which will be described in the _Bug fixes_ section.
|
||||
* We received a large number of contributions from translators via our [weblate instance](https://weblate.cryptpad.fr/projects/cryptpad/app/). Most notably, Italian is the fourth language to be fully translated with Finnish and Spanish seemingly in line to take the fifth and sixth spots.
|
||||
* We've addressed some usability issues in our whiteboard app in response to increased interest. Its canvas now automatically resizes according to the size of your screen and the content you've drawn. Unfortunately, we noticed that the "embed image" functionality was imposing some additional strain on our server, so we decided to implement an admittedly arbitrary limit of 1MB on the size of images embedded in whiteboards. We'll consider removing this restriction when we have time to design a more efficient embedding system.
|
||||
* We've removed the per-user setting which previously allowed registered users to skip the "pad creation screen" which is displayed before creating a document. This setting has not been the default for some time and was not actively tested, so this "feature" is our way of guaranteeing no future regressions in its behaviour.
|
||||
* As a part of our effort to improve the server's scalability we evaluated which clientside requests could be sent less often. One such request came from the "usage bar" found in users' drives, teams, and settings pages. Previously it would update every 30 seconds no matter what. Now it only updates if that tab is focused.
|
||||
* Most actions that an administrator can take with regard to a user's account require the "public key" which is used to identify their account. This key is available on the user's settings page, but many users share their profile URL instead. We've added a button to profile pages which copies the user's public key to the clipboard, so now either page will be sufficient.
|
||||
* We've updated our [mermaidjs](https://mermaid-js.github.io/mermaid/#/) dependency. For those that don't know, Mermaid is a powerful markup syntax for producing a variety of charts. It's integrated into our code editor. This updated version supports GANTT chart tasks with multiple dependencies, pie charts, and a variety of other useful formats.
|
||||
* We found that in practice our mermaid charts and other embedded media were sufficiently detailed that they became difficult to read on some screens. In response we've added the ability to view these elements in a "lightbox UI" which is nearly full-screen. This interface is can be used to view media contained in the "preview pane" of the code editor as well as within user and team drives, as well as a few other places where Markdown is used.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
This release contains fixes for a lot of bugs. We'll provide a brief overview, but in the interest of putting more time towards development I'll just put my strong recommendation that you update.
|
||||
|
||||
* The server process didn't always close file descriptors that it opened, resulting in an EMFILE error when the system ran out of available file descriptors. Now it closes them.
|
||||
* The server also kept an unbounded amount of data in an in-memory cache under certain circumstances. Now it doesn't.
|
||||
* A simple check to ignore the `premiumUploadSize` config value if it was less than `maxUploadSize` incorrectly compared against `defaultStorageLimit`. Premium upload sizes were disabled on our instance when we increased the default storage limit to 1GB. It's fixed now.
|
||||
* We accepted a [PR](https://github.com/xwiki-labs/cryptpad/pull/513) to prevent a typeError when logging to disk was entirely disabled.
|
||||
* We identified and fixed the cause of [This issue](https://github.com/xwiki-labs/cryptpad/issues/518) which caused spreadsheets not to load.
|
||||
* Emojis at the start of users display names were not displayed correctly in the Kanban's "cursor"
|
||||
* We (once again) believe we've fixed the [duplicated text bug](https://github.com/xwiki-labs/cryptpad/issues/352). Time will tell.
|
||||
* Our existing Mermaidjs integration supported the special syntax to make elements clickable, but the resulting links don't work within CryptPad. We now remove them.
|
||||
* Rather than having messages time out if they are not received by the server within a certain timeframe we now wait until the client reconnects, at which point we can check whether those messages exist in the document's history. On a related note we now detect when the realtime system is in a bad state and recreate it.
|
||||
* Finally, we've fixed a variety of errors in spreadsheets.
|
||||
|
||||
# OrienteCaveRat release (3.14.0)
|
||||
|
||||
## Goals
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
"secure-fabric.js": "secure-v1.7.9",
|
||||
"hyperjson": "~1.4.0",
|
||||
"chainpad-crypto": "^0.2.0",
|
||||
"chainpad-listmap": "^0.8.1",
|
||||
"chainpad": "^5.1.0",
|
||||
"chainpad-listmap": "^0.9.0",
|
||||
"chainpad": "^5.2.0",
|
||||
"file-saver": "1.3.1",
|
||||
"alertifyjs": "1.0.11",
|
||||
"scrypt-async": "1.2.0",
|
||||
|
||||
@ -107,7 +107,7 @@ define([
|
||||
])*/
|
||||
])
|
||||
]),
|
||||
h('div.cp-version-footer', "CryptPad v3.14.0 (OrienteCaveRat)")
|
||||
h('div.cp-version-footer', "CryptPad v3.15.0 (PigFootedBandicoot)")
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@ -90,6 +90,11 @@
|
||||
border: 1px solid #BBB;
|
||||
}
|
||||
|
||||
pre.mermaid {
|
||||
svg {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markdown_preformatted-code (@color: #333) {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
@import (reference) "./colortheme-all.less";
|
||||
@import (reference) "./variables.less";
|
||||
@import (reference) './buttons.less';
|
||||
|
||||
|
||||
|
||||
.modal_base() {
|
||||
font-family: @colortheme_font;
|
||||
@ -24,6 +27,9 @@
|
||||
.cp-modal-container {
|
||||
display: none;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 100000; //Z modal container
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -33,15 +39,19 @@
|
||||
background-color: @colortheme_modal-dim;
|
||||
|
||||
.cp-modal {
|
||||
.buttons_main();
|
||||
|
||||
background-color: @colortheme_modal-bg;
|
||||
color: @colortheme_modal-fg;
|
||||
box-shadow: @variables_shadow;
|
||||
|
||||
padding: @variables_padding;
|
||||
|
||||
position: absolute;
|
||||
top: 15vh; bottom: 15vh;
|
||||
left: 10vw; right: 10vw;
|
||||
position: relative;
|
||||
//top: 15vh; bottom: 15vh;
|
||||
//left: 10vw; right: 10vw;
|
||||
width: 90vw;
|
||||
max-height: 95vh;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
@ -70,6 +80,7 @@
|
||||
background-color: @colortheme_modal-input-fg;
|
||||
color: @cryptpad_text_col;
|
||||
border: 1px solid @colortheme_modal-input;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -106,4 +106,63 @@
|
||||
.cp-teams-help {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
// mediatag preview
|
||||
#cp-mediatag-preview-modal {
|
||||
.cp-modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.cp-mediatag-container {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
media-tag {
|
||||
& > * {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
video, iframe {
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
button {
|
||||
line-height: 1.5;
|
||||
}
|
||||
& > iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 75vh;
|
||||
}
|
||||
& > .plain-text-reader {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
color: @cryptpad_text_col;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
pre.mermaid {
|
||||
overflow: unset;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.cp-spinner {
|
||||
border-color: @colortheme_logo-1;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
}
|
||||
.cp-mediatag-control {
|
||||
align-self: center;
|
||||
.fa {
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.cp-mediatag-outer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,7 +371,7 @@
|
||||
width: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.fa {
|
||||
.fa, .cptools {
|
||||
font-size: 32px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
@ -49,16 +49,19 @@ var loadUserPins = function (Env, safeKey, cb) {
|
||||
// only put this into the cache if it completes
|
||||
session.channels = value;
|
||||
}
|
||||
session.channels = value;
|
||||
done(value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var truthyKeys = function (O) {
|
||||
return Object.keys(O).filter(function (k) {
|
||||
return O[k];
|
||||
});
|
||||
try {
|
||||
return Object.keys(O).filter(function (k) {
|
||||
return O[k];
|
||||
});
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
var getChannelList = Pinning.getChannelList = function (Env, safeKey, _cb) {
|
||||
|
||||
@ -383,7 +383,7 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
|
||||
* it has a side-effect of filling the index cache if it's empty
|
||||
1. if you provided a lastKnownHash and that message does not exist in the history:
|
||||
* either the client has made a mistake or the history they knew about no longer exists
|
||||
* call back with EINVAL
|
||||
* call back with EUNKNOWN
|
||||
2. if you did not provide a lastKnownHash
|
||||
* and there are fewer than two checkpoints:
|
||||
* return 0 (read from the start of the file)
|
||||
|
||||
63
lib/pins.js
63
lib/pins.js
@ -7,6 +7,9 @@ const Path = require("path");
|
||||
const Util = require("./common-util");
|
||||
const Plan = require("./plan");
|
||||
|
||||
const Semaphore = require('saferphore');
|
||||
const nThen = require('nthen');
|
||||
|
||||
/* Accepts a reference to an object, and...
|
||||
either a string describing which log is being processed (backwards compatibility),
|
||||
or a function which will log the error with all relevant data
|
||||
@ -194,3 +197,63 @@ Pins.list = function (_done, config) {
|
||||
}).start();
|
||||
});
|
||||
};
|
||||
|
||||
Pins.load = function (cb, config) {
|
||||
const sema = Semaphore.create(config.workers || 5);
|
||||
|
||||
let dirList;
|
||||
const fileList = [];
|
||||
const pinned = {};
|
||||
|
||||
var pinPath = config.pinPath || './pins';
|
||||
var done = Util.once(cb);
|
||||
|
||||
nThen((waitFor) => {
|
||||
// recurse over the configured pinPath, or the default
|
||||
Fs.readdir(pinPath, waitFor((err, list) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
dirList = [];
|
||||
return; // this ends up calling back with an empty object
|
||||
}
|
||||
waitFor.abort();
|
||||
return void done(err);
|
||||
}
|
||||
dirList = list;
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
dirList.forEach((f) => {
|
||||
sema.take((returnAfter) => {
|
||||
// iterate over all the subdirectories in the pin store
|
||||
Fs.readdir(Path.join(pinPath, f), waitFor(returnAfter((err, list2) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void done(err);
|
||||
}
|
||||
list2.forEach((ff) => {
|
||||
if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; }
|
||||
fileList.push(Path.join(pinPath, f, ff));
|
||||
});
|
||||
})));
|
||||
});
|
||||
});
|
||||
}).nThen((waitFor) => {
|
||||
fileList.forEach((f) => {
|
||||
sema.take((returnAfter) => {
|
||||
Fs.readFile(f, waitFor(returnAfter((err, content) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void done(err);
|
||||
}
|
||||
const hashes = Pins.calculateFromLog(content.toString('utf8'), f);
|
||||
hashes.forEach((x) => {
|
||||
(pinned[x] = pinned[x] || {})[f.replace(/.*\/([^/]*).ndjson$/, (x, y)=>y)] = 1;
|
||||
});
|
||||
})));
|
||||
});
|
||||
});
|
||||
}).nThen(() => {
|
||||
done(void 0, pinned);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -14,6 +14,28 @@ const readFileBin = require("../stream-file").readFileBin;
|
||||
const BatchRead = require("../batch-read");
|
||||
|
||||
const Schedule = require("../schedule");
|
||||
|
||||
/* Each time you write to a channel it will either use an open file descriptor
|
||||
for that channel or open a new descriptor if one is not available. These are
|
||||
automatically closed after this window to prevent a file descriptor leak, so
|
||||
writes that take longer than this time may be dropped! */
|
||||
const CHANNEL_WRITE_WINDOW = 300000;
|
||||
|
||||
/* Each time you read a channel it will have this many milliseconds to complete
|
||||
otherwise it will be closed to prevent a file descriptor leak. The server will
|
||||
lock up if it uses all available file descriptors, so it's important to close
|
||||
them. The tradeoff with this timeout is that some functions, the stream, and
|
||||
and the timeout itself are stored in memory. A longer timeout uses more memory
|
||||
and running out of memory will also kill the server. */
|
||||
const STREAM_CLOSE_TIMEOUT = 300000;
|
||||
|
||||
/* The above timeout closes the stream, but apparently that doesn't always work.
|
||||
We set yet another timeout to allow the runtime to gracefully close the stream
|
||||
(flushing all pending writes/reads and doing who knows what else). After this timeout
|
||||
it will be MERCILESSLY DESTROYED. This isn't graceful, but again, file descriptor
|
||||
leaks are bad. */
|
||||
const STREAM_DESTROY_TIMEOUT = 30000;
|
||||
|
||||
const isValidChannelId = function (id) {
|
||||
return typeof(id) === 'string' &&
|
||||
id.length >= 32 && id.length < 50 &&
|
||||
@ -64,7 +86,7 @@ const destroyStream = function (stream) {
|
||||
try { stream.close(); } catch (err) { console.error(err); }
|
||||
setTimeout(function () {
|
||||
try { stream.destroy(); } catch (err) { console.error(err); }
|
||||
}, 15000);
|
||||
}, STREAM_DESTROY_TIMEOUT);
|
||||
};
|
||||
|
||||
const ensureStreamCloses = function (stream, id, ms) {
|
||||
@ -74,7 +96,7 @@ const ensureStreamCloses = function (stream, id, ms) {
|
||||
// this can only be a timeout error...
|
||||
console.log("stream close error:", err, id);
|
||||
}
|
||||
}), ms || 45000), []);
|
||||
}), ms || STREAM_CLOSE_TIMEOUT), []);
|
||||
};
|
||||
|
||||
// readMessagesBin asynchronously iterates over the messages in a channel log
|
||||
@ -729,7 +751,7 @@ var getChannel = function (env, id, _callback) {
|
||||
delete env.channels[id];
|
||||
destroyStream(channel.writeStream, path);
|
||||
//console.log("closing writestream");
|
||||
}, 120000);
|
||||
}, CHANNEL_WRITE_WINDOW);
|
||||
channel.delayClose();
|
||||
env.channels[id] = channel;
|
||||
done(void 0, channel);
|
||||
@ -1044,7 +1066,6 @@ module.exports.create = function (conf, _cb) {
|
||||
|
||||
getWeakLock: function (channelName, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
//if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } // XXX
|
||||
schedule.unordered(channelName, cb);
|
||||
},
|
||||
|
||||
|
||||
@ -135,8 +135,6 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
|
||||
|
||||
var state = workers[workerIndex];
|
||||
|
||||
// XXX insert a queue here to prevent timeouts
|
||||
|
||||
const txid = guid();
|
||||
msg.txid = txid;
|
||||
msg.pid = PID;
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cryptpad",
|
||||
"version": "3.14.0",
|
||||
"version": "3.15.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cryptpad",
|
||||
"description": "realtime collaborative visual editor with zero knowlege server",
|
||||
"version": "3.14.0",
|
||||
"version": "3.15.0",
|
||||
"license": "AGPL-3.0+",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
42
scripts/compare-pin-methods.js
Normal file
42
scripts/compare-pin-methods.js
Normal file
@ -0,0 +1,42 @@
|
||||
/* jshint esversion: 6, node: true */
|
||||
const nThen = require("nthen");
|
||||
const Pins = require("../lib/pins");
|
||||
const Assert = require("assert");
|
||||
|
||||
const config = require("../lib/load-config");
|
||||
|
||||
var compare = function () {
|
||||
console.log(config);
|
||||
var conf = {
|
||||
pinPath: config.pinPath,
|
||||
};
|
||||
|
||||
var list, load;
|
||||
|
||||
nThen(function (w) {
|
||||
Pins.list(w(function (err, p) {
|
||||
if (err) { throw err; }
|
||||
list = p;
|
||||
console.log(p);
|
||||
console.log(list);
|
||||
console.log();
|
||||
}), conf);
|
||||
}).nThen(function (w) {
|
||||
Pins.load(w(function (err, p) {
|
||||
if (err) { throw err; }
|
||||
load = p;
|
||||
console.log(load);
|
||||
console.log();
|
||||
}), conf);
|
||||
}).nThen(function () {
|
||||
console.log({
|
||||
listLength: Object.keys(list).length,
|
||||
loadLength: Object.keys(load).length,
|
||||
});
|
||||
|
||||
Assert.deepEqual(list, load);
|
||||
console.log("methods are equivalent");
|
||||
});
|
||||
};
|
||||
|
||||
compare();
|
||||
@ -42,7 +42,7 @@ nThen(function (w) {
|
||||
store = _;
|
||||
})); // load the list of pinned files so you know which files
|
||||
// should not be archived or deleted
|
||||
Pins.list(w(function (err, _) {
|
||||
Pins.load(w(function (err, _) {
|
||||
if (err) {
|
||||
w.abort();
|
||||
return void console.error(err);
|
||||
|
||||
@ -43,6 +43,9 @@ if (process.env.PACKAGE) {
|
||||
}
|
||||
|
||||
config.httpUnsafeOrigin = config.httpUnsafeOrigin.trim();
|
||||
if (typeof(config.httpSafeOrigin) === 'string') {
|
||||
config.httpSafeOrigin = config.httpSafeOrigin.trim().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
// fall back to listening on a local address
|
||||
// if httpAddress is not a string
|
||||
@ -129,7 +132,7 @@ var setHeaders = (function () {
|
||||
/^\/common\/onlyoffice\/.*\/index\.html.*/,
|
||||
/^\/(sheet|ooslide|oodoc)\/inner\.html.*/,
|
||||
].some((regex) => {
|
||||
return regex.test(req.url)
|
||||
return regex.test(req.url);
|
||||
}) ? padHeaders : headers;
|
||||
for (let header in h) { res.setHeader(header, h[header]); }
|
||||
};
|
||||
|
||||
@ -71,7 +71,7 @@ define([
|
||||
// Get contacts and extract their avatar channel and key
|
||||
var getData = function (obj, href) {
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (parsed.type !== "file") { return; }
|
||||
if (!parsed || parsed.type !== "file") { return; }
|
||||
var secret = Hash.getSecrets('file', parsed.hash);
|
||||
if (!secret.keys || !secret.channel) { return; }
|
||||
obj.avatarKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
|
||||
@ -81,6 +81,7 @@ define([
|
||||
contacts.friends = proxy.friends || {};
|
||||
Object.keys(contacts.friends).map(function (key) {
|
||||
var friend = contacts.friends[key];
|
||||
if (!friend) { return; }
|
||||
var ret = {
|
||||
edPublic: friend.edPublic,
|
||||
name: friend.displayName,
|
||||
@ -90,6 +91,7 @@ define([
|
||||
});
|
||||
Object.keys(contacts.teams).map(function (key) {
|
||||
var team = contacts.teams[key];
|
||||
if (!team) { return; }
|
||||
var avatar = team.metadata && team.metadata.avatar;
|
||||
var ret = {
|
||||
edPublic: team.keys && team.keys.drive && team.keys.drive.edPublic,
|
||||
|
||||
4
www/code/mermaid-new.css
Normal file
4
www/code/mermaid-new.css
Normal file
@ -0,0 +1,4 @@
|
||||
.sectionTitle, .titleText {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
59134
www/code/mermaid.js
59134
www/code/mermaid.js
File diff suppressed because one or more lines are too long
56
www/code/mermaid.min.js
vendored
Normal file
56
www/code/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -462,6 +462,42 @@ define([
|
||||
return frame;
|
||||
};
|
||||
|
||||
UI.createModal = function (cfg) {
|
||||
var $body = cfg.$body || $('body');
|
||||
var $blockContainer = $body.find('#'+cfg.id);
|
||||
if (!$blockContainer.length) {
|
||||
$blockContainer = $(h('div.cp-modal-container#'+cfg.id, {
|
||||
tabindex: 1
|
||||
}));
|
||||
}
|
||||
var hide = function () {
|
||||
if (cfg.onClose) { return void cfg.onClose(); }
|
||||
$blockContainer.hide();
|
||||
if (cfg.onClosed) { cfg.onClosed(); }
|
||||
};
|
||||
$blockContainer.html('').appendTo($body);
|
||||
var $block = $(h('div.cp-modal')).appendTo($blockContainer);
|
||||
$(h('span.cp-modal-close.fa.fa-times', {
|
||||
title: Messages.filePicker_close
|
||||
})).click(hide).appendTo($block);
|
||||
$body.click(hide);
|
||||
$block.click(function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$body.keydown(function (e) {
|
||||
if (e.which === 27) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
return {
|
||||
$modal: $blockContainer,
|
||||
show: function () {
|
||||
$blockContainer.css('display', 'flex');
|
||||
},
|
||||
hide: hide
|
||||
};
|
||||
};
|
||||
|
||||
UI.alert = function (msg, cb, opt) {
|
||||
var force = false;
|
||||
if (typeof(opt) === 'object') {
|
||||
@ -1254,5 +1290,65 @@ define([
|
||||
};
|
||||
};
|
||||
|
||||
UI.createContextMenu = function (menu) {
|
||||
var $menu = $(menu).appendTo($('body'));
|
||||
|
||||
var display = function (e) {
|
||||
$menu.css({ display: "block" });
|
||||
var h = $menu.outerHeight();
|
||||
var w = $menu.outerWidth();
|
||||
var wH = window.innerHeight;
|
||||
var wW = window.innerWidth;
|
||||
if (h > wH) {
|
||||
$menu.css({
|
||||
top: '0px',
|
||||
bottom: ''
|
||||
});
|
||||
} else if (e.pageY + h <= wH) {
|
||||
$menu.css({
|
||||
top: e.pageY+'px',
|
||||
bottom: ''
|
||||
});
|
||||
} else {
|
||||
$menu.css({
|
||||
bottom: '0px',
|
||||
top: ''
|
||||
});
|
||||
}
|
||||
if(w > wW) {
|
||||
$menu.css({
|
||||
left: '0px',
|
||||
right: ''
|
||||
});
|
||||
} else if (e.pageX + w <= wW) {
|
||||
$menu.css({
|
||||
left: e.pageX+'px',
|
||||
right: ''
|
||||
});
|
||||
} else {
|
||||
$menu.css({
|
||||
left: '',
|
||||
right: '0px',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var hide = function () {
|
||||
$menu.hide();
|
||||
};
|
||||
var remove = function () {
|
||||
$menu.remove();
|
||||
};
|
||||
|
||||
$('body').click(hide);
|
||||
|
||||
return {
|
||||
menu: menu,
|
||||
show: display,
|
||||
hide: hide,
|
||||
remove: remove
|
||||
};
|
||||
};
|
||||
|
||||
return UI;
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -467,6 +467,27 @@
|
||||
return false;
|
||||
};
|
||||
|
||||
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
|
||||
var isEmoji = function (str) {
|
||||
return emoji_patt.test(str);
|
||||
};
|
||||
var emojiStringToArray = function (str) {
|
||||
var split = str.split(emoji_patt);
|
||||
var arr = [];
|
||||
for (var i=0; i<split.length; i++) {
|
||||
var char = split[i];
|
||||
if (char !== "") {
|
||||
arr.push(char);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
Util.getFirstCharacter = function (str) {
|
||||
if (!str || !str.trim()) { return '?'; }
|
||||
var emojis = emojiStringToArray(str);
|
||||
return isEmoji(emojis[0])? emojis[0]: str[0];
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = Util;
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
|
||||
@ -879,7 +879,8 @@ define([
|
||||
postMessage("LEAVE_PAD", data, cb);
|
||||
};
|
||||
pad.sendPadMsg = function (data, cb) {
|
||||
postMessage("SEND_PAD_MSG", data, cb);
|
||||
// -1 ==> no timeout, we may receive the callback only when we reconnect
|
||||
postMessage("SEND_PAD_MSG", data, cb, { timeout: -1 });
|
||||
};
|
||||
pad.onReadyEvent = Util.mkEvent();
|
||||
pad.onMessageEvent = Util.mkEvent();
|
||||
@ -1970,7 +1971,9 @@ define([
|
||||
common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]);
|
||||
var _parsed1 = Hash.parsePadUrl(common.fromFileData.href);
|
||||
var _parsed2 = Hash.parsePadUrl(window.location.href);
|
||||
if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; }
|
||||
if (_parsed1.hashData.type === 'pad') {
|
||||
if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; }
|
||||
}
|
||||
delete sessionStorage[Constants.newPadFileData];
|
||||
}
|
||||
|
||||
|
||||
@ -5,13 +5,14 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/media-tag.js',
|
||||
'/common/highlight/highlight.pack.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'css!/common/highlight/styles/github.css'
|
||||
],function ($, ApiConfig, Marked, Hash, Util, h, MediaTag, Highlight, Messages) {
|
||||
],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Highlight, Messages) {
|
||||
var DiffMd = {};
|
||||
|
||||
var DiffDOM = window.diffDOM;
|
||||
@ -22,7 +23,7 @@ define([
|
||||
init: function () {}
|
||||
};
|
||||
|
||||
require(['/code/mermaid.js', 'css!/code/mermaid.css'], function (_Mermaid) {
|
||||
require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) {
|
||||
Mermaid = _Mermaid;
|
||||
});
|
||||
|
||||
@ -84,7 +85,7 @@ define([
|
||||
var defaultCode = renderer.code;
|
||||
renderer.code = function (code, language) {
|
||||
if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) {
|
||||
return '<pre class="mermaid">'+code+'</pre>';
|
||||
return '<pre class="mermaid">'+Util.fixHTML(code)+'</pre>';
|
||||
} else {
|
||||
return defaultCode.apply(renderer, arguments);
|
||||
}
|
||||
@ -287,6 +288,23 @@ define([
|
||||
return patch;
|
||||
};
|
||||
|
||||
var removeMermaidClickables = function ($el) {
|
||||
// find all links in the tree and do the following for each one
|
||||
$el.find('a').each(function (index, a) {
|
||||
var parent = a.parentElement;
|
||||
if (!parent) { return; }
|
||||
// iterate over the links' children and transform them into preceding children
|
||||
// to preserve their visible ordering
|
||||
slice(a.children).forEach(function (child) {
|
||||
parent.insertBefore(child, a);
|
||||
});
|
||||
// remove the link once it has been emptied
|
||||
$(a).remove();
|
||||
});
|
||||
// finally, find all 'clickable' items and remove the class
|
||||
$el.find('.clickable').removeClass('clickable');
|
||||
};
|
||||
|
||||
DiffMd.apply = function (newHtml, $content, common) {
|
||||
var contextMenu = common.importMediaTagMenu();
|
||||
var id = $content.attr('id');
|
||||
@ -348,6 +366,53 @@ define([
|
||||
|
||||
var oldDom = domFromHTML($content[0].outerHTML);
|
||||
|
||||
var onPreview = function ($mt) {
|
||||
return function () {
|
||||
var isSvg = $mt.is('pre.mermaid');
|
||||
var mts = [];
|
||||
$content.find('media-tag, pre.mermaid').each(function (i, el) {
|
||||
if (el.nodeName.toLowerCase() === "pre") {
|
||||
return void mts.push({
|
||||
svg: el.cloneNode(true)
|
||||
});
|
||||
}
|
||||
var $el = $(el);
|
||||
mts.push({
|
||||
src: $el.attr('src'),
|
||||
key: $el.attr('data-crypto-key')
|
||||
});
|
||||
});
|
||||
|
||||
// Find initial position
|
||||
var idx = -1;
|
||||
mts.some(function (obj, i) {
|
||||
if (isSvg && $mt.find('svg').attr('id') === $(obj.svg).find('svg').attr('id')) {
|
||||
idx = i;
|
||||
return true;
|
||||
}
|
||||
if (!isSvg && obj.src === $mt.attr('src')) {
|
||||
idx = i;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (idx === -1) {
|
||||
if (isSvg) {
|
||||
mts.unshift({
|
||||
svg: $mt[0].cloneNode(true)
|
||||
});
|
||||
} else {
|
||||
mts.unshift({
|
||||
src: $mt.attr('src'),
|
||||
key: $mt.attr('data-crypto-key')
|
||||
});
|
||||
}
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
common.getMediaTagPreview(mts, idx);
|
||||
};
|
||||
};
|
||||
|
||||
var patch = makeDiff(oldDom, Dom, id);
|
||||
if (typeof(patch) === 'string') {
|
||||
throw new Error(patch);
|
||||
@ -355,22 +420,30 @@ define([
|
||||
DD.apply($content[0], patch);
|
||||
var $mts = $content.find('media-tag:not(:has(*))');
|
||||
$mts.each(function (i, el) {
|
||||
$(el).contextmenu(function (e) {
|
||||
var $mt = $(el).contextmenu(function (e) {
|
||||
e.preventDefault();
|
||||
$(contextMenu.menu).data('mediatag', $(el));
|
||||
$(contextMenu.menu).find('li').show();
|
||||
contextMenu.show(e);
|
||||
});
|
||||
MediaTag(el);
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'childList') {
|
||||
var list_values = [].slice.call(mutation.target.children)
|
||||
var list_values = slice(mutation.target.children)
|
||||
.map(function (el) { return el.outerHTML; })
|
||||
.join('');
|
||||
mediaMap[mutation.target.getAttribute('src')] = list_values;
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
$mt.off('dblclick preview');
|
||||
$mt.on('preview', onPreview($mt));
|
||||
if ($mt.find('img').length) {
|
||||
$mt.on('dblclick', function () {
|
||||
$mt.trigger('preview');
|
||||
});
|
||||
}
|
||||
});
|
||||
observer.observe(el, {
|
||||
attributes: false,
|
||||
@ -390,6 +463,19 @@ define([
|
||||
|
||||
// loop over mermaid elements in the rendered content
|
||||
$content.find('pre.mermaid').each(function (index, el) {
|
||||
var $el = $(el);
|
||||
$el.off('contextmenu').on('contextmenu', function (e) {
|
||||
e.preventDefault();
|
||||
$(contextMenu.menu).data('mediatag', $el);
|
||||
$(contextMenu.menu).find('li:not(.cp-svg)').hide();
|
||||
contextMenu.show(e);
|
||||
});
|
||||
$el.off('dblclick preview');
|
||||
$el.on('preview', onPreview($el));
|
||||
$el.on('dblclick', function () {
|
||||
$el.trigger('preview');
|
||||
});
|
||||
|
||||
// since you've simply drawn the content that was supplied via markdown
|
||||
// you can assume that the index of your rendered charts matches that
|
||||
// of those in the markdown source.
|
||||
@ -400,7 +486,12 @@ define([
|
||||
// check if you had cached a pre-rendered instance of the supplied source
|
||||
if (typeof(cached) !== 'object') {
|
||||
try {
|
||||
Mermaid.init(undefined, $(el));
|
||||
Mermaid.init(undefined, $el);
|
||||
// clickable elements in mermaid don't work well with our sandboxing setup
|
||||
// the function below strips clickable elements but still leaves behind some artifacts
|
||||
// tippy tooltips might still be useful, so they're not removed. It would be
|
||||
// preferable to just support links, but this covers up a rough edge in the meantime
|
||||
removeMermaidClickables($el);
|
||||
} catch (e) { console.error(e); }
|
||||
return;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ define([
|
||||
'/common/common-feedback.js',
|
||||
|
||||
'/common/inner/access.js',
|
||||
'/common/inner/properties.js',
|
||||
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/hyperscript.js',
|
||||
@ -27,6 +28,7 @@ define([
|
||||
Constants,
|
||||
Feedback,
|
||||
Access,
|
||||
Properties,
|
||||
nThen,
|
||||
h,
|
||||
ProxyManager,
|
||||
@ -78,6 +80,7 @@ define([
|
||||
var faCollapseAll = 'fa-minus-square-o';
|
||||
var faShared = 'fa-shhare-alt';
|
||||
var faReadOnly = 'fa-eye';
|
||||
var faPreview = 'fa-eye';
|
||||
var faOpenInCode = 'cptools-code';
|
||||
var faRename = 'fa-pencil';
|
||||
var faColor = 'cptools-palette';
|
||||
@ -315,6 +318,10 @@ define([
|
||||
'style': 'display:block;position:static;margin-bottom:5px;'
|
||||
}, [
|
||||
h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"),
|
||||
h('li', h('a.cp-app-drive-context-preview.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faPreview,
|
||||
}, Messages.pad_mediatagPreview)),
|
||||
h('li', h('a.cp-app-drive-context-open.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faFolderOpen,
|
||||
@ -536,10 +543,10 @@ define([
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
var user = metadataMgr.getUserData();
|
||||
|
||||
// Initialization
|
||||
Util.extend(APP, driveConfig.APP);
|
||||
APP.$limit = driveConfig.$limit;
|
||||
var proxy = driveConfig.proxy;
|
||||
var folders = driveConfig.folders;
|
||||
var files = proxy.drive;
|
||||
@ -585,11 +592,6 @@ define([
|
||||
|
||||
// TOOLBAR
|
||||
|
||||
/* add a "change username" button */
|
||||
if (!APP.readOnly) {
|
||||
APP.$displayName.text(user.name || Messages.anonymous);
|
||||
}
|
||||
|
||||
// DRIVE
|
||||
var currentPath = APP.currentPath = LS.getLastOpenedFolder();
|
||||
if (APP.newSharedFolder) {
|
||||
@ -1045,12 +1047,57 @@ define([
|
||||
return ret;
|
||||
};
|
||||
|
||||
var openFile = function (el, isRo) {
|
||||
var previewMediaTag = function (data) {
|
||||
var mts = [];
|
||||
$content.find('.cp-app-drive-element.cp-border-color-file').each(function (i, el) {
|
||||
var path = $(el).data('path');
|
||||
var id = manager.find(path);
|
||||
if (!id) { return; }
|
||||
var _data = manager.getFileData(id);
|
||||
if (!_data || _data.channel < 48) { return; }
|
||||
mts.push({
|
||||
channel: _data.channel,
|
||||
href: _data.href,
|
||||
password: _data.password
|
||||
});
|
||||
});
|
||||
|
||||
// Find initial position
|
||||
var idx = -1;
|
||||
mts.some(function (obj, i) {
|
||||
if (obj.channel === data.channel) {
|
||||
idx = i;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (idx === -1) {
|
||||
mts.unshift({
|
||||
href: data.href,
|
||||
password: data.password
|
||||
});
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
common.getMediaTagPreview(mts, idx);
|
||||
};
|
||||
|
||||
// `app`: true (force open wiht the app), false (force open in preview),
|
||||
// falsy (open in preview if default is not using the app)
|
||||
var defaultInApp = ['application/pdf'];
|
||||
var openFile = function (el, isRo, app) {
|
||||
var data = manager.getFileData(el);
|
||||
if (!data || (!data.href && !data.roHref)) {
|
||||
return void logError("Missing data for the file", el, data);
|
||||
}
|
||||
|
||||
var href = isRo ? data.roHref : (data.href || data.roHref);
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
|
||||
if (parsed.hashData && parsed.hashData.type === 'file' && !app
|
||||
&& (defaultInApp.indexOf(data.fileType) === -1 || app === false)) {
|
||||
return void previewMediaTag(data);
|
||||
}
|
||||
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
|
||||
if (useUnsafe !== false) { // true of undefined: use unsafe links
|
||||
@ -1058,7 +1105,6 @@ define([
|
||||
}
|
||||
|
||||
// Get hidden hash
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
|
||||
var opts = {};
|
||||
if (isRo) { opts.view = true; }
|
||||
@ -1178,6 +1224,7 @@ define([
|
||||
if (!$element.is('.cp-border-color-file')) {
|
||||
//hide.push('download');
|
||||
hide.push('openincode');
|
||||
hide.push('preview');
|
||||
}
|
||||
if ($element.is('.cp-border-color-sheet')) {
|
||||
hide.push('download');
|
||||
@ -1195,6 +1242,9 @@ define([
|
||||
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
|
||||
hide.push('openincode');
|
||||
}
|
||||
if (metadata.channel && metadata.channel.length < 48) {
|
||||
hide.push('preview');
|
||||
}
|
||||
if (!metadata.channel || metadata.channel.length > 32 || metadata.rtChannel) {
|
||||
hide.push('makeacopy'); // Not for blobs
|
||||
}
|
||||
@ -1263,6 +1313,7 @@ define([
|
||||
hide.push('savelocal');
|
||||
hide.push('openincode'); // can't because of race condition
|
||||
hide.push('makeacopy');
|
||||
hide.push('preview');
|
||||
}
|
||||
if (containsFolder && paths.length > 1) {
|
||||
// Cannot open multiple folders
|
||||
@ -1279,12 +1330,12 @@ define([
|
||||
show = ['newfolder', 'newsharedfolder', 'uploadfiles', 'uploadfolder', 'newdoc'];
|
||||
break;
|
||||
case 'tree':
|
||||
show = ['open', 'openro', 'openincode', 'expandall', 'collapseall',
|
||||
show = ['open', 'openro', 'preview', 'openincode', 'expandall', 'collapseall',
|
||||
'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy',
|
||||
'deleteowned', 'removesf', 'access', 'properties', 'hashtag'];
|
||||
break;
|
||||
case 'default':
|
||||
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy'];
|
||||
show = ['open', 'openro', 'preview', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy'];
|
||||
break;
|
||||
case 'trashtree': {
|
||||
show = ['empty'];
|
||||
@ -2895,17 +2946,18 @@ define([
|
||||
$element.append($('<span>', {'class': 'cp-app-drive-element-name'})
|
||||
.text(Messages.fm_newFile));
|
||||
$element.click(function () {
|
||||
var $modal = UIElements.createModal({
|
||||
var modal = UI.createModal({
|
||||
id: 'cp-app-drive-new-ghost-dialog',
|
||||
$body: $('body')
|
||||
});
|
||||
var $modal = modal.$modal;
|
||||
var $title = $('<h3>').text(Messages.fm_newFile);
|
||||
var $description = $('<p>').text(Messages.fm_newButtonTitle);
|
||||
$modal.find('.cp-modal').append($title);
|
||||
$modal.find('.cp-modal').append($description);
|
||||
var $content = createNewPadIcons($modal, isInRoot);
|
||||
$modal.find('.cp-modal').append($content);
|
||||
window.setTimeout(function () { $modal.show(); });
|
||||
window.setTimeout(function () { modal.show(); });
|
||||
addNewPadHandlers($modal, isInRoot);
|
||||
});
|
||||
};
|
||||
@ -3873,7 +3925,7 @@ define([
|
||||
var ro = folders[el] && folders[el].version >= 2;
|
||||
if (!ro) { opts.noReadOnly = true; }
|
||||
}
|
||||
UIElements.getProperties(common, opts, cb);
|
||||
Properties.getPropertiesModal(common, opts, cb);
|
||||
};
|
||||
APP.getAccess = function (el, cb) {
|
||||
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
|
||||
@ -3983,11 +4035,15 @@ define([
|
||||
else if ($this.hasClass('cp-app-drive-context-deleteowned')) {
|
||||
deleteOwnedPaths(paths);
|
||||
}
|
||||
else if ($this.hasClass('cp-app-drive-context-preview')) {
|
||||
if (paths.length !== 1) { return; }
|
||||
el = manager.find(paths[0].path);
|
||||
openFile(el, null, false);
|
||||
}
|
||||
else if ($this.hasClass('cp-app-drive-context-open')) {
|
||||
paths.forEach(function (p) {
|
||||
var $element = p.element;
|
||||
$element.click();
|
||||
$element.dblclick();
|
||||
var el = manager.find(p.path);
|
||||
openFile(el, false, true);
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass('cp-app-drive-context-openro')) {
|
||||
@ -4002,7 +4058,7 @@ define([
|
||||
} else {
|
||||
if (!el || manager.isFolder(el)) { return; }
|
||||
}
|
||||
openFile(el, true);
|
||||
openFile(el, true, true);
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass('cp-app-drive-context-makeacopy')) {
|
||||
|
||||
@ -4,59 +4,22 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/inner/common-modal.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function ($, Util, Hash, UI, UIElements, h,
|
||||
], function ($, Util, Hash, UI, UIElements, Modal, h,
|
||||
Messages, nThen) {
|
||||
var Access = {};
|
||||
|
||||
|
||||
|
||||
var evRedrawAll = Util.mkEvent();
|
||||
|
||||
// Override metadata values from data
|
||||
var override = function (data, obj) {
|
||||
data.owners = obj.owners;
|
||||
data.expire = obj.expire;
|
||||
data.pending_owners = obj.pending_owners;
|
||||
data.mailbox = obj.mailbox;
|
||||
data.restricted = obj.restricted;
|
||||
data.allowed = obj.allowed;
|
||||
data.rejected = obj.rejected;
|
||||
};
|
||||
var loadMetadata = function (common, data, waitFor, redraw) {
|
||||
common.getPadMetadata({
|
||||
channel: data.channel
|
||||
}, waitFor(function (md) {
|
||||
override(data, md);
|
||||
if (redraw) { evRedrawAll.fire(redraw); }
|
||||
}));
|
||||
};
|
||||
|
||||
var isOwned = function (common, data) {
|
||||
data = data || {};
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var edPublic = priv.edPublic;
|
||||
var owned = false;
|
||||
if (Array.isArray(data.owners) && data.owners.length) {
|
||||
if (data.owners.indexOf(edPublic) !== -1) {
|
||||
owned = true;
|
||||
} else {
|
||||
Object.keys(priv.teams || {}).some(function (id) {
|
||||
var team = priv.teams[id] || {};
|
||||
if (team.viewer) { return; }
|
||||
if (data.owners.indexOf(team.edPublic) === -1) { return; }
|
||||
owned = id;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
return owned;
|
||||
};
|
||||
|
||||
var getOwnersTab = function (common, data, opts, _cb) {
|
||||
var getOwnersTab = function (Env, data, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
|
||||
var parsed = Hash.parsePadUrl(data.href || data.roHref);
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
|
||||
if (disabled) { return void cb(); }
|
||||
|
||||
var friends = common.getFriends(true);
|
||||
var sframeChan = common.getSframeChannel();
|
||||
@ -340,10 +303,13 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
var called = false;
|
||||
redrawAll = function (reload) {
|
||||
if (called) { return; }
|
||||
called = true;
|
||||
nThen(function (waitFor) {
|
||||
if (!reload) { return; }
|
||||
loadMetadata(common, data, waitFor, "owner");
|
||||
Modal.loadMetadata(Env, data, waitFor, "owner");
|
||||
}).nThen(function () {
|
||||
owners = data.owners || [];
|
||||
pending_owners = data.pending_owners || [];
|
||||
@ -352,11 +318,12 @@ define([
|
||||
$div1.append(h('p', Messages.owner_text));
|
||||
$div1.append(drawRemove(false)).append(drawRemove(true));
|
||||
$div2.append(drawAdd());
|
||||
called = false;
|
||||
});
|
||||
};
|
||||
redrawAll();
|
||||
|
||||
evRedrawAll.reg(function (type) {
|
||||
Env.evRedrawAll.reg(function (type) {
|
||||
if (type === "owner") { return; }
|
||||
setTimeout(function () {
|
||||
redrawAll();
|
||||
@ -372,8 +339,16 @@ define([
|
||||
cb(void 0, link);
|
||||
};
|
||||
|
||||
var getAllowTab = function (common, data, opts, _cb) {
|
||||
var getAllowTab = function (Env, data, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
|
||||
var parsed = Hash.parsePadUrl(data.href || data.roHref);
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
|
||||
var allowDisabled = parsed.type === 'drive';
|
||||
if (disabled || allowDisabled) { return void cb(); }
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
var friends = common.getFriends(true);
|
||||
@ -636,10 +611,13 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
var called = false;
|
||||
redrawAll = function (reload) {
|
||||
if (called) { return; }
|
||||
called = true;
|
||||
nThen(function (waitFor) {
|
||||
if (!reload) { return; }
|
||||
loadMetadata(common, data, waitFor, "allow");
|
||||
Modal.loadMetadata(Env, data, waitFor, "allow");
|
||||
}).nThen(function () {
|
||||
owners = data.owners || [];
|
||||
restricted = data.restricted || false;
|
||||
@ -649,11 +627,12 @@ define([
|
||||
$div1.append(drawRemove());
|
||||
$div2.append(drawAdd());
|
||||
setLock(!restricted);
|
||||
called = false;
|
||||
});
|
||||
};
|
||||
redrawAll();
|
||||
|
||||
evRedrawAll.reg(function (type) {
|
||||
Env.evRedrawAll.reg(function (type) {
|
||||
if (type === "allow") { return; }
|
||||
setTimeout(function () {
|
||||
redrawAll();
|
||||
@ -730,8 +709,9 @@ define([
|
||||
}, function () {});
|
||||
};
|
||||
|
||||
var getAccessTab = function (common, data, opts, _cb) {
|
||||
var getAccessTab = function (Env, data, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
opts = opts || {};
|
||||
|
||||
var sframeChan = common.getSframeChannel();
|
||||
@ -753,7 +733,7 @@ define([
|
||||
|
||||
var $d = $('<div>');
|
||||
|
||||
var owned = isOwned(common, data);
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
|
||||
if (!opts.noExpiration) {
|
||||
var expire = Messages.creation_expireFalse;
|
||||
@ -949,7 +929,7 @@ define([
|
||||
|
||||
// Mute access requests
|
||||
var edPublic = priv.edPublic;
|
||||
var owned = isOwned(common, data);
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
var canMute = data.mailbox && owned === true && (
|
||||
(typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
|
||||
data.mailbox[edPublic]);
|
||||
@ -1007,7 +987,7 @@ define([
|
||||
};
|
||||
redraw();
|
||||
|
||||
evRedrawAll.reg(function (ownersOrAllow) {
|
||||
Env.evRedrawAll.reg(function (ownersOrAllow) {
|
||||
setTimeout(function () {
|
||||
redraw(ownersOrAllow);
|
||||
});
|
||||
@ -1016,136 +996,25 @@ define([
|
||||
cb(void 0, $div);
|
||||
};
|
||||
|
||||
var getAccessData = function (common, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
opts = opts || {};
|
||||
var data = {};
|
||||
nThen(function (waitFor) {
|
||||
var base = common.getMetadataMgr().getPrivateData().origin;
|
||||
common.getPadAttribute('', waitFor(function (err, val) {
|
||||
if (err || !val) {
|
||||
waitFor.abort();
|
||||
return void cb(err || 'EEMPTY');
|
||||
}
|
||||
if (!val.fileType) {
|
||||
delete val.owners;
|
||||
delete val.expire;
|
||||
}
|
||||
Util.extend(data, val);
|
||||
if (data.href) { data.href = base + data.href; }
|
||||
if (data.roHref) { data.roHref = base + data.roHref; }
|
||||
}), opts.href);
|
||||
|
||||
// If this is a file, don't try to look for metadata
|
||||
if (opts.channel && opts.channel.length > 34) { return; }
|
||||
common.getPadMetadata({
|
||||
channel: opts.channel // optional, fallback to current pad
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { console.error(obj.error); return; }
|
||||
loadMetadata(common, data, waitFor);
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb(void 0, data);
|
||||
});
|
||||
};
|
||||
Access.getAccessModal = function (common, opts, cb) {
|
||||
var data;
|
||||
var tab1, tab2, tab3;
|
||||
var disabled = false;
|
||||
var allowDisabled = false;
|
||||
var button = [{
|
||||
className: 'cancel',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {},
|
||||
keys: [13,27]
|
||||
cb = cb || function () {};
|
||||
opts = opts || {};
|
||||
opts.wide = true;
|
||||
opts.access = true;
|
||||
var tabs = [{
|
||||
getTab: getAccessTab,
|
||||
title: Messages.access_main,
|
||||
icon: "fa fa-unlock-alt",
|
||||
}, {
|
||||
getTab: getAllowTab,
|
||||
title: Messages.access_allow,
|
||||
icon: "fa fa-list",
|
||||
}, {
|
||||
getTab: getOwnersTab,
|
||||
title: Messages.creation_owners,
|
||||
icon: "fa fa-id-badge",
|
||||
}];
|
||||
nThen(function (waitFor) {
|
||||
getAccessData(common, opts, waitFor(function (e, _data) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
data = _data;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var owned = isOwned(common, data);
|
||||
if (typeof(owned) !== "boolean") {
|
||||
data.teamId = Number(owned);
|
||||
}
|
||||
var parsed = Hash.parsePadUrl(data.href || data.roHref);
|
||||
disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
|
||||
allowDisabled = parsed.type === 'drive';
|
||||
|
||||
getAccessTab(common, data, opts, waitFor(function (e, c) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
tab1 = UI.dialog.customModal(c[0], {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
|
||||
if (disabled) { return; }
|
||||
|
||||
if (!allowDisabled) {
|
||||
getAllowTab(common, data, opts, waitFor(function (e, c) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
tab2 = UI.dialog.customModal(c, {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
}
|
||||
getOwnersTab(common, data, opts, waitFor(function (e, c) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
tab3 = UI.dialog.customModal(c, {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
}).nThen(function () {
|
||||
var tabs = UI.dialog.tabs([{
|
||||
title: Messages.access_main,
|
||||
icon: "fa fa-unlock-alt",
|
||||
content: tab1
|
||||
}, {
|
||||
title: Messages.access_allow,
|
||||
disabled: disabled || allowDisabled,
|
||||
icon: "fa fa-list",
|
||||
content: tab2
|
||||
}, {
|
||||
title: Messages.creation_owners,
|
||||
disabled: disabled,
|
||||
icon: "fa fa-id-badge",
|
||||
content: tab3
|
||||
}]);
|
||||
var modal = UI.openCustomModal(tabs, {
|
||||
wide: true
|
||||
});
|
||||
cb (void 0, modal);
|
||||
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
|
||||
if (!$(modal).length) {
|
||||
return void handler.stop();
|
||||
}
|
||||
override(data, Util.clone(md));
|
||||
evRedrawAll.fire();
|
||||
});
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var f = function () {
|
||||
if (!$(modal).length) {
|
||||
return void metadataMgr.off('change', f);
|
||||
}
|
||||
evRedrawAll.fire();
|
||||
};
|
||||
metadataMgr.onChange(f);
|
||||
});
|
||||
Modal.getModal(common, opts, tabs, cb);
|
||||
};
|
||||
|
||||
return Access;
|
||||
|
||||
401
www/common/inner/common-mediatag.js
Normal file
401
www/common/inner/common-mediatag.js
Normal file
@ -0,0 +1,401 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/media-tag.js',
|
||||
'/customize/messages.js',
|
||||
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/croppie/croppie.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'css!/bower_components/croppie/croppie.css',
|
||||
], function ($, Util, Hash, UI, h, MediaTag, Messages) {
|
||||
var MT = {};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
|
||||
// Configure MediaTags to use our local viewer
|
||||
if (MediaTag) {
|
||||
MediaTag.setDefaultConfig('pdf', {
|
||||
viewer: '/common/pdfjs/web/viewer.html'
|
||||
});
|
||||
}
|
||||
|
||||
// Cache of the avatars outer html (including <media-tag>)
|
||||
var avatars = {};
|
||||
|
||||
MT.getCursorAvatar = function (cursor) {
|
||||
var html = '<span class="cp-cursor-avatar">';
|
||||
html += (cursor.avatar && avatars[cursor.avatar]) || '';
|
||||
html += Util.fixHTML(cursor.name) + '</span>';
|
||||
return html;
|
||||
};
|
||||
|
||||
MT.displayMediatagImage = function (Common, $tag, _cb) {
|
||||
var cb = Util.once(_cb);
|
||||
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.addedNodes.length) {
|
||||
if (mutation.addedNodes.length > 1 ||
|
||||
mutation.addedNodes[0].nodeName !== 'IMG') {
|
||||
return void cb('NOT_IMAGE');
|
||||
}
|
||||
var $image = $tag.find('img');
|
||||
var onLoad = function () {
|
||||
cb(null, $image);
|
||||
};
|
||||
if ($image[0].complete) { onLoad(); }
|
||||
$image.on('load', onLoad);
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe($tag[0], {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
characterData: false
|
||||
});
|
||||
MediaTag($tag[0]).on('error', function (data) {
|
||||
console.error(data);
|
||||
});
|
||||
};
|
||||
|
||||
MT.displayAvatar = function (common, $container, href, name, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb || function () {}));
|
||||
var displayDefault = function () {
|
||||
var text = (href && typeof(href) === "string") ? href : Util.getFirstCharacter(name);
|
||||
var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
|
||||
$container.append($avatar);
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
|
||||
if (!href || href.length === 1) { return void displayDefault(); }
|
||||
|
||||
if (avatars[href]) {
|
||||
var nodes = $.parseHTML(avatars[href]);
|
||||
var $el = $(nodes[0]);
|
||||
$container.append($el);
|
||||
return void cb($el);
|
||||
}
|
||||
|
||||
var centerImage = function ($img, $image) {
|
||||
var img = $image[0];
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
if (w>h) {
|
||||
$image.css('max-height', '100%');
|
||||
$img.css('flex-direction', 'column');
|
||||
avatars[href] = $img[0].outerHTML;
|
||||
if (cb) { cb($img); }
|
||||
return;
|
||||
}
|
||||
$image.css('max-width', '100%');
|
||||
$img.css('flex-direction', 'row');
|
||||
avatars[href] = $img[0].outerHTML;
|
||||
if (cb) { cb($img); }
|
||||
};
|
||||
|
||||
// No password for avatars
|
||||
var privateData = common.getMetadataMgr().getPrivateData();
|
||||
var origin = privateData.fileHost || privateData.origin;
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
var secret = Hash.getSecrets('file', parsed.hash);
|
||||
if (secret.keys && secret.channel) {
|
||||
var hexFileName = secret.channel;
|
||||
var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
|
||||
var src = origin + Hash.getBlobPathFromHex(hexFileName);
|
||||
common.getFileSize(hexFileName, function (e, data) {
|
||||
if (e || !data) { return void displayDefault(); }
|
||||
if (typeof data !== "number") { return void displayDefault(); }
|
||||
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
|
||||
var $img = $('<media-tag>').appendTo($container);
|
||||
$img.attr('src', src);
|
||||
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
|
||||
MT.displayMediatagImage(common, $img, function (err, $image) {
|
||||
if (err) { return void console.error(err); }
|
||||
centerImage($img, $image);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
var transformAvatar = function (file, cb) {
|
||||
if (file.type === 'image/gif') { return void cb(file); }
|
||||
var $croppie = $('<div>', {
|
||||
'class': 'cp-app-profile-resizer'
|
||||
});
|
||||
|
||||
if (typeof ($croppie.croppie) !== "function") {
|
||||
return void cb(file);
|
||||
}
|
||||
|
||||
var todo = function () {
|
||||
UI.confirm($croppie[0], function (yes) {
|
||||
if (!yes) { return; }
|
||||
$croppie.croppie('result', {
|
||||
type: 'blob',
|
||||
size: {width: 300, height: 300}
|
||||
}).then(function(blob) {
|
||||
blob.lastModifiedDate = new Date();
|
||||
blob.name = 'avatar';
|
||||
cb(blob);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$croppie.croppie({
|
||||
url: e.target.result,
|
||||
viewport: { width: 100, height: 100 },
|
||||
boundary: { width: 400, height: 300 },
|
||||
});
|
||||
todo();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
MT.addAvatar = function (common, cb) {
|
||||
var AVATAR_SIZE_LIMIT = 0.5;
|
||||
var allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
];
|
||||
var fmConfig = {
|
||||
noHandlers: true,
|
||||
noStore: true,
|
||||
body: $('body'),
|
||||
onUploaded: cb
|
||||
};
|
||||
var FM = common.createFileManager(fmConfig);
|
||||
var accepted = ".gif,.jpg,.jpeg,.png";
|
||||
var data = {
|
||||
FM: FM,
|
||||
filter: function (file) {
|
||||
var sizeMB = Util.bytesToMegabytes(file.size);
|
||||
var type = file.type;
|
||||
// We can't resize .gif so we have to display an error if it is too big
|
||||
if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') {
|
||||
UI.log(Messages._getKey('profile_uploadSizeError', [
|
||||
Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT])
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
// Display an error if the image type is not allowed
|
||||
if (allowedMediaTypes.indexOf(type) === -1) {
|
||||
UI.log(Messages._getKey('profile_uploadTypeError', [
|
||||
accepted.split(',').join(', ')
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
transformer: transformAvatar,
|
||||
accept: accepted
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
MT.getMediaTagPreview = function (common, tags, start) {
|
||||
if (!Array.isArray(tags) || !tags.length) { return; }
|
||||
|
||||
var i = start;
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
|
||||
var left, right;
|
||||
|
||||
var modal = UI.createModal({
|
||||
id: 'cp-mediatag-preview-modal',
|
||||
$body: $('body')
|
||||
});
|
||||
modal.show();
|
||||
var $modal = modal.$modal.focus();
|
||||
var $container = $modal.find('.cp-modal').append([
|
||||
h('div.cp-mediatag-control', left = h('span.fa.fa-chevron-left')),
|
||||
h('div.cp-mediatag-container', [
|
||||
h('div.cp-loading-spinner-container', h('span.cp-spinner')),
|
||||
]),
|
||||
h('div.cp-mediatag-control', right = h('span.fa.fa-chevron-right')),
|
||||
]);
|
||||
var $left = $(left);
|
||||
var $right = $(right);
|
||||
var $inner = $container.find('.cp-mediatag-container');
|
||||
|
||||
var $spinner = $container.find('.cp-loading-spinner-container');
|
||||
|
||||
var locked = false;
|
||||
var show = function (_i) {
|
||||
if (locked) { return; }
|
||||
locked = true;
|
||||
if (_i < 0) { i = 0; }
|
||||
else if (_i > tags.length -1) { i = tags.length - 1; }
|
||||
else { i = _i; }
|
||||
|
||||
// Show/hide controls
|
||||
$left.css('visibility', '');
|
||||
$right.css('visibility', '');
|
||||
if (i === 0) {
|
||||
$left.css('visibility', 'hidden');
|
||||
}
|
||||
if (i === tags.length - 1) {
|
||||
$right.css('visibility', 'hidden');
|
||||
}
|
||||
|
||||
// Reset modal
|
||||
$inner.find('media-tag, pre.mermaid').detach();
|
||||
$spinner.show();
|
||||
|
||||
// Check src and cryptkey
|
||||
var cfg = tags[i];
|
||||
|
||||
if (cfg.svg) {
|
||||
$spinner.hide();
|
||||
$inner.append(cfg.svg);
|
||||
locked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var src = cfg.src;
|
||||
var key = cfg.key;
|
||||
if (cfg.href) {
|
||||
var parsed = Hash.parsePadUrl(cfg.href);
|
||||
var secret = Hash.getSecrets(parsed.type, parsed.hash, cfg.password);
|
||||
var host = priv.fileHost || priv.origin || '';
|
||||
src = host + Hash.getBlobPathFromHex(secret.channel);
|
||||
var _key = secret.keys && secret.keys.cryptKey;
|
||||
if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); }
|
||||
}
|
||||
if (!src || !key) {
|
||||
locked = false;
|
||||
$spinner.hide();
|
||||
return void UI.log(Messages.error);
|
||||
}
|
||||
|
||||
var tag = h('media-tag', {
|
||||
src: src,
|
||||
'data-crypto-key': key
|
||||
});
|
||||
$inner.append(tag);
|
||||
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function() {
|
||||
locked = false;
|
||||
$spinner.hide();
|
||||
});
|
||||
});
|
||||
observer.observe(tag, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
characterData: false
|
||||
});
|
||||
MediaTag(tag).on('error', function () {
|
||||
locked = false;
|
||||
$spinner.hide();
|
||||
UI.log(Messages.error);
|
||||
});
|
||||
};
|
||||
|
||||
show(i);
|
||||
var previous = function () {
|
||||
if (i === 0) { return; }
|
||||
show(i - 1);
|
||||
};
|
||||
var next = function () {
|
||||
if (i === tags.length - 1) { return; }
|
||||
show(i + 1);
|
||||
};
|
||||
$left.click(previous);
|
||||
$right.click(next);
|
||||
|
||||
$modal.on('keydown', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$modal.on('keyup', function (e) {
|
||||
//if (!Slide.shown) { return; }
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey) { return; }
|
||||
switch(e.which) {
|
||||
case 33: // pageup
|
||||
case 38: // up
|
||||
case 37: // left
|
||||
previous();
|
||||
break;
|
||||
case 34: // pagedown
|
||||
case 32: // space
|
||||
case 40: // down
|
||||
case 39: // right
|
||||
next();
|
||||
break;
|
||||
case 27: // esc
|
||||
$modal.hide();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var mediatagContextMenu;
|
||||
MT.importMediaTagMenu = function (common) {
|
||||
if (mediatagContextMenu) { return mediatagContextMenu; }
|
||||
|
||||
// Create context menu
|
||||
var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
|
||||
h('ul.dropdown-menu', {
|
||||
'role': 'menu',
|
||||
'aria-labelledBy': 'dropdownMenu',
|
||||
'style': 'display:block;position:static;margin-bottom:5px;'
|
||||
}, [
|
||||
h('li.cp-svg', h('a.cp-app-code-context-open.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-eye",
|
||||
}, Messages.pad_mediatagPreview)),
|
||||
h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-cloud-upload",
|
||||
}, Messages.pad_mediatagImport)),
|
||||
h('li', h('a.cp-app-code-context-download.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-download",
|
||||
}, Messages.download_mt_button)),
|
||||
])
|
||||
]);
|
||||
// create the icon for each contextmenu option
|
||||
$(menu).find("li a.dropdown-item").each(function (i, el) {
|
||||
var $icon = $("<span>");
|
||||
if ($(el).attr('data-icon')) {
|
||||
var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa';
|
||||
$icon.addClass(font).addClass($(el).attr('data-icon'));
|
||||
} else {
|
||||
$icon.text($(el).text());
|
||||
}
|
||||
$(el).prepend($icon);
|
||||
});
|
||||
var m = UI.createContextMenu(menu);
|
||||
|
||||
mediatagContextMenu = m;
|
||||
|
||||
var $menu = $(m.menu);
|
||||
$menu.on('click', 'a', function (e) {
|
||||
e.stopPropagation();
|
||||
m.hide();
|
||||
var $mt = $menu.data('mediatag');
|
||||
if ($(this).hasClass("cp-app-code-context-saveindrive")) {
|
||||
common.importMediaTag($mt);
|
||||
}
|
||||
else if ($(this).hasClass("cp-app-code-context-download")) {
|
||||
var media = $mt[0]._mediaObject;
|
||||
window.saveAs(media._blob.content, media.name);
|
||||
}
|
||||
else if ($(this).hasClass("cp-app-code-context-open")) {
|
||||
$mt.trigger('preview');
|
||||
}
|
||||
});
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
return MT;
|
||||
});
|
||||
167
www/common/inner/common-modal.js
Normal file
167
www/common/inner/common-modal.js
Normal file
@ -0,0 +1,167 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function ($, Util, Hash, UI, UIElements, Messages, nThen) {
|
||||
var Modal = {};
|
||||
|
||||
Modal.override = function (data, obj) {
|
||||
data.owners = obj.owners;
|
||||
data.expire = obj.expire;
|
||||
data.pending_owners = obj.pending_owners;
|
||||
data.mailbox = obj.mailbox;
|
||||
data.restricted = obj.restricted;
|
||||
data.allowed = obj.allowed;
|
||||
data.rejected = obj.rejected;
|
||||
};
|
||||
Modal.loadMetadata = function (Env, data, waitFor, redraw) {
|
||||
Env.common.getPadMetadata({
|
||||
channel: data.channel
|
||||
}, waitFor(function (md) {
|
||||
if (md && md.error) { return void console.error(md.error); }
|
||||
Modal.override(data, md);
|
||||
if (redraw) { Env.evRedrawAll.fire(redraw); }
|
||||
}));
|
||||
};
|
||||
Modal.getPadData = function (Env, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
opts = opts || {};
|
||||
var data = {};
|
||||
nThen(function (waitFor) {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var base = priv.origin;
|
||||
common.getPadAttribute('', waitFor(function (err, val) {
|
||||
if (err || !val) {
|
||||
if (opts.access) {
|
||||
data.password = priv.password;
|
||||
// Access modal and the pad is not stored: we're not an owner
|
||||
// so we don't need the correct href, just the type
|
||||
var h = Hash.createRandomHash(priv.app, priv.password);
|
||||
data.href = base + priv.pathname + '#' + h;
|
||||
} else {
|
||||
waitFor.abort();
|
||||
return void cb(err || 'EEMPTY');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!val.fileType) {
|
||||
delete val.owners;
|
||||
delete val.expire;
|
||||
}
|
||||
Util.extend(data, val);
|
||||
if (data.href) { data.href = base + data.href; }
|
||||
if (data.roHref) { data.roHref = base + data.roHref; }
|
||||
}), opts.href);
|
||||
|
||||
// If this is a file, don't try to look for metadata
|
||||
if (opts.channel && opts.channel.length > 34) { return; }
|
||||
if (opts.channel) { data.channel = opts.channel; }
|
||||
Modal.loadMetadata(Env, data, waitFor);
|
||||
}).nThen(function () {
|
||||
cb(void 0, data);
|
||||
});
|
||||
};
|
||||
Modal.isOwned = function (Env, data) {
|
||||
var common = Env.common;
|
||||
data = data || {};
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var edPublic = priv.edPublic;
|
||||
var owned = false;
|
||||
if (Array.isArray(data.owners) && data.owners.length) {
|
||||
if (data.owners.indexOf(edPublic) !== -1) {
|
||||
owned = true;
|
||||
} else {
|
||||
Object.keys(priv.teams || {}).some(function (id) {
|
||||
var team = priv.teams[id] || {};
|
||||
if (team.viewer) { return; }
|
||||
if (data.owners.indexOf(team.edPublic) === -1) { return; }
|
||||
owned = Number(id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
return owned;
|
||||
};
|
||||
|
||||
var blocked = false;
|
||||
Modal.getModal = function (common, opts, _tabs, cb) {
|
||||
if (blocked) { return; }
|
||||
blocked = true;
|
||||
var Env = {
|
||||
common: common,
|
||||
evRedrawAll: Util.mkEvent()
|
||||
};
|
||||
var data;
|
||||
var button = [{
|
||||
className: 'cancel',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {},
|
||||
keys: [13,27]
|
||||
}];
|
||||
var tabs = [];
|
||||
nThen(function (waitFor) {
|
||||
Modal.getPadData(Env, opts, waitFor(function (e, _data) {
|
||||
if (e) {
|
||||
blocked = false;
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
data = _data;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
if (typeof(owned) !== "boolean") {
|
||||
data.teamId = Number(owned);
|
||||
}
|
||||
_tabs.forEach(function (obj, i) {
|
||||
obj.getTab(Env, data, opts, waitFor(function (e, c) {
|
||||
if (e) {
|
||||
blocked = false;
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
var node = (c instanceof $) ? c[0] : c;
|
||||
tabs[i] = {
|
||||
content: c && UI.dialog.customModal(node, {
|
||||
buttons: obj.buttons || button,
|
||||
onClose: function () { blocked = false; }
|
||||
}),
|
||||
disabled: !c,
|
||||
title: obj.title,
|
||||
icon: obj.icon
|
||||
};
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
var tabsContent = UI.dialog.tabs(tabs);
|
||||
var modal = UI.openCustomModal(tabsContent, {
|
||||
wide: opts.wide
|
||||
});
|
||||
cb (void 0, modal);
|
||||
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
|
||||
if (!$(modal).length) {
|
||||
return void handler.stop();
|
||||
}
|
||||
Modal.override(data, Util.clone(md));
|
||||
Env.evRedrawAll.fire();
|
||||
});
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var f = function () {
|
||||
if (!$(modal).length) {
|
||||
return void metadataMgr.off('change', f);
|
||||
}
|
||||
Env.evRedrawAll.fire();
|
||||
};
|
||||
metadataMgr.onChange(f);
|
||||
});
|
||||
};
|
||||
|
||||
return Modal;
|
||||
});
|
||||
181
www/common/inner/properties.js
Normal file
181
www/common/inner/properties.js
Normal file
@ -0,0 +1,181 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/inner/common-modal.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function ($, Util, Hash, UI, UIElements, Modal, h,
|
||||
Messages, nThen) {
|
||||
var Properties = {};
|
||||
|
||||
var getPadProperties = function (Env, data, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
opts = opts || {};
|
||||
var $d = $('<div>');
|
||||
if (!data) { return void cb(void 0, $d); }
|
||||
|
||||
if (data.href) {
|
||||
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
|
||||
$d.append(UI.dialog.selectable(data.href, {
|
||||
id: 'cp-app-prop-link',
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.roHref && !opts.noReadOnly) {
|
||||
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
|
||||
$d.append(UI.dialog.selectable(data.roHref, {
|
||||
id: 'cp-app-prop-rolink',
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.tags && Array.isArray(data.tags)) {
|
||||
$d.append(h('div.cp-app-prop', [Messages.fm_prop_tagsList, h('br'), h('span.cp-app-prop-content', data.tags.join(', '))]));
|
||||
}
|
||||
|
||||
if (data.ctime) {
|
||||
$d.append(h('div.cp-app-prop', [Messages.fm_creation, h('br'), h('span.cp-app-prop-content', new Date(data.ctime).toLocaleString())]));
|
||||
}
|
||||
|
||||
if (data.atime) {
|
||||
$d.append(h('div.cp-app-prop', [Messages.fm_lastAccess, h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
|
||||
}
|
||||
|
||||
if (!common.isLoggedIn()) { return void cb(void 0, $d); }
|
||||
|
||||
// File and history size...
|
||||
var owned = Modal.isOwned(Env, data);
|
||||
|
||||
// check the size of this file, including additional channels
|
||||
var bytes = 0;
|
||||
var historyBytes;
|
||||
var chan = [data.channel];
|
||||
if (data.rtChannel) { chan.push(data.rtChannel); }
|
||||
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
|
||||
|
||||
// Get the channels with history (no blobs)
|
||||
var channels = chan.filter(function (c) { return c.length === 32; }).map(function (id) {
|
||||
if (id === data.rtChannel && data.lastVersion && data.lastCpHash) {
|
||||
return {
|
||||
channel: id,
|
||||
lastKnownHash: data.lastCpHash
|
||||
};
|
||||
}
|
||||
return {
|
||||
channel: id
|
||||
};
|
||||
});
|
||||
|
||||
var history = common.makeUniversal('history');
|
||||
var trimChannels = [];
|
||||
nThen(function (waitFor) {
|
||||
// Get total size
|
||||
chan.forEach(function (c) {
|
||||
common.getFileSize(c, waitFor(function (e, _bytes) {
|
||||
if (e) {
|
||||
// there was a problem with the RPC
|
||||
console.error(e);
|
||||
}
|
||||
bytes += _bytes;
|
||||
}));
|
||||
});
|
||||
|
||||
if (!owned) { return; }
|
||||
// Get history size
|
||||
history.execCommand('GET_HISTORY_SIZE', {
|
||||
pad: true,
|
||||
channels: channels,
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
historyBytes = obj.size;
|
||||
trimChannels = obj.channels;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (bytes === 0) { return void cb(void 0, $d); }
|
||||
var formatted = UIElements.prettySize(bytes);
|
||||
|
||||
if (!owned || !historyBytes || historyBytes > bytes || historyBytes < 0) {
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
return void cb(void 0, $d);
|
||||
}
|
||||
|
||||
|
||||
var p = Math.round((historyBytes / bytes) * 100);
|
||||
var historyPrettySize = UIElements.prettySize(historyBytes);
|
||||
var contentsPrettySize = UIElements.prettySize(bytes - historyBytes);
|
||||
var button;
|
||||
var spinner = UI.makeSpinner();
|
||||
var size = h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('div.cp-app-prop-size-container', [
|
||||
h('div.cp-app-prop-size-history', { style: 'width:'+p+'%;' })
|
||||
]),
|
||||
h('div.cp-app-prop-size-legend', [
|
||||
h('div.cp-app-prop-history-size', [
|
||||
h('span.cp-app-prop-history-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_historySize', [historyPrettySize]))
|
||||
]),
|
||||
h('div.cp-app-prop-contents-size', [
|
||||
h('span.cp-app-prop-contents-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_contentsSize', [contentsPrettySize]))
|
||||
]),
|
||||
]),
|
||||
button = h('button.btn.btn-danger-alt.no-margin', Messages.trimHistory_button),
|
||||
spinner.spinner
|
||||
]);
|
||||
$d.append(size);
|
||||
|
||||
var $button = $(button);
|
||||
UI.confirmButton(button, {
|
||||
classes: 'btn-danger'
|
||||
}, function () {
|
||||
$button.remove();
|
||||
spinner.spin();
|
||||
history.execCommand('TRIM_HISTORY', {
|
||||
pad: true,
|
||||
channels: trimChannels,
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, function (obj) {
|
||||
spinner.hide();
|
||||
if (obj && obj.error) {
|
||||
$(size).append(h('div.alert.alert-danger', Messages.trimHistory_error));
|
||||
return;
|
||||
}
|
||||
$(size).remove();
|
||||
var formatted = UIElements.prettySize(bytes - historyBytes);
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
$d.append(h('div.alert.alert-success', Messages.trimHistory_success));
|
||||
});
|
||||
});
|
||||
|
||||
cb(void 0, $d);
|
||||
});
|
||||
};
|
||||
|
||||
Properties.getPropertiesModal = function (common, opts, cb) {
|
||||
cb = cb || function () {};
|
||||
opts = opts || {};
|
||||
var tabs = [{
|
||||
getTab: getPadProperties,
|
||||
title: Messages.fc_prop,
|
||||
icon: "fa fa-info-circle",
|
||||
}];
|
||||
Modal.getModal(common, opts, tabs, cb);
|
||||
};
|
||||
|
||||
return Properties;
|
||||
});
|
||||
@ -879,7 +879,7 @@ define([
|
||||
h('i.fa.fa-bell'),
|
||||
Messages.contacts_unmute || 'unmute'
|
||||
]);
|
||||
UIElements.displayAvatar(common, $(avatar), data.avatar, data.name);
|
||||
common.displayAvatar($(avatar), data.avatar, data.name);
|
||||
$(button).click(function () {
|
||||
unmuteUser(curve, button);
|
||||
execCommand('UNMUTE_USER', curve, function (e, data) {
|
||||
|
||||
@ -1499,6 +1499,13 @@ define([
|
||||
|
||||
return;
|
||||
}
|
||||
var onError = function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
|
||||
// If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel
|
||||
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
|
||||
Store.leavePad(null, data, function () {});
|
||||
};
|
||||
var conf = {
|
||||
onReady: function (pad) {
|
||||
var padData = pad.metadata || {};
|
||||
@ -1522,14 +1529,8 @@ define([
|
||||
onLeave: function (m) {
|
||||
channel.bcast("PAD_LEAVE", m);
|
||||
},
|
||||
onError: function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
Store.leavePad(null, data, function () {});
|
||||
},
|
||||
onChannelError: function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
Store.leavePad(null, data, function () {});
|
||||
},
|
||||
onError: onError,
|
||||
onChannelError: onError,
|
||||
onRejected: function (allowed, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
@ -1595,7 +1596,10 @@ define([
|
||||
onConnect: function (wc, sendMessage) {
|
||||
channel.sendMessage = function (msg, cId, cb) {
|
||||
// Send to server
|
||||
sendMessage(msg, function () {
|
||||
sendMessage(msg, function (err) {
|
||||
if (err) {
|
||||
return void cb({ error: err });
|
||||
}
|
||||
// Broadcast to other tabs
|
||||
channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
|
||||
channel.bcast("PAD_MESSAGE", {
|
||||
|
||||
@ -481,7 +481,9 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
if (!config.anon_rpc) { return void cb("EXPECTED_ANON_RPC"); }
|
||||
|
||||
|
||||
var response = Util.response();
|
||||
var response = Util.response(function (label, info) {
|
||||
console.error('ROSTER_RESPONSE__' + label, info);
|
||||
});
|
||||
var anon_rpc = config.anon_rpc;
|
||||
var keys = config.keys;
|
||||
var me = keys.myCurvePublic;
|
||||
|
||||
@ -45,10 +45,13 @@ define([
|
||||
var txid = mkTxid();
|
||||
opts = opts || {};
|
||||
var to = opts.timeout || 30000;
|
||||
var timeout = setTimeout(function () {
|
||||
delete queries[txid];
|
||||
cb('TIMEOUT');
|
||||
}, to);
|
||||
var timeout;
|
||||
if (to > 0) {
|
||||
timeout = setTimeout(function () {
|
||||
delete queries[txid];
|
||||
cb('TIMEOUT');
|
||||
}, to);
|
||||
}
|
||||
acks[txid] = function (err) {
|
||||
clearTimeout(timeout);
|
||||
delete acks[txid];
|
||||
|
||||
@ -10,6 +10,7 @@ define([
|
||||
json: '/bower_components/requirejs-plugins/src/json',
|
||||
// jquery declares itself as literally "jquery" so it cannot be pulled by path :(
|
||||
"jquery": "/bower_components/jquery/dist/jquery.min",
|
||||
"mermaid": "/code/mermaid.min",
|
||||
// json.sortify same
|
||||
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
|
||||
//"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
|
||||
|
||||
@ -325,6 +325,11 @@ define([
|
||||
|
||||
UI.updateLoadingProgress({ state: -1 }, false);
|
||||
|
||||
if (toolbar) {
|
||||
// Check if we have a new chainpad instance
|
||||
toolbar.resetChainpad(cpNfInner.chainpad);
|
||||
}
|
||||
|
||||
var newPad = false;
|
||||
if (newContentStr === '') { newPad = true; }
|
||||
|
||||
@ -364,6 +369,9 @@ define([
|
||||
}).nThen(function () {
|
||||
stateChange(STATE.READY);
|
||||
firstConnection = false;
|
||||
|
||||
oldContent = undefined;
|
||||
|
||||
if (!readOnly) { onLocal(); }
|
||||
evOnReady.fire(newPad);
|
||||
|
||||
@ -394,12 +402,6 @@ define([
|
||||
Thumb.initPadThumbnails(common, options.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
|
||||
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
|
||||
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
|
||||
common.openTemplatePicker();
|
||||
}
|
||||
});
|
||||
};
|
||||
var onConnectionChange = function (info) {
|
||||
@ -517,18 +519,23 @@ define([
|
||||
}
|
||||
});
|
||||
$embedButton = common.createButton('mediatag', true).click(function () {
|
||||
common.openFilePicker({
|
||||
var cfg = {
|
||||
types: ['file'],
|
||||
where: ['root']
|
||||
});
|
||||
};
|
||||
if ($embedButton.data('filter')) {
|
||||
cfg.filter = $embedButton.data('filter');
|
||||
}
|
||||
common.openFilePicker(cfg);
|
||||
}).appendTo(toolbar.$rightside).hide();
|
||||
};
|
||||
var setMediaTagEmbedder = function (mte) {
|
||||
var setMediaTagEmbedder = function (mte, filter) {
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
if (!mte || readOnly) {
|
||||
$embedButton.hide();
|
||||
return;
|
||||
}
|
||||
if (filter) { $embedButton.data('filter', filter); }
|
||||
$embedButton.show();
|
||||
mediaTagEmbedder = mte;
|
||||
};
|
||||
|
||||
@ -49,24 +49,31 @@ define([
|
||||
config = undefined;
|
||||
|
||||
var evPatchSent = Util.mkEvent();
|
||||
var chainpad;
|
||||
|
||||
var chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
patchTransformer: patchTransformer,
|
||||
validateContent: validateContent,
|
||||
avgSyncMilliseconds: avgSyncMilliseconds,
|
||||
logLevel: logLevel
|
||||
});
|
||||
chainpad.onMessage(function(message, cb) {
|
||||
sframeChan.query('Q_RT_MESSAGE', message, function (err) {
|
||||
if (!err) { evPatchSent.fire(); }
|
||||
cb(err);
|
||||
var makeChainPad = function () {
|
||||
var _chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
patchTransformer: patchTransformer,
|
||||
validateContent: validateContent,
|
||||
avgSyncMilliseconds: avgSyncMilliseconds,
|
||||
logLevel: logLevel
|
||||
});
|
||||
});
|
||||
chainpad.onPatch(function () {
|
||||
onRemote({ realtime: chainpad });
|
||||
});
|
||||
_chainpad.onMessage(function(message, cb) {
|
||||
// -1 ==> no timeout, we may receive the callback only when we reconnect
|
||||
sframeChan.query('Q_RT_MESSAGE', message, function (_err, obj) {
|
||||
var err = _err || (obj && obj.error);
|
||||
if (!err) { evPatchSent.fire(); }
|
||||
cb(err);
|
||||
}, { timeout: -1 });
|
||||
});
|
||||
_chainpad.onPatch(function () {
|
||||
onRemote({ realtime: chainpad });
|
||||
});
|
||||
return _chainpad;
|
||||
};
|
||||
chainpad = makeChainPad();
|
||||
|
||||
var myID;
|
||||
var isReady = false;
|
||||
@ -96,15 +103,25 @@ define([
|
||||
sframeChan.on('EV_RT_ERROR', function (err) {
|
||||
isReady = false;
|
||||
chainpad.abort();
|
||||
if (err.type === 'EUNKNOWN') {
|
||||
// Recoverable error: make a new chainpad
|
||||
chainpad = makeChainPad();
|
||||
return;
|
||||
}
|
||||
onError(err);
|
||||
});
|
||||
sframeChan.on('EV_RT_CONNECT', function (content) {
|
||||
//content.members.forEach(userList.onJoin);
|
||||
if (isReady && myID === content.myID) {
|
||||
// We are connected and we are "reconnecting" ==> we probably had to rejoin
|
||||
// the channel because of a server error (enoent), don't update the toolbar
|
||||
return;
|
||||
}
|
||||
isReady = false;
|
||||
if (myID) {
|
||||
// it's a reconnect
|
||||
myID = content.myID;
|
||||
chainpad.start();
|
||||
//chainpad.start();
|
||||
onConnectionChange({ state: true, myId: myID });
|
||||
return;
|
||||
}
|
||||
@ -149,15 +166,18 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
return Object.freeze({
|
||||
var cpNfInner = {
|
||||
getMyID: function () { return myID; },
|
||||
metadataMgr: metadataMgr,
|
||||
whenRealtimeSyncs: whenRealtimeSyncs,
|
||||
onInfiniteSpinner: evInfiniteSpinner.reg,
|
||||
onPatchSent: evPatchSent.reg,
|
||||
offPatchSent: evPatchSent.unreg,
|
||||
chainpad: chainpad,
|
||||
};
|
||||
cpNfInner.__defineGetter__("chainpad", function () {
|
||||
return chainpad;
|
||||
});
|
||||
return Object.freeze(cpNfInner);
|
||||
};
|
||||
return Object.freeze(module.exports);
|
||||
});
|
||||
|
||||
@ -45,7 +45,8 @@ define([], function () {
|
||||
return decryptedMsg;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return msg;
|
||||
console.warn(peer, msg);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -81,6 +82,7 @@ define([], function () {
|
||||
validateKey = msgObj.validateKey;
|
||||
}
|
||||
var message = msgIn(msgObj.user, msgObj.msg);
|
||||
if (!message) { return; }
|
||||
|
||||
verbose(message);
|
||||
|
||||
|
||||
@ -4,11 +4,12 @@ define([
|
||||
'/common/themes.js',
|
||||
'/customize/messages.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/text-cursor.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, Modes, Themes, Messages, UIElements, Hash, Util, TextCursor, ChainPad) {
|
||||
], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad) {
|
||||
var module = {};
|
||||
|
||||
var cursorToPos = function(cursor, oldText) {
|
||||
@ -454,12 +455,7 @@ define([
|
||||
})[0];
|
||||
};
|
||||
var makeTippy = function (cursor) {
|
||||
var html = '<span class="cp-cursor-avatar">';
|
||||
if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) {
|
||||
html += UIElements.getAvatar(cursor.avatar);
|
||||
}
|
||||
html += cursor.name + '</span>';
|
||||
return html;
|
||||
return MT.getCursorAvatar(cursor);
|
||||
};
|
||||
var marks = {};
|
||||
exp.removeCursors = function () {
|
||||
|
||||
@ -32,7 +32,9 @@ define([
|
||||
module.create = function (common, config) {
|
||||
var File = {};
|
||||
var origin = common.getMetadataMgr().getPrivateData().origin;
|
||||
var response = Util.response();
|
||||
var response = Util.response(function (label, info) {
|
||||
console.error('COMMON_UPLOAD__' + label, info);
|
||||
});
|
||||
|
||||
var teamId = config.teamId;
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ define([
|
||||
'/common/sframe-common-codemirror.js',
|
||||
'/common/sframe-common-cursor.js',
|
||||
'/common/sframe-common-mailbox.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/metadata-manager.js',
|
||||
|
||||
'/customize/application_config.js',
|
||||
@ -35,6 +36,7 @@ define([
|
||||
CodeMirror,
|
||||
Cursor,
|
||||
Mailbox,
|
||||
MT,
|
||||
MetadataMgr,
|
||||
AppConfig,
|
||||
CommonRealtime,
|
||||
@ -90,8 +92,8 @@ define([
|
||||
funcs.initFilePicker = callWithCommon(UIElements.initFilePicker);
|
||||
funcs.openFilePicker = callWithCommon(UIElements.openFilePicker);
|
||||
funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker);
|
||||
funcs.displayMediatagImage = callWithCommon(UIElements.displayMediatagImage);
|
||||
funcs.displayAvatar = callWithCommon(UIElements.displayAvatar);
|
||||
funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage);
|
||||
funcs.displayAvatar = callWithCommon(MT.displayAvatar);
|
||||
funcs.createButton = callWithCommon(UIElements.createButton);
|
||||
funcs.createUsageBar = callWithCommon(UIElements.createUsageBar);
|
||||
funcs.updateTags = callWithCommon(UIElements.updateTags);
|
||||
@ -102,7 +104,8 @@ define([
|
||||
funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning);
|
||||
funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal);
|
||||
funcs.onServerError = callWithCommon(UIElements.onServerError);
|
||||
funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu);
|
||||
funcs.importMediaTagMenu = callWithCommon(MT.importMediaTagMenu);
|
||||
funcs.getMediaTagPreview = callWithCommon(MT.getMediaTagPreview);
|
||||
|
||||
// Thumb
|
||||
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
|
||||
@ -294,10 +297,9 @@ define([
|
||||
var priv = ctx.metadataMgr.getPrivateData();
|
||||
if (priv.isNewFile) {
|
||||
var c = (priv.settings.general && priv.settings.general.creation) || {};
|
||||
var skip = !AppConfig.displayCreationScreen || (c.skip && !priv.forceCreationScreen);
|
||||
// If this is a new file but we have a hash in the URL and pad creation screen is
|
||||
// not displayed, then display an error...
|
||||
if (priv.isDeleted && (!funcs.isLoggedIn() || skip)) {
|
||||
if (priv.isDeleted && !funcs.isLoggedIn()) {
|
||||
UI.errorLoadingScreen(Messages.inactiveError, false, function () {
|
||||
UI.addLoadingScreen();
|
||||
return void funcs.createPad({}, waitFor());
|
||||
@ -306,7 +308,7 @@ define([
|
||||
}
|
||||
// Otherwise, if we don't display the screen, it means it is not a deleted pad
|
||||
// so we can continue and start realtime...
|
||||
if (!funcs.isLoggedIn() || skip) {
|
||||
if (!funcs.isLoggedIn()) {
|
||||
return void funcs.createPad(c, waitFor());
|
||||
}
|
||||
// If we display the pad creation screen, it will handle deleted pads directly
|
||||
|
||||
@ -7,10 +7,11 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/messenger-ui.js',
|
||||
'/customize/messages.js',
|
||||
], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h,
|
||||
], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, MT, h,
|
||||
MessengerUI, Messages) {
|
||||
var Common;
|
||||
|
||||
@ -345,17 +346,9 @@ MessengerUI, Messages) {
|
||||
window.open(origin+'/profile/#' + data.profile);
|
||||
});
|
||||
}
|
||||
if (data.avatar && UIElements.getAvatar(data.avatar)) {
|
||||
$span.append(UIElements.getAvatar(data.avatar));
|
||||
Common.displayAvatar($span, data.avatar, name, function () {
|
||||
$span.append($rightCol);
|
||||
} else {
|
||||
Common.displayAvatar($span, data.avatar, name, function ($img) {
|
||||
if (data.avatar && $img && $img.length) {
|
||||
UIElements.setAvatar(data.avatar, $img[0].outerHTML);
|
||||
}
|
||||
$span.append($rightCol);
|
||||
});
|
||||
}
|
||||
});
|
||||
$span.data('uid', data.uid);
|
||||
$editUsersList.append($span);
|
||||
});
|
||||
@ -1331,6 +1324,15 @@ MessengerUI, Messages) {
|
||||
showColors = true;
|
||||
};
|
||||
|
||||
// If we had to create a new chainpad instance, reset the one used in the toolbar
|
||||
toolbar.resetChainpad = function (chainpad) {
|
||||
if (config.realtime !== chainpad) {
|
||||
config.realtime = chainpad;
|
||||
config.realtime.onPatch(ks(toolbar, config));
|
||||
config.realtime.onMessage(ks(toolbar, config, true));
|
||||
}
|
||||
};
|
||||
|
||||
// On log out, remove permanently the realtime elements of the toolbar
|
||||
Common.onLogout(function () {
|
||||
failed();
|
||||
|
||||
@ -938,7 +938,7 @@
|
||||
"creation_expiration": "Ablaufdatum",
|
||||
"creation_passwordValue": "Passwort",
|
||||
"creation_propertiesTitle": "Verfügbarkeit",
|
||||
"creation_appMenuName": "Fortgeschrittener Modus (Strg + E)",
|
||||
"creation_appMenuName": "Neues Pad (Strg + E)",
|
||||
"creation_newPadModalDescription": "Klicke auf einen Pad-Typ, um das entsprechende Pad zu erstellen. Du kannst auch die <b>Tab</b>-Taste für die Auswahl und die <b>Enter</b>-Taste zum Bestätigen benutzen.",
|
||||
"creation_newPadModalDescriptionAdvanced": "Du kannst das Kästchen markieren (oder den Wert mit der Leertaste ändern), um den Dialog bei der Pad-Erstellung anzuzeigen (für eigene oder auslaufende Dokumente etc.).",
|
||||
"creation_newPadModalAdvanced": "Dialog bei der Pad-Erstellung anzeigen",
|
||||
|
||||
@ -281,7 +281,7 @@
|
||||
"profile_description": "Kuvaus",
|
||||
"profile_fieldSaved": "Uusi arvo tallennettu: {0}",
|
||||
"profile_viewMyProfile": "Näytä oma profiili",
|
||||
"userlist_addAsFriendTitle": "Lähetä ystäväpyyntö käyttäjälle \"{0}\"",
|
||||
"userlist_addAsFriendTitle": "Lähetä kontaktipyyntö käyttäjälle \"{0}\"",
|
||||
"contacts_title": "Yhteystiedot",
|
||||
"contacts_addError": "Virhe lisätessä yhteystietoa listaan",
|
||||
"contacts_added": "Yhteystietopyyntö hyväksytty.",
|
||||
@ -301,7 +301,7 @@
|
||||
"contacts_confirmRemoveHistory": "Oletko varma, että haluat tyhjentää keskusteluhistorian pysyvästi? Tietoja ei voida palauttaa",
|
||||
"contacts_removeHistoryServerError": "Keskusteluhistorian poistamisessa tapahtui virhe. Yritä myöhemmin uudelleen",
|
||||
"contacts_fetchHistory": "Hae vanhempia viestejä",
|
||||
"contacts_friends": "Kaverit",
|
||||
"contacts_friends": "Kontaktit",
|
||||
"contacts_rooms": "Keskusteluhuoneet",
|
||||
"contacts_leaveRoom": "Lähde keskusteluhuoneesta",
|
||||
"contacts_online": "Toinen käyttäjä tästä keskusteluhuoneesta on online-tilassa",
|
||||
@ -354,7 +354,7 @@
|
||||
"fm_info_root": "Voit luoda haluamasi määrän sisäkkäisiä kansioita tiedostojesi järjestämiseen.",
|
||||
"fm_info_unsorted": "Sisältää kaikki käyttämäsi tiedostot, joita ei ole vielä lajiteltu \"Tiedostot\"-näkymässä tai siirretty roskakoriin.",
|
||||
"fm_info_template": "Sisältää kaikki mallipohjiksi tallennetut padit, joita voit käyttää uudelleen luodessasi uuden padin.",
|
||||
"fm_info_recent": "Näytä viimeksi muokatut tai avatut padit.",
|
||||
"fm_info_recent": "Tässä näytetään sinun tai yhteistyökumppaniesi äskettäin avaamat tai muokkaamat padit.",
|
||||
"fm_info_trash": "Tyhjennä roskakorisi vapauttaaksesi CryptDrive-tallennustilaa.",
|
||||
"fm_info_allFiles": "Sisältää kaikki tiedostot \"Dokumentit\"- \"Lajittelemattomat\"- ja \"Roskakori\"-näkymistä. Et voi siirtää tai poistaa tiedostoja täältä.",
|
||||
"fm_info_anonymous": "Et ole kirjautunut sisään. Padisi vanhenevat kolmen kuukauden kuluttua (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">(lue lisää)</a>). Padit säilytetään paikallisesti selaimessasi, joten selaimen historiatietojen tyhjentäminen saattaa hävittää ne.<br><a href=\"/register/\">Rekisteröidy</a> tai <a href=\"/login/\">kirjaudu sisään</a> säilyttääksesi padisi palvelimella.<br>",
|
||||
@ -513,7 +513,7 @@
|
||||
"settings_pinningError": "Jokin meni pieleen",
|
||||
"settings_usageAmount": "Kiinnitetyt padisi käyttävät {0}Mt",
|
||||
"settings_logoutEverywhereButton": "Kirjaudu ulos",
|
||||
"settings_logoutEverywhereTitle": "Kirjaudu ulos kaikkialta",
|
||||
"settings_logoutEverywhereTitle": "Sulje etäistunnot",
|
||||
"settings_logoutEverywhere": "Pakota uloskirjautuminen kaikista web-sessioista",
|
||||
"settings_logoutEverywhereConfirm": "Oletko varma? Joudut kirjautumaan kaikilla laitteillasi uudelleen sisään.",
|
||||
"settings_driveDuplicateTitle": "Omistettujen padien kaksoiskappaleet",
|
||||
@ -534,8 +534,8 @@
|
||||
"settings_creationSkipFalse": "Näytä",
|
||||
"settings_templateSkip": "Ohita mallipohjan valinta-dialogi",
|
||||
"settings_templateSkipHint": "Luodessasi uutta padia sinulta kysytään, haluatko käyttää mallipohjaa, jos sinulla on tallennettuja mallipohjia tälle padityypille. Tällä asetuksella voit valita, ettei mallipohjan valinta-dialogia näytetä ja siten mallipohjia ei koskaan käytetä.",
|
||||
"settings_ownDriveTitle": "Ota käyttöön viimeisimmät tiliominaisuudet",
|
||||
"settings_ownDriveHint": "Teknisistä syistä vanhemmilla käyttäjätileillä ei suoraan ole pääsyä CryptPadin uusimpiin ominaisuuksiin. Ilmainen päivitys uuteen käyttäjätiliin valmistelee CryptDrivesi tulevia ominaisuuksia varten häiritsemättä tavanomaista toimintaasi.",
|
||||
"settings_ownDriveTitle": "Päivitä käyttäjätili",
|
||||
"settings_ownDriveHint": "Vanhemmilla käyttäjätileillä ei ole teknisistä syistä pääsyä CryptPadin uusimpiin ominaisuuksiin. Ilmainen päivitys uuteen käyttäjätiliin valmistelee CryptDrivesi tulevia ominaisuuksia varten häiritsemättä tavanomaista toimintaasi.",
|
||||
"settings_ownDriveButton": "Päivitä käyttäjätilisi",
|
||||
"settings_ownDriveConfirm": "Käyttäjätilin päivitykseen voi mennä jonkin aikaa. Joudut kirjautumaan uudelleen sisään kaikilla laitteillasi. Oletko varma, että haluat aloittaa päivityksen?",
|
||||
"settings_ownDrivePending": "Käyttäjätiliäsi päivitetään. Ole hyvä, äläkä sulje tai lataa tätä sivua uudelleen, ennen kuin toimenpide on valmis.",
|
||||
@ -850,7 +850,7 @@
|
||||
"features_f_social": "Sosiaaliset sovellukset",
|
||||
"features_f_social_note": "Luo käyttäjäprofiili, käytä avatar-kuvaa, keskustele yhteystietojen kanssa",
|
||||
"features_f_file1": "Lataa ja jaa tiedostoja",
|
||||
"features_f_file1_note": "Jaa tiedostoja kavereidesi kanssa tai upota ne padeihisi",
|
||||
"features_f_file1_note": "Jaa tiedostoja kontaktiesi kanssa tai upota ne padeihisi",
|
||||
"features_f_storage1": "Pysyvä tallennustila (50Mt)",
|
||||
"features_f_storage1_note": "CryptDriveen tallennettuja padeja ei koskaan poisteta käyttämättömyyden takia",
|
||||
"features_f_register": "Rekisteröidy ilmaiseksi",
|
||||
@ -953,7 +953,7 @@
|
||||
"a": "Rekisteröityneille käyttäjille on tarjolla joitakin toimintoja, jotka eivät ole saatavilla rekisteröitymättömille käyttäjille. Löydät nämä toiminnot <a href='/features.html' target='_blank'>luomastamme kaaviosta</a>."
|
||||
},
|
||||
"share": {
|
||||
"q": "Miten jaan salattuja padeja kavereideni kanssa?",
|
||||
"q": "Miten jaan salattuja padeja kontaktieni kanssa?",
|
||||
"a": "CryptPad laittaa URL-osoitteessa padisi salaisen salausavaimen <em>#</em>-merkin jälkeen. Tämän merkin jälkeen laitettuja tietoja ei lähetetä palvelimelle, joten emme pääse koskaan käyttämään salausavaimiasi. Jakaessasi linkin padiin jaat oikeuden lukea ja käyttää sitä."
|
||||
},
|
||||
"remove": {
|
||||
@ -985,7 +985,7 @@
|
||||
"title": "Muita kysymyksiä",
|
||||
"pay": {
|
||||
"q": "Miksi minun täytyisi maksaa, kun niin monet toiminnot ovat ilmaisia?",
|
||||
"a": "Annamme tukijoillemme lisätallennustilaa ja mahdollisuuden kasvattaa kavereiden tallennustilakiintiöitä (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>lue lisää</a>).<br><br>Näiden lyhytaikaisten etujen lisäksi premium-tilaus auttaa rahoittamaan CryptPadin jatkuvaa, aktiivista kehitystyötä. Tähän kuuluu bugien korjaamista, uusien ominaisuuksien lisäämistä ja CryptPad-instanssien pystyttämisen ja ylläpidon helpottamista. Lisäksi autat näyttämään muille palveluntarjoajille, että ihmiset ovat valmiita tukemaan yksityisyyttä parantavia teknologioita. Toivomme, että käyttäjätietojen myymiseen perustuvat liiketoimintamallit jäävät lopulta menneeseen.<br><br>Lopuksi, tarjoamme suurimman osan CryptPadin toiminnallisuudesta ilmaiseksi, koska uskomme yksityisyyden kuuluvan kaikille - ei vain niille, joilla on varaa maksaa siitä. Tukemalla meitä autat tarjoamaan heikommassa asemassa oleville väestöille pääsyn näihin peruspalveluihin."
|
||||
"a": "Annamme tukijoillemme lisätallennustilaa ja mahdollisuuden kasvattaa kontaktien tallennustilakiintiöitä (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>lue lisää</a>).<br><br>Näiden lyhytaikaisten etujen lisäksi premium-tilaus auttaa rahoittamaan CryptPadin jatkuvaa, aktiivista kehitystyötä. Tähän kuuluu bugien korjaamista, uusien ominaisuuksien lisäämistä ja CryptPad-instanssien pystyttämisen ja ylläpidon helpottamista. Lisäksi autat näyttämään muille palveluntarjoajille, että ihmiset ovat valmiita tukemaan yksityisyyttä parantavia teknologioita. Toivomme, että käyttäjätietojen myymiseen perustuvat liiketoimintamallit jäävät lopulta menneeseen.<br><br>Lopuksi, tarjoamme suurimman osan CryptPadin toiminnallisuudesta ilmaiseksi, koska uskomme yksityisyyden kuuluvan kaikille - ei vain niille, joilla on varaa maksaa siitä. Tukemalla meitä autat tarjoamaan heikommassa asemassa oleville väestöille pääsyn näihin peruspalveluihin."
|
||||
},
|
||||
"goal": {
|
||||
"q": "Mitkä ovat tavoitteenne?",
|
||||
@ -1047,7 +1047,7 @@
|
||||
"colors": "Vaihda tekstin ja taustan väriä <span class=\"fa fa-i-cursor\"></span> ja <span class=\"fa fa-square\"></span> -painikkeilla"
|
||||
},
|
||||
"poll": {
|
||||
"decisions": "Tee päätöksiä luotettujen ystävien kesken",
|
||||
"decisions": "Tee päätöksiä luotettujen kontaktien kesken",
|
||||
"options": "Ehdota vaihtoehtoja ja tuo ilmi mielipiteesi",
|
||||
"choices": "Napsauta sarakkeesi soluja valitaksesi kyllä- (<strong>✔</strong>), ehkä- (<strong>~</strong>), tai ei (<strong>✖</strong>) -vaihtoehdon",
|
||||
"submit": "Napsauta <strong>Lähetä</strong> tehdäksesi valintasi näkyviksi muille"
|
||||
@ -1058,12 +1058,12 @@
|
||||
"embed": "Upota kuvia kovalevyltäsi <span class=\"fa fa-file-image-o\"></span> tai CryptDrivestasi <span class=\"fa fa-image\"></span> ja vie ne PNG-tiedostomuodossa kovalevyllesi <span class=\"fa fa-download\"></span> tai CryptDriveesi <span class=\"fa fa-cloud-upload\"></span>"
|
||||
},
|
||||
"kanban": {
|
||||
"add": "Lisää uusia tauluja oikeassa yläkulmassa olevalla <span class=\"fa fa-plus\"></span> -painikkeella",
|
||||
"task": "Siirrä kohtia raahaamalla ja pudottamalla ne yhdestä taulusta toiseen",
|
||||
"color": "Vaihda värejä napsauttamalla taulun otsikon vieressä olevaa värillistä osaa"
|
||||
"add": "Lisää uusia kortteja ja tauluja <span class=\"fa fa-plus\"></span> -painikkeella",
|
||||
"task": "Siirrä kohtia raahaamalla ja pudottamalla, raahaa <span class=\"fa fa-trash\"></span> roskakoriin poistaaksesi",
|
||||
"color": "Muokkaa otsikoita, sisältöä, tunnisteita ja värejä korttien ja taulujen otsikoiden vieressä olevalla span class=\"fa fa-pencil\"></span> -painikkeella"
|
||||
}
|
||||
},
|
||||
"driveReadmeTitle": "Mikä on CryptPad?",
|
||||
"readme_welcome": "Tervetuloa CryptPadiin!",
|
||||
"readme_p1": "Tervetuloa CryptPadiin, täällä voit tehdä muistiinpanoja yksin tai ystäviesi kanssa."
|
||||
"readme_p1": "Tervetuloa CryptPadiin, täällä voit tehdä muistiinpanoja yksin tai kontaktiesi kanssa."
|
||||
}
|
||||
|
||||
@ -945,7 +945,7 @@
|
||||
"creation_expiration": "Date d'expiration",
|
||||
"creation_passwordValue": "Mot de passe",
|
||||
"creation_propertiesTitle": "Disponibilité",
|
||||
"creation_appMenuName": "Mode avancé (Ctrl + E)",
|
||||
"creation_appMenuName": "Nouveau pad (Ctrl + E)",
|
||||
"creation_newPadModalDescription": "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur <b>Tab</b> pour sélectionner un type et appuyer sur <b>Entrée</b> pour valider.",
|
||||
"creation_newPadModalDescriptionAdvanced": "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur.",
|
||||
"creation_newPadModalAdvanced": "Afficher l'écran de création de pads",
|
||||
|
||||
@ -1306,7 +1306,7 @@
|
||||
"team_cat_general": "Info",
|
||||
"properties_passwordWarning": "La password è stata cambiata ma non siamo in grado di aggiornare il tuo CryptDrive con i nuovi dati. Devi rimuovere i vecchi pad manualmente.<br>Premi OK per ricaricare ed aggiornare i tuoi diritti di accesso.",
|
||||
"share_embedCategory": "Incorporamento",
|
||||
"chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra tab e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.",
|
||||
"chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra scheda e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.",
|
||||
"crowdfunding_popup_text": "<h3>Abbiamo bisogno del tuo aiuto!</h3>Per garantire che CryptPad sia attivamente sviluppato, valuta se aiutare il progetto sulla <a href=\"https://opencollective.com/cryptpad\">pagina OpenCollective</a>, dove potrai vedere la nostra <b>Roadmap</b> e gli <b>obiettivi di finanziamento</b>.",
|
||||
"admin_updateLimitHint": "Forzare un aggiornamento dei limiti dello spazio utente può essere fatto in qualsiasi momento, ma è necessario solo in caso di errori",
|
||||
"admin_flushCacheTitle": "Svuota la cache HTTP",
|
||||
@ -1316,7 +1316,7 @@
|
||||
"fm_info_sharedFolderHistory": "questa è solo la cronologia delle tue cartelle condivise: <b>{0}</b><br/>Il tuo CryptDrive rimarrà in sola lettura durante la navigazione.",
|
||||
"share_description": "Scegli cosa vuoi condividere e prendi il link o invialo direttamente ai tuoi contatti CryptPad.",
|
||||
"admin_supportInitHelp": "Il tuo server non è ancora configurato per avere una mailbox di supporto. Se vuoi una mailbox di supporto per ricevere messaggi dai tuoi utenti, chiedi all'amministratore del server di avviare lo script posizionato in \"./scripts/generate-admin-keys.js\", quindi salvare la chiave pubblica nel file \"config.js\" ed inviarti la chiave privata.",
|
||||
"admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa la form che segue per aggiungere o aggiornare la chiave privata del tuo account.",
|
||||
"admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa il modulo che segue per aggiungere o aggiornare la chiave privata del tuo account.",
|
||||
"admin_supportInitHint": "Puoi configurare una mailbox di supporto per dare agli utenti del tuo CryptPad un modo per contattarti in maniera sicura se hanno problemi con i loro account.",
|
||||
"admin_supportListHint": "Questa è la lista dei ticket inviati dagli utenti alla mailbox di supporto. Tutti gli amministratori possono vedere i messaggi e le risposte. Un ticket chiuso non può essere riaperto. Puoi solo rimuovere (nascondere) i ticket chiusi, ma i ticket rimossi rimangono visibili agli altri amministratori.",
|
||||
"requestEdit_confirm": "{1} ha richiesto la possibilità di modificare il pad <b>{0}</b>. Vuoi fornirgli l'accesso?",
|
||||
|
||||
@ -966,7 +966,7 @@
|
||||
"creation_expiration": "Expiration time",
|
||||
"creation_passwordValue": "Password",
|
||||
"creation_propertiesTitle": "Availability",
|
||||
"creation_appMenuName": "Advanced mode (Ctrl + E)",
|
||||
"creation_appMenuName": "New pad (Ctrl + E)",
|
||||
"creation_newPadModalDescription": "Click on a pad type to create it. You can also press <b>Tab</b> to select the type and press <b>Enter</b> to confirm.",
|
||||
"creation_newPadModalDescriptionAdvanced": "You can check the box (or press <b>Space</b> to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).",
|
||||
"creation_newPadModalAdvanced": "Display the pad creation screen",
|
||||
|
||||
@ -177,6 +177,7 @@ define([
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var user = metadataMgr.getUserData();
|
||||
|
||||
APP.disableSF = !privateData.enableSF && AppConfig.disableSharedFolders;
|
||||
if (APP.newSharedFolder && !APP.loggedIn) {
|
||||
@ -204,13 +205,19 @@ define([
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
|
||||
APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
|
||||
var $displayName = APP.$bar.find('.' + Toolbar.constants.username);
|
||||
metadataMgr.onChange(function () {
|
||||
var name = metadataMgr.getUserData().name || Messages.anonymous;
|
||||
$displayName.text(name);
|
||||
});
|
||||
$displayName.text(user.name || Messages.anonymous);
|
||||
|
||||
|
||||
/* add the usage */
|
||||
var usageBar;
|
||||
if (APP.loggedIn) {
|
||||
common.createUsageBar(null, function (err, $limitContainer) {
|
||||
usageBar = common.createUsageBar(null, function (err) {
|
||||
if (err) { return void DriveUI.logError(err); }
|
||||
APP.$limit = $limitContainer;
|
||||
}, true);
|
||||
}
|
||||
|
||||
@ -256,16 +263,12 @@ define([
|
||||
.addClass('fa-ban');
|
||||
}
|
||||
|
||||
metadataMgr.onChange(function () {
|
||||
var name = metadataMgr.getUserData().name || Messages.anonymous;
|
||||
APP.$displayName.text(name);
|
||||
});
|
||||
|
||||
$('body').css('display', '');
|
||||
if (!proxy.drive || typeof(proxy.drive) !== 'object') {
|
||||
throw new Error("Corrupted drive");
|
||||
}
|
||||
var drive = DriveUI.create(common, {
|
||||
$limit: usageBar && usageBar.$container,
|
||||
proxy: proxy,
|
||||
folders: folders,
|
||||
updateObject: updateObject,
|
||||
|
||||
@ -27,8 +27,6 @@
|
||||
}
|
||||
|
||||
.cp-filepicker-content-element {
|
||||
@darker: darken(@colortheme_modal-fg, 30%);
|
||||
|
||||
width: 125px;
|
||||
//min-width: 200px;
|
||||
//height: 1em;
|
||||
@ -45,8 +43,9 @@
|
||||
cursor: pointer;
|
||||
|
||||
background-color: @colortheme_modal-bg;
|
||||
box-shadow: 2px 2px 5px #000;
|
||||
color: @darker;
|
||||
|
||||
border: 1px solid @colortheme_logo-2;
|
||||
color: @colortheme_logo-2;
|
||||
|
||||
transition: all 0.1s;
|
||||
|
||||
|
||||
@ -81,11 +81,13 @@ define([
|
||||
var createFileDialog = function () {
|
||||
var types = filters.types || [];
|
||||
// Create modal
|
||||
var $blockContainer = UIElements.createModal({
|
||||
var modal = UI.createModal({
|
||||
id: 'cp-filepicker-dialog',
|
||||
$body: $body,
|
||||
onClose: hideFileDialog
|
||||
}).show();
|
||||
});
|
||||
modal.show();
|
||||
var $blockContainer = modal.$modal;
|
||||
// Set the fixed content
|
||||
var $block = $blockContainer.find('.cp-modal');
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/modes.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
@ -40,6 +41,7 @@ define([
|
||||
Hash,
|
||||
UI,
|
||||
UIElements,
|
||||
MT,
|
||||
Modes,
|
||||
Messages,
|
||||
h,
|
||||
@ -90,13 +92,9 @@ define([
|
||||
|
||||
var getAvatar = function (cursor, noClear) {
|
||||
// Tippy
|
||||
var html = '<span class="cp-cursor-avatar">';
|
||||
if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) {
|
||||
html += UIElements.getAvatar(cursor.avatar);
|
||||
}
|
||||
html += cursor.name + '</span>';
|
||||
var html = MT.getCursorAvatar(cursor);
|
||||
|
||||
var l = UIElements.getFirstCharacter(cursor.name || Messages.anonymous);
|
||||
var l = Util.getFirstCharacter(cursor.name || Messages.anonymous);
|
||||
|
||||
var text = '';
|
||||
if (cursor.color) {
|
||||
@ -135,8 +133,12 @@ define([
|
||||
var PROPERTIES = ['title', 'body', 'tags', 'color'];
|
||||
var BOARD_PROPERTIES = ['title', 'color'];
|
||||
var createEditModal = function (framework, kanban) {
|
||||
if (framework.isReadOnly()) { return; }
|
||||
if (editModal) { return editModal; }
|
||||
|
||||
var dataObject = {};
|
||||
var isBoard, id;
|
||||
var offline = false;
|
||||
|
||||
var update = Util.throttle(function () {
|
||||
kanban.setBoards(kanban.options.boards);
|
||||
@ -147,7 +149,7 @@ define([
|
||||
framework.localChange();
|
||||
update();
|
||||
};
|
||||
if (editModal) { return editModal; }
|
||||
|
||||
var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
|
||||
var content = h('div', [
|
||||
conflictContainer = h('div#cp-kanban-edit-conflicts', [
|
||||
@ -167,7 +169,6 @@ define([
|
||||
]);
|
||||
var $tags = $(tagsDiv);
|
||||
|
||||
|
||||
var $conflict = $(conflicts);
|
||||
var $cc = $(conflictContainer);
|
||||
var conflict = {
|
||||
@ -282,6 +283,7 @@ define([
|
||||
});
|
||||
|
||||
var commitTags = function () {
|
||||
if (offline) { return; }
|
||||
setTimeout(function () {
|
||||
dataObject.tags = Util.deduplicateString(_field.getTokens().map(function (t) {
|
||||
return t.toLowerCase();
|
||||
@ -305,6 +307,7 @@ define([
|
||||
var $color = $(h('span.cp-kanban-palette.fa'));
|
||||
$color.addClass('cp-kanban-palette-'+(color || 'nocolor'));
|
||||
$color.click(function () {
|
||||
if (offline) { return; }
|
||||
if (color === selectedColor) { return; }
|
||||
selectedColor = color;
|
||||
$colors.find('.cp-kanban-palette').removeClass('fa-check');
|
||||
@ -363,6 +366,17 @@ define([
|
||||
buttons: button
|
||||
});
|
||||
modal.classList.add('cp-kanban-edit-modal');
|
||||
var $modal = $(modal);
|
||||
|
||||
framework.onEditableChange(function (unlocked) {
|
||||
editor.setOption('readOnly', !unlocked);
|
||||
$title.prop('disabled', unlocked ? '' : 'disabled');
|
||||
$(_field.element).tokenfield(unlocked ? 'enable' : 'disable');
|
||||
|
||||
$modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled');
|
||||
offline = !unlocked;
|
||||
});
|
||||
|
||||
|
||||
var setId = function (_isBoard, _id) {
|
||||
// Reset the mdoal with a new id
|
||||
@ -386,7 +400,7 @@ define([
|
||||
.show();
|
||||
}
|
||||
// Also reset the buttons
|
||||
$(modal).find('nav').after(UI.dialog.getButtons(button)).remove();
|
||||
$modal.find('nav').after(UI.dialog.getButtons(button)).remove();
|
||||
};
|
||||
|
||||
onRemoteChange.reg(function () {
|
||||
@ -975,6 +989,7 @@ define([
|
||||
}
|
||||
kanban.options.readOnly = true;
|
||||
$container.addClass('cp-app-readonly');
|
||||
$container.find('.kanban-edit-item').remove();
|
||||
});
|
||||
|
||||
var getCursor = function () {
|
||||
|
||||
@ -41,12 +41,6 @@ define([
|
||||
var cursors = {};
|
||||
|
||||
var makeTippy = function (cursor) {
|
||||
/*var html = '<span class="cp-cursor-avatar">';
|
||||
if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) {
|
||||
html += UIElements.getAvatar(cursor.avatar);
|
||||
}
|
||||
html += cursor.name + '</span>';
|
||||
return html;*/
|
||||
return cursor.name;
|
||||
};
|
||||
|
||||
|
||||
@ -794,6 +794,11 @@ define([
|
||||
var userDoc = JSON.stringify(proxy);
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
|
||||
if (APP.toolbar && APP.rt.cpCnInner) {
|
||||
// Check if we have a new chainpad instance
|
||||
APP.toolbar.resetChainpad(APP.rt.cpCnInner.chainpad);
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
if (proxy.info) {
|
||||
// Migration
|
||||
@ -1061,13 +1066,6 @@ define([
|
||||
common.openPadChat(function () {});
|
||||
|
||||
UI.removeLoadingScreen();
|
||||
var privateDat = metadataMgr.getPrivateData();
|
||||
var skipTemp = Util.find(privateDat,
|
||||
['settings', 'general', 'creation', 'noTemplate']);
|
||||
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
|
||||
if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
|
||||
common.openTemplatePicker();
|
||||
}
|
||||
};
|
||||
|
||||
var onError = function (info) {
|
||||
|
||||
@ -10,6 +10,7 @@ define([
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/clipboard.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/customize/application_config.js',
|
||||
@ -36,6 +37,7 @@ define([
|
||||
UIElements,
|
||||
Realtime,
|
||||
Clipboard,
|
||||
MT,
|
||||
h,
|
||||
Messages,
|
||||
AppConfig,
|
||||
@ -351,7 +353,7 @@ define([
|
||||
displayAvatar();
|
||||
if (APP.readOnly) { return; }
|
||||
|
||||
var data = UIElements.addAvatar(common, function (ev, data) {
|
||||
var data = MT.addAvatar(common, function (ev, data) {
|
||||
var old = common.getMetadataMgr().getUserData().avatar;
|
||||
var todo = function () {
|
||||
APP.module.execCommand("SET", {
|
||||
|
||||
@ -62,12 +62,6 @@ define([
|
||||
'cp-settings-safe-links',
|
||||
'cp-settings-userfeedback',
|
||||
],
|
||||
'creation': [
|
||||
'cp-settings-creation-owned',
|
||||
'cp-settings-creation-expire',
|
||||
'cp-settings-creation-skip',
|
||||
'cp-settings-creation-template'
|
||||
],
|
||||
'drive': [
|
||||
'cp-settings-drive-duplicate',
|
||||
'cp-settings-thumbnails',
|
||||
@ -101,9 +95,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
if (!AppConfig.displayCreationScreen) {
|
||||
delete categories.creation;
|
||||
}
|
||||
if (AppConfig.disableFeedback) {
|
||||
var feedbackIdx = categories.account.indexOf('cp-settings-userfeedback');
|
||||
categories.account.splice(feedbackIdx, 1);
|
||||
@ -610,242 +601,6 @@ define([
|
||||
cb($cbox);
|
||||
}, true);
|
||||
|
||||
// Pad Creation settings
|
||||
|
||||
var setHTML = function (e, html) {
|
||||
e.innerHTML = html;
|
||||
return e;
|
||||
};
|
||||
create['creation-owned'] = function () {
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
var owned = h('div.cp-settings-creation-owned.cp-sidebarlayout-element', [
|
||||
h('label', [
|
||||
Messages.creation_ownedTitle
|
||||
]),
|
||||
setHTML(h('p.cp-sidebarlayout-description'),
|
||||
Messages.creation_owned1 + '<br>' + Messages.creation_owned2),
|
||||
h('input#cp-creation-owned-true.cp-creation-owned-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-owned',
|
||||
value: 1,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-owned-true' }, Messages.creation_ownedTrue),
|
||||
h('input#cp-creation-owned-false.cp-creation-owned-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-owned',
|
||||
value: 0,
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-owned-false' }, Messages.creation_ownedFalse),
|
||||
h('span.fa.fa-check', {title: Messages.saved}),
|
||||
h('span.fa.fa-spinner.fa-pulse'),
|
||||
]);
|
||||
|
||||
var $owned = $(owned);
|
||||
|
||||
var $ok = $owned.find('.fa-check').hide();
|
||||
var $spinner = $owned.find('.fa-spinner').hide();
|
||||
|
||||
$owned.find('input').change(function () {
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
var val = parseInt($owned.find('[name="cp-creation-owned"]:checked').val());
|
||||
common.setAttribute(['general', 'creation', 'owned'], val, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
});
|
||||
});
|
||||
common.getAttribute(['general', 'creation', 'owned'], function (e, val) {
|
||||
if (!val && typeof val !== "undefined") {
|
||||
$owned.find('#cp-creation-owned-false').attr('checked', true);
|
||||
}
|
||||
});
|
||||
|
||||
return $owned;
|
||||
};
|
||||
create['creation-expire'] = function () {
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
var expire = h('div.cp-settings-creation-expire.cp-sidebarlayout-element', [
|
||||
h('label', [
|
||||
Messages.creation_expireTitle
|
||||
]),
|
||||
setHTML(h('p.cp-sidebarlayout-description'),
|
||||
Messages.creation_expire1 + '<br>' + Messages.creation_expire2),
|
||||
h('input#cp-creation-expire-false.cp-creation-expire-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-expire',
|
||||
value: 0,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-expire-false' }, Messages.creation_expireFalse),
|
||||
h('input#cp-creation-expire-true.cp-creation-expire-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-expire',
|
||||
value: 1
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-expire-true' }, [
|
||||
Messages.creation_expireTrue,
|
||||
h('span.cp-creation-expire-picker', [
|
||||
h('input#cp-creation-expire-val', {
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
value: 3
|
||||
}),
|
||||
h('select#cp-creation-expire-unit', [
|
||||
h('option', { value: 'hour' }, Messages.creation_expireHours),
|
||||
h('option', { value: 'day' }, Messages.creation_expireDays),
|
||||
h('option', {
|
||||
value: 'month',
|
||||
selected: 'selected'
|
||||
}, Messages.creation_expireMonths)
|
||||
])
|
||||
])
|
||||
]),
|
||||
h('span.fa.fa-check', {title: Messages.saved}),
|
||||
h('span.fa.fa-spinner.fa-pulse'),
|
||||
]);
|
||||
|
||||
var $expire = $(expire);
|
||||
|
||||
var $ok = $expire.find('.fa-check').hide();
|
||||
var $spinner = $expire.find('.fa-spinner').hide();
|
||||
|
||||
var getValue = function () {
|
||||
if(!parseInt($expire.find('[name="cp-creation-expire"]:checked').val())) { return 0; }
|
||||
var unit = 0;
|
||||
switch ($expire.find('#cp-creation-expire-unit').val()) {
|
||||
case "hour" : unit = 3600; break;
|
||||
case "day" : unit = 3600 * 24; break;
|
||||
case "month": unit = 3600 * 24 * 30; break;
|
||||
default: unit = 0;
|
||||
}
|
||||
return ($expire.find('#cp-creation-expire-val').val() || 0) * unit;
|
||||
};
|
||||
$expire.find('input, select').change(function () {
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
common.setAttribute(['general', 'creation', 'expire'], getValue(), function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
});
|
||||
});
|
||||
common.getAttribute(['general', 'creation', 'expire'], function (e, val) {
|
||||
UIElements.setExpirationValue(val, $expire);
|
||||
});
|
||||
|
||||
return $expire;
|
||||
};
|
||||
create['creation-skip'] = function () {
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
var skip = h('div.cp-settings-creation-skip.cp-sidebarlayout-element', [
|
||||
h('label', [
|
||||
Messages.settings_creationSkip
|
||||
]),
|
||||
setHTML(h('p.cp-sidebarlayout-description'), Messages.settings_creationSkipHint),
|
||||
h('input#cp-creation-skip-true.cp-creation-skip-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-skip',
|
||||
value: 1,
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-skip-true' }, Messages.settings_creationSkipTrue),
|
||||
h('input#cp-creation-skip-false.cp-creation-skip-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-skip',
|
||||
value: 0,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-skip-false' }, Messages.settings_creationSkipFalse),
|
||||
h('span.fa.fa-check', {title: Messages.saved}),
|
||||
h('span.fa.fa-spinner.fa-pulse'),
|
||||
]);
|
||||
|
||||
var $div = $(skip);
|
||||
|
||||
var $ok = $div.find('.fa-check').hide();
|
||||
var $spinner = $div.find('.fa-spinner').hide();
|
||||
|
||||
$div.find('input').change(function () {
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
var val = parseInt($div.find('[name="cp-creation-skip"]:checked').val());
|
||||
// If we don't skip the pad creation screen, we dont' need settings to hide the templates
|
||||
// modal
|
||||
if (!val) {
|
||||
$('.cp-settings-creation-template').addClass('cp-settings-creation-skipped');
|
||||
} else {
|
||||
$('.cp-settings-creation-template').removeClass('cp-settings-creation-skipped');
|
||||
}
|
||||
common.setAttribute(['general', 'creation', 'skip'], val, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
});
|
||||
});
|
||||
common.getAttribute(['general', 'creation', 'skip'], function (e, val) {
|
||||
if (val) {
|
||||
$div.find('#cp-creation-skip-true').attr('checked', true);
|
||||
return;
|
||||
}
|
||||
// If we don't skip the pad creation screen, we dont' need settings to hide the templates
|
||||
// modal
|
||||
$('.cp-settings-creation-template').addClass('cp-settings-creation-skipped');
|
||||
});
|
||||
|
||||
return $div;
|
||||
};
|
||||
create['creation-template'] = function () {
|
||||
var skip = h('div.cp-settings-creation-template.cp-sidebarlayout-element', [
|
||||
h('label', [
|
||||
Messages.settings_templateSkip
|
||||
]),
|
||||
setHTML(h('p.cp-sidebarlayout-description'), Messages.settings_templateSkipHint),
|
||||
h('input#cp-creation-template-true.cp-creation-template-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-template',
|
||||
value: 1,
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-template-true' }, Messages.settings_creationSkipTrue),
|
||||
h('input#cp-creation-template-false.cp-creation-template-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-template',
|
||||
value: 0,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-template-false' }, Messages.settings_creationSkipFalse),
|
||||
h('span.fa.fa-check', {title: Messages.saved}),
|
||||
h('span.fa.fa-spinner.fa-pulse'),
|
||||
]);
|
||||
|
||||
var $div = $(skip);
|
||||
|
||||
var $ok = $div.find('.fa-check').hide();
|
||||
var $spinner = $div.find('.fa-spinner').hide();
|
||||
|
||||
$div.find('input').change(function () {
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
var val = parseInt($div.find('[name="cp-creation-template"]:checked').val());
|
||||
common.setAttribute(['general', 'creation', 'noTemplate'], val, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
});
|
||||
});
|
||||
common.getAttribute(['general', 'creation', 'noTemplate'], function (e, val) {
|
||||
if (val) {
|
||||
$div.find('#cp-creation-template-true').attr('checked', true);
|
||||
}
|
||||
});
|
||||
|
||||
return $div;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// Drive settings
|
||||
|
||||
create['drive-duplicate'] = function () {
|
||||
@ -1720,7 +1475,6 @@ define([
|
||||
if (key === 'cursor') { $category.append($('<span>', {'class': 'fa fa-i-cursor' })); }
|
||||
if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); }
|
||||
if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); }
|
||||
if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); }
|
||||
if (key === 'security') { $category.append($('<span>', {'class': 'fa fa-lock' })); }
|
||||
if (key === 'subscription') { $category.append($('<span>', {'class': 'fa fa-star-o' })); }
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ define([
|
||||
'/common/sframe-common.js',
|
||||
'/common/proxy-manager.js',
|
||||
'/common/userObject.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/messenger-ui.js',
|
||||
@ -35,6 +36,7 @@ define([
|
||||
SFCommon,
|
||||
ProxyManager,
|
||||
UserObject,
|
||||
MT,
|
||||
h,
|
||||
AppConfig,
|
||||
MessengerUI,
|
||||
@ -295,22 +297,28 @@ define([
|
||||
if (!proxy.drive || typeof(proxy.drive) !== 'object') {
|
||||
throw new Error("Corrupted drive");
|
||||
}
|
||||
if (APP.usageBar) { APP.usageBar.stop(); }
|
||||
APP.usageBar = common.createUsageBar(APP.team, function (err, $limitContainer) {
|
||||
if (err) { return void DriveUI.logError(err); }
|
||||
driveAPP.$limit = $limitContainer;
|
||||
$limitContainer.attr('title', Messages.team_quota);
|
||||
}, true);
|
||||
driveAPP.team = id;
|
||||
|
||||
// Provide secondaryKey
|
||||
var teamData = (privateData.teams || {})[id] || {};
|
||||
driveAPP.readOnly = !teamData.hasSecondaryKey;
|
||||
|
||||
if (APP.usageBar) { APP.usageBar.stop(); }
|
||||
APP.usageBar = undefined;
|
||||
if (!driveAPP.readOnly) {
|
||||
APP.usageBar = common.createUsageBar(APP.team, function (err, $limitContainer) {
|
||||
if (err) { return void DriveUI.logError(err); }
|
||||
$limitContainer.attr('title', Messages.team_quota);
|
||||
}, true);
|
||||
}
|
||||
|
||||
var drive = DriveUI.create(common, {
|
||||
proxy: proxy,
|
||||
folders: folders,
|
||||
updateObject: updateObject,
|
||||
updateSharedFolders: updateSharedFolders,
|
||||
|
||||
$limit: APP.usageBar && APP.usageBar.$container,
|
||||
APP: driveAPP,
|
||||
edPublic: APP.teamEdPublic,
|
||||
editKey: teamData.secondaryKey
|
||||
@ -527,9 +535,11 @@ define([
|
||||
};
|
||||
|
||||
var makePermissions = function () {
|
||||
var $blockContainer = UIElements.createModal({
|
||||
var modal= UI.createModal({
|
||||
id: 'cp-teams-roster-dialog',
|
||||
}).show();
|
||||
});
|
||||
modal.show();
|
||||
var $blockContainer = modal.$modal;
|
||||
|
||||
var makeRow = function (arr, first) {
|
||||
return arr.map(function (val) {
|
||||
@ -960,7 +970,7 @@ define([
|
||||
// Upload
|
||||
var avatar = h('div.cp-team-avatar.cp-avatar');
|
||||
var $avatar = $(avatar);
|
||||
var data = UIElements.addAvatar(common, function (ev, data) {
|
||||
var data = MT.addAvatar(common, function (ev, data) {
|
||||
if (!data.url) { return void UI.warn(Messages.error); }
|
||||
APP.module.execCommand('GET_TEAM_METADATA', {
|
||||
teamId: APP.team
|
||||
@ -1045,7 +1055,7 @@ define([
|
||||
|
||||
var displayUser = function (common, data) {
|
||||
var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar');
|
||||
UIElements.displayAvatar(common, $(avatar), data.avatar, data.displayName);
|
||||
common.displayAvatar($(avatar), data.avatar, data.displayName);
|
||||
return h('div.cp-teams-invite-from-author', [
|
||||
avatar,
|
||||
h('span.cp-teams-invite-from-name', data.displayName)
|
||||
@ -1281,6 +1291,7 @@ define([
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var user = metadataMgr.getUserData();
|
||||
|
||||
readOnly = driveAPP.readOnly = metadataMgr.getPrivateData().readOnly;
|
||||
|
||||
@ -1305,11 +1316,12 @@ define([
|
||||
var toolbar = Toolbar.create(configTb);
|
||||
toolbar.$rightside.hide(); // hide the bottom part of the toolbar
|
||||
// Update the name in the user menu
|
||||
driveAPP.$displayName = $bar.find('.' + Toolbar.constants.username);
|
||||
var $displayName = $bar.find('.' + Toolbar.constants.username);
|
||||
metadataMgr.onChange(function () {
|
||||
var name = metadataMgr.getUserData().name || Messages.anonymous;
|
||||
driveAPP.$displayName.text(name);
|
||||
$displayName.text(name);
|
||||
});
|
||||
$displayName.text(user.name || Messages.anonymous);
|
||||
|
||||
// Load the Team module
|
||||
var onEvent = function (obj) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@import (reference) '../../customize/src/less2/include/tools.less';
|
||||
@import (reference) "../../customize/src/less2/include/framework.less";
|
||||
@import (reference) "../../customize/src/less2/include/buttons.less";
|
||||
|
||||
&.cp-app-whiteboard {
|
||||
|
||||
@ -36,6 +37,7 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
// created in the html
|
||||
@ -74,6 +76,8 @@
|
||||
|
||||
padding: 10px;
|
||||
|
||||
.buttons_main();
|
||||
|
||||
& > * + * {
|
||||
margin: 0;
|
||||
margin-left: 1em;
|
||||
@ -82,10 +86,27 @@
|
||||
#cp-app-whiteboard-width, #cp-app-whiteboard-opacity {
|
||||
.middle;
|
||||
}
|
||||
#cp-app-whiteboard-clear, #cp-app-whiteboard-delete, #cp-app-whiteboard-toggledraw {
|
||||
|
||||
#cp-app-whiteboard-clear {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#cp-app-whiteboard-delete {
|
||||
min-width: 40px;
|
||||
}
|
||||
.cp-whiteboard-type {
|
||||
button {
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
&:not(:first-child) {
|
||||
margin-left: -1px;
|
||||
}
|
||||
&.btn-primary:hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -96,27 +117,34 @@
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-range-group {
|
||||
.cp-whiteboard-brush {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
input[type="range"] {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
& > span {
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.cp-app-whiteboard-range-group:first-of-type {
|
||||
margin-left: 2em;
|
||||
}
|
||||
.cp-app-whiteboard-range-group:last-of-type {
|
||||
margin-right: 1em;
|
||||
.cp-app-whiteboard-range-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
margin-right: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
background-color: inherit;
|
||||
margin-right: 5px;
|
||||
width: 150px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& > span {
|
||||
width: 50px;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,22 +154,22 @@
|
||||
z-index: 100;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 10px;
|
||||
flex-wrap: wrap;
|
||||
max-width: 320px;
|
||||
|
||||
span.cp-app-whiteboard-palette-color {
|
||||
height: 4vw;
|
||||
width: 4vw;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: block;
|
||||
margin: 5px;
|
||||
border: 1px solid black;
|
||||
border: 1px solid #bbb;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,9 @@ define([
|
||||
var $widthLabel = $('label[for="cp-app-whiteboard-width"]');
|
||||
var $opacity = $('#cp-app-whiteboard-opacity');
|
||||
var $opacityLabel = $('label[for="cp-app-whiteboard-opacity"]');
|
||||
var $toggle = $('#cp-app-whiteboard-toggledraw');
|
||||
var $type = $('.cp-whiteboard-type');
|
||||
var $brush = $('.cp-whiteboard-type .brush');
|
||||
var $move = $('.cp-whiteboard-type .move');
|
||||
var $deleteButton = $('#cp-app-whiteboard-delete');
|
||||
|
||||
var metadataMgr = framework._.cpNfInner.metadataMgr;
|
||||
@ -97,7 +99,7 @@ define([
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Messages._getKey("canvas_widthLabel", [val]));
|
||||
$widthLabel.text(Messages._getKey("canvas_widthLabel", ['']));
|
||||
$('#cp-app-whiteboard-width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
@ -108,7 +110,7 @@ define([
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$opacityLabel.text(Messages._getKey("canvas_opacityLabel", ['']));
|
||||
$('#cp-app-whiteboard-opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
@ -116,17 +118,27 @@ define([
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
APP.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
$brush.click(function () {
|
||||
if (APP.draw) { return; }
|
||||
canvas.deactivateAll().renderAll();
|
||||
APP.draw = !APP.draw;
|
||||
APP.draw = true;
|
||||
canvas.isDrawingMode = APP.draw;
|
||||
$toggle.text(APP.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (APP.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
$type.find('button').removeClass('btn-primary');
|
||||
$brush.addClass('btn-primary');
|
||||
$deleteButton.prop('disabled', 'disabled');
|
||||
});
|
||||
$move.click(function () {
|
||||
if (!APP.draw) { return; }
|
||||
canvas.deactivateAll().renderAll();
|
||||
APP.draw = false;
|
||||
canvas.isDrawingMode = APP.draw;
|
||||
$type.find('button').removeClass('btn-primary');
|
||||
$move.addClass('btn-primary');
|
||||
$deleteButton.prop('disabled', '');
|
||||
});
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (APP.draw) { return; }
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
@ -211,6 +223,7 @@ define([
|
||||
if (first || Sortify(palette) !== Sortify(newPalette)) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
$colors.css('width', (palette.length * 20)+'px');
|
||||
palette.forEach(addColorToPalette);
|
||||
first = false;
|
||||
}
|
||||
@ -350,6 +363,11 @@ define([
|
||||
});
|
||||
};
|
||||
var addImageToCanvas = function (img) {
|
||||
// 1 MB maximum
|
||||
if (img.src && img.src.length > 1 * 1024 * 1024) {
|
||||
UI.warn(Messages.upload_tooLargeBrief);
|
||||
return;
|
||||
}
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
if (w<h) {
|
||||
@ -368,6 +386,11 @@ define([
|
||||
var onUpload = function (e) {
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
// 1 MB maximum
|
||||
if (file.size > 1 * 1024 * 1024) {
|
||||
UI.warn(Messages.upload_tooLargeBrief);
|
||||
return;
|
||||
}
|
||||
reader.onload = function () {
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
@ -386,32 +409,18 @@ define([
|
||||
}).appendTo($rightside);
|
||||
|
||||
if (framework._.sfCommon.isLoggedIn()) {
|
||||
var fileDialogCfg = {
|
||||
onSelect: function (data) {
|
||||
if (data.type === 'file') {
|
||||
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
|
||||
framework._.sfCommon.displayMediatagImage($(mt), function (err, $image) {
|
||||
Util.blobURLToImage($image.attr('src'), function (imgSrc) {
|
||||
var img = new Image();
|
||||
img.onload = function () { addImageToCanvas(img); };
|
||||
img.src = imgSrc;
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
framework._.sfCommon.initFilePicker(fileDialogCfg);
|
||||
framework._.sfCommon.createButton('mediatag', true).click(function () {
|
||||
var pickerCfg = {
|
||||
types: ['file'],
|
||||
where: ['root'],
|
||||
filter: {
|
||||
fileType: ['image/']
|
||||
}
|
||||
};
|
||||
framework._.sfCommon.openFilePicker(pickerCfg);
|
||||
}).appendTo($rightside);
|
||||
framework.setMediaTagEmbedder(function ($mt) {
|
||||
framework._.sfCommon.displayMediatagImage($mt, function (err, $image) {
|
||||
// Convert src from blob URL to base64 data URL
|
||||
Util.blobURLToImage($image.attr('src'), function (imgSrc) {
|
||||
var img = new Image();
|
||||
img.onload = function () { addImageToCanvas(img); };
|
||||
img.src = imgSrc;
|
||||
});
|
||||
});
|
||||
}, {
|
||||
fileType: ['image/']
|
||||
});
|
||||
|
||||
// Export to drive as PNG
|
||||
framework._.sfCommon.createButton('savetodrive', true, {}).click(function () {
|
||||
@ -494,6 +503,7 @@ define([
|
||||
framework.start();
|
||||
};
|
||||
|
||||
|
||||
var initialContent = function () {
|
||||
return [
|
||||
h('div#cp-toolbar.cp-toolbar-container'),
|
||||
@ -509,42 +519,56 @@ define([
|
||||
}
|
||||
}, [
|
||||
h('button#cp-app-whiteboard-clear.btn.btn-danger', Messages.canvas_clear), ' ',
|
||||
h('div.cp-whiteboard-type', [
|
||||
h('button.brush.fa.fa-paint-brush.btn-primary', {title: Messages.canvas_brush}),
|
||||
h('button.move.fa.fa-arrows', {title: Messages.canvas_select}),
|
||||
]),
|
||||
h('button.fa.fa-trash#cp-app-whiteboard-delete', {
|
||||
disabled: 'disabled',
|
||||
title: Messages.canvas_delete
|
||||
}),
|
||||
/*
|
||||
h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Messages.canvas_disable),
|
||||
h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Messages.canvas_disable),
|
||||
h('button#cp-app-whiteboard-delete.btn.btn-secondary', {
|
||||
style: {
|
||||
display: 'none',
|
||||
}
|
||||
}, Messages.canvas_delete),
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'cp-app-whiteboard-width'
|
||||
}, Messages.canvas_width),
|
||||
h('input#cp-app-whiteboard-width', {
|
||||
type: 'range',
|
||||
min: "1",
|
||||
max: "100"
|
||||
}),
|
||||
h('span#cp-app-whiteboard-width-val', '5px')
|
||||
}, Messages.canvas_delete),*/
|
||||
h('div.cp-whiteboard-brush', [
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'cp-app-whiteboard-width'
|
||||
}, Messages.canvas_width),
|
||||
h('input#cp-app-whiteboard-width', {
|
||||
type: 'range',
|
||||
value: "20",
|
||||
min: "1",
|
||||
max: "100"
|
||||
}),
|
||||
h('span#cp-app-whiteboard-width-val', '5px')
|
||||
]),
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'cp-app-whiteboard-opacity',
|
||||
}, Messages.canvas_opacity),
|
||||
h('input#cp-app-whiteboard-opacity', {
|
||||
type: 'range',
|
||||
value: "1",
|
||||
min: "0.1",
|
||||
max: "1",
|
||||
step: "0.1"
|
||||
}),
|
||||
h('span#cp-app-whiteboard-opacity-val', '100%')
|
||||
]),
|
||||
]),
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'cp-app-whiteboard-opacity',
|
||||
}, Messages.canvas_opacity),
|
||||
h('input#cp-app-whiteboard-opacity', {
|
||||
type: 'range',
|
||||
min: "0.1",
|
||||
max: "1",
|
||||
step: "0.1"
|
||||
}),
|
||||
h('span#cp-app-whiteboard-opacity-val', '100%')
|
||||
]),
|
||||
h('span.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [
|
||||
h('div.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [
|
||||
h('img', {
|
||||
title: Messages.canvas_currentBrush
|
||||
})
|
||||
])
|
||||
]),
|
||||
UI.setHTML(h('div#cp-app-whiteboard-colors'), ' '),
|
||||
]),
|
||||
UI.setHTML(h('div#cp-app-whiteboard-colors'), ' '),
|
||||
h('div#cp-app-whiteboard-cursors', {
|
||||
style: {
|
||||
display: 'none',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user