Merge branch 'staging' into responsive_modals

pull/1/head
David Benqué 4 years ago
commit 1413de20d9

@ -16,6 +16,7 @@ server.js
www/common/old-media-tag.js
www/scratch
www/lib
www/accounts
www/common/toolbar.js
www/common/hyperscript.js

@ -1,3 +1,39 @@
# VietnameseRhinoceros (3.21.0)
## Goals
This release was developed over a longer period than usual due to holidays, our yearly company seminar, and generally working on some important software-adjacent projects. As such, we opted not to aim for any major features and instead introduce some minor improvements and address some users' complaints.
## Update notes
We've had a few disgruntled administrators contact us about our apparent _failure to provide a docker image_ or to otherwise support their preferred configuration. With that in mind, this is a periodic reminder that CryptPad is provided to the public under the terms of the AGPL (found within this repository in the [LICENSE file](./LICENSE)) which implies on our part no warranty, liability, or responsibility to configure your server for you. We do our best to provide the necessary information to correctly launch your own instance of the software given our limited budget, however, all such files are provided **AS IS** and are only intended to function under the narrow circumstances of usage which we recommend within the comments of the provided example configuration files.
With that said, the vast majority of our community acts kindly and courteously towards us and each other. We really do appreciate it, and we'll continue to help you to the best of our ability. With that in mind, we're happy to announce that we've written and deployed a first version of our user guide, available at https://docs.cryptpad.fr. The work that went into this was funded by NLnet foundation as an NGI Zero PET (Privacy-Enhancing Technology) grant. We are currently working on two more guides intended for developers and administrators, and will deploy them to the same domain as they are completed. In the meantime we have begun to update our README, GitHub wiki, and other resources to reflect the current recommended practices and remove references to unsupported configurations.
If you're only reading this for instructions on how to update your instance from 3.20.1 to 3.21.0:
1. Stop your server
2. Get the latest platform code with git
3. Install client-side dependencies with `bower update`
4. Install server-side dependencies with `npm install`
4. Restart the CryptPad API server
## Features
* We spent a little bit of time during our company seminar and implemented a first version of an automatically generated _table of contents_ in our rich text editor. It is populated using header styles applied with the editor's dropdown menus, and can be hidden by clicking the "Outline" button in the app toolbar.
* We also made it possible to change the default behaviour of the Kanban tag filter via the settings page. You may choose to compound the selection of multiple tags as AND, resulting in the display of cards that have all the selected tags rather than the default OR behaviour which displays any card including any one of the selected tags.
* We've integrated a third-party Org-mode library into our code editor which features some fancy click-handlers that toggle the state of certain org-mode classifications.
* The search results interface which is present in individual and team drives has been improved such that it displays a spinner while a search is pending and that it indicates when there are no results for a given term.
* We've added a Japanese font (Komorebi-gothic) for use within the spreadsheet editor and have received and integrated Japanese translations from a contributor via our weblate instance (https://weblate.cryptpad.fr).
* Finally, we've modified some behaviour in individual and team drives, making it possible to move a shared folder to the trash where it was previously only possible to directly remove it from your drive.
## Bug fixes
* We've corrected a minor server issue in which it would respond to requests to destroy non-existent files with an E_NO_OWNERS error, rather than an ENOENT (doesn't exist) error. The client code interpreted this as the file existing without them having the rights to delete it, rather than realizing that it no longer existed. This made it more difficult to remove files from your drive since destruction would fail rather than be interpreted as unnecessary.
* We now guard against race conditions in our internal _write-queue_ library, preventing a rare occurrence of a type error triggered by unknown circumstances.
* We discovered that Firefox had enabled (by default) half of the functionality required to export sheets to an XLSX format. We interpreted the presence of this feature as sufficient cause to display XLSX as an export option, even though the export would fail if you tried to use it. The second half of the required functionality is available in Firefox, but requires specific HTTP headers to be sent by our server. We're currently testing the configuration parameters and expect to make XLSX export available on CryptPad.fr very soon, along with an update to our recommended configuration which would enable it on other instances.
* Lastly, we discovered an incompatibility betweeen our "safe links" behaviour and the process of redirecting users to log in or register to access specific functionality. Users that were redirected from pads accessed with safe links were redirected to that safe link whether or not they had imported the pad's keys into their newly created drive. This could result in a temporary loss of access to the pad, even though its credentials were still stored within their browser. We've corrected the redirect process to preserve the full document credentials for after you have logged in.
# UplandMoa's revenge (3.20.1)
Once again we've decided to follow up our last major release with a minor "revenge" release that we wanted to make available as soon as possible.

@ -62,7 +62,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.versionString = "CryptPad v3.20.1 (UplandMoa's revenge)";
Pages.versionString = "CryptPad v3.21.0 (VietnameseRhinoceros)";
// used for the about menu
Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined;

@ -996,6 +996,9 @@
.cp-toolbar-tools {
order: 7;
}
.cp-toolbar-icon-pad_toc {
order: 8;
}
.cp-toolbar-file {
button {
&.fa-plus { order: 0; }

24
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "cryptpad",
"version": "3.20.1",
"version": "3.21.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -393,12 +393,12 @@
}
},
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
"dev": true,
"requires": {
"is-obj": "^1.0.0"
"is-obj": "^2.0.0"
}
},
"ecc-jsbn": {
@ -770,9 +770,9 @@
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"dev": true
},
"is-typedarray": {
@ -1243,12 +1243,12 @@
}
},
"postcss-selector-parser": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz",
"integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz",
"integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==",
"dev": true,
"requires": {
"dot-prop": "^4.1.1",
"dot-prop": "^5.2.0",
"indexes-of": "^1.0.1",
"uniq": "^1.0.1"
}

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "3.20.1",
"version": "3.21.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",

@ -1,6 +1,6 @@
[![An XWiki Labs Project](https://raw.githubusercontent.com/xwiki-labs/xwiki-labs-logo/master/projects/xwikilabs/xlabs-project.png "XWiki labs")](https://labs.xwiki.com/xwiki/bin/view/Main/WebHome)
![CryptPad screenshot](https://github.com/xwiki-labs/cryptpad/raw/master/screenshot.png "Pads are an easy way to collaborate")
![CryptPad screenshot](screenshot.png "Private real-time collaboration on a Rich Text document.")
CryptPad is the **Zero Knowledge** realtime collaborative editor.
@ -24,9 +24,6 @@ The most recent version and all past release notes can be found [here](https://g
See [Cryptpad-Docker](https://github.com/xwiki-labs/cryptpad-docker) repository for details on how to get up-and-running with Cryptpad in Docker. This repository is maintained by the community and not officially supported.
## Setup using Ansible
See [Ansible Role for Cryptpad](https://github.com/systemli/ansible-role-cryptpad).
# Security

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 116 KiB

@ -113,7 +113,7 @@ define(function() {
poll: 'cptools-poll',
whiteboard: 'cptools-whiteboard',
todo: 'cptools-todo',
contacts: 'cptools-contacts',
contacts: 'fa-address-book',
kanban: 'cptools-kanban',
oodoc: 'fa-file-word-o',
ooslide: 'fa-file-powerpoint-o',

@ -2438,7 +2438,7 @@ define([
attributes: {
'target': '_blank',
'href': origin+'/contacts/',
'class': 'cptools cptools-contacts'
'class': 'fa fa-address-book'
},
content: h('span', Messages.type.contacts)
});
@ -4104,5 +4104,11 @@ define([
};
};
UIElements.isVisible = function (el, $container) {
var size = $container.outerHeight();
var pos = el.getBoundingClientRect();
return (pos.bottom < size) && (pos.y > 0);
};
return UIElements;
});

@ -2273,7 +2273,7 @@ define([
var o = e.oldValue;
var n = e.newValue;
if (!o && n) {
document.location.reload();
LocalStore.loginReload();
} else if (o && !n) {
LocalStore.logout();
}

@ -312,7 +312,7 @@ define([
}
};
handlers['INVITE_TO_TEAM_ANSWER'] = function(common, data) {
handlers['INVITE_TO_TEAM_ANSWERED'] = function(common, data) {
var content = data.content;
var msg = content.msg;

@ -533,6 +533,7 @@ define([
};
var loadLastDocument = function (lastCp, onCpError, cb) {
ooChannel.cpIndex = lastCp.index || 0;
ooChannel.lastHash = lastCp.hash;
var parsed = Hash.parsePadUrl(lastCp.file);
var secret = Hash.getSecrets('file', parsed.hash);
if (!secret || !secret.channel) { return; }
@ -1390,6 +1391,9 @@ define([
});
};
var supportsXLSX = function () {
return !(typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined");
};
var exportXLSXFile = function() {
var text = getContent();
@ -1403,7 +1407,7 @@ define([
ext = ['.docx', /*'.odt',*/ '.bin'];
}
if (typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined") {
if (!supportsXLSX()) {
ext = ['.bin'];
warning = '<div class="alert alert-info cp-alert-top">'+Messages.oo_exportChrome+'</div>';
}
@ -1583,7 +1587,7 @@ define([
if (ext === "bin") {
return void importFile(content);
}
if (typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined") {
if (!supportsXLSX()) {
return void UI.alert(Messages.oo_invalidFormat);
}
var div = h('div.cp-oo-x2tXls', [
@ -1758,7 +1762,7 @@ define([
} else if (type === "oodoc") {
accept = ['.bin', '.odt', '.docx'];
}
if (typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined") {
if (!supportsXLSX()) {
accept = ['.bin'];
}

@ -129,6 +129,18 @@ define([
if (cb) { cb(); }
};
var loginHandlers = [];
LocalStore.loginReload = function () {
loginHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
document.location.reload();
};
LocalStore.onLogin = function (h) {
if (typeof (h) !== "function") { return; }
if (loginHandlers.indexOf(h) !== -1) { return; }
loginHandlers.push(h);
};
LocalStore.onLogout = function (h) {
if (typeof (h) !== "function") { return; }
if (logoutHandlers.indexOf(h) !== -1) { return; }

@ -444,11 +444,21 @@ define([
// If they declined the invitation, remove them from the roster (as a pending member)
try {
var module = ctx.store.modules['team'];
module.removeFromTeam(teamId, msg.author);
module.removeFromTeam(teamId, msg.author, true);
} catch (e) { console.error(e); }
}
cb(false);
var userData = content.user || content;
box.sendMessage({
type: 'INVITE_TO_TEAM_ANSWERED',
content: {
user: userData,
team: team,
answer: content.answer
}
}, function () {});
cb(true);
};
handlers['TEAM_EDIT_RIGHTS'] = function (ctx, box, data, cb) {
@ -648,9 +658,15 @@ define([
if (!data.msg) { return void cb(true); }
// Check if the request is valid (sent by the correct user)
var myCurve = Util.find(ctx, ['store', 'proxy', 'curvePublic']);
var curve = Util.find(data, ['msg', 'content', 'user', 'curvePublic']) ||
Util.find(data, ['msg', 'content', 'curvePublic']);
if (curve && data.msg.author !== curve) { console.error('blocked'); return void cb(true); }
// Block messages that are not coming from the user described in the message
// except if the author is ourselves.
if (curve && data.msg.author !== curve && data.msg.author !== myCurve) {
console.error('blocked');
return void cb(true);
}
var type = data.msg.type;

@ -258,6 +258,7 @@ proxy.mailboxes = {
} catch (e) {
console.error(e);
}
console.error('test');
sendMessage(msg, function (err, hash) {
if (err) { return void console.error(err); }
box.history.push(hash);

@ -578,6 +578,12 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
console.error("CHANNEL_ERROR", info);
};
var onConnectionChange = function (info) {
if (info.state) { return; }
// Disconnect: don't send event anymore until ready
ready = false;
};
var onConnect = function (/* wc, sendMessage */) {
console.log("ROSTER CONNECTED");
};
@ -621,12 +627,12 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
// if a checkpoint was successfully applied, emit an event
if (parsed[0] === 'CHECKPOINT' && changed) {
events.checkpoint.fire(hash);
if (isReady()) { events.checkpoint.fire(hash); }
// reset the counter for messages since the last checkpoint
ref.internal.sinceLastCheckpoint = 0;
ref.internal.lastCheckpointHash = hash;
} else if (changed) {
events.change.fire();
if (isReady()) { events.change.fire(); }
}
// CHECKPOINT logic...
@ -833,7 +839,7 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
onChannelError: onChannelError,
onReady: onReady,
onConnect: onConnect,
onConnectionChange: function () {},
onConnectionChange: onConnectionChange,
onMessage: onMessage,
noChainPad: true,

@ -221,6 +221,11 @@ define([
roster.on('change', function () {
var state = roster.getState();
var me = Util.find(ctx, ['store', 'proxy', 'curvePublic']);
if (!state.members || !Object.keys(state.members).length) {
// invalid roster, don't leave the team
console.error(JSON.stringify(state));
return;
}
if (!state.members[me]) {
return void closeTeam(ctx, id);
}
@ -1698,8 +1703,22 @@ define([
team.getTeams = function () {
return Object.keys(ctx.teams);
};
team.removeFromTeam = function (teamId, curve) {
var isPending = function (teamId, curve) {
var team = ctx.teams[teamId];
if (!team) { return; }
var state = team.roster && team.roster.getState();
if (!state.members) { return; }
var m = state.members[curve] || {};
return m.pending;
};
team.removeFromTeam = function (teamId, curve, pendingOnly) {
if (!teams[teamId]) { return; }
// When receiving a negative answer to a team invitation, remove
// the pending user from the roster.
if (pendingOnly && !isPending(teamId, curve)) { return; }
if (ctx.onReadyHandlers[teamId]) {
ctx.onReadyHandlers[teamId].push({cb : function () {
ctx.teams[teamId].roster.remove([curve], function (err) {

@ -511,7 +511,19 @@ define([
Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
Utils.LocalStore.onLogin(function () {
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = currentPad.hash;
window.onhashchange = ohc;
ohc({reset: true});
});
Utils.LocalStore.onLogout(function () {
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = currentPad.hash;
window.onhashchange = ohc;
ohc({reset: true});
sframeChan.event('EV_LOGOUT');
});

@ -1398,5 +1398,11 @@
"support_formCategoryError": "Fehler: Kategorie ist leer",
"fm_restricted": "Du kannst auf dieses Element nicht zugreifen",
"fm_emptyTrashOwned": "In deinem Papierkorb sind Dokumente gespeichert, deren Eigentümer du bist. Du kannst sie aus deinem CryptDrive <b>entfernen</b> oder für alle Benutzer <b>zerstören</b>.",
"fm_noResult": "Keine Ergebnisse gefunden"
"fm_noResult": "Keine Ergebnisse gefunden",
"pad_tocHide": "Gliederung",
"settings_kanbanTagsTitle": "Tag-Filter",
"settings_cat_kanban": "Kanban",
"settings_kanbanTagsOr": "ODER",
"settings_kanbanTagsAnd": "UND",
"settings_kanbanTagsHint": "Wähle aus, wie sich der Tag-Filter bei der Auswahl mehrerer Tags verhalten soll: nur Karten mit allen ausgewählten Tags anzeigen (UND) oder Karten mit irgendeinem der ausgewählten Tags anzeigen (ODER)"
}

@ -1148,7 +1148,7 @@
"owner_removeButton": "Supprimer les propriétaires sélectionnés",
"owner_removePendingButton": "Annuler les offres sélectionnées",
"owner_addButton": "Proposer d'être propriétaire",
"owner_removeConfirm": "Êtes-vous sûr de vouloir suppprimer les droits de propriétaire pour les utilisateurs sélectionnés ? Ils seront notifiés de cette action.",
"owner_removeConfirm": "Êtes-vous sûr de vouloir supprimer les droits de propriétaire pour les utilisateurs sélectionnés ? Ils seront notifiés de cette action.",
"owner_removeMeConfirm": "Vous êtes sur le point de renoncer à vos droits de propriétaire. Vous ne serez pas en mesure d'annuler cette action. Continuer ?",
"owner_addConfirm": "Les co-propriétaires seront en mesure de changer le contenu du document et pourront supprimer vos droits de propriétaire. Continuer ?",
"owner_openModalButton": "Gérer les propriétaires",
@ -1381,7 +1381,7 @@
"support_languagesPreamble": "L'équipe de support parle les langues suivantes :",
"info_privacyFlavour": "<a>Description de la confidentialité</a> de vos données.",
"user_about": "À propos de CryptPad",
"info_imprintFlavour": "<a>Informations légales sur les administateurs de cette instance</a>.",
"info_imprintFlavour": "<a>Informations légales sur les administrateurs de cette instance</a>.",
"support_cat_all": "Tout",
"support_cat_other": "Autre",
"support_cat_bug": "Rapport de bug",
@ -1397,5 +1397,12 @@
"support_category": "Choisir une catégorie",
"fm_restricted": "Vous n'avez pas accès à cet élément",
"fm_emptyTrashOwned": "Votre corbeille contient des documents dont vous êtes propriétaire. Vous pouvez les <b>supprimer</b> de votre drive uniquement, ou les <b>détruire</b> pour tous les utilisateurs.",
"fm_noResult": "Pas de résultat"
"fm_noResult": "Pas de résultat",
"support_formCategoryError": "Erreur : la catégorie est vide",
"pad_tocHide": "Plan",
"settings_cat_kanban": "Kanban",
"settings_kanbanTagsOr": "OU",
"settings_kanbanTagsAnd": "ET",
"settings_kanbanTagsHint": "Sélectionnez le fonctionnement du filtre lorsque vous sélectionnez plusieurs mots-clés : afficher uniquement les cartes contenant tous les mots-clés sélectionnés (ET) ou afficher les cartes contenant l'un des mots-clés sélectionnés (OU)",
"settings_kanbanTagsTitle": "Filtre par mot-clé"
}

@ -177,5 +177,184 @@
"profileButton": "プロフィール",
"profile_urlPlaceholder": "URL",
"profile_avatar": "アバター",
"profile_upload": " 新しいアバターをアップロード"
"profile_upload": " 新しいアバターをアップロード",
"teams_table_generic_edit": "編集: フォルダとパッドの作成、変更、削除が可能。",
"teams_table_generic_view": "表示: フォルダとパッドへのアクセス(閲覧のみ)。",
"teams_table_generic_own": "チームの管理: チーム名とチームアバターの変更、所有者の追加または削除、チームのサブスクリプションの変更、チームの削除が可能。",
"teams_table_owners": "チームの管理",
"teams_table_generic_admin": "メンバーの管理: メンバーの招待および取り消し、メンバーに管理者までの権限の付与が可能。",
"teams_table_admins": "メンバーの管理",
"teams_table_generic": "権限一覧",
"teams_table": "権限",
"contacts_fetchHistory": "古い履歴を取得する",
"contacts_warning": "ここに入力したすべてのものは永続的であり、このパッドの現在および将来のすべてのユーザーが利用できます。機密情報の入力は推奨されません!",
"contacts_typeHere": "メッセージを入力...",
"team_listLoad": "開く",
"team_cat_drive": "ドライブ",
"team_cat_chat": "チャット",
"team_cat_members": "メンバー",
"team_cat_admin": "管理",
"adminPage": "管理",
"team_deleteButton": "削除",
"team_deleteHint": "チーム自体とチームが所有しているすべてのドキュメントを削除します。",
"team_deleteTitle": "チームの削除",
"team_avatarHint": "容量 500KB 以下 (png 、jpg 、jpeg 、gif)",
"team_avatarTitle": "チームアバター",
"team_nameHint": "チームの名前を設定します",
"team_nameTitle": "チーム名",
"team_members": "メンバー",
"team_owner": "所有者",
"team_admins": "管理者",
"editors": "編集者",
"viewers": "閲覧者",
"contacts_padTitle": "チャット",
"chatButton": "チャット",
"contacts_unmute": "ミュート解除",
"contacts_mute": "ミュート",
"contacts": "連絡先",
"share_linkOpen": "プレビュー",
"share_linkView": "表示",
"view": "表示",
"settings_exportError": "表示エラー",
"pad_mediatagPreview": "プレビュー",
"share_linkAccess": "アクセス権限",
"viewer": "閲覧者",
"reconnecting": "再接続中",
"synchronizing": "同期中",
"initializing": "初期化中...",
"yourself": "あなた",
"anonymous": "匿名",
"editor": "編集者",
"anonymousUser": "匿名の編集者",
"anonymousUsers": "匿名の編集者",
"typing": "編集中",
"team_infoLabel": "チームについて",
"support_cat_all": "全て",
"support_addAttachment": "添付ファイルを追加",
"support_attachments": "添付ファイル",
"support_cat_bug": "バグの報告",
"support_cat_data": "コンテンツの損失",
"support_cat_account": "ユーザーアカウント",
"support_formCategoryError": "エラー: カテゴリーが選択されていません",
"support_category": "カテゴリーを選択",
"support_languagesPreamble": "サポートチームは次の言語に対応可能です:",
"support_listHint": "管理者に送信されたチケットとその回答のリストは以下の通りです。閉じたチケットを再開することはできませんが、新しいチケットを作成することはできます。閉じたチケットは非表示にできます。",
"support_listTitle": "サポートチケット",
"settings_padNotifCheckbox": "コメント通知を無効化",
"settings_padNotifTitle": "コメント通知",
"notifications_dismissAll": "全て確認済みにする",
"notifications_cat_archived": "履歴",
"notifications_cat_friends": "連絡先リクエスト",
"notifications_dismiss": "確認済みにする",
"settings_autostoreMaybe": "手動 (確認しない)",
"settings_autostoreNo": "手動 (常に確認する)",
"settings_autostoreHint": "<b>自動</b> あなたがアクセスしたすべてのパッドを、あなたの CryptDrive に保存します。<br><b>手動 (常に確認する)</b> まだ保存していないパッドにアクセスした場合に、あなたの CryptDrive に保存するかどうか尋ねます。<br><b>手動 (確認しない)</b> アクセス先のパッドがあなたの CryptDrive に自動的に保存されなくなります。保存オプションは表示されなくなります。",
"settings_userFeedback": "ユーザーフィードバックを有効化",
"settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。",
"settings_userFeedbackHint1": "CryptPad は、あなたの経験を向上させる方法を知るために、サーバーにいくつかの非常に基本的なフィードバックを提供します。 ",
"settings_userFeedbackTitle": "フィードバック",
"settings_autostoreYes": "自動",
"settings_importConfirm": "このブラウザで最近使用したパッドを、あなたのユーザーアカウントの CryptDrive にインポートしますか?",
"settings_importDone": "インポートが完了しました",
"settings_import": "インポート",
"settings_importTitle": "このブラウザでの最近のパッドをあなたの CryptDrive にインポートします",
"settings_trimHistoryHint": "ドライブと通知の履歴を削除して、ストレージ容量を節約します。これはパッドの履歴には影響しません。パッドの履歴は、プロパティダイアログから削除できます。",
"trimHistory_currentSize": "現在の履歴容量: <b>{0}</b>",
"support_cat_other": "その他",
"user_about": "CryptPad について",
"fc_delete": "ごみ箱へ移動",
"fc_remove": "削除",
"fc_restore": "復元",
"fc_delete_owned": "完全削除",
"creation_create": "作成",
"creation_password": "パスワードの追加",
"creation_expireMonths": "か月",
"creation_expireDays": "日",
"creation_expireHours": "時間",
"creation_expireFalse": "無制限",
"pad_wordCount": "単語数: {0}",
"teams_table_role": "権限",
"templateSaved": "テンプレートを保存しました",
"saveTemplateButton": "テンプレートとして保存",
"fm_rootName": "ドキュメント",
"team_listTitle": "あなたのチーム",
"team_createName": "チーム名",
"features_f_devices_note": "ユーザーアカウントでどこからでも CryptDrive にアクセスできます",
"features_f_devices": "全てのデバイスであなたのパッドを利用",
"features_f_cryptdrive1_note": "フォルダ、共有フォルダ、テンプレート、タグ",
"features_f_cryptdrive1": "CryptDrive への完全なアクセス",
"features_f_anon_note": "匿名ユーザーが利用可能な全機能を利用できます",
"features_f_anon": "匿名ユーザーの全機能",
"features_f_storage0_note": "作成されたパッドは、3か月以上使用されないと削除される可能性があります",
"features_f_storage0": "一時的な保存",
"features_f_cryptdrive0_note": "後で開けるようにブラウザにアクセスしたパッドを保存する機能",
"features_f_cryptdrive0": "CryptDrive への限定的なアクセス",
"features_f_file0_note": "他のユーザーが共有したファイルを表示およびダウンロードできます",
"features_f_file0": "ファイルを開く",
"features_f_core_note": "編集、インポートとエクスポート、履歴、ユーザーリスト、チャット",
"features_f_core": "アプリケーションの一般的な機能",
"features_f_apps": "主なアプリケーションへのアクセス",
"features_premium": "プレミアムユーザー",
"features_registered": "登録ユーザー",
"features_title": "機能の比較",
"features_anon": "匿名ユーザー",
"register_whyRegister": "登録するとどの様な利点がありますか?",
"historyText": "履歴",
"help_button": "ヘルプ",
"show_help_button": "ヘルプを表示",
"hide_help_button": "ヘルプを非表示",
"cancel": "キャンセル",
"ok": "OK",
"okButton": "OK (enter)",
"mustLogin": "このページにアクセスするにはログインする必要があります",
"fm_noResult": "見つかりませんでした",
"fm_info_trash": "ごみ箱を空にすると、あなたの CryptDrive の使用可能容量を増やせます。",
"features_f_file1": "ファイルのアップロードと共有",
"features_f_social_note": "プロフィールの作成、アバターの使用、連絡先とのチャット",
"features_f_social": "特別なアプリケーション",
"policy_whatweknow_p1": "ウェブ上でホストされるアプリケーションとして、CryptPad は HTTP プロトコルによって公開されるメタデータにアクセスできます。これには、IP アドレス、および特定のブラウザを識別するために使用可能なその他のさまざまな HTTP ヘッダーの情報が含まれます。ブラウザから共有される情報は、<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending\" title=\"what http headers is my browser sending\">WhatIsMyBrowser.com</a> (外部サイト)にアクセスすると確認できます。",
"policy_whatweknow": "私たちが収集可能なあなたについての情報",
"tos_e2ee": "CryptPad のコンテンツは、パッドのフラグメント識別子を推測または取得できる人物なら誰でも閲覧や編集が行えます。エンドツーエンド暗号化(E2EE)を採用したメッセンジャーサービスなどを使用してリンクを共有し、リンク漏洩が発生しないよう対策を行ってください。",
"contact_chat": "チャット",
"contact_bug": "バグの報告",
"footer_product": "製品",
"main_info": "<h2>安全にコラボレーション</h2><strong>ゼロ知識技術</strong>があなたのプライバシーを保護しながら、安全にドキュメントを共有してあなたのアイデアを成長させます。<strong>私たちサービス管理者ですらあなたのドキュメントを閲覧することはできません</strong>。",
"features_f_subscribe": "プレミアムプランに登録",
"features_pricing": "月額 {0}€ {2}€",
"features_emailRequired": "メールアドレスが必要です",
"features_f_subscribe_note": "まず CryptPad にログインする必要があります",
"features_f_supporter": "プライバシーの支援者になる",
"features_f_supporter_note": "プライバシーを保護するソフトウェアが標準であることを世界に示すのを手伝うことができます",
"features_f_support_note": "チームプランには電子メールによる高度なサポートが付属します",
"features_f_support": "優先的なサポート",
"features_f_storage2_note": "選択したプランに応じて 5GB から 50GB までの追加ストレージ容量が利用可能です",
"features_f_storage2": "追加のストレージ容量",
"features_f_reg_note": "その上で CryptPad の開発者を支援できます",
"features_f_reg": "登録ユーザーの全機能",
"homePage": "ホームページ",
"features_noData": "登録に個人情報は必要ありません",
"features_f_register_note": "登録にメールアドレスや個人情報は必要ありません",
"features_f_register": "無料登録",
"features_f_storage1_note": "CryptDrive に保存されたパッドが有効期限切れで削除されることはありません",
"features_f_storage1": "永続的ストレージ (50MB)",
"oo_sheetMigration_complete": "新しいバージョンが利用可能です。「OK」を押して再読み込みしてください。",
"oo_sheetMigration_loading": "あなたのスプレッドシートを最新バージョンにアップグレードしています",
"settings_ownDriveButton": "アカウントをアップグレード",
"features_f_file1_note": "連絡先とファイルを共有したり、パッドを埋め込む",
"policy_choices_open": "私たちのソフトウェアのコードはオープンソースなので、いつでも CryptPad を独自のインスタンスでホストすることが可能です。",
"policy_choices": "あなたの選択肢",
"policy_ads": "広告の掲載は行いません",
"policy_ads_p1": "私たちはいかなるオンライン広告も表示しませんが、私たちに資金を提供している団体へのリンクを掲載する可能性はあります。",
"policy_links_p1": "このサイトには、他の団体によって作成されたサイトを含む、外部サイトへのリンクが含まれています。私たちは、プライバシー上の取り扱いや外部サイトの内容について責任を負いません。一般的に、外部サイトへのリンクは、新しいブラウザウィンドウで起動され、CryptPad.fr を離れることを明確にします。",
"policy_links": "他のサイトへのリンク",
"policy_whatwetell": "私たちが第三者に共有する可能性のあるあなたの情報",
"policy_whatwetell_p1": "私たちは、法的に要求されない限り、私たちが収集した情報またはあなたから私たちに提供された情報を第三者に共有しません。",
"policy_howweuse_p2": "ブラウザに関する情報(デスクトップなのかモバイル端末の OS なのか)は、機能の改善に優先順位を付ける際に決定を下すのに役立ちます。私たちの開発チームは小さく、できる限り多くのユーザーのエクスペリエンスを向上させる選択を行います。",
"policy_howweuse": "私たちが収集した情報を何に使うか",
"policy_howweuse_p1": "収集した情報を使用して、過去の取り組みのうちどれが成功したかを評価することで、CryptPad の宣伝に関するより良い意思決定を行います。あなたの所在地に関する情報により、英語以外の言語に対するサポートの改善を検討すべきかどうかがわかります。",
"crowdfunding_home2": "以下のボタンをクリックして、クラウドファンディングキャンペーンについてご確認ください。",
"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": "チャット履歴を削除"
}

@ -1398,5 +1398,11 @@
"support_formCategoryError": "Error: category is empty",
"fm_emptyTrashOwned": "Your trash contains documents you own. You can <b>remove</b> them from your drive only, or <b>destroy</b> them for all users.",
"fm_restricted": "You do not have access",
"fm_noResult": "No results found"
"fm_noResult": "No results found",
"pad_tocHide": "Outline",
"settings_kanbanTagsTitle": "Tag filter",
"settings_kanbanTagsHint": "Select how you want the tag filter to act when selecting multiple tags: only show cards containing all the selected tags (AND) or show cards containing any of the selected tags (OR)",
"settings_kanbanTagsAnd": "AND",
"settings_kanbanTagsOr": "OR",
"settings_cat_kanban": "Kanban"
}

@ -608,6 +608,8 @@ define([
framework._.sfCommon.openUnsafeURL(href);
};
var md = framework._.cpNfInner.metadataMgr.getPrivateData();
var _tagsAnd = Util.find(md, ['settings', 'kanban', 'tagsAnd']);
var kanban = new jKanban({
element: '#cp-app-kanban-content',
@ -615,6 +617,7 @@ define([
widthBoard: '300px',
buttonContent: '❌',
readOnly: framework.isReadOnly(),
tagsAnd: _tagsAnd,
refresh: function () {
onRedraw.fire();
},
@ -830,6 +833,17 @@ define([
boards: boards
});
framework._.cpNfInner.metadataMgr.onChange(function () {
var md = framework._.cpNfInner.metadataMgr.getPrivateData();
var tagsAnd = Util.find(md, ['settings', 'kanban', 'tagsAnd']);
if (_tagsAnd === tagsAnd) { return; }
// If the rendering has changed, update the value and redraw
kanban.options.tagsAnd = tagsAnd;
_tagsAnd = tagsAnd;
kanban.setBoards(kanban.options.boards);
});
if (migrated) { framework.localChange(); }
var addBoardDefault = document.getElementById('kanban-addboard');

@ -506,10 +506,18 @@ define([
nodeItem.appendChild(nodeItemText);
// Check if this card is filtered out
if (Array.isArray(self.options.tags) && self.options.tags.length) {
var hide = !Array.isArray(element.tags) ||
!element.tags.some(function (tag) {
return self.options.tags.indexOf(tag) !== -1;
});
var hide;
if (self.options.tagsAnd) {
hide = !Array.isArray(element.tags) ||
!self.options.tags.every(function (tag) {
return element.tags.indexOf(tag) !== -1;
});
} else {
hide = !Array.isArray(element.tags) ||
!element.tags.some(function (tag) {
return self.options.tags.indexOf(tag) !== -1;
});
}
if (hide) {
nodeItem.classList.add('kanban-item-hidden');
}

@ -24,6 +24,33 @@ body.cp-app-pad {
overflow: hidden;
}
#cp-app-pad-toc {
@toc-level-indent: 15px;
margin-top: 10px;
margin-left: 10px;
width: 200px;
color: @cryptpad_text_col;
h2 {
font-size: 1.5rem;
}
p {
margin-bottom: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
a {
color: @cryptpad_text_col;
}
&.cp-pad-toc-2 {
margin-left: @toc-level-indent;
}
&.cp-pad-toc-3 {
margin-left: @toc-level-indent * 2;
}
}
}
.cke_toolbox_main {
background-color: @colortheme_pad-toolbar-bg;
.cke_toolbar {

@ -5,8 +5,9 @@ define([
'/common/common-hash.js',
'/common/hyperscript.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/customize/messages.js'
], function($, Sortify, Util, Hash, h, UI, Messages) {
], function($, Sortify, Util, Hash, h, UI, UIElements, Messages) {
var Comments = {};
/*
@ -273,12 +274,6 @@ define([
]);
};
var isVisible = function(el, $container) {
var size = $container.outerHeight();
var pos = el.getBoundingClientRect();
return (pos.bottom < size) && (pos.y > 0);
};
var redrawComments = function(Env) {
// Don't redraw if there were no change
var str = Sortify(Env.comments || {});
@ -558,7 +553,7 @@ define([
// Scroll into view
if (!$last.length) { return; }
var visible = isVisible($last[0], Env.$inner);
var visible = UIElements.isVisible($last[0], Env.$inner);
if (!visible) { $last[0].scrollIntoView(); }
};
@ -574,7 +569,7 @@ define([
focusContent();
var visible = isVisible(div, Env.$container);
var visible = UIElements.isVisible(div, Env.$container);
if (!visible) { div.scrollIntoView(); }
});

@ -45,7 +45,6 @@
<div id="cp-app-pad-toolbar" class="cp-toolbar-container"></div>
<div id="cp-app-pad-editor">
<textarea style="display:none" id="editor1" name="editor1"></textarea>
<div id="cp-app-pad-comments"></div>
</div>
</body>
</html>

@ -42,6 +42,7 @@ define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/hyperscript.js',
'/bower_components/chainpad/chainpad.dist.js',
'/customize/application_config.js',
@ -69,6 +70,7 @@ define([
Hash,
Util,
UI,
UIElements,
h,
ChainPad,
AppConfig,
@ -424,6 +426,41 @@ define([
});
};
var addTOCHideBtn = function(framework, $toc) {
// Expand / collapse the toolbar
var onClick = function(visible) {
framework._.sfCommon.setAttribute(['pad', 'showTOC'], visible);
};
framework._.sfCommon.getAttribute(['pad', 'showTOC'], function(err, data) {
var state = false;
if (($(window).height() >= 800 || $(window).width() >= 800) &&
(typeof(data) === "undefined" || data)) {
state = true;
$toc.show();
} else {
$toc.hide();
}
var $tocButton = framework._.sfCommon.createButton('', true, {
drawer: false,
text: Messages.pad_tocHide,
name: 'pad_toc',
icon: 'fa-list-ul',
}, function () {
$tocButton.removeClass('cp-toolbar-button-active');
$toc.toggle();
state = $toc.is(':visible');
if (state) {
$tocButton.addClass('cp-toolbar-button-active');
}
onClick(state);
});
framework._.toolbar.$bottomL.append($tocButton);
if (state) {
$tocButton.addClass('cp-toolbar-button-active');
}
});
};
var displayMediaTags = function(framework, dom, mediaTagMap) {
setTimeout(function() { // Just in case
var tags = dom.querySelectorAll('media-tag:empty');
@ -537,6 +574,9 @@ define([
$container: $('#cp-app-pad-comments')
});
var $toc = $('#cp-app-pad-toc');
addTOCHideBtn(framework, $toc);
// My cursor
var cursor = module.cursor = Cursor(inner);
@ -607,6 +647,34 @@ define([
}, 500); // 500ms to make sure it is sent after chainpad sync
};
var updateTOC = Util.throttle(function () {
var toc = [];
$inner.find('h1, h2, h3').each(function (i, el) {
toc.push({
level: Number(el.tagName.slice(1)),
el: el,
title: Util.stripTags($(el).text())
});
});
var content = [h('h2', Messages.markdown_toc)];
toc.forEach(function (obj) {
// Only include level 2 headings
var level = obj.level;
var a = h('a.cp-pad-toc-link', {
href: '#',
});
$(a).click(function (e) {
e.preventDefault();
e.stopPropagation();
if (!obj.el || UIElements.isVisible(obj.el, $inner)) { return; }
obj.el.scrollIntoView();
});
a.innerHTML = obj.title;
content.push(h('p.cp-pad-toc-'+level, a));
});
$toc.html('').append(content);
}, 400);
// apply patches, and try not to lose the cursor in the process!
framework.onContentUpdate(function(hjson) {
if (!Array.isArray(hjson)) { throw new Error(Messages.typeError); }
@ -666,6 +734,8 @@ define([
}
comments.onContentUpdate();
updateTOC();
});
framework.setTextContentGetter(function() {
@ -877,8 +947,12 @@ define([
framework.localChange();
updateCursor();
editor.fire('cp-wc'); // Update word count
updateTOC();
});
editor.on('change', function () {
framework.localChange();
updateTOC();
});
editor.on('change', framework.localChange);
var wordCount = h('span.cp-app-pad-wordCount');
$('.cke_toolbox_main').append(wordCount);
@ -1077,6 +1151,7 @@ define([
var $ckeToolbar = $('#cke_1_top').find('.cke_toolbox_main');
$mainContainer.prepend($ckeToolbar.addClass('cke_reset_all'));
$contentContainer.append(h('div#cp-app-pad-comments'));
$contentContainer.prepend(h('div#cp-app-pad-toc'));
$ckeToolbar.find('.cke_button__image_icon').parent().hide();
}).nThen(waitFor());

@ -1,19 +1,28 @@
define([
'jquery',
'/common/hyperscript.js',
'/common/common-ui-elements.js',
'/customize/messages.js'
], function ($, h, Messages) {
], function ($, h, UIElements, Messages) {
var onLinkClicked = function (e, inner) {
var $target = $(e.target);
if (!$target.is('a')) { return; }
var href = $target.attr('href');
if (!href || href[0] === '#') { return; }
if (!href) { return; }
var $inner = $(inner);
e.preventDefault();
e.stopPropagation();
if (href[0] === '#') {
var anchor = $inner.find(href);
if (!anchor.length) { return; }
anchor[0].scrollIntoView();
return;
}
var $iframe = $('html').find('iframe').contents();
var $inner = $(inner);
var rect = e.target.getBoundingClientRect();
var rect0 = inner.getBoundingClientRect();

@ -167,7 +167,7 @@
}
}
.cp-settings-autostore-radio {
.cp-settings-radio-container {
display: flex;
align-items: center;
flex-wrap: wrap;

@ -86,6 +86,9 @@ define([
'cp-settings-code-font-size',
'cp-settings-code-spellcheck',
],
'kanban': [
'cp-settings-kanban-tags',
],
'subscription': {
onClick: function() {
var urls = common.getMetadataMgr().getPrivateData().accounts;
@ -290,7 +293,7 @@ define([
input: { value: 1 },
label: { class: 'noTitle' }
});
var $div2 = $(h('div.cp-settings-autostore-radio', [
var $div2 = $(h('div.cp-settings-radio-container', [
opt3,
opt2,
opt1
@ -1470,6 +1473,46 @@ define([
};
makeBlock('kanban-tags', function(cb) {
var opt1 = UI.createRadio('cp-settings-kanban-tags', 'cp-settings-kanban-tags-and',
Messages.settings_kanbanTagsAnd, false, {
input: { value: 1 },
label: { class: 'noTitle' }
});
var opt2 = UI.createRadio('cp-settings-kanban-tags', 'cp-settings-kanban-tags-or',
Messages.settings_kanbanTagsOr, true, {
input: { value: 0 },
label: { class: 'noTitle' }
});
var div = h('div.cp-settings-radio-container', [
opt1,
opt2,
]);
var $d = $(div);
var spinner = UI.makeSpinner($d);
$d.find('input[type="radio"]').on('change', function() {
spinner.spin();
var val = $('input:radio[name="cp-settings-kanban-tags"]:checked').val();
val = Number(val) || 0;
common.setAttribute(['kanban', 'tagsAnd'], val, function() {
spinner.done();
});
});
common.getAttribute(['kanban', 'tagsAnd'], function(e, val) {
if (e) { return void console.error(e); }
if (val) {
$(opt1).find('input').attr('checked', 'checked');
}
});
cb($d);
}, true);
// Settings app
var createUsageButton = function() {
@ -1506,6 +1549,7 @@ define([
if (key === 'pad') { $category.append($('<span>', { 'class': 'fa fa-file-word-o' })); }
if (key === 'security') { $category.append($('<span>', { 'class': 'fa fa-lock' })); }
if (key === 'subscription') { $category.append($('<span>', { 'class': 'fa fa-star-o' })); }
if (key === 'kanban') { $category.append($('<span>', { 'class': 'cptools cptools-kanban' })); }
if (key === active) {
$category.addClass('cp-leftside-active');

Loading…
Cancel
Save