1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Desktop: Add Share Notebook menu item

This commit is contained in:
Laurent Cozic 2021-05-16 15:21:55 +02:00
parent 12cc64008b
commit 6f2f24171d
11 changed files with 153 additions and 22 deletions

View File

@ -1106,6 +1106,9 @@ packages/lib/services/UndoRedoService.js.map
packages/lib/services/WhenClause.d.ts
packages/lib/services/WhenClause.js
packages/lib/services/WhenClause.js.map
packages/lib/services/WhenClause.test.d.ts
packages/lib/services/WhenClause.test.js
packages/lib/services/WhenClause.test.js.map
packages/lib/services/commands/MenuUtils.d.ts
packages/lib/services/commands/MenuUtils.js
packages/lib/services/commands/MenuUtils.js.map

View File

@ -76,7 +76,7 @@ module.exports = {
// Warn only for now because fixing everything would take too much
// refactoring, but new code should try to stick to it.
'complexity': ['warn', { max: 10 }],
// 'complexity': ['warn', { max: 10 }],
// Checks rules of Hooks
'react-hooks/rules-of-hooks': 'error',

3
.gitignore vendored
View File

@ -1092,6 +1092,9 @@ packages/lib/services/UndoRedoService.js.map
packages/lib/services/WhenClause.d.ts
packages/lib/services/WhenClause.js
packages/lib/services/WhenClause.js.map
packages/lib/services/WhenClause.test.d.ts
packages/lib/services/WhenClause.test.js
packages/lib/services/WhenClause.test.js.map
packages/lib/services/commands/MenuUtils.d.ts
packages/lib/services/commands/MenuUtils.js
packages/lib/services/commands/MenuUtils.js.map

View File

@ -18,6 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
},
});
},
enabledCondition: 'folderIsShareRootAndOwnedByUser || !folderIsShared',
enabledCondition: 'joplinServerConnected && (folderIsShareRootAndOwnedByUser || !folderIsShared)',
};
};

View File

@ -18,5 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
},
});
},
enabledCondition: 'joplinServerConnected && oneNoteSelected',
};
};

View File

@ -695,11 +695,18 @@ function useMenu(props: Props) {
},
],
},
folder: {
label: _('Note&book'),
submenu: [
menuItemDic.showShareFolderDialog,
],
},
note: {
label: _('&Note'),
submenu: [
menuItemDic.toggleExternalEditing,
menuItemDic.setTags,
menuItemDic.showShareNoteDialog,
separator(),
menuItemDic.showNoteContentProperties,
],
@ -818,6 +825,7 @@ function useMenu(props: Props) {
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.folder,
rootMenus.note,
rootMenus.tools,
rootMenus.help,

View File

@ -45,5 +45,7 @@ export default function() {
'editor.swapLineUp',
'editor.swapLineDown',
'toggleSafeMode',
'showShareNoteDialog',
'showShareFolderDialog',
];
}

View File

@ -0,0 +1,39 @@
import WhenClause from './WhenClause';
describe('WhenClause', function() {
test('should work with simple condition', async function() {
const wc = new WhenClause('test1 && test2');
expect(wc.evaluate({
test1: true,
test2: true,
})).toBe(true);
expect(wc.evaluate({
test1: true,
test2: false,
})).toBe(false);
});
test('should work with parenthesis', async function() {
const wc = new WhenClause('(test1 && test2) || test3 && (test4 && !test5)');
expect(wc.evaluate({
test1: true,
test2: true,
test3: true,
test4: true,
test5: true,
})).toBe(true);
expect(wc.evaluate({
test1: false,
test2: true,
test3: false,
test4: false,
test5: true,
})).toBe(false);
});
});

View File

@ -1,17 +1,71 @@
import { ContextKeyExpr, ContextKeyExpression } from './contextkey/contextkey';
import { ContextKeyExpr, ContextKeyExpression, IContext } from './contextkey/contextkey';
// We would like to support expressions with brackets but VSCode When Clauses
// don't support this. To support this, we split the expressions with brackets
// into sub-expressions, which can then be parsed and executed separately by the
// When Clause library.
interface AdvancedExpression {
// (test1 && test2) || test3
original: string;
// __sub_1 || test3
compiledText: string;
// { __sub_1: "test1 && test2" }
subExpressions: any;
}
function parseAdvancedExpression(advancedExpression: string): AdvancedExpression {
let subExpressionIndex = -1;
let subExpressions: string = '';
let currentSubExpressionKey = '';
const subContext: any = {};
let inBrackets = false;
for (let i = 0; i < advancedExpression.length; i++) {
const c = advancedExpression[i];
if (c === '(') {
if (inBrackets) throw new Error('Nested brackets not supported');
inBrackets = true;
subExpressionIndex++;
currentSubExpressionKey = `__sub_${subExpressionIndex}`;
subContext[currentSubExpressionKey] = '';
continue;
}
if (c === ')') {
if (!inBrackets) throw new Error('Closing bracket without an opening one');
inBrackets = false;
subExpressions += currentSubExpressionKey;
currentSubExpressionKey = '';
continue;
}
if (inBrackets) {
subContext[currentSubExpressionKey] += c;
} else {
subExpressions += c;
}
}
return {
compiledText: subExpressions,
subExpressions: subContext,
original: advancedExpression,
};
}
export default class WhenClause {
private expression_: string;
private expression_: AdvancedExpression;
private validate_: boolean;
private rules_: ContextKeyExpression = null;
private ruleCache_: Record<string, ContextKeyExpression> = {};
constructor(expression: string, validate: boolean) {
this.expression_ = expression;
public constructor(expression: string, validate: boolean = true) {
this.expression_ = parseAdvancedExpression(expression);
this.validate_ = validate;
}
private createContext(ctx: any) {
private createContext(ctx: any): IContext {
return {
getValue: (key: string) => {
return ctx[key];
@ -19,21 +73,28 @@ export default class WhenClause {
};
}
private get rules(): ContextKeyExpression {
if (!this.rules_) {
this.rules_ = ContextKeyExpr.deserialize(this.expression_);
}
return this.rules_;
private rules(exp: string): ContextKeyExpression {
if (this.ruleCache_[exp]) return this.ruleCache_[exp];
this.ruleCache_[exp] = ContextKeyExpr.deserialize(exp);
return this.ruleCache_[exp];
}
public evaluate(context: any): boolean {
if (this.validate_) this.validate(context);
return this.rules.evaluate(this.createContext(context));
const subContext: any = {};
for (const k in this.expression_.subExpressions) {
const subExp = this.expression_.subExpressions[k];
subContext[k] = this.rules(subExp).evaluate(this.createContext(context));
}
const fullContext = { ...context, ...subContext };
return this.rules(this.expression_.compiledText).evaluate(this.createContext(fullContext));
}
public validate(context: any) {
const keys = this.rules.keys();
const keys = this.rules(this.expression_.original.replace(/[()]/g, ' ')).keys();
for (const key of keys) {
if (!(key in context)) throw new Error(`No such key: ${key}`);
}

View File

@ -27,6 +27,7 @@ export interface WhenClauseContext {
noteIsHtml: boolean;
folderIsShareRootAndOwnedByUser: boolean;
folderIsShared: boolean;
joplinServerConnected: boolean;
}
export default function stateToWhenClauseContext(state: State, options: WhenClauseContextOptions = null): WhenClauseContext {
@ -42,7 +43,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
// const commandNoteId = options.commandNoteId || selectedNoteId;
// const commandNote:NoteEntity = commandNoteId ? BaseModel.byId(state.notes, commandNoteId) : null;
const commandFolderId = options.commandFolderId;
const commandFolderId = options.commandFolderId || state.selectedFolderId;
const commandFolder: FolderEntity = commandFolderId ? BaseModel.byId(state.folders, commandFolderId) : null;
return {
@ -75,5 +76,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
// Current context folder
folderIsShareRootAndOwnedByUser: commandFolder ? isRootSharedFolder(commandFolder) && isSharedFolderOwner(state, commandFolder.id) : false,
folderIsShared: commandFolder ? !!commandFolder.share_id : false,
joplinServerConnected: state.settings['sync.target'] === 9,
};
}

View File

@ -27,11 +27,12 @@ export interface Command {
execute(...args: any[]): Promise<any | void>;
/**
* Defines whether the command should be enabled or disabled, which in turns affects
* the enabled state of any associated button or menu item.
* Defines whether the command should be enabled or disabled, which in turns
* affects the enabled state of any associated button or menu item.
*
* The condition should be expressed as a "when-clause" (as in Visual Studio Code). It's a simple boolean expression that evaluates to
* `true` or `false`. It supports the following operators:
* The condition should be expressed as a "when-clause" (as in Visual Studio
* Code). It's a simple boolean expression that evaluates to `true` or
* `false`. It supports the following operators:
*
* Operator | Symbol | Example
* -- | -- | --
@ -40,7 +41,17 @@ export interface Command {
* Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
* And | && | "oneNoteSelected && !inConflictFolder"
*
* Currently the supported context variables aren't documented, but you can [find the list here](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts).
* Joplin, unlike VSCode, also supports parenthesis, which allows creating
* more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
* level of parenthesis is possible (nested ones aren't supported).
*
* Currently the supported context variables aren't documented, but you can
* find the list below:
*
* - [Global When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts).
* - [Desktop app When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts).
*
* Note: Commands are enabled by default unless you use this property.
*/