mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-16 23:02:35 +08:00
Merge pull request #1549 from TriliumNext/feature/touchbar
Basic touchbar integration
This commit is contained in:
commit
e79730a707
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user