From 48e6652d4b98f8847ca4bd4769b5ad833256d092 Mon Sep 17 00:00:00 2001 From: swithek <52840391+swithek@users.noreply.github.com> Date: Fri, 2 Oct 2026 10:11:59 +0200 Subject: [PATCH] add hook diff --- web/app/components/CalendarInput.nuxt.test.ts | 24 +- web/app/components/NotificationBox.vue | 2 +- .../editor/ContentEditor.nuxt.test.ts | 6 +- web/app/components/editor/ContentEditor.vue | 17 +- .../DocumentContainer.client.nuxt.test.ts | 44 +++ .../editor/DocumentContainer.client.vue | 15 +- .../editor/DocumentTagList.nuxt.test.ts | 25 +- web/app/components/editor/DocumentTagList.vue | 18 +- .../components/editor/NameEditor.nuxt.test.ts | 123 +++++- web/app/components/editor/NameEditor.vue | 80 ++-- .../editor/diff/DiffCountPill.nuxt.test.ts | 106 ++++++ .../components/editor/diff/DiffCountPill.vue | 66 ++++ .../editor/diff/DiffEditor.nuxt.test.ts | 2 + web/app/components/editor/diff/DiffEditor.vue | 13 + .../drag-handle/BlockHandle.nuxt.test.ts | 56 ++- .../editor/drag-handle/BlockHandle.vue | 44 ++- .../drag-handle/handle-plugin.browser.test.ts | 64 ++++ .../editor/drag-handle/handle-plugin.ts | 21 + .../hooks/BlockHookDiffMarkers.nuxt.test.ts | 263 +++++++++++++ .../editor/hooks/BlockHookDiffMarkers.vue | 141 +++++++ .../editor/hooks/HookDiffMarker.nuxt.test.ts | 80 ++++ .../editor/hooks/HookDiffMarker.vue | 63 +++ .../editor/hooks/HookExplanation.nuxt.test.ts | 60 +++ .../editor/hooks/HookExplanation.vue | 29 ++ .../editor/hooks/HookMenuContent.nuxt.test.ts | 160 +++++++- .../editor/hooks/HookMenuContent.vue | 151 +++++--- .../hooks/HookReadonlyField.nuxt.test.ts | 41 ++ .../editor/hooks/HookReadonlyField.vue | 34 ++ .../editor/hooks/HookSubTrigger.nuxt.test.ts | 149 ++++++++ .../editor/hooks/HookSubTrigger.vue | 79 ++++ .../ConfigMenu.nuxt.test.ts | 85 +++++ .../container-image-watcher/ConfigMenu.vue | 58 +-- .../github-tracking/ConfigMenu.nuxt.test.ts | 103 +++++ .../hooks/github-tracking/ConfigMenu.vue | 101 +++-- .../components/editor/hooks/hook-diff.test.ts | 358 ++++++++++++++++++ web/app/components/editor/hooks/hook-diff.ts | 235 ++++++++++++ .../ConfigMenu.nuxt.test.ts | 159 +++++++- .../hooks/scheduled-reminder/ConfigMenu.vue | 181 +++++---- .../components/editor/hooks/test-helpers.ts | 18 + .../hooks/url-watcher/ConfigMenu.nuxt.test.ts | 83 ++++ .../editor/hooks/url-watcher/ConfigMenu.vue | 56 +-- .../composables/useEditorMeta.nuxt.test.ts | 22 ++ web/app/composables/useEditorMeta.ts | 5 + web/eslint.config.mjs | 4 +- web/i18n/locales/en/editor.json | 19 +- 45 files changed, 3135 insertions(+), 328 deletions(-) create mode 100644 web/app/components/editor/diff/DiffCountPill.nuxt.test.ts create mode 100644 web/app/components/editor/diff/DiffCountPill.vue create mode 100644 web/app/components/editor/hooks/BlockHookDiffMarkers.nuxt.test.ts create mode 100644 web/app/components/editor/hooks/BlockHookDiffMarkers.vue create mode 100644 web/app/components/editor/hooks/HookDiffMarker.nuxt.test.ts create mode 100644 web/app/components/editor/hooks/HookDiffMarker.vue create mode 100644 web/app/components/editor/hooks/HookExplanation.nuxt.test.ts create mode 100644 web/app/components/editor/hooks/HookExplanation.vue create mode 100644 web/app/components/editor/hooks/HookReadonlyField.nuxt.test.ts create mode 100644 web/app/components/editor/hooks/HookReadonlyField.vue create mode 100644 web/app/components/editor/hooks/HookSubTrigger.nuxt.test.ts create mode 100644 web/app/components/editor/hooks/HookSubTrigger.vue create mode 100644 web/app/components/editor/hooks/hook-diff.test.ts create mode 100644 web/app/components/editor/hooks/hook-diff.ts diff --git a/web/app/components/CalendarInput.nuxt.test.ts b/web/app/components/CalendarInput.nuxt.test.ts index 75fc1cc1..42efe0ad 100644 --- a/web/app/components/CalendarInput.nuxt.test.ts +++ b/web/app/components/CalendarInput.nuxt.test.ts @@ -25,29 +25,29 @@ describe("", { concurrent: false }, () => { it("shows the placeholder while nothing is picked", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), }) expect(wrapper.text()).toContain( - t("editor.hooks.time-expiration.calendar-placeholder"), + t("editor.hooks.scheduled-reminder.calendar-placeholder"), ) }) it("shows the picked date instead of the placeholder", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) expect(wrapper.text()).toContain("March 14, 2026") expect(wrapper.text()).not.toContain( - t("editor.hooks.time-expiration.calendar-placeholder"), + t("editor.hooks.scheduled-reminder.calendar-placeholder"), ) }) it("greys out the trigger while nothing is picked", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), }) expect(wrapper.get("button").classes()).toContain("text-muted-foreground") @@ -57,7 +57,7 @@ describe("", { concurrent: false }, () => { expect, }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) @@ -70,7 +70,7 @@ describe("", { concurrent: false }, () => { expect, }) => { await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), }) expect(popoverText()).not.toContain("March") @@ -78,7 +78,7 @@ describe("", { concurrent: false }, () => { it("opens the calendar when the trigger is pressed", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) @@ -91,7 +91,7 @@ describe("", { concurrent: false }, () => { it("closes the calendar once a date is picked", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) await wrapper.get("button").trigger("click") @@ -105,7 +105,7 @@ describe("", { concurrent: false }, () => { it("reports the day the user clicks in the calendar", async ({ expect }) => { const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) await wrapper.get("button").trigger("click") @@ -125,7 +125,7 @@ describe("", { concurrent: false }, () => { it("leaves every day selectable by default", async ({ expect }) => { vi.setSystemTime(new Date("2026-03-14T12:00:00Z")) const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 14), }) @@ -142,7 +142,7 @@ describe("", { concurrent: false }, () => { }) => { vi.setSystemTime(new Date("2026-03-14T12:00:00Z")) const wrapper = await mountInput({ - placeholder: t("editor.hooks.time-expiration.calendar-placeholder"), + placeholder: t("editor.hooks.scheduled-reminder.calendar-placeholder"), modelValue: new CalendarDate(2026, 3, 20), availableFromTomorrow: true, }) diff --git a/web/app/components/NotificationBox.vue b/web/app/components/NotificationBox.vue index eb75b6b4..e627740a 100644 --- a/web/app/components/NotificationBox.vue +++ b/web/app/components/NotificationBox.vue @@ -178,7 +178,7 @@ function buildNotificationDescription(notification: Notification) { hook = t("editor.hooks.github-tracking.title") break case DocumentHookType.ScheduledReminder: - hook = t("editor.hooks.time-expiration.title") + hook = t("editor.hooks.scheduled-reminder.title") break case DocumentHookType.ContainerImageWatcher: hook = t("editor.hooks.container-image-watcher.title") diff --git a/web/app/components/editor/ContentEditor.nuxt.test.ts b/web/app/components/editor/ContentEditor.nuxt.test.ts index 5662777f..55bfd64f 100644 --- a/web/app/components/editor/ContentEditor.nuxt.test.ts +++ b/web/app/components/editor/ContentEditor.nuxt.test.ts @@ -70,7 +70,7 @@ function paragraph(text: string, uid: string) { } function mountEditor( - options: { branch?: Branch; documentHooks?: DocumentHook[] } = {}, + options: { branch?: Branch; activeBranchHooks?: DocumentHook[] } = {}, ) { const branch = options.branch ?? makeBranch() @@ -78,7 +78,7 @@ function mountEditor( props: { activeBranchProvider: branch.provider, activeBranchYdoc: branch.ydoc, - documentHooks: options.documentHooks ?? [], + activeBranchHooks: options.activeBranchHooks ?? [], nameEditor: null, userCaretDetails: { name: "Me", color: "#ff0000" }, }, @@ -300,7 +300,7 @@ describe("", { concurrent: false }, () => { const called = watchCommands(editorOf(wrapper)) await wrapper.setProps({ - documentHooks: [ + activeBranchHooks: [ { id: "hook-1", blockId: "block-1", diff --git a/web/app/components/editor/ContentEditor.vue b/web/app/components/editor/ContentEditor.vue index 52e4ad1e..84cb4fbf 100644 --- a/web/app/components/editor/ContentEditor.vue +++ b/web/app/components/editor/ContentEditor.vue @@ -48,7 +48,7 @@ import { SUPPRESS_SCROLL_TO_SELECTION_META } from "./scroll-control" const props = defineProps<{ activeBranchProvider: HocuspocusProvider activeBranchYdoc: Y.Doc - documentHooks: DocumentHook[] + activeBranchHooks: DocumentHook[] nameEditor: Editor | null | undefined userCaretDetails: { name: string; color: string } }>() @@ -66,13 +66,10 @@ const linkBubbleMenuElem = useTemplateRef("link-bubble-menu") const editorStore = useEditorStore() const { uploadDocumentFile } = useDocumentFileAPI() const suppressNextSelectionScroll = ref(false) -const { isEditable } = useEditorMeta() -const isEditingDisabled = computed(() => { - return !isEditable.value || editorStore.reviewableDiffActive -}) +const { isReadOnlyOrDiff } = useEditorMeta() const textCommentState = ref(null) -const editorExtensions = contentExtensionsWithIDs(t, isEditingDisabled, { +const editorExtensions = contentExtensionsWithIDs(t, isReadOnlyOrDiff, { onIndicatorStateChange: (v) => { textCommentState.value = v }, @@ -174,7 +171,7 @@ const contentEditor = useEditor({ }), HookDecorator.configure({ attributeName: "uid", - getHooks: () => props.documentHooks, + getHooks: () => props.activeBranchHooks, }), Typography, Collaboration.configure({ @@ -308,7 +305,7 @@ watch( ) watchDeep( - () => props.documentHooks, + () => props.activeBranchHooks, () => { contentEditor.value?.commands.refreshHookDecorations() }, @@ -375,7 +372,7 @@ function setTextCommentIndicatorHoverChange( ref="editor-container" :class="[ 'group/content-editor content-editor relative z-editor-content w-full min-w-0 flex-1 bg-transparent', - { 'editor-not-editable': isEditingDisabled }, + { 'editor-not-editable': isReadOnlyOrDiff }, ]" >