You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Add Share Notebook menu item
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -18,6 +18,6 @@ export const runtime = (comp: any): CommandRuntime => { | ||||
| 				}, | ||||
| 			}); | ||||
| 		}, | ||||
| 		enabledCondition: 'folderIsShareRootAndOwnedByUser || !folderIsShared', | ||||
| 		enabledCondition: 'joplinServerConnected && (folderIsShareRootAndOwnedByUser || !folderIsShared)', | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -18,5 +18,6 @@ export const runtime = (comp: any): CommandRuntime => { | ||||
| 				}, | ||||
| 			}); | ||||
| 		}, | ||||
| 		enabledCondition: 'joplinServerConnected && oneNoteSelected', | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -45,5 +45,7 @@ export default function() { | ||||
| 		'editor.swapLineUp', | ||||
| 		'editor.swapLineDown', | ||||
| 		'toggleSafeMode', | ||||
| 		'showShareNoteDialog', | ||||
| 		'showShareFolderDialog', | ||||
| 	]; | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								packages/lib/services/WhenClause.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								packages/lib/services/WhenClause.test.ts
									
									
									
									
									
										Normal 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); | ||||
| 	}); | ||||
|  | ||||
| }); | ||||
| @@ -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}`); | ||||
| 		} | ||||
|   | ||||
| @@ -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, | ||||
| 	}; | ||||
| } | ||||
|   | ||||
| @@ -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. | ||||
| 	 */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user