1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-26 22:41:17 +02:00

All: Add support for sharing notebooks with Joplin Server (#4772)

- Joplin Server: Adds support for sharing a notebook
- Desktop: Adds support for sharing a notebook with Joplin Server
- Mobile: Adds support for reading and writing to a shared notebook (not possible to share a notebook)
- Cli: Adds support for reading and writing to a shared notebook (not possible to share a notebook)
This commit is contained in:
Laurent
2021-05-13 18:57:37 +02:00
committed by GitHub
parent 09ad70983a
commit 0765cf5955
165 changed files with 8803 additions and 3072 deletions

View File

@@ -2,10 +2,11 @@ import { FolderEntity } from '../services/database/types';
import BaseModel from '../BaseModel';
import time from '../time';
import { _ } from '../locale';
import Note from './Note';
import Database from '../database';
import BaseItem from './BaseItem';
import Resource from './Resource';
import { isRootSharedFolder } from '../services/share/reducer';
const { substrWithEllipsis } = require('../string-utils.js');
interface FolderEntityWithChildren extends FolderEntity {
@@ -75,8 +76,10 @@ export default class Folder extends BaseItem {
}
static async delete(folderId: string, options: any = null) {
if (!options) options = {};
if (!('deleteChildren' in options)) options.deleteChildren = true;
options = {
deleteChildren: true,
...options,
};
const folder = await Folder.load(folderId);
if (!folder) return; // noop
@@ -256,6 +259,120 @@ export default class Folder extends BaseItem {
}
}
public static async allChildrenFolders(folderId: string): Promise<FolderEntity[]> {
const sql = `
WITH RECURSIVE
folders_cte(id, parent_id, share_id) AS (
SELECT id, parent_id, share_id
FROM folders
WHERE parent_id = ?
UNION ALL
SELECT folders.id, folders.parent_id, folders.share_id
FROM folders
INNER JOIN folders_cte AS folders_cte ON (folders.parent_id = folders_cte.id)
)
SELECT id, parent_id, share_id FROM folders_cte;
`;
return this.db().selectAll(sql, [folderId]);
}
private static async rootSharedFolders(): Promise<FolderEntity[]> {
return this.db().selectAll('SELECT id, share_id FROM folders WHERE parent_id = "" AND share_id != ""');
}
public static async updateFolderShareIds(): Promise<void> {
// Get all the sub-folders of the shared folders, and set the share_id
// property.
const rootFolders = await this.rootSharedFolders();
let sharedFolderIds: string[] = [];
for (const rootFolder of rootFolders) {
const children = await this.allChildrenFolders(rootFolder.id);
for (const child of children) {
if (child.share_id !== rootFolder.share_id) {
await this.save({
id: child.id,
share_id: rootFolder.share_id,
updated_time: Date.now(),
}, { autoTimestamp: false });
}
}
sharedFolderIds.push(rootFolder.id);
sharedFolderIds = sharedFolderIds.concat(children.map(c => c.id));
}
// Now that we've set the share ID on all the sub-folders of the shared
// folders, those that remain should not be shared anymore. For example,
// if they've been moved out of a shared folder.
// await this.unshareItems(ModelType.Folder, sharedFolderIds);
const sql = ['SELECT id FROM folders WHERE share_id != ""'];
if (sharedFolderIds.length) {
sql.push(` AND id NOT IN ("${sharedFolderIds.join('","')}")`);
}
const foldersToUnshare = await this.db().selectAll(sql.join(' '));
for (const item of foldersToUnshare) {
await this.save({
id: item.id,
share_id: '',
updated_time: Date.now(),
}, { autoTimestamp: false });
}
}
public static async updateNoteShareIds() {
// Find all the notes where the share_id is not the same as the
// parent share_id because we only need to update those.
const rows = await this.db().selectAll(`
SELECT notes.id, folders.share_id
FROM notes
LEFT JOIN folders ON notes.parent_id = folders.id
WHERE notes.share_id != folders.share_id
`);
for (const row of rows) {
await Note.save({
id: row.id,
share_id: row.share_id || '',
updated_time: Date.now(),
}, { autoTimestamp: false });
}
}
public static async updateResourceShareIds() {
// Find all resources where share_id is different from parent note
// share_id. Then update share_id on all these resources. Essentially it
// makes it match the resource share_id to the note share_id.
const rows = await this.db().selectAll(`
SELECT r.id, n.share_id, n.is_shared
FROM note_resources nr
LEFT JOIN resources r ON nr.resource_id = r.id
LEFT JOIN notes n ON nr.note_id = n.id
WHERE n.share_id != r.share_id
OR n.is_shared != r.is_shared
`);
for (const row of rows) {
await Resource.save({
id: row.id,
share_id: row.share_id || '',
is_shared: row.is_shared,
updated_time: Date.now(),
}, { autoTimestamp: false });
}
}
public static async updateAllShareIds() {
await this.updateFolderShareIds();
await this.updateNoteShareIds();
await this.updateResourceShareIds();
}
static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
const all = folders ? folders : await this.all(options);
@@ -392,6 +509,9 @@ export default class Folder extends BaseItem {
static async canNestUnder(folderId: string, targetFolderId: string) {
if (folderId === targetFolderId) return false;
const folder = await Folder.load(folderId);
if (isRootSharedFolder(folder)) return false;
const conflictFolderId = Folder.conflictFolderId();
if (folderId == conflictFolderId || targetFolderId == conflictFolderId) return false;