Merge branch 'soon' into staging

pull/1/head
David Benqué 4 years ago
commit 5e7eb4cbf8

@ -1,6 +1,6 @@
/*@flow*/ /*@flow*/
/* jshint esversion: 6 */ /* jshint esversion: 6 */
/* global Buffer */ /* globals Buffer */
var Fs = require("fs"); var Fs = require("fs");
var Fse = require("fs-extra"); var Fse = require("fs-extra");
var Path = require("path"); var Path = require("path");
@ -66,6 +66,10 @@ var mkTempPath = function (env, channelId) {
return mkPath(env, channelId) + '.temp'; return mkPath(env, channelId) + '.temp';
}; };
var mkOffsetPath = function (env, channelId) {
return mkPath(env, channelId) + '.offset';
};
// pass in the path so we can reuse the same function for archived files // pass in the path so we can reuse the same function for archived files
var channelExists = function (filepath, cb) { var channelExists = function (filepath, cb) {
Fs.stat(filepath, function (err, stat) { Fs.stat(filepath, function (err, stat) {
@ -131,7 +135,9 @@ const readMessagesBin = (env, id, start, msgHandler, cb) => {
const collector = createIdleStreamCollector(stream); const collector = createIdleStreamCollector(stream);
const handleMessageAndKeepStreamAlive = Util.both(msgHandler, collector.keepAlive); const handleMessageAndKeepStreamAlive = Util.both(msgHandler, collector.keepAlive);
const done = Util.both(cb, collector); const done = Util.both(cb, collector);
return void readFileBin(stream, handleMessageAndKeepStreamAlive, done); return void readFileBin(stream, handleMessageAndKeepStreamAlive, done, {
offset: start,
});
}; };
// reads classic metadata from a channel log and aborts // reads classic metadata from a channel log and aborts
@ -190,6 +196,37 @@ var closeChannel = function (env, channelName, cb) {
} }
}; };
var clearOffset = function (env, channelId, cb) {
var path = mkOffsetPath(env, channelId);
// we should always be able to recover from invalid offsets, so failure to delete them
// is not catastrophic. Anything calling this function can optionally ignore errors it might report
Fs.unlink(path, cb);
};
var writeOffset = function (env, channelId, data, cb) {
var path = mkOffsetPath(env, channelId);
var s_data;
try {
s_data = JSON.stringify(data);
} catch (err) {
return void cb(err);
}
Fs.writeFile(path, s_data, cb);
};
var getOffset = function (env, channelId, cb) {
var path = mkOffsetPath(env, channelId);
Fs.readFile(path, function (err, content) {
if (err) { return void cb(err); }
try {
var json = JSON.parse(content);
cb(void 0, json);
} catch (err2) {
cb(err2);
}
});
};
// truncates a file to the end of its metadata line // truncates a file to the end of its metadata line
// TODO write the metadata in a dedicated file // TODO write the metadata in a dedicated file
var clearChannel = function (env, channelId, _cb) { var clearChannel = function (env, channelId, _cb) {
@ -213,6 +250,7 @@ var clearChannel = function (env, channelId, _cb) {
cb(); cb();
}); });
}); });
clearOffset(env, channelId, function () {});
}); });
}; };
@ -389,6 +427,7 @@ var removeChannel = function (env, channelName, cb) {
CB(labelError("E_METADATA_REMOVAL", err)); CB(labelError("E_METADATA_REMOVAL", err));
} }
})); }));
clearOffset(env, channelName, w());
}).nThen(function () { }).nThen(function () {
if (errors === 2) { if (errors === 2) {
return void CB(labelError('E_REMOVE_CHANNEL', new Error("ENOENT"))); return void CB(labelError('E_REMOVE_CHANNEL', new Error("ENOENT")));
@ -604,6 +643,8 @@ var archiveChannel = function (env, channelName, cb) {
return void cb(err); return void cb(err);
} }
})); }));
}).nThen(function (w) {
clearOffset(env, channelName, w());
}).nThen(function (w) { }).nThen(function (w) {
// archive the dedicated metadata channel // archive the dedicated metadata channel
var metadataPath = mkMetadataPath(env, channelName); var metadataPath = mkMetadataPath(env, channelName);
@ -861,6 +902,7 @@ var trimChannel = function (env, channelName, hash, _cb) {
} }
})); }));
}).nThen(function (w) { }).nThen(function (w) {
clearOffset(env, channelName, w());
cleanUp(w(function (err) { cleanUp(w(function (err) {
if (err) { if (err) {
w.abort(); w.abort();
@ -1177,6 +1219,25 @@ module.exports.create = function (conf, _cb) {
}); });
}, },
// OFFSETS
// these exist strictly as an optimization
// you can always remove them without data loss
clearOffset: function (channelName, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
clearOffset(env, channelName, cb);
},
writeOffset: function (channelName, data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
writeOffset(env, channelName, data, cb);
},
getOffset: function (channelName, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
getOffset(env, channelName, cb);
},
// METADATA METHODS // METADATA METHODS
// fetch the metadata for a channel // fetch the metadata for a channel
getChannelMetadata: function (channelName, cb) { getChannelMetadata: function (channelName, cb) {

@ -44,8 +44,8 @@ const mkBufferSplit = () => {
// return a streaming function which transforms buffers into objects // return a streaming function which transforms buffers into objects
// containing the buffer and the offset from the start of the stream // containing the buffer and the offset from the start of the stream
const mkOffsetCounter = () => { const mkOffsetCounter = (offset) => {
let offset = 0; offset = offset || 0;
return Pull.map((buff) => { return Pull.map((buff) => {
const out = { offset: offset, buff: buff }; const out = { offset: offset, buff: buff };
// +1 for the eaten newline // +1 for the eaten newline
@ -59,13 +59,14 @@ const mkOffsetCounter = () => {
// that this function has a lower memory profile than our classic method // that this function has a lower memory profile than our classic method
// of reading logs line by line. // of reading logs line by line.
// it also allows the handler to abort reading at any time // it also allows the handler to abort reading at any time
Stream.readFileBin = (stream, msgHandler, cb) => { Stream.readFileBin = (stream, msgHandler, cb, opt) => {
opt = opt || {};
//const stream = Fs.createReadStream(path, { start: start }); //const stream = Fs.createReadStream(path, { start: start });
let keepReading = true; let keepReading = true;
Pull( Pull(
ToPull.read(stream), ToPull.read(stream),
mkBufferSplit(), mkBufferSplit(),
mkOffsetCounter(), mkOffsetCounter(opt.offset),
Pull.asyncMap((data, moreCb) => { Pull.asyncMap((data, moreCb) => {
msgHandler(data, moreCb, () => { msgHandler(data, moreCb, () => {
try { try {

@ -1,5 +1,5 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
/* global process */ /* globals process, Buffer */
const HK = require("../hk-util"); const HK = require("../hk-util");
const Store = require("../storage/file"); const Store = require("../storage/file");
@ -114,14 +114,15 @@ const init = function (config, _cb) {
* including the initial metadata line, if it exists * including the initial metadata line, if it exists
*/ */
const computeIndex = function (data, cb) {
if (!data || !data.channel) {
return void cb('E_NO_CHANNEL');
}
const channelName = data.channel; const OPEN_CURLY_BRACE = Buffer.from('{');
const CHECKPOINT_PREFIX = Buffer.from('cp|');
const isValidOffsetNumber = function (n) {
return typeof(n) === 'number' && n >= 0;
};
const cpIndex = []; const computeIndexFromOffset = function (channelName, offset, cb) {
let cpIndex = [];
let messageBuf = []; let messageBuf = [];
let i = 0; let i = 0;
@ -129,27 +130,42 @@ const computeIndex = function (data, cb) {
const offsetByHash = {}; const offsetByHash = {};
let offsetCount = 0; let offsetCount = 0;
let size = 0; let size = offset || 0;
var start = offset || 0;
let unconventional = false;
nThen(function (w) { nThen(function (w) {
// iterate over all messages in the channel log // iterate over all messages in the channel log
// old channels can contain metadata as the first message of the log // old channels can contain metadata as the first message of the log
// skip over metadata as that is handled elsewhere // skip over metadata as that is handled elsewhere
// otherwise index important messages in the log // otherwise index important messages in the log
store.readMessagesBin(channelName, 0, (msgObj, readMore) => { store.readMessagesBin(channelName, start, (msgObj, readMore, abort) => {
let msg; let msg;
// keep an eye out for the metadata line if you haven't already seen it // keep an eye out for the metadata line if you haven't already seen it
// but only check for metadata on the first line // but only check for metadata on the first line
if (!i && msgObj.buff.indexOf('{') === 0) { if (i) {
i++; // always increment the message counter // fall through intentionally because the following blocks are invalid
// for all but the first message
} else if (msgObj.buff.includes(OPEN_CURLY_BRACE)) {
msg = HK.tryParse(Env, msgObj.buff.toString('utf8')); msg = HK.tryParse(Env, msgObj.buff.toString('utf8'));
if (typeof msg === "undefined") { return readMore(); } if (typeof msg === "undefined") {
i++; // always increment the message counter
return readMore();
}
// validate that the current line really is metadata before storing it as such // validate that the current line really is metadata before storing it as such
// skip this, as you already have metadata... // skip this, as you already have metadata...
if (HK.isMetadataMessage(msg)) { return readMore(); } if (HK.isMetadataMessage(msg)) {
i++; // always increment the message counter
return readMore();
}
} else if (!(msg = HK.tryParse(Env, msgObj.buff.toString('utf8')))) {
w.abort();
abort();
return CB("OFFSET_ERROR");
} }
i++; i++;
if (msgObj.buff.indexOf('cp|') > -1) { if (msgObj.buff.includes(CHECKPOINT_PREFIX)) {
msg = msg || HK.tryParse(Env, msgObj.buff.toString('utf8')); msg = msg || HK.tryParse(Env, msgObj.buff.toString('utf8'));
if (typeof msg === "undefined") { return readMore(); } if (typeof msg === "undefined") { return readMore(); }
// cache the offsets of checkpoints if they can be parsed // cache the offsets of checkpoints if they can be parsed
@ -164,6 +180,7 @@ const computeIndex = function (data, cb) {
} }
} else if (messageBuf.length > 100 && cpIndex.length === 0) { } else if (messageBuf.length > 100 && cpIndex.length === 0) {
// take the last 50 messages // take the last 50 messages
unconventional = true;
messageBuf = messageBuf.slice(-50); messageBuf = messageBuf.slice(-50);
} }
// if it's not metadata or a checkpoint then it should be a regular message // if it's not metadata or a checkpoint then it should be a regular message
@ -192,11 +209,38 @@ const computeIndex = function (data, cb) {
size = msgObj.offset + msgObj.buff.length + 1; size = msgObj.offset + msgObj.buff.length + 1;
}); });
})); }));
}).nThen(function (w) {
cpIndex = HK.sliceCpIndex(cpIndex, i);
var new_start;
if (cpIndex.length) {
new_start = cpIndex[0].offset;
} else if (unconventional && messageBuf.length && isValidOffsetNumber(messageBuf[0].offset)) {
new_start = messageBuf[0].offset;
}
if (new_start === start) { return; }
if (!isValidOffsetNumber(new_start)) { return; }
// store the offset of the earliest relevant line so that you can start from there next time...
store.writeOffset(channelName, {
start: new_start,
created: +new Date(),
}, w(function () {
var diff = new_start - start;
Env.Log.info('WORKER_OFFSET_UPDATE', {
channel: channelName,
old_start: start,
new_start: new_start,
diff: diff,
diffMB: diff / 1024 / 1024,
});
}));
}).nThen(function () { }).nThen(function () {
// return the computed index // return the computed index
CB(null, { CB(null, {
// Only keep the checkpoints included in the last 100 messages // Only keep the checkpoints included in the last 100 messages
cpIndex: HK.sliceCpIndex(cpIndex, i), cpIndex: cpIndex,
offsetByHash: offsetByHash, offsetByHash: offsetByHash,
offsets: offsetCount, offsets: offsetCount,
size: size, size: size,
@ -206,6 +250,47 @@ const computeIndex = function (data, cb) {
}); });
}; };
const computeIndex = function (data, cb) {
if (!data || !data.channel) {
return void cb('E_NO_CHANNEL');
}
const channelName = data.channel;
const CB = Util.once(cb);
var start = 0;
nThen(function (w) {
store.getOffset(channelName, w(function (err, obj) {
if (err) { return; }
if (obj && typeof(obj.start) === 'number' && obj.start > 0) {
start = obj.start;
Env.Log.verbose('WORKER_OFFSET_RECOVERY', {
channel: channelName,
start: start,
startMB: start / 1024 / 1024,
});
}
}));
}).nThen(function (w) {
computeIndexFromOffset(channelName, start, w(function (err, index) {
if (err === 'OFFSET_ERROR') {
return Env.Log.error("WORKER_OFFSET_ERROR", {
channel: channelName,
});
}
w.abort();
CB(err, index);
}));
}).nThen(function (w) {
// if you're here there was an OFFSET_ERROR..
// first remove the offset that caused the problem to begin with
store.clearOffset(channelName, w());
}).nThen(function () {
// now get the history as though it were the first time
computeIndexFromOffset(channelName, 0, CB);
});
};
const computeMetadata = function (data, cb) { const computeMetadata = function (data, cb) {
const ref = {}; const ref = {};
const lineHandler = Meta.createLineHandler(ref, Env.Log.error); const lineHandler = Meta.createLineHandler(ref, Env.Log.error);

@ -1598,9 +1598,9 @@ define([
content: h('span', Messages.profileButton), content: h('span', Messages.profileButton),
action: function () { action: function () {
if (padType) { if (padType) {
window.open(origin+'/profile/'); Common.openURL(origin+'/profile/');
} else { } else {
window.parent.location = origin+'/profile/'; Common.gotoURL(origin+'/profile/');
} }
}, },
}); });
@ -1609,33 +1609,36 @@ define([
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'href': origin+'/drive/',
'class': 'fa fa-hdd-o' 'class': 'fa fa-hdd-o'
}, },
content: h('span', Messages.type.drive) content: h('span', Messages.type.drive),
action: function () {
Common.openURL(origin+'/drive/');
},
}); });
} }
if (padType !== 'teams' && accountName) { if (padType !== 'teams' && accountName) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'href': origin+'/teams/',
'class': 'fa fa-users' 'class': 'fa fa-users'
}, },
content: h('span', Messages.type.teams) content: h('span', Messages.type.teams),
action: function () {
Common.openURL('/teams/');
},
}); });
} }
if (padType !== 'contacts' && accountName) { if (padType !== 'contacts' && accountName) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'href': origin+'/contacts/',
'class': 'fa fa-address-book' 'class': 'fa fa-address-book'
}, },
content: h('span', Messages.type.contacts) content: h('span', Messages.type.contacts),
action: function () {
Common.openURL('/contacts/');
},
}); });
} }
if (padType !== 'settings') { if (padType !== 'settings') {
@ -1645,9 +1648,9 @@ define([
content: h('span', Messages.settingsButton), content: h('span', Messages.settingsButton),
action: function () { action: function () {
if (padType) { if (padType) {
window.open(origin+'/settings/'); Common.openURL(origin+'/settings/');
} else { } else {
window.parent.location = origin+'/settings/'; Common.gotoURL(origin+'/settings/');
} }
}, },
}); });
@ -1662,9 +1665,9 @@ define([
content: h('span', Messages.adminPage || 'Admin'), content: h('span', Messages.adminPage || 'Admin'),
action: function () { action: function () {
if (padType) { if (padType) {
window.open(origin+'/admin/'); Common.openURL(origin+'/admin/');
} else { } else {
window.parent.location = origin+'/admin/'; Common.gotoURL(origin+'/admin/');
} }
}, },
}); });
@ -1676,9 +1679,9 @@ define([
content: h('span', Messages.supportPage || 'Support'), content: h('span', Messages.supportPage || 'Support'),
action: function () { action: function () {
if (padType) { if (padType) {
window.open(origin+'/support/'); Common.openURL(origin+'/support/');
} else { } else {
window.parent.location = origin+'/support/'; Common.gotoURL(origin+'/support/');
} }
}, },
}); });
@ -1687,13 +1690,11 @@ define([
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'rel': 'noopener',
'href': AppConfig.surveyURL,
'class': 'cp-toolbar-survey fa fa-graduation-cap' 'class': 'cp-toolbar-survey fa fa-graduation-cap'
}, },
content: h('span', Messages.survey), content: h('span', Messages.survey),
action: function () { action: function () {
Common.openUnsafeURL(AppConfig.surveyURL);
Feedback.send('SURVEY_CLICKED'); Feedback.send('SURVEY_CLICKED');
}, },
}); });
@ -1712,11 +1713,12 @@ define([
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'href': origin+'/index.html',
'class': 'fa fa-home' 'class': 'fa fa-home'
}, },
content: h('span', Messages.homePage) content: h('span', Messages.homePage),
action: function () {
Common.openURL('/index.html');
},
}); });
// Add the change display name button if not in read only mode // Add the change display name button if not in read only mode
/* /*
@ -1732,23 +1734,24 @@ define([
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'href': priv.plan ? priv.accounts.upgradeURL : origin+'/features.html',
'class': 'fa fa-star-o' 'class': 'fa fa-star-o'
}, },
content: h('span', priv.plan ? Messages.settings_cat_subscription : Messages.pricing) content: h('span', priv.plan ? Messages.settings_cat_subscription : Messages.pricing),
action: function () {
Common.openURL(priv.plan ? priv.accounts.upgradeURL :'/features.html');
},
}); });
} }
if (!priv.plan && !Config.removeDonateButton) { if (!priv.plan && !Config.removeDonateButton) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank',
'rel': 'noopener',
'href': priv.accounts.donateURL,
'class': 'fa fa-gift' 'class': 'fa fa-gift'
}, },
content: h('span', Messages.crowdfunding_button2) content: h('span', Messages.crowdfunding_button2),
action: function () {
Common.openUnsafeURL(priv.accounts.donateURL);
},
}); });
} }
@ -1763,7 +1766,7 @@ define([
content: h('span', Messages.logoutEverywhere), content: h('span', Messages.logoutEverywhere),
action: function () { action: function () {
Common.getSframeChannel().query('Q_LOGOUT_EVERYWHERE', null, function () { Common.getSframeChannel().query('Q_LOGOUT_EVERYWHERE', null, function () {
window.parent.location = origin + '/'; Common.gotoURL(origin + '/');
}); });
}, },
}); });
@ -1773,7 +1776,7 @@ define([
content: h('span', Messages.logoutButton), content: h('span', Messages.logoutButton),
action: function () { action: function () {
Common.logout(function () { Common.logout(function () {
window.parent.location = origin+'/'; Common.gotoURL(origin+'/');
}); });
}, },
}); });

@ -1365,6 +1365,10 @@ define([
}); });
}; };
APP.openURL = function (url) {
common.openUnsafeURL(url);
};
APP.loadingImage = 0; APP.loadingImage = 0;
APP.getImageURL = function(name, callback) { APP.getImageURL = function(name, callback) {
var mediasSources = getMediasSources(); var mediasSources = getMediasSources();
@ -2176,6 +2180,12 @@ define([
url: newLatest.file url: newLatest.file
}, function () { }); }, function () { });
newDoc = !content.hashes || Object.keys(content.hashes).length === 0; newDoc = !content.hashes || Object.keys(content.hashes).length === 0;
} else if (!privateData.isNewFile) {
// This is an empty doc but not a new file: error
UI.errorLoadingScreen(Messages.unableToDisplay, false, function () {
common.gotoURL('');
});
throw new Error("Empty chainpad for a non-empty doc");
} else { } else {
Title.updateTitle(Title.defaultTitle); Title.updateTitle(Title.defaultTitle);
} }

File diff suppressed because one or more lines are too long

@ -136,13 +136,11 @@ define([
file.uid = Util.uid(); file.uid = Util.uid();
response.expect(file.uid, function (href) { response.expect(file.uid, function (href) {
var mdMgr = common.getMetadataMgr();
var origin = mdMgr.getPrivateData().origin;
$link.prepend($('<span>', {'class': 'fa fa-external-link'})); $link.prepend($('<span>', {'class': 'fa fa-external-link'}));
$link.attr('href', href) $link.attr('href', href)
.click(function (e) { .click(function (e) {
e.preventDefault(); e.preventDefault();
window.open(origin + $link.attr('href'), '_blank'); common.openURL($link.attr('href'));
}); });
var title = metadata.name; var title = metadata.name;
if (!config.noStore) { if (!config.noStore) {

@ -723,7 +723,16 @@ define([
sframeChan.on('EV_OPEN_URL', function (url) { sframeChan.on('EV_OPEN_URL', function (url) {
if (url) { if (url) {
window.open(url); var a = window.open(url);
if (!a) {
sframeChan.event('EV_POPUP_BLOCKED');
}
}
});
sframeChan.on('EV_OPEN_UNSAFE_URL', function (url) {
if (url) {
window.open(ApiConfig.httpSafeOrigin + '/bounce/#' + encodeURIComponent(url));
} }
}); });

@ -612,6 +612,10 @@ define([
funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); }; funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); };
funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', url); }; funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', url); };
funcs.openUnsafeURL = function (url) { funcs.openUnsafeURL = function (url) {
var app = ctx.metadataMgr.getPrivateData().app;
if (app === "sheet") {
return void ctx.sframeChan.event('EV_OPEN_UNSAFE_URL', url);
}
var bounceHref = window.location.origin + '/bounce/#' + encodeURIComponent(url); var bounceHref = window.location.origin + '/bounce/#' + encodeURIComponent(url);
window.open(bounceHref); window.open(bounceHref);
}; };
@ -742,6 +746,10 @@ define([
UI.errorLoadingScreen(Messages.password_error_seed); UI.errorLoadingScreen(Messages.password_error_seed);
}); });
ctx.sframeChan.on("EV_POPUP_BLOCKED", function () {
UI.alert(Messages.errorPopupBlocked);
});
ctx.sframeChan.on("EV_EXPIRED_ERROR", function () { ctx.sframeChan.on("EV_EXPIRED_ERROR", function () {
funcs.onServerError({ funcs.onServerError({
type: 'EEXPIRED' type: 'EEXPIRED'

@ -338,7 +338,7 @@ MessengerUI, Messages) {
if (data.profile) { if (data.profile) {
$span.addClass('cp-userlist-clickable'); $span.addClass('cp-userlist-clickable');
$span.click(function () { $span.click(function () {
window.open(origin+'/profile/#' + data.profile); Common.openURL(origin+'/profile/#' + data.profile);
}); });
} }
Common.displayAvatar($span, data.avatar, name, function () { Common.displayAvatar($span, data.avatar, name, function () {
@ -838,10 +838,10 @@ MessengerUI, Messages) {
var onClick = function (e) { var onClick = function (e) {
e.preventDefault(); e.preventDefault();
if (e.ctrlKey) { if (e.ctrlKey) {
window.open(href); Common.openURL(href);
return; return;
} }
window.parent.location = href; Common.gotoURL(href);
}; };
var onContext = function (e) { e.stopPropagation(); }; var onContext = function (e) { e.stopPropagation(); };

@ -1469,5 +1469,7 @@
"loading_state_0": "Oberfläche vorbereiten", "loading_state_0": "Oberfläche vorbereiten",
"loading_state_5": "Dokument rekonstruieren", "loading_state_5": "Dokument rekonstruieren",
"error_unhelpfulScriptError": "Skriptfehler: Siehe Konsole im Browser für Details", "error_unhelpfulScriptError": "Skriptfehler: Siehe Konsole im Browser für Details",
"documentID": "Kennung des Dokuments" "documentID": "Kennung des Dokuments",
"errorPopupBlocked": "Für die Funktionsweise von CryptPad ist es erforderlich, dass neue Tabs geöffnet werden können. Bitte erlaube Pop-up-Fenster in der Adressleiste deines Browsers. Diese Fenster werden niemals dafür verwendet, dir Werbung anzuzeigen.",
"unableToDisplay": "Das Dokument kann nicht angezeigt werden. Drücke ESC, um die Seite neu zu laden."
} }

@ -41,7 +41,7 @@
"saved": "Enregistré", "saved": "Enregistré",
"synced": "Tout est enregistré", "synced": "Tout est enregistré",
"deleted": "Supprimé", "deleted": "Supprimé",
"deletedFromServer": "Pad supprimé du serveur", "deletedFromServer": "Document supprimé",
"mustLogin": "Vous devez être enregistré pour avoir accès à cette page", "mustLogin": "Vous devez être enregistré pour avoir accès à cette page",
"disabledApp": "Cette application a été désactivée. Pour plus d'information, veuillez contacter l'administrateur de ce CryptPad.", "disabledApp": "Cette application a été désactivée. Pour plus d'information, veuillez contacter l'administrateur de ce CryptPad.",
"realtime_unrecoverableError": "Une erreur critique est survenue. Cliquez sur OK pour recharger la page.", "realtime_unrecoverableError": "Une erreur critique est survenue. Cliquez sur OK pour recharger la page.",
@ -554,7 +554,7 @@
"upload_notEnoughSpace": "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier.", "upload_notEnoughSpace": "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier.",
"upload_notEnoughSpaceBrief": "Pas assez d'espace", "upload_notEnoughSpaceBrief": "Pas assez d'espace",
"upload_tooLarge": "Ce fichier dépasse la taille maximale autorisée pour votre compte.", "upload_tooLarge": "Ce fichier dépasse la taille maximale autorisée pour votre compte.",
"upload_tooLargeBrief": "Fichier trop volumineux", "upload_tooLargeBrief": "Le fichier dépasse la limite de {0} Mo",
"upload_choose": "Choisir un fichier", "upload_choose": "Choisir un fichier",
"upload_pending": "En attente", "upload_pending": "En attente",
"upload_cancelled": "Annulé", "upload_cancelled": "Annulé",
@ -1469,5 +1469,31 @@
"tag_edit": "Modifier", "tag_edit": "Modifier",
"tag_add": "Ajouter", "tag_add": "Ajouter",
"error_unhelpfulScriptError": "Erreur de script : consultez la console du navigateur pour plus de détails", "error_unhelpfulScriptError": "Erreur de script : consultez la console du navigateur pour plus de détails",
"documentID": "Référence du document" "documentID": "Référence du document",
"unableToDisplay": "Impossible d'afficher le document. Veuillez recharger la page avec la touche Échap. Si le problème persiste, veuillez contacter le support.",
"errorPopupBlocked": "CryptPad doit pouvoir ouvrir de nouveaux onglets pour fonctionner. Veuillez autoriser les fenêtres pop-up dans la barre d'adresse de votre navigateur. Ces fenêtres ne seront jamais utilisées pour vous montrer de la publicité.",
"settings_mediatagSizeHint": "Taille maximale en mégaoctets (Mo) pour le chargement automatique des pièces jointes (images, vidéos, pdf) intégrés dans les documents. Les pièces jointes dont la taille est supérieure à la taille spécifiée peuvent être chargés manuellement. Utilisez \"-1\" pour toujours charger automatiquement les pièces jointes.",
"settings_mediatagSizeTitle": "Limite de téléchargement automatique",
"mediatag_notReady": "Merci de compléter le téléchargement",
"pad_mediatagOpen": "Ouvrir ce fichier",
"pad_mediatagShare": "Partager ce fichier",
"mediatag_saveButton": "Sauvegarder",
"Offline": "Déconnecté",
"download_zip_file": "Fichier {0}/{1}",
"download_zip": "Construction du fichier ZIP...",
"fileTableHeader": "Téléchargements et imports",
"allowNotifications": "Autoriser les notifications",
"archivedFromServer": "Document archivé",
"restoredFromServer": "Document restauré",
"admin_archiveInval": "Document invalide",
"admin_archiveInput2": "Mot de passe du document",
"admin_archiveInput": "URL du document",
"admin_unarchiveButton": "Restaurer",
"admin_unarchiveHint": "Restaurer un document qui avait été précédemment archivé",
"admin_unarchiveTitle": "Restaurer les documents",
"admin_archiveButton": "Archiver",
"admin_archiveHint": "Rendre un document indisponible sans le supprimer définitivement. Il sera placé dans un répertoire \"archive\" et supprimé après quelques jours (configurable dans le fichier de configuration du serveur).",
"admin_archiveTitle": "Archiver les documents",
"braveWarning": "Il semble que vous utilisiez actuellement le navigateur Brave. Nous avons reçu des rapports d'utilisateurs ne pouvant pas confirmer le formulaire de paiement en raison de paramètres de confidentialité stricts sur ce navigateur. Si le bouton de paiement ne fonctionne pas, vous pouvez essayer avec un autre navigateur ou nous contacter avec le formulaire de support pour trouver une solution alternative.",
"mediatag_loadButton": "Charger la pièce jointe"
} }

@ -249,7 +249,7 @@
"settings_autostoreMaybe": "手動 (確認しない)", "settings_autostoreMaybe": "手動 (確認しない)",
"settings_autostoreNo": "手動 (常に確認する)", "settings_autostoreNo": "手動 (常に確認する)",
"settings_autostoreHint": "<b>自動</b> あなたがアクセスしたすべてのパッドを、あなたの CryptDrive に保存します。<br><b>手動 (常に確認する)</b> まだ保存していないパッドにアクセスした場合に、あなたの CryptDrive に保存するかどうか尋ねます。<br><b>手動 (確認しない)</b> アクセス先のパッドがあなたの CryptDrive に自動的に保存されなくなります。保存オプションは表示されなくなります。", "settings_autostoreHint": "<b>自動</b> あなたがアクセスしたすべてのパッドを、あなたの CryptDrive に保存します。<br><b>手動 (常に確認する)</b> まだ保存していないパッドにアクセスした場合に、あなたの CryptDrive に保存するかどうか尋ねます。<br><b>手動 (確認しない)</b> アクセス先のパッドがあなたの CryptDrive に自動的に保存されなくなります。保存オプションは表示されなくなります。",
"settings_userFeedback": "ユーザーフィードバックを有効", "settings_userFeedback": "ユーザーフィードバックを有効にする",
"settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。", "settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。",
"settings_userFeedbackHint1": "CryptPad は、あなたの経験を向上させる方法を知るために、サーバーにいくつかの非常に基本的なフィードバックを提供します。 ", "settings_userFeedbackHint1": "CryptPad は、あなたの経験を向上させる方法を知るために、サーバーにいくつかの非常に基本的なフィードバックを提供します。 ",
"settings_userFeedbackTitle": "フィードバック", "settings_userFeedbackTitle": "フィードバック",
@ -356,5 +356,72 @@
"crowdfunding_button": "CryptPad を支援", "crowdfunding_button": "CryptPad を支援",
"crowdfunding_home1": "CryptPad はあなたの支援を必要としています!", "crowdfunding_home1": "CryptPad はあなたの支援を必要としています!",
"policy_choices_vpn": "私たちがホストするインスタンスを使用したいが、IP アドレスを私たちに公開したくない場合は、<a href=\"https://www.torproject.org/projects/torbrowser.html.en\" title=\"downloads from the Tor project\" target=\"_blank\" rel=\"noopener noreferrer\">Tor Browser</a> または <a href=\"https://riseup.net/en/vpn\" title=\"VPNs provided by Riseup\" target=\"_blank\" rel=\"noopener noreferrer\">VPN</a> を使用してあなたの IP アドレスを保護できます。", "policy_choices_vpn": "私たちがホストするインスタンスを使用したいが、IP アドレスを私たちに公開したくない場合は、<a href=\"https://www.torproject.org/projects/torbrowser.html.en\" title=\"downloads from the Tor project\" target=\"_blank\" rel=\"noopener noreferrer\">Tor Browser</a> または <a href=\"https://riseup.net/en/vpn\" title=\"VPNs provided by Riseup\" target=\"_blank\" rel=\"noopener noreferrer\">VPN</a> を使用してあなたの IP アドレスを保護できます。",
"contacts_removeHistoryTitle": "チャット履歴を削除" "contacts_removeHistoryTitle": "チャット履歴を削除",
"properties_passwordSuccessFile": "パスワードは正常に変更されました。",
"drive_sfPasswordError": "誤ったパスワードです",
"team_title": "チーム: {0}",
"password_error": "パッドが存在しません!<br>このエラーは「誤ったパスワードが入力された」場合、または「パッドがサーバーから削除された」場合に発生します。",
"password_error_seed": "パッドが存在しません!<br>このエラーは「パスワードが追加・変更された」場合、または「パッドがサーバーから削除された」場合に発生します。",
"password_submit": "送信",
"password_placeholder": "パスワードを入力...",
"password_info": "開こうとしているパッドが存在しないか、パスワードで保護されています。コンテンツにアクセスするには、正しいパスワードを入力してください。",
"properties_confirmNew": "パスワードを追加すると、このパッドの URL が変更され、履歴が削除されます。パスワードを知らないユーザーは、このパッドへアクセスできなくなります。続行してよろしいですか?",
"properties_changePassword": "パスワードの変更",
"password_show": "表示",
"properties_addPassword": "パスワードの追加",
"history_close": "閉じる",
"history_restore": "復元",
"fm_emptyTrashOwned": "ごみ箱に、あなたが所有しているドキュメントが入っています。あなたのドライブからのみ<b>削除</b>するか、すべてのユーザーから<b>完全削除</b>するかを選択できます。",
"access_destroyPad": "このドキュメントまたはフォルダを完全に削除する",
"accessButton": "アクセス",
"access_allow": "リスト",
"makeACopy": "コピーを作成",
"trimHistory_noHistory": "削除可能な履歴がありません",
"areYouSure": "クリックして実行",
"settings_safeLinksCheckbox": "安全なリンクを有効にする",
"settings_safeLinksTitle": "安全なリンク",
"settings_safeLinksHint": "CryptPad では、あなたのパッドを解読するための鍵がリンクに含まれています。これは、あなたのブラウザの閲覧履歴にアクセスできる人が、潜在的にあなたの CryptPad のデータを解読・閲覧できることを意味します。この「ブラウザの閲覧履歴にアクセスできる人」には、デバイス間で履歴を同期させる侵入的なブラウザ拡張機能やブラウザが含まれます。「安全なリンク」を有効にすると、鍵がブラウザの閲覧履歴に残ったり、アドレスバーに表示されたりするのを可能な限り防ぐことができます。この機能を有効にした上で共有メニューを使用することを強く推奨します。",
"settings_autostoreTitle": "CryptDrive へのパッドの保存",
"settings_logoutEverywhereConfirm": "すべてのデバイスでログインが取り消されるため、今後利用する際にもう一度ログインするよう求められます。続行しますか?",
"settings_logoutEverywhere": "他のすべてのウェブセッションからログアウトします",
"settings_logoutEverywhereTitle": "リモートセッションを閉じる",
"loading_state_5": "ドキュメントを再構築",
"loading_state_4": "チームを読み込み",
"loading_state_3": "共有フォルダを読み込み",
"loading_state_2": "コンテンツを更新",
"loading_state_1": "ドライブを読み込み",
"loading_state_0": "インターフェースを構築",
"error_unhelpfulScriptError": "スクリプトエラー: ブラウザコンソールで詳細をご確認ください",
"tag_edit": "編集",
"tag_add": "追加",
"team_exportButton": "ダウンロード",
"admin_cat_quota": "ユーザーストレージ",
"admin_invalKey": "無効な公開鍵です",
"admin_limitPlan": "プラン: {0}",
"admin_registrationAllow": "開く",
"admin_registrationButton": "閉じる",
"oo_version": "バージョン: ",
"snapshots_delete": "削除",
"snapshots_close": "閉じる",
"snapshots_restore": "復元",
"snapshots_open": "開く",
"snapshots_placeholder": "スナップショット名",
"snapshots_new": "新規スナップショット",
"snaphot_title": "スナップショット",
"snapshots_button": "スナップショット",
"filePicker_description": "埋め込むファイルを CryptDrive から選択するか、新規にアップロードしてください",
"uploadButtonTitle": "CryptDrive に新規ファイルをアップロード",
"uploadFolderButton": "フォルダをアップロード",
"uploadButton": "ファイルをアップロード",
"filePicker_filter": "ファイル名で検索",
"toolbar_theme": "テーマ",
"themeButton": "テーマ",
"team_cat_back": "チームに戻る",
"team_cat_list": "チーム",
"allow_checkbox": "アクセスリストを有効にする",
"allow_label": "アクセスリスト: {0}",
"share_contactCategory": "連絡先",
"share_linkCategory": "リンク",
"share_linkEdit": "編集",
"previewButtonTitle": "マークダウンのプレビューを表示または非表示にします"
} }

@ -43,7 +43,7 @@
"saved": "Saved", "saved": "Saved",
"synced": "Everything is saved", "synced": "Everything is saved",
"deleted": "Deleted", "deleted": "Deleted",
"deletedFromServer": "Pad deleted from the server", "deletedFromServer": "Document destroyed",
"mustLogin": "You must be logged in to access this page", "mustLogin": "You must be logged in to access this page",
"disabledApp": "This application has been disabled. Contact the administrator of this CryptPad for more information.", "disabledApp": "This application has been disabled. Contact the administrator of this CryptPad for more information.",
"realtime_unrecoverableError": "An unrecoverable error has occured. Click OK to reload.", "realtime_unrecoverableError": "An unrecoverable error has occured. Click OK to reload.",
@ -571,7 +571,7 @@
"upload_notEnoughSpace": "There is not enough space for this file in your CryptDrive.", "upload_notEnoughSpace": "There is not enough space for this file in your CryptDrive.",
"upload_notEnoughSpaceBrief": "Not enough space", "upload_notEnoughSpaceBrief": "Not enough space",
"upload_tooLarge": "This file exceeds the maximum upload size allowed for your account.", "upload_tooLarge": "This file exceeds the maximum upload size allowed for your account.",
"upload_tooLargeBrief": "File too large", "upload_tooLargeBrief": "File exceeds the {0}MB limit",
"upload_choose": "Choose a file", "upload_choose": "Choose a file",
"upload_pending": "Pending", "upload_pending": "Pending",
"upload_cancelled": "Cancelled", "upload_cancelled": "Cancelled",
@ -1469,5 +1469,31 @@
"tag_add": "Add", "tag_add": "Add",
"tag_edit": "Edit", "tag_edit": "Edit",
"error_unhelpfulScriptError": "Script Error: See browser console for details", "error_unhelpfulScriptError": "Script Error: See browser console for details",
"documentID": "Document identifier" "documentID": "Document identifier",
"unableToDisplay": "Unable to display the document. Please press Esc to reload the page. If the problem persists, please contact support.",
"errorPopupBlocked": "CryptPad needs to be able to open new tabs to operate. Please allow popup windows in your browser's address bar. These windows will never be used to show you advertising.",
"braveWarning": "It seems you are currently using the Brave browser. We have received reports of users being unable to confirm the payment form due to strict privacy settings on this browser. If the payment button does not work, you can try with a different browser or contact us with the Support form to find an alternative solution.",
"admin_archiveTitle": "Archive documents",
"admin_archiveHint": "Make a document unavailable without deleting it permanently. It will be placed in an 'archive' directory and deleted after a few days (configurable in the server configuration file).",
"admin_archiveButton": "Archive",
"admin_unarchiveTitle": "Restore documents",
"admin_unarchiveHint": "Restore a document that had previously been archived",
"admin_unarchiveButton": "Restore",
"admin_archiveInput": "Document URL",
"admin_archiveInput2": "Document password",
"admin_archiveInval": "Invalid document",
"restoredFromServer": "Document restored",
"archivedFromServer": "Document archived",
"allowNotifications": "Allow notifications",
"fileTableHeader": "Downloads and uploads",
"download_zip": "Building ZIP file...",
"download_zip_file": "File {0}/{1}",
"Offline": "Offline",
"mediatag_saveButton": "Save",
"pad_mediatagShare": "Share file",
"pad_mediatagOpen": "Open file",
"mediatag_notReady": "Please complete the download",
"settings_mediatagSizeTitle": "Automatic download limit",
"settings_mediatagSizeHint": "Maximum size in megabytes (MB) for automatically loading media elements (images, videos, pdf) embedded into documents. Elements bigger than the specified size can be loaded manually. Use \"-1\" to always load the media elements automatically.",
"mediatag_loadButton": "Load attachment"
} }

@ -9,9 +9,11 @@
</head> </head>
<body class="cp-app-oodoc"> <body class="cp-app-oodoc">
<div id="cp-toolbar" class="cp-toolbar-container"></div> <div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-app-oo-container">
<div id="cp-app-oo-editor"> <div id="cp-app-oo-editor">
<div id="cp-app-oo-placeholder"></div> <div id="cp-app-oo-placeholder-a"></div>
<script type="text/javascript" src="/common/onlyoffice/web-apps/apps/api/documents/api.js"></script> <!--<script type="text/javascript" src="/common/onlyoffice/web-apps/apps/api/documents/api.js"></script>-->
</div>
</div> </div>
</body> </body>

@ -9,9 +9,11 @@
</head> </head>
<body class="cp-app-ooslide"> <body class="cp-app-ooslide">
<div id="cp-toolbar" class="cp-toolbar-container"></div> <div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-app-oo-container">
<div id="cp-app-oo-editor"> <div id="cp-app-oo-editor">
<div id="cp-app-oo-placeholder"></div> <div id="cp-app-oo-placeholder-a"></div>
<script type="text/javascript" src="/common/onlyoffice/web-apps/apps/api/documents/api.js"></script> <!--<script type="text/javascript" src="/common/onlyoffice/web-apps/apps/api/documents/api.js"></script>-->
</div>
</div> </div>
</body> </body>

Loading…
Cancel
Save