sharing WIP

This commit is contained in:
zadam 2021-12-20 17:30:47 +01:00
parent 16d97b95af
commit 3860028a9e
25 changed files with 342 additions and 203 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -40,6 +40,7 @@
"electron-dl": "3.3.0",
"electron-find": "1.0.7",
"electron-window-state": "5.0.3",
"@electron/remote": "2.0.1",
"express": "4.17.2",
"express-partial-content": "^1.0.2",
"express-rate-limit": "5.5.1",
@ -83,7 +84,6 @@
"devDependencies": {
"cross-env": "7.0.3",
"electron": "16.0.5",
"@electron/remote": "2.0.1",
"electron-builder": "22.14.5",
"electron-packager": "15.4.0",
"electron-rebuild": "3.2.5",

View File

@ -136,7 +136,10 @@ class Note extends AbstractEntity {
return this.parentBranches;
}
/** @returns {Branch[]} */
/**
* @returns {Branch[]}
* @deprecated use getParentBranches() instead
*/
getBranches() {
return this.parentBranches;
}

View File

@ -130,18 +130,38 @@ class NoteShort {
}
}
/** @returns {string[]} */
getBranchIds() {
/**
* @returns {string[]}
*/
getParentBranchIds() {
return Object.values(this.parentToBranch);
}
/** @returns {Branch[]} */
getBranches() {
/**
* @returns {string[]}
* @deprecated use getParentBranchIds() instead
*/
getBranchIds() {
return this.getParentBranchIds();
}
/**
* @returns {Branch[]}
*/
getParentBranches() {
const branchIds = Object.values(this.parentToBranch);
return this.froca.getBranches(branchIds);
}
/**
* @returns {Branch[]}
* @deprecated use getParentBranches() instead
*/
getBranches() {
return this.getParentBranches();
}
/** @returns {boolean} */
hasChildren() {
return this.children.length > 0;
@ -620,8 +640,8 @@ class NoteShort {
});
}
hasAncestor(ancestorNote, visitedNoteIds = null) {
if (this.noteId === ancestorNote.noteId) {
hasAncestor(ancestorNoteId, visitedNoteIds = null) {
if (this.noteId === ancestorNoteId) {
return true;
}
@ -635,13 +655,13 @@ class NoteShort {
visitedNoteIds.add(this.noteId);
for (const templateNote of this.getTemplateNotes()) {
if (templateNote.hasAncestor(ancestorNote, visitedNoteIds)) {
if (templateNote.hasAncestor(ancestorNoteId, visitedNoteIds)) {
return true;
}
}
for (const parentNote of this.getParentNotes()) {
if (parentNote.hasAncestor(ancestorNote, visitedNoteIds)) {
if (parentNote.hasAncestor(ancestorNoteId, visitedNoteIds)) {
return true;
}
}

View File

@ -47,6 +47,7 @@ import MermaidWidget from "../widgets/mermaid.js";
import BookmarkButtons from "../widgets/bookmark_buttons.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/backlinks.js";
import SharedInfoWidget from "../widgets/shared_info.js";
export default class DesktopLayout {
constructor(customWidgets) {
@ -147,6 +148,7 @@ export default class DesktopLayout {
.titlePlacement("bottom"))
.button(new NoteActionsWidget())
)
.child(new SharedInfoWidget())
.child(new NoteUpdateStatusWidget())
.child(new BacklinksWidget())
.child(new MermaidWidget())

View File

@ -50,7 +50,7 @@ function isAffecting(attrRow, affectedNote) {
if (this.isInheritable) {
for (const owningNote of owningNotes) {
if (owningNote.hasAncestor(attrNote)) {
if (owningNote.hasAncestor(attrNote.noteId)) {
return true;
}
}

View File

@ -188,7 +188,7 @@ class Froca {
froca.notes[note.noteId].childToBranch = {};
}
const branches = [...note.getBranches(), ...note.getChildBranches()];
const branches = [...note.getParentBranches(), ...note.getChildBranches()];
searchResultNoteIds.forEach((resultNoteId, index) => branches.push({
// branchId should be repeatable since sometimes we reload some notes without rerendering the tree

View File

@ -340,10 +340,12 @@ function initHelpDropdown($el) {
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/"
function openHelp(e) {
window.open(wikiBaseUrl + $(e.target).attr("data-help-page"), '_blank');
}
function initHelpButtons($el) {
$el.on("click", "*[data-help-page]", e => {
window.open(wikiBaseUrl + $(e.target).attr("data-help-page"), '_blank');
});
$el.on("click", "*[data-help-page]", e => openHelp(e));
}
function filterAttributeName(name) {
@ -397,6 +399,7 @@ export default {
timeLimit,
initHelpDropdown,
initHelpButtons,
openHelp,
filterAttributeName,
isValidAttributeName
};

View File

@ -33,11 +33,7 @@ const TPL = `
.backlinks-close-ticker {
cursor: pointer;
}
.backlinks-ticker:hover {
opacity: 100%;
}
.backlinks-items {
z-index: 10;
position: absolute;

View File

@ -1,96 +1,32 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import protectedSessionService from "../services/protected_session.js";
import attributeService from "../services/attributes.js";
import SwitchWidget from "./switch.js";
const TPL = `
<div class="bookmark-switch">
<style>
/* The switch - the box around the slider */
.bookmark-switch .switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
float: right;
}
/* The slider */
.bookmark-switch .slider {
border-radius: 24px;
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--more-accented-background-color);
transition: .4s;
}
.bookmark-switch .slider:before {
border-radius: 50%;
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: var(--main-background-color);
-webkit-transition: .4s;
transition: .4s;
}
.bookmark-switch .slider.checked {
background-color: var(--main-text-color);
}
.bookmark-switch .slider.checked:before {
transform: translateX(26px);
}
</style>
<div class="add-bookmark-button">
Bookmark
&nbsp;
<span title="Bookmark this note to the left side panel">
<label class="switch">
<span class="slider"></span>
</span>
</div>
<div class="remove-bookmark-button">
Bookmark
&nbsp;
<span title="Remove bookmark">
<label class="switch">
<span class="slider checked"></span>
</span>
</div>
</div>`;
export default class BookmarkSwitchWidget extends NoteContextAwareWidget {
export default class BookmarkSwitchWidget extends SwitchWidget {
doRender() {
this.$widget = $(TPL);
super.doRender();
this.$addBookmarkButton = this.$widget.find(".add-bookmark-button");
this.$addBookmarkButton.on('click', () => attributeService.setLabel(this.noteId, 'bookmarked'));
this.$switchOnName.text("Bookmark");
this.$switchOnButton.attr("title", "Bookmark this note to the left side panel");
this.$removeBookmarkButton = this.$widget.find(".remove-bookmark-button");
this.$removeBookmarkButton.on('click', async () => {
for (const label of this.note.getLabels('bookmarked')) {
await attributeService.removeAttributeById(this.noteId, label.attributeId);
}
});
this.$switchOffName.text("Bookmark");
this.$switchOffButton.attr("title", "Remove bookmark");
}
async switchOff() {
for (const label of this.note.getLabels('bookmarked')) {
await attributeService.removeAttributeById(this.noteId, label.attributeId);
}
}
switchOn() {
return attributeService.setLabel(this.noteId, 'bookmarked');
}
refreshWithNote(note) {
const isBookmarked = note.hasLabel('bookmarked');
this.$addBookmarkButton.toggle(!isBookmarked);
this.$removeBookmarkButton.toggle(isBookmarked);
this.$switchOn.toggle(!isBookmarked);
this.$switchOff.toggle(isBookmarked);
}
entitiesReloadedEvent({loadResults}) {

View File

@ -1,89 +1,28 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import protectedSessionService from "../services/protected_session.js";
import SwitchWidget from "./switch.js";
const TPL = `
<div class="protected-note-switch">
<style>
/* The switch - the box around the slider */
.protected-note-switch .switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
float: right;
}
/* The slider */
.protected-note-switch .slider {
border-radius: 24px;
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--more-accented-background-color);
transition: .4s;
}
.protected-note-switch .slider:before {
border-radius: 50%;
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: var(--main-background-color);
-webkit-transition: .4s;
transition: .4s;
}
.protected-note-switch .slider.checked {
background-color: var(--main-text-color);
}
.protected-note-switch .slider.checked:before {
transform: translateX(26px);
}
</style>
<div class="protect-button">
Protect the note
&nbsp;
<span title="Note is not protected, click to make it protected">
<label class="switch">
<span class="slider"></span>
</span>
</div>
<div class="unprotect-button">
Unprotect the note
&nbsp;
<span title="Note is protected, click to make it unprotected">
<label class="switch">
<span class="slider checked"></span>
</span>
</div>
</div>`;
export default class ProtectedNoteSwitchWidget extends NoteContextAwareWidget {
export default class ProtectedNoteSwitchWidget extends SwitchWidget {
doRender() {
this.$widget = $(TPL);
super.doRender();
this.$protectButton = this.$widget.find(".protect-button");
this.$protectButton.on('click', () => protectedSessionService.protectNote(this.noteId, true, false));
this.$switchOnName.text("Protect the note");
this.$switchOnButton.attr("title", "Note is not protected, click to make it protected");
this.$unprotectButton = this.$widget.find(".unprotect-button");
this.$unprotectButton.on('click', () => protectedSessionService.protectNote(this.noteId, false, false));
this.$switchOffName.text("Unprotect the note");
this.$switchOffButton.attr("title", "Note is protected, click to make it unprotected");
}
switchOn() {
protectedSessionService.protectNote(this.noteId, true, false);
}
switchOff() {
protectedSessionService.protectNote(this.noteId, false, false)
}
refreshWithNote(note) {
this.$protectButton.toggle(!note.isProtected);
this.$unprotectButton.toggle(!!note.isProtected);
this.$switchOn.toggle(!note.isProtected);
this.$switchOff.toggle(!!note.isProtected);
}
entitiesReloadedEvent({loadResults}) {

View File

@ -3,6 +3,7 @@ import NoteTypeWidget from "../note_type.js";
import ProtectedNoteSwitchWidget from "../protected_note_switch.js";
import EditabilitySelectWidget from "../editability_select.js";
import BookmarkSwitchWidget from "../bookmark_switch.js";
import SharedSwitchWidget from "../shared_switch.js";
const TPL = `
<div class="basic-properties-widget">
@ -36,6 +37,8 @@ const TPL = `
</div>
<div class="bookmark-switch-container"></div>
<div class="shared-switch-container"></div>
</div>`;
export default class BasicPropertiesWidget extends NoteContextAwareWidget {
@ -46,12 +49,14 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
this.protectedNoteSwitchWidget = new ProtectedNoteSwitchWidget().contentSized();
this.editabilitySelectWidget = new EditabilitySelectWidget().contentSized();
this.bookmarkSwitchWidget = new BookmarkSwitchWidget().contentSized();
this.sharedSwitchWidget = new SharedSwitchWidget().contentSized();
this.child(
this.noteTypeWidget,
this.protectedNoteSwitchWidget,
this.editabilitySelectWidget,
this.bookmarkSwitchWidget
this.bookmarkSwitchWidget,
this.sharedSwitchWidget
);
}
@ -83,6 +88,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
this.$widget.find(".protected-note-switch-container").append(this.protectedNoteSwitchWidget.render());
this.$widget.find(".editability-select-container").append(this.editabilitySelectWidget.render());
this.$widget.find(".bookmark-switch-container").append(this.bookmarkSwitchWidget.render());
this.$widget.find(".shared-switch-container").append(this.sharedSwitchWidget.render());
}
async refreshWithNote(note) {

View File

@ -0,0 +1,45 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import options from "../services/options.js";
const TPL = `
<div class="shared-info-widget alert alert-warning">
<style>
.shared-info-widget {
margin: 10px;
contain: none;
padding: 10px;
font-weight: bold;
}
</style>
<span class="share-text"></span> <a class="share-link external"></a>. For help visit <a href="https://github.com/zadam/trilium/wiki/Sharing">wiki</a>.
</div>`;
export default class SharedInfoWidget extends NoteContextAwareWidget {
isEnabled() {
return super.isEnabled() && this.note.hasAncestor('share');
}
doRender() {
this.$widget = $(TPL);
this.$shareLink = this.$widget.find(".share-link");
this.$shareText = this.$widget.find(".share-text");
this.contentSized();
}
async refreshWithNote(note) {
const syncServerHost = options.get("syncServerHost");
let link;
if (syncServerHost) {
link = syncServerHost + "/share/" + note.noteId;
this.$shareText.text("This note is shared publicly on");
}
else {
link = location.protocol + '//' + location.host + location.pathname + "share/" + note.noteId;
this.$shareText.text("This note is shared locally on");
}
this.$shareLink.attr("href", link).text(link);
}
}

View File

@ -0,0 +1,68 @@
import SwitchWidget from "./switch.js";
import froca from "../services/froca.js";
import branchService from "../services/branches.js";
import server from "../services/server.js";
import utils from "../services/utils.js";
export default class SharedSwitchWidget extends SwitchWidget {
doRender() {
super.doRender();
this.$switchOnName.text("Shared");
this.$switchOnButton.attr("title", "Share the note");
this.$switchOffName.text("Shared");
this.$switchOffButton.attr("title", "Unshare the note");
this.$helpButton.attr("data-help-page", "Sharing").show();
this.$helpButton.on('click', e => utils.openHelp(e));
}
switchOn() {
branchService.cloneNoteTo(this.noteId, 'share');
}
async switchOff() {
const shareBranch = this.note.getParentBranches().find(b => b.parentNoteId === 'share');
if (!shareBranch) {
return;
}
if (this.note.getParentBranches().length === 1) {
const confirmDialog = await import('../dialogs/confirm.js');
const text = "This note exists only as a shared note, unsharing would delete it. Do you want to continue and thus delete this note?";
if (!await confirmDialog.confirm(text)) {
return;
}
}
await server.remove(`branches/${shareBranch.branchId}`);
}
async refreshWithNote(note) {
const isShared = note.hasAncestor('share');
const canBeUnshared = isShared && note.getParentBranches().find(b => b.parentNoteId === 'share');
const switchDisabled = isShared && !canBeUnshared;
this.$switchOn.toggle(!isShared);
this.$switchOff.toggle(!!isShared);
if (switchDisabled) {
this.$widget.attr("title", "Note cannot be unshared here because it is shared through inheritance from an ancestor.");
this.$switchOff.addClass("switch-disabled");
}
else {
this.$widget.removeAttr("title");
this.$switchOff.removeClass("switch-disabled");
}
}
entitiesReloadedEvent({loadResults}) {
if (loadResults.getBranches().find(b => b.noteId === this.noteId)) {
this.refresh();
}
}
}

View File

@ -0,0 +1,117 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = `
<div class="switch-widget">
<style>
.switch-widget {
display: flex;
align-items: center;
}
/* The switch - the box around the slider */
.switch-widget .switch {
position: relative;
display: block;
width: 50px;
height: 24px;
margin: 0;
}
.switch-on, .switch-off {
display: flex;
}
/* The slider */
.switch-widget .slider {
border-radius: 24px;
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--more-accented-background-color);
transition: .4s;
}
.switch-widget .slider:before {
border-radius: 50%;
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: var(--main-background-color);
-webkit-transition: .4s;
transition: .4s;
}
.switch-widget .slider.checked {
background-color: var(--main-text-color);
}
.switch-widget .slider.checked:before {
transform: translateX(26px);
}
.switch-widget .switch-disabled {
opacity: 70%;
pointer-events: none;
}
.switch-widget .switch-help-button {
font-weight: 900;
border: 0;
background: none;
cursor: pointer;
}
</style>
<div class="switch-on">
<span class="switch-on-name"></span>
&nbsp;
<span class="switch-on-button">
<label class="switch">
<span class="slider"></span>
</span>
</div>
<div class="switch-off">
<span class="switch-off-name"></span>
&nbsp;
<span class="switch-off-button">
<label class="switch">
<span class="slider checked"></span>
</span>
</div>
<button class="switch-help-button" type="button" data-help-page="" title="Open help page" style="display: none;">?</button>
</div>`;
export default class SwitchWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$switchOn = this.$widget.find(".switch-on");
this.$switchOnName = this.$widget.find(".switch-on-name");
this.$switchOnButton = this.$widget.find(".switch-on-button");
this.$switchOnButton.on('click', () => this.switchOn());
this.$switchOff = this.$widget.find(".switch-off");
this.$switchOffName = this.$widget.find(".switch-off-name");
this.$switchOffButton = this.$widget.find(".switch-off-button");
this.$switchOffButton.on('click', () => this.switchOff());
this.$helpButton = this.$widget.find(".switch-help-button");
}
switchOff() {}
switchOn() {}
}

View File

@ -73,7 +73,7 @@ function deleteNote(req) {
const taskContext = TaskContext.getInstance(taskId, 'delete-notes');
for (const branch of note.getBranches()) {
for (const branch of note.getParentBranches()) {
noteService.deleteBranch(branch, deleteId, taskContext);
}
@ -239,7 +239,7 @@ function getDeleteNotesPreview(req) {
const note = branch.getNote();
if (deleteAllClones || note.getBranches().length <= branchCountToDelete[branch.branchId]) {
if (deleteAllClones || note.getParentBranches().length <= branchCountToDelete[branch.branchId]) {
noteIdsToBeDeleted.add(note.noteId);
for (const childBranch of note.getChildBranches()) {

View File

@ -11,6 +11,12 @@ const becca = require("../becca/becca");
const beccaService = require("../becca/becca_service");
function cloneNoteToParent(noteId, parentBranchId, prefix) {
if (parentBranchId === 'share') {
const specialNotesService = require('./special_notes');
// share root note is created lazily
specialNotesService.getShareRoot();
}
const parentBranch = becca.getBranch(parentBranchId);
if (isNoteDeleted(noteId) || isNoteDeleted(parentBranch.noteId)) {

View File

@ -118,6 +118,7 @@ function createNewNote(params) {
note.setContent(params.content);
const branch = new Branch({
branchId: params.branchId,
noteId: note.noteId,
parentNoteId: params.parentNoteId,
notePosition: params.notePosition !== undefined ? params.notePosition : getNewNotePosition(params.parentNoteId),
@ -540,7 +541,7 @@ function deleteBranch(branch, deleteId, taskContext) {
branch.markAsDeleted(deleteId);
const note = branch.getNote();
const notDeletedBranches = note.getBranches();
const notDeletedBranches = note.getParentBranches();
if (notDeletedBranches.length === 0) {
for (const childBranch of note.getChildBranches()) {
@ -785,7 +786,7 @@ function duplicateSubtree(origNoteId, newParentNoteId) {
const origNote = becca.notes[origNoteId];
// might be null if orig note is not in the target newParentNoteId
const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === newParentNoteId);
const origBranch = origNote.getParentBranches().find(branch => branch.parentNoteId === newParentNoteId);
const noteIdMapping = getNoteIdMapping(origNote);

View File

@ -206,11 +206,12 @@ function getShareRoot() {
if (!shareRoot) {
shareRoot = noteService.createNewNote({
branchId: 'share',
noteId: 'share',
title: 'share',
title: 'Shared notes',
type: 'text',
content: '',
parentNoteId: getHiddenRoot().noteId
parentNoteId: 'root'
}).note;
}
@ -223,7 +224,7 @@ function createMissingSpecialNotes() {
getSinglesNoteRoot();
getSinglesNoteRoot();
getGlobalNoteMap();
getShareRoot();
// share root is not automatically created since it's visible in the tree and many won't need it/use it
const hidden = getHiddenRoot();
@ -238,5 +239,6 @@ module.exports = {
saveSqlConsole,
createSearchNote,
saveSearchNote,
createMissingSpecialNotes
createMissingSpecialNotes,
getShareRoot
};

View File

@ -173,7 +173,7 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder
let position = 10;
for (const note of notes) {
const branch = note.getBranches().find(b => b.parentNoteId === parentNoteId);
const branch = note.getParentBranches().find(b => b.parentNoteId === parentNoteId);
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
[position, branch.branchId]);

View File

@ -1,7 +1,6 @@
"use strict";
const AbstractEntity = require('./abstract_entity');
const shareRoot = require("../../share_root");
class Branch extends AbstractEntity {
constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) {
@ -18,10 +17,6 @@ class Branch extends AbstractEntity {
/** @param {boolean} */
this.isExpanded = !!isExpanded;
if (this.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
return;
}
const childNote = this.childNote;
const parentNote = this.parentNote;

View File

@ -38,7 +38,7 @@ function load() {
new Note(row);
}
for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0 AND noteId IN (${noteIdStr}) ORDER BY notePosition`)) {
for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0 AND parentNoteId IN (${noteIdStr}) ORDER BY notePosition`)) {
new Branch(row);
}