mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	Merge pull request #1549 from TriliumNext/feature/touchbar
Basic touchbar integration
This commit is contained in:
		| @@ -3,7 +3,7 @@ import bundleService from "../services/bundle.js"; | |||||||
| import RootCommandExecutor from "./root_command_executor.js"; | import RootCommandExecutor from "./root_command_executor.js"; | ||||||
| import Entrypoints, { type SqlExecuteResults } from "./entrypoints.js"; | import Entrypoints, { type SqlExecuteResults } from "./entrypoints.js"; | ||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import utils from "../services/utils.js"; | import utils, { hasTouchBar } from "../services/utils.js"; | ||||||
| import zoomComponent from "./zoom.js"; | import zoomComponent from "./zoom.js"; | ||||||
| import TabManager from "./tab_manager.js"; | import TabManager from "./tab_manager.js"; | ||||||
| import Component from "./component.js"; | import Component from "./component.js"; | ||||||
| @@ -24,7 +24,8 @@ import type NoteTreeWidget from "../widgets/note_tree.js"; | |||||||
| import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | ||||||
| import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | ||||||
| import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js"; | import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js"; | ||||||
| import type FAttribute from "../entities/fattribute.js"; | import type { NativeImage, TouchBar } from "electron"; | ||||||
|  | import TouchBarComponent from "./touch_bar.js"; | ||||||
|  |  | ||||||
| interface Layout { | interface Layout { | ||||||
|     getRootWidget: (appContext: AppContext) => RootWidget; |     getRootWidget: (appContext: AppContext) => RootWidget; | ||||||
| @@ -170,6 +171,8 @@ export type CommandMappings = { | |||||||
|     moveNoteDownInHierarchy: ContextMenuCommandData; |     moveNoteDownInHierarchy: ContextMenuCommandData; | ||||||
|     selectAllNotesInParent: ContextMenuCommandData; |     selectAllNotesInParent: ContextMenuCommandData; | ||||||
|  |  | ||||||
|  |     createNoteIntoInbox: CommandData; | ||||||
|  |  | ||||||
|     addNoteLauncher: ContextMenuCommandData; |     addNoteLauncher: ContextMenuCommandData; | ||||||
|     addScriptLauncher: ContextMenuCommandData; |     addScriptLauncher: ContextMenuCommandData; | ||||||
|     addWidgetLauncher: ContextMenuCommandData; |     addWidgetLauncher: ContextMenuCommandData; | ||||||
| @@ -249,6 +252,7 @@ export type CommandMappings = { | |||||||
|     scrollToEnd: CommandData; |     scrollToEnd: CommandData; | ||||||
|     closeThisNoteSplit: CommandData; |     closeThisNoteSplit: CommandData; | ||||||
|     moveThisNoteSplit: CommandData & { isMovingLeft: boolean }; |     moveThisNoteSplit: CommandData & { isMovingLeft: boolean }; | ||||||
|  |     jumpToNote: CommandData; | ||||||
|  |  | ||||||
|     // Geomap |     // Geomap | ||||||
|     deleteFromMap: { noteId: string }; |     deleteFromMap: { noteId: string }; | ||||||
| @@ -263,6 +267,14 @@ export type CommandMappings = { | |||||||
|  |  | ||||||
|     refreshResults: {}; |     refreshResults: {}; | ||||||
|     refreshSearchDefinition: {}; |     refreshSearchDefinition: {}; | ||||||
|  |  | ||||||
|  |     geoMapCreateChildNote: CommandData; | ||||||
|  |  | ||||||
|  |     buildTouchBar: CommandData & { | ||||||
|  |         TouchBar: typeof TouchBar; | ||||||
|  |         buildIcon(name: string): NativeImage; | ||||||
|  |     }; | ||||||
|  |     refreshTouchBar: CommandData; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| type EventMappings = { | type EventMappings = { | ||||||
| @@ -467,6 +479,10 @@ export class AppContext extends Component { | |||||||
|         if (utils.isElectron()) { |         if (utils.isElectron()) { | ||||||
|             this.child(zoomComponent); |             this.child(zoomComponent); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (hasTouchBar) { | ||||||
|  |             this.child(new TouchBarComponent()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderWidgets() { |     renderWidgets() { | ||||||
|   | |||||||
							
								
								
									
										135
									
								
								src/public/app/components/touch_bar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/public/app/components/touch_bar.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | import utils from "../services/utils.js"; | ||||||
|  | import Component from "./component.js"; | ||||||
|  | import appContext from "./app_context.js"; | ||||||
|  | import type { TouchBarButton, TouchBarGroup, TouchBarSegmentedControl, TouchBarSpacer } from "@electron/remote"; | ||||||
|  |  | ||||||
|  | export type TouchBarItem = (TouchBarButton | TouchBarSpacer | TouchBarGroup | TouchBarSegmentedControl); | ||||||
|  |  | ||||||
|  | export function buildSelectedBackgroundColor(isSelected: boolean) { | ||||||
|  |     return isSelected ? "#757575" : undefined; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default class TouchBarComponent extends Component { | ||||||
|  |  | ||||||
|  |     nativeImage: typeof import("electron").nativeImage; | ||||||
|  |     remote: typeof import("@electron/remote"); | ||||||
|  |     lastFocusedComponent?: Component; | ||||||
|  |     private $activeModal?: JQuery<HTMLElement>; | ||||||
|  |  | ||||||
|  |     constructor() { | ||||||
|  |         super(); | ||||||
|  |         this.nativeImage = utils.dynamicRequire("electron").nativeImage; | ||||||
|  |         this.remote = utils.dynamicRequire("@electron/remote") as typeof import("@electron/remote"); | ||||||
|  |         this.$widget = $("<div>"); | ||||||
|  |  | ||||||
|  |         $(window).on("focusin", async (e) => { | ||||||
|  |             const $target = $(e.target); | ||||||
|  |  | ||||||
|  |             this.$activeModal = $target.closest(".modal-dialog"); | ||||||
|  |             const parentComponentEl = $target.closest(".component"); | ||||||
|  |             this.lastFocusedComponent = appContext.getComponentByEl(parentComponentEl[0]); | ||||||
|  |             this.#refreshTouchBar(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildIcon(name: string) { | ||||||
|  |         const sourceImage = this.nativeImage.createFromNamedImage(name, [-1, 0, 1]); | ||||||
|  |         const { width, height } = sourceImage.getSize(); | ||||||
|  |         const newImage = this.nativeImage.createEmpty(); | ||||||
|  |         newImage.addRepresentation({ | ||||||
|  |             scaleFactor: 1, | ||||||
|  |             width: width / 2, | ||||||
|  |             height: height / 2, | ||||||
|  |             buffer: sourceImage.resize({ height: height / 2 }).toBitmap() | ||||||
|  |         }); | ||||||
|  |         newImage.addRepresentation({ | ||||||
|  |             scaleFactor: 2, | ||||||
|  |             width: width, | ||||||
|  |             height: height, | ||||||
|  |             buffer: sourceImage.toBitmap() | ||||||
|  |         }); | ||||||
|  |         return newImage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #refreshTouchBar() { | ||||||
|  |         const { TouchBar } = this.remote; | ||||||
|  |         const parentComponent = this.lastFocusedComponent; | ||||||
|  |         let touchBar = null; | ||||||
|  |  | ||||||
|  |         if (this.$activeModal?.length) { | ||||||
|  |             touchBar = this.#buildModalTouchBar(); | ||||||
|  |         } else if (parentComponent) { | ||||||
|  |             const items = parentComponent.triggerCommand("buildTouchBar", { | ||||||
|  |                 TouchBar, | ||||||
|  |                 buildIcon: this.buildIcon.bind(this) | ||||||
|  |             }) as unknown as TouchBarItem[]; | ||||||
|  |             touchBar = this.#buildTouchBar(items); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (touchBar) { | ||||||
|  |             this.remote.getCurrentWindow().setTouchBar(touchBar); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #buildModalTouchBar() { | ||||||
|  |         const { TouchBar } = this.remote; | ||||||
|  |         const { TouchBarButton, TouchBarLabel, TouchBarSpacer } = this.remote.TouchBar; | ||||||
|  |         const items: TouchBarItem[] = []; | ||||||
|  |  | ||||||
|  |         // Look for the modal title. | ||||||
|  |         const $title = this.$activeModal?.find(".modal-title"); | ||||||
|  |         if ($title?.length) { | ||||||
|  |             items.push(new TouchBarLabel({ label: $title.text() })) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         items.push(new TouchBarSpacer({ size: "flexible" })); | ||||||
|  |  | ||||||
|  |         // Look for buttons in the modal. | ||||||
|  |         const $buttons = this.$activeModal?.find(".modal-footer button"); | ||||||
|  |         for (const button of $buttons ?? []) { | ||||||
|  |             items.push(new TouchBarButton({ | ||||||
|  |                 label: button.innerText, | ||||||
|  |                 click: () => button.click(), | ||||||
|  |                 enabled: !button.hasAttribute("disabled") | ||||||
|  |             })); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         items.push(new TouchBarSpacer({ size: "flexible" })); | ||||||
|  |         return new TouchBar({ items }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #buildTouchBar(componentSpecificItems?: TouchBarItem[]) { | ||||||
|  |         const { TouchBar } = this.remote; | ||||||
|  |         const { TouchBarButton, TouchBarSpacer, TouchBarGroup, TouchBarSegmentedControl, TouchBarOtherItemsProxy } = this.remote.TouchBar; | ||||||
|  |  | ||||||
|  |         // Disregard recursive calls or empty results. | ||||||
|  |         if (!componentSpecificItems || "then" in componentSpecificItems) { | ||||||
|  |             componentSpecificItems = []; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const items = [ | ||||||
|  |             new TouchBarButton({ | ||||||
|  |                 icon: this.buildIcon("NSTouchBarComposeTemplate"), | ||||||
|  |                 click: () => this.triggerCommand("createNoteIntoInbox") | ||||||
|  |             }), | ||||||
|  |             new TouchBarSpacer({ size: "small" }), | ||||||
|  |             ...componentSpecificItems, | ||||||
|  |             new TouchBarSpacer({ size: "flexible" }), | ||||||
|  |             new TouchBarOtherItemsProxy(), | ||||||
|  |             new TouchBarButton({ | ||||||
|  |                 icon: this.buildIcon("NSTouchBarAddDetailTemplate"), | ||||||
|  |                 click: () => this.triggerCommand("jumpToNote") | ||||||
|  |             }) | ||||||
|  |         ].flat(); | ||||||
|  |  | ||||||
|  |         console.log("Update ", items); | ||||||
|  |         return new TouchBar({ | ||||||
|  |             items | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     refreshTouchBarEvent() { | ||||||
|  |         this.#refreshTouchBar(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -83,7 +83,7 @@ import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_ref | |||||||
| import ScrollPaddingWidget from "../widgets/scroll_padding.js"; | import ScrollPaddingWidget from "../widgets/scroll_padding.js"; | ||||||
| import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | ||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import utils from "../services/utils.js"; | import utils, { hasTouchBar } from "../services/utils.js"; | ||||||
| import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js"; | import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js"; | ||||||
| import ContextualHelpButton from "../widgets/floating_buttons/help_button.js"; | import ContextualHelpButton from "../widgets/floating_buttons/help_button.js"; | ||||||
| import CloseZenButton from "../widgets/close_zen_button.js"; | import CloseZenButton from "../widgets/close_zen_button.js"; | ||||||
|   | |||||||
| @@ -147,6 +147,8 @@ function isMac() { | |||||||
|     return navigator.platform.indexOf("Mac") > -1; |     return navigator.platform.indexOf("Mac") > -1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export const hasTouchBar = (isMac() && isElectron()); | ||||||
|  |  | ||||||
| function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) { | function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) { | ||||||
|     return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); |     return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -339,6 +339,11 @@ declare global { | |||||||
|             mention: MentionConfig |             mention: MentionConfig | ||||||
|         }); |         }); | ||||||
|         enableReadOnlyMode(reason: string); |         enableReadOnlyMode(reason: string); | ||||||
|  |         commands: { | ||||||
|  |             get(name: string): { | ||||||
|  |                 value: unknown; | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|         model: { |         model: { | ||||||
|             document: { |             document: { | ||||||
|                 on(event: string, cb: () => void); |                 on(event: string, cb: () => void); | ||||||
|   | |||||||
| @@ -23,7 +23,6 @@ export default class EditButton extends OnClickButtonWidget { | |||||||
|                     this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; |                     this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; | ||||||
|                     appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); |                     appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); | ||||||
|                 } |                 } | ||||||
|                 this.refresh(); |  | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -68,6 +67,10 @@ export default class EditButton extends OnClickButtonWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     readOnlyTemporarilyDisabledEvent() { | ||||||
|  |         this.refresh(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> { |     async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> { | ||||||
|         if (this.isNote(noteId)) { |         if (this.isNote(noteId)) { | ||||||
|             await this.refresh(); |             await this.refresh(); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import NoteContextAwareWidget from "./note_context_aware_widget.js"; | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
| import NoteListRenderer from "../services/note_list_renderer.js"; | import NoteListRenderer from "../services/note_list_renderer.js"; | ||||||
| import type FNote from "../entities/fnote.js"; | import type FNote from "../entities/fnote.js"; | ||||||
| import type { EventData } from "../components/app_context.js"; | import type { CommandListener, CommandListenerData, EventData } from "../components/app_context.js"; | ||||||
| import type ViewMode from "./view_widgets/view_mode.js"; | import type ViewMode from "./view_widgets/view_mode.js"; | ||||||
|  |  | ||||||
| const TPL = /*html*/` | const TPL = /*html*/` | ||||||
| @@ -127,4 +127,11 @@ export default class NoteListWidget extends NoteContextAwareWidget { | |||||||
|             this.checkRenderStatus(); |             this.checkRenderStatus(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         if (this.viewMode && "buildTouchBarCommand" in this.viewMode) { | ||||||
|  |             return (this.viewMode as CommandListener<"buildTouchBar">).buildTouchBarCommand(data); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ import type FNote from "../entities/fnote.js"; | |||||||
| import type { NoteType } from "../entities/fnote.js"; | import type { NoteType } from "../entities/fnote.js"; | ||||||
| import type { AttributeRow, BranchRow } from "../services/load_results.js"; | import type { AttributeRow, BranchRow } from "../services/load_results.js"; | ||||||
| import type { SetNoteOpts } from "../components/note_context.js"; | import type { SetNoteOpts } from "../components/note_context.js"; | ||||||
|  | import type { TouchBarItem } from "../components/touch_bar.js"; | ||||||
|  | import type { TreeCommandNames } from "../menus/tree_context_menu.js"; | ||||||
|  |  | ||||||
| const TPL = /*html*/` | const TPL = /*html*/` | ||||||
| <div class="tree-wrapper"> | <div class="tree-wrapper"> | ||||||
| @@ -1763,4 +1765,38 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         appContext.tabManager.getActiveContext()?.setNote(resp.note.noteId); |         appContext.tabManager.getActiveContext()?.setNote(resp.note.noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         const triggerCommand = (command: TreeCommandNames) => { | ||||||
|  |             const node = this.getActiveNode(); | ||||||
|  |             const notePath = treeService.getNotePath(node); | ||||||
|  |  | ||||||
|  |             this.triggerCommand<TreeCommandNames>(command, { | ||||||
|  |                 node, | ||||||
|  |                 notePath, | ||||||
|  |                 noteId: node.data.noteId, | ||||||
|  |                 selectedOrActiveBranchIds: this.getSelectedOrActiveBranchIds(node), | ||||||
|  |                 selectedOrActiveNoteIds: this.getSelectedOrActiveNoteIds(node) | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const items: TouchBarItem[] = [ | ||||||
|  |             new TouchBar.TouchBarButton({ | ||||||
|  |                 icon: buildIcon("NSImageNameTouchBarAddTemplate"), | ||||||
|  |                 click: () => { | ||||||
|  |                     const node = this.getActiveNode(); | ||||||
|  |                     const notePath = treeService.getNotePath(node); | ||||||
|  |                     noteCreateService.createNote(notePath, { | ||||||
|  |                         isProtected: node.data.isProtected | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }), | ||||||
|  |             new TouchBar.TouchBarButton({ | ||||||
|  |                 icon: buildIcon("NSImageNameTouchBarDeleteTemplate"), | ||||||
|  |                 click: () => triggerCommand("deleteNotes") | ||||||
|  |             }) | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         return items; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| import type { EventData } from "../../components/app_context.js"; | import type { CommandListenerData, EventData } from "../../components/app_context.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import { t } from "../../services/i18n.js"; | import { t } from "../../services/i18n.js"; | ||||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | import keyboardActionService from "../../services/keyboard_actions.js"; | ||||||
| import options from "../../services/options.js"; | import options from "../../services/options.js"; | ||||||
| import AbstractCodeTypeWidget from "./abstract_code_type_widget.js"; | import AbstractCodeTypeWidget from "./abstract_code_type_widget.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
|  | import type { TouchBarItem } from "../../components/touch_bar.js"; | ||||||
|  | import { hasTouchBar } from "../../services/utils.js"; | ||||||
|  |  | ||||||
| const TPL = /*html*/` | const TPL = /*html*/` | ||||||
| <div class="note-detail-code note-detail-printable"> | <div class="note-detail-code note-detail-printable"> | ||||||
| @@ -61,6 +64,10 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.show(); |         this.show(); | ||||||
|  |  | ||||||
|  |         if (this.parent && hasTouchBar) { | ||||||
|  |             this.triggerCommand("refreshTouchBar"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getData() { |     getData() { | ||||||
| @@ -78,4 +85,19 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { | |||||||
|  |  | ||||||
|         resolve(this.codeEditor); |         resolve(this.codeEditor); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         const items: TouchBarItem[] = []; | ||||||
|  |         const note = this.note; | ||||||
|  |  | ||||||
|  |         if (note?.mime.startsWith("application/javascript") || note?.mime === "text/x-sqlite;schema=trilium") { | ||||||
|  |             items.push(new TouchBar.TouchBarButton({ | ||||||
|  |                 icon: buildIcon("NSImageNameTouchBarPlayTemplate"), | ||||||
|  |                 click: () => appContext.triggerCommand("runActiveNote") | ||||||
|  |             })); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return items; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,18 +2,19 @@ import { t } from "../../services/i18n.js"; | |||||||
| import libraryLoader from "../../services/library_loader.js"; | import libraryLoader from "../../services/library_loader.js"; | ||||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||||
| import mimeTypesService from "../../services/mime_types.js"; | import mimeTypesService from "../../services/mime_types.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils, { hasTouchBar } from "../../services/utils.js"; | ||||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | import keyboardActionService from "../../services/keyboard_actions.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | ||||||
| import link from "../../services/link.js"; | import link from "../../services/link.js"; | ||||||
| import appContext, { type EventData } from "../../components/app_context.js"; | import appContext, { type CommandListenerData, type EventData } from "../../components/app_context.js"; | ||||||
| import dialogService from "../../services/dialog.js"; | import dialogService from "../../services/dialog.js"; | ||||||
| import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js"; | import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js"; | ||||||
| import options from "../../services/options.js"; | import options from "../../services/options.js"; | ||||||
| import toast from "../../services/toast.js"; | import toast from "../../services/toast.js"; | ||||||
| import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js"; | import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js"; | ||||||
|  | import { buildSelectedBackgroundColor } from "../../components/touch_bar.js"; | ||||||
| import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; | import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import { getMermaidConfig } from "../../services/mermaid.js"; | import { getMermaidConfig } from "../../services/mermaid.js"; | ||||||
| @@ -280,6 +281,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|                 CKEditorInspector.attach(editor); |                 CKEditorInspector.attach(editor); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // Touch bar integration | ||||||
|  |             if (hasTouchBar) { | ||||||
|  |                 for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) { | ||||||
|  |                     editor.commands.get(event).on("change", () => this.triggerCommand("refreshTouchBar")); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             return editor; |             return editor; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -544,4 +552,60 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         await this.reinitialize(data); |         await this.reinitialize(data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         const { TouchBar, buildIcon } = data; | ||||||
|  |         const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar; | ||||||
|  |         const { editor } = this.watchdog; | ||||||
|  |  | ||||||
|  |         const commandButton = (icon: string, command: string) => new TouchBarButton({ | ||||||
|  |             icon: buildIcon(icon), | ||||||
|  |             click: () => editor.execute(command), | ||||||
|  |             backgroundColor: buildSelectedBackgroundColor(editor.commands.get(command).value as boolean) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         let headingSelectedIndex = undefined; | ||||||
|  |         const headingCommand = editor.commands.get("heading"); | ||||||
|  |         const paragraphCommand = editor.commands.get("paragraph"); | ||||||
|  |         if (paragraphCommand.value) { | ||||||
|  |             headingSelectedIndex = 0; | ||||||
|  |         } else if (headingCommand.value === "heading2") { | ||||||
|  |             headingSelectedIndex = 1; | ||||||
|  |         } else if (headingCommand.value === "heading3") { | ||||||
|  |             headingSelectedIndex = 2; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return [ | ||||||
|  |             new TouchBarSegmentedControl({ | ||||||
|  |                 segments: [ | ||||||
|  |                     { label: "P" }, | ||||||
|  |                     { label: "H2" }, | ||||||
|  |                     { label: "H3" } | ||||||
|  |                 ], | ||||||
|  |                 change(selectedIndex, isSelected) { | ||||||
|  |                     switch (selectedIndex) { | ||||||
|  |                         case 0: | ||||||
|  |                             editor.execute("paragraph") | ||||||
|  |                             break; | ||||||
|  |                         case 1: | ||||||
|  |                             editor.execute("heading", { value: "heading2" }); | ||||||
|  |                             break; | ||||||
|  |                         case 2: | ||||||
|  |                             editor.execute("heading", { value: "heading3" }); | ||||||
|  |                             break; | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 selectedIndex: headingSelectedIndex | ||||||
|  |             }), | ||||||
|  |             new TouchBarGroup({ | ||||||
|  |                 items: new TouchBar({ | ||||||
|  |                     items: [ | ||||||
|  |                         commandButton("NSTouchBarTextBoldTemplate", "bold"), | ||||||
|  |                         commandButton("NSTouchBarTextItalicTemplate", "italic"), | ||||||
|  |                         commandButton("NSTouchBarTextUnderlineTemplate", "underline") | ||||||
|  |                     ] | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import TypeWidget from "./type_widget.js"; | |||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import toastService from "../../services/toast.js"; | import toastService from "../../services/toast.js"; | ||||||
| import dialogService from "../../services/dialog.js"; | import dialogService from "../../services/dialog.js"; | ||||||
| import type { EventData } from "../../components/app_context.js"; | import type { CommandListenerData, EventData } from "../../components/app_context.js"; | ||||||
| import { t } from "../../services/i18n.js"; | import { t } from "../../services/i18n.js"; | ||||||
| import attributes from "../../services/attributes.js"; | import attributes from "../../services/attributes.js"; | ||||||
| import openContextMenu from "./geo_map_context_menu.js"; | import openContextMenu from "./geo_map_context_menu.js"; | ||||||
| @@ -15,6 +15,7 @@ import appContext from "../../components/app_context.js"; | |||||||
|  |  | ||||||
| import markerIcon from "leaflet/dist/images/marker-icon.png"; | import markerIcon from "leaflet/dist/images/marker-icon.png"; | ||||||
| import markerIconShadow from "leaflet/dist/images/marker-shadow.png"; | import markerIconShadow from "leaflet/dist/images/marker-shadow.png"; | ||||||
|  | import { hasTouchBar } from "../../services/utils.js"; | ||||||
|  |  | ||||||
| const TPL = /*html*/`\ | const TPL = /*html*/`\ | ||||||
| <div class="note-detail-geo-map note-detail-printable"> | <div class="note-detail-geo-map note-detail-printable"> | ||||||
| @@ -107,6 +108,7 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|     private currentMarkerData: Record<string, Marker>; |     private currentMarkerData: Record<string, Marker>; | ||||||
|     private currentTrackData: Record<string, GPX>; |     private currentTrackData: Record<string, GPX>; | ||||||
|     private gpxLoaded?: boolean; |     private gpxLoaded?: boolean; | ||||||
|  |     private ignoreNextZoomEvent?: boolean; | ||||||
|  |  | ||||||
|     static getType() { |     static getType() { | ||||||
|         return "geoMap"; |         return "geoMap"; | ||||||
| @@ -151,6 +153,16 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|         map.on("moveend", updateFn); |         map.on("moveend", updateFn); | ||||||
|         map.on("zoomend", updateFn); |         map.on("zoomend", updateFn); | ||||||
|         map.on("click", (e) => this.#onMapClicked(e)); |         map.on("click", (e) => this.#onMapClicked(e)); | ||||||
|  |  | ||||||
|  |         if (hasTouchBar) { | ||||||
|  |             map.on("zoom", () => { | ||||||
|  |                 if (!this.ignoreNextZoomEvent) { | ||||||
|  |                     this.triggerCommand("refreshTouchBar"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 this.ignoreNextZoomEvent = false; | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async #restoreViewportAndZoom() { |     async #restoreViewportAndZoom() { | ||||||
| @@ -279,6 +291,9 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|     #changeState(newState: State) { |     #changeState(newState: State) { | ||||||
|         this._state = newState; |         this._state = newState; | ||||||
|         this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote); |         this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote); | ||||||
|  |         if (hasTouchBar) { | ||||||
|  |             this.triggerCommand("refreshTouchBar"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async #onMapClicked(e: LeafletMouseEvent) { |     async #onMapClicked(e: LeafletMouseEvent) { | ||||||
| @@ -388,4 +403,30 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|         this.moveMarker(noteId, null); |         this.moveMarker(noteId, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand({ TouchBar }: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         const map = this.geoMapWidget.map; | ||||||
|  |         const that = this; | ||||||
|  |         if (!map) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return [ | ||||||
|  |             new TouchBar.TouchBarSlider({ | ||||||
|  |                 label: "Zoom", | ||||||
|  |                 value: map.getZoom(), | ||||||
|  |                 minValue: map.getMinZoom(), | ||||||
|  |                 maxValue: map.getMaxZoom(), | ||||||
|  |                 change(newValue) { | ||||||
|  |                     that.ignoreNextZoomEvent = true; | ||||||
|  |                     map.setZoom(newValue); | ||||||
|  |                 }, | ||||||
|  |             }), | ||||||
|  |             new TouchBar.TouchBarButton({ | ||||||
|  |                 label: "New geo note", | ||||||
|  |                 click: () => this.triggerCommand("geoMapCreateChildNote", { ntxId: this.ntxId }), | ||||||
|  |                 enabled: (this._state === State.Normal) | ||||||
|  |             }) | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,12 +2,13 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | |||||||
| import libraryLoader from "../../services/library_loader.js"; | import libraryLoader from "../../services/library_loader.js"; | ||||||
| import { applySyntaxHighlight } from "../../services/syntax_highlight.js"; | import { applySyntaxHighlight } from "../../services/syntax_highlight.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import type { EventData } from "../../components/app_context.js"; | import type { CommandListenerData, EventData } from "../../components/app_context.js"; | ||||||
| import { getLocaleById } from "../../services/i18n.js"; | import { getLocaleById } from "../../services/i18n.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
| import { getMermaidConfig } from "../../services/mermaid.js"; | import { getMermaidConfig } from "../../services/mermaid.js"; | ||||||
|  |  | ||||||
| const TPL = /*html*/` | const TPL = /*html*/` | ||||||
| <div class="note-detail-readonly-text note-detail-printable"> | <div class="note-detail-readonly-text note-detail-printable" tabindex="100"> | ||||||
|     <style> |     <style> | ||||||
|     /* h1 should not be used at all since semantically that's a note title */ |     /* h1 should not be used at all since semantically that's a note title */ | ||||||
|     .note-detail-readonly-text h1 { font-size: 1.8em; } |     .note-detail-readonly-text h1 { font-size: 1.8em; } | ||||||
| @@ -169,4 +170,20 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         this.$widget.attr("dir", isRtl ? "rtl" : "ltr"); |         this.$widget.attr("dir", isRtl ? "rtl" : "ltr"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         return [ | ||||||
|  |             new TouchBar.TouchBarSpacer({ size: "flexible" }), | ||||||
|  |             new TouchBar.TouchBarButton({ | ||||||
|  |                 icon: buildIcon("NSLockUnlockedTemplate"), | ||||||
|  |                 click: () => { | ||||||
|  |                     if (this.noteContext?.viewScope) { | ||||||
|  |                         this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; | ||||||
|  |                         appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); | ||||||
|  |                     } | ||||||
|  |                     this.refresh(); | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,12 +7,14 @@ import { t } from "../../services/i18n.js"; | |||||||
| import options from "../../services/options.js"; | import options from "../../services/options.js"; | ||||||
| import dialogService from "../../services/dialog.js"; | import dialogService from "../../services/dialog.js"; | ||||||
| import attributes from "../../services/attributes.js"; | import attributes from "../../services/attributes.js"; | ||||||
| import type { EventData } from "../../components/app_context.js"; | import type { CommandListenerData, EventData } from "../../components/app_context.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils, { hasTouchBar } from "../../services/utils.js"; | ||||||
| import date_notes from "../../services/date_notes.js"; | import date_notes from "../../services/date_notes.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
| import type { EventImpl } from "@fullcalendar/core/internal"; | import type { EventImpl } from "@fullcalendar/core/internal"; | ||||||
| import debounce, { type DebouncedFunction } from "debounce"; | import debounce, { type DebouncedFunction } from "debounce"; | ||||||
|  | import type { TouchBarItem } from "../../components/touch_bar.js"; | ||||||
|  | import type { SegmentedControlSegment } from "electron"; | ||||||
|  |  | ||||||
| const TPL = /*html*/` | const TPL = /*html*/` | ||||||
| <div class="calendar-view"> | <div class="calendar-view"> | ||||||
| @@ -69,7 +71,7 @@ const TPL = /*html*/` | |||||||
|     } |     } | ||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|     <div class="calendar-container"> |     <div class="calendar-container" tabindex="100"> | ||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
| `; | `; | ||||||
| @@ -266,6 +268,10 @@ export default class CalendarView extends ViewMode { | |||||||
|  |  | ||||||
|         this.debouncedSaveView(); |         this.debouncedSaveView(); | ||||||
|         this.lastView = currentView; |         this.lastView = currentView; | ||||||
|  |  | ||||||
|  |         if (hasTouchBar) { | ||||||
|  |             appContext.triggerCommand("refreshTouchBar"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async #onCalendarSelection(e: DateSelectArg) { |     async #onCalendarSelection(e: DateSelectArg) { | ||||||
| @@ -595,4 +601,71 @@ export default class CalendarView extends ViewMode { | |||||||
|         return newDate; |         return newDate; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) { | ||||||
|  |         if (!this.calendar) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const items: TouchBarItem[] = []; | ||||||
|  |         const $toolbarItems = this.$calendarContainer.find(".fc-toolbar-chunk .fc-button-group, .fc-toolbar-chunk > button"); | ||||||
|  |  | ||||||
|  |         for (const item of $toolbarItems) { | ||||||
|  |             // Button groups. | ||||||
|  |             if (item.classList.contains("fc-button-group")) { | ||||||
|  |                 let mode: "single" | "buttons" = "single"; | ||||||
|  |                 let selectedIndex = 0; | ||||||
|  |                 const segments: SegmentedControlSegment[] = []; | ||||||
|  |                 const subItems = item.childNodes as NodeListOf<HTMLElement>; | ||||||
|  |                 let index = 0; | ||||||
|  |                 for (const subItem of subItems) { | ||||||
|  |                     if (subItem.ariaPressed === "true") { | ||||||
|  |                         selectedIndex = index; | ||||||
|  |                     } | ||||||
|  |                     index++; | ||||||
|  |  | ||||||
|  |                     // Text button. | ||||||
|  |                     if (subItem.innerText) { | ||||||
|  |                         segments.push({ label: subItem.innerText }); | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Icon button. | ||||||
|  |                     const iconEl = subItem.querySelector("span.fc-icon"); | ||||||
|  |                     let icon = null; | ||||||
|  |                     if (iconEl?.classList.contains("fc-icon-chevron-left")) { | ||||||
|  |                         icon = "NSImageNameTouchBarGoBackTemplate"; | ||||||
|  |                         mode = "buttons"; | ||||||
|  |                     } else if (iconEl?.classList.contains("fc-icon-chevron-right")) { | ||||||
|  |                         icon = "NSImageNameTouchBarGoForwardTemplate"; | ||||||
|  |                         mode = "buttons"; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (icon) { | ||||||
|  |                         segments.push({ | ||||||
|  |                             icon: buildIcon(icon) | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 items.push(new TouchBar.TouchBarSegmentedControl({ | ||||||
|  |                     mode, | ||||||
|  |                     segments, | ||||||
|  |                     selectedIndex, | ||||||
|  |                     change: (selectedIndex, isSelected) => subItems[selectedIndex].click() | ||||||
|  |                 })); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Standalone item. | ||||||
|  |             if (item.innerText) { | ||||||
|  |                 items.push(new TouchBar.TouchBarButton({ | ||||||
|  |                     label: item.innerText, | ||||||
|  |                     click: () => item.click() | ||||||
|  |                 })); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return items; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user