Merge branch 'soon' into cacheRT

pull/1/head
yflory 4 years ago
commit 5bbde5fec5

@ -1,6 +1,6 @@
/*@flow*/
/* jshint esversion: 6 */
/* global Buffer */
/* globals Buffer */
var Fs = require("fs");
var Fse = require("fs-extra");
var Path = require("path");
@ -66,6 +66,10 @@ var mkTempPath = function (env, channelId) {
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
var channelExists = function (filepath, cb) {
Fs.stat(filepath, function (err, stat) {
@ -131,7 +135,9 @@ const readMessagesBin = (env, id, start, msgHandler, cb) => {
const collector = createIdleStreamCollector(stream);
const handleMessageAndKeepStreamAlive = Util.both(msgHandler, collector.keepAlive);
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
@ -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
// TODO write the metadata in a dedicated file
var clearChannel = function (env, channelId, _cb) {
@ -213,6 +250,7 @@ var clearChannel = function (env, channelId, _cb) {
cb();
});
});
clearOffset(env, channelId, function () {});
});
};
@ -389,6 +427,7 @@ var removeChannel = function (env, channelName, cb) {
CB(labelError("E_METADATA_REMOVAL", err));
}
}));
clearOffset(env, channelName, w());
}).nThen(function () {
if (errors === 2) {
return void CB(labelError('E_REMOVE_CHANNEL', new Error("ENOENT")));
@ -604,6 +643,8 @@ var archiveChannel = function (env, channelName, cb) {
return void cb(err);
}
}));
}).nThen(function (w) {
clearOffset(env, channelName, w());
}).nThen(function (w) {
// archive the dedicated metadata channel
var metadataPath = mkMetadataPath(env, channelName);
@ -861,6 +902,7 @@ var trimChannel = function (env, channelName, hash, _cb) {
}
}));
}).nThen(function (w) {
clearOffset(env, channelName, w());
cleanUp(w(function (err) {
if (err) {
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
// fetch the metadata for a channel
getChannelMetadata: function (channelName, cb) {

@ -44,8 +44,8 @@ const mkBufferSplit = () => {
// return a streaming function which transforms buffers into objects
// containing the buffer and the offset from the start of the stream
const mkOffsetCounter = () => {
let offset = 0;
const mkOffsetCounter = (offset) => {
offset = offset || 0;
return Pull.map((buff) => {
const out = { offset: offset, buff: buff };
// +1 for the eaten newline
@ -59,13 +59,14 @@ const mkOffsetCounter = () => {
// that this function has a lower memory profile than our classic method
// of reading logs line by line.
// 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 });
let keepReading = true;
Pull(
ToPull.read(stream),
mkBufferSplit(),
mkOffsetCounter(),
mkOffsetCounter(opt.offset),
Pull.asyncMap((data, moreCb) => {
msgHandler(data, moreCb, () => {
try {

@ -1,5 +1,5 @@
/* jshint esversion: 6 */
/* global process */
/* globals process, Buffer */
const HK = require("../hk-util");
const Store = require("../storage/file");
@ -114,14 +114,15 @@ const init = function (config, _cb) {
* 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 i = 0;
@ -129,27 +130,42 @@ const computeIndex = function (data, cb) {
const offsetByHash = {};
let offsetCount = 0;
let size = 0;
let size = offset || 0;
var start = offset || 0;
let unconventional = false;
nThen(function (w) {
// iterate over all messages in the channel log
// old channels can contain metadata as the first message of the log
// skip over metadata as that is handled elsewhere
// otherwise index important messages in the log
store.readMessagesBin(channelName, 0, (msgObj, readMore) => {
store.readMessagesBin(channelName, start, (msgObj, readMore, abort) => {
let msg;
// keep an eye out for the metadata line if you haven't already seen it
// but only check for metadata on the first line
if (!i && msgObj.buff.indexOf('{') === 0) {
i++; // always increment the message counter
if (i) {
// 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'));
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
// 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++;
if (msgObj.buff.indexOf('cp|') > -1) {
if (msgObj.buff.includes(CHECKPOINT_PREFIX)) {
msg = msg || HK.tryParse(Env, msgObj.buff.toString('utf8'));
if (typeof msg === "undefined") { return readMore(); }
// 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) {
// take the last 50 messages
unconventional = true;
messageBuf = messageBuf.slice(-50);
}
// 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;
});
}));
}).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 () {
// return the computed index
CB(null, {
// Only keep the checkpoints included in the last 100 messages
cpIndex: HK.sliceCpIndex(cpIndex, i),
cpIndex: cpIndex,
offsetByHash: offsetByHash,
offsets: offsetCount,
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 ref = {};
const lineHandler = Meta.createLineHandler(ref, Env.Log.error);

@ -1598,9 +1598,9 @@ define([
content: h('span', Messages.profileButton),
action: function () {
if (padType) {
window.open(origin+'/profile/');
Common.openURL(origin+'/profile/');
} else {
window.parent.location = origin+'/profile/';
Common.gotoURL(origin+'/profile/');
}
},
});
@ -1609,33 +1609,36 @@ define([
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': origin+'/drive/',
'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) {
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': origin+'/teams/',
'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) {
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': origin+'/contacts/',
'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') {
@ -1645,9 +1648,9 @@ define([
content: h('span', Messages.settingsButton),
action: function () {
if (padType) {
window.open(origin+'/settings/');
Common.openURL(origin+'/settings/');
} else {
window.parent.location = origin+'/settings/';
Common.gotoURL(origin+'/settings/');
}
},
});
@ -1662,9 +1665,9 @@ define([
content: h('span', Messages.adminPage || 'Admin'),
action: function () {
if (padType) {
window.open(origin+'/admin/');
Common.openURL(origin+'/admin/');
} else {
window.parent.location = origin+'/admin/';
Common.gotoURL(origin+'/admin/');
}
},
});
@ -1676,9 +1679,9 @@ define([
content: h('span', Messages.supportPage || 'Support'),
action: function () {
if (padType) {
window.open(origin+'/support/');
Common.openURL(origin+'/support/');
} else {
window.parent.location = origin+'/support/';
Common.gotoURL(origin+'/support/');
}
},
});
@ -1687,13 +1690,11 @@ define([
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'rel': 'noopener',
'href': AppConfig.surveyURL,
'class': 'cp-toolbar-survey fa fa-graduation-cap'
},
content: h('span', Messages.survey),
action: function () {
Common.openUnsafeURL(AppConfig.surveyURL);
Feedback.send('SURVEY_CLICKED');
},
});
@ -1712,11 +1713,12 @@ define([
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': origin+'/index.html',
'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
/*
@ -1732,23 +1734,24 @@ define([
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': priv.plan ? priv.accounts.upgradeURL : origin+'/features.html',
'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) {
options.push({
tag: 'a',
attributes: {
'target': '_blank',
'rel': 'noopener',
'href': priv.accounts.donateURL,
'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),
action: 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),
action: function () {
Common.logout(function () {
window.parent.location = origin+'/';
Common.gotoURL(origin+'/');
});
},
});

@ -12,8 +12,6 @@ define([
Messages, nThen) {
var Properties = {};
Messages.documentID = Messages.documentID || 'Document identifier'; // XXX
var getPadProperties = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;

@ -1365,6 +1365,10 @@ define([
});
};
APP.openURL = function (url) {
common.openUnsafeURL(url);
};
APP.loadingImage = 0;
APP.getImageURL = function(name, callback) {
var mediasSources = getMediasSources();
@ -2176,6 +2180,12 @@ define([
url: newLatest.file
}, function () { });
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 {
Title.updateTitle(Title.defaultTitle);
}

File diff suppressed because one or more lines are too long

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

@ -739,7 +739,16 @@ define([
sframeChan.on('EV_OPEN_URL', function (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));
}
});

@ -635,6 +635,10 @@ define([
funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); };
funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', 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);
window.open(bounceHref);
};
@ -765,6 +769,10 @@ define([
UI.errorLoadingScreen(Messages.password_error_seed);
});
ctx.sframeChan.on("EV_POPUP_BLOCKED", function () {
UI.alert(Messages.errorPopupBlocked);
});
ctx.sframeChan.on("EV_EXPIRED_ERROR", function () {
funcs.onServerError({
type: 'EEXPIRED'

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

@ -1468,5 +1468,6 @@
"loading_state_1": "Drive laden",
"loading_state_0": "Oberfläche vorbereiten",
"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"
}

@ -1469,5 +1469,7 @@
"tag_edit": "Modifier",
"tag_add": "Ajouter",
"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.",
"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é."
}

@ -249,7 +249,7 @@
"settings_autostoreMaybe": "手動 (確認しない)",
"settings_autostoreNo": "手動 (常に確認する)",
"settings_autostoreHint": "<b>自動</b> あなたがアクセスしたすべてのパッドを、あなたの CryptDrive に保存します。<br><b>手動 (常に確認する)</b> まだ保存していないパッドにアクセスした場合に、あなたの CryptDrive に保存するかどうか尋ねます。<br><b>手動 (確認しない)</b> アクセス先のパッドがあなたの CryptDrive に自動的に保存されなくなります。保存オプションは表示されなくなります。",
"settings_userFeedback": "ユーザーフィードバックを有効",
"settings_userFeedback": "ユーザーフィードバックを有効にする",
"settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。",
"settings_userFeedbackHint1": "CryptPad は、あなたの経験を向上させる方法を知るために、サーバーにいくつかの非常に基本的なフィードバックを提供します。 ",
"settings_userFeedbackTitle": "フィードバック",
@ -356,5 +356,72 @@
"crowdfunding_button": "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 アドレスを保護できます。",
"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": "マークダウンのプレビューを表示または非表示にします"
}

@ -1469,5 +1469,7 @@
"tag_add": "Add",
"tag_edit": "Edit",
"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.",
"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."
}

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

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

Loading…
Cancel
Save