|
@@ -0,0 +1,256 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { computed } from "vue"
|
|
|
|
|
+import type { FormattingHandlers } from "@/lib/formatting"
|
|
|
|
|
+
|
|
|
|
|
+const props = defineProps<{
|
|
|
|
|
+ handlers: FormattingHandlers | null
|
|
|
|
|
+ selectionVersion?: number
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const active = computed(() => {
|
|
|
|
|
+ if (!props.handlers) return {}
|
|
|
|
|
+ void props.selectionVersion
|
|
|
|
|
+ return {
|
|
|
|
|
+ bold: props.handlers.isActive("bold"),
|
|
|
|
|
+ italic: props.handlers.isActive("italic"),
|
|
|
|
|
+ code: props.handlers.isActive("code"),
|
|
|
|
|
+ strikethrough: props.handlers.isActive("strikethrough"),
|
|
|
|
|
+ highlight: props.handlers.isActive("highlight"),
|
|
|
|
|
+ inlineMath: props.handlers.isActive("inlineMath"),
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentHeading = computed(() => {
|
|
|
|
|
+ if (!props.handlers) return null
|
|
|
|
|
+ void props.selectionVersion
|
|
|
|
|
+ return props.handlers.getActiveHeading()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentTask = computed(() => {
|
|
|
|
|
+ if (!props.handlers) return null
|
|
|
|
|
+ void props.selectionVersion
|
|
|
|
|
+ return props.handlers.getActiveTask()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const headingLabel = computed(() => {
|
|
|
|
|
+ const level = currentHeading.value
|
|
|
|
|
+ return level === null ? "Paragraph" : `H${level}`
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const headingIcon = computed(() => {
|
|
|
|
|
+ const level = currentHeading.value
|
|
|
|
|
+ if (level === null) return "i-lucide-text"
|
|
|
|
|
+ return `i-lucide-heading-${level}` as const
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const headingItems = computed(() => {
|
|
|
|
|
+ const h = props.handlers
|
|
|
|
|
+ if (!h) return []
|
|
|
|
|
+ const current = currentHeading.value
|
|
|
|
|
+ return [
|
|
|
|
|
+ { label: "Paragraph", icon: "i-lucide-text", active: current === null, onSelect: () => h.setHeading(null) },
|
|
|
|
|
+ { type: "separator" as const },
|
|
|
|
|
+ { label: "Heading 1", icon: "i-lucide-heading-1", active: current === 1, onSelect: () => h.setHeading(1) },
|
|
|
|
|
+ { label: "Heading 2", icon: "i-lucide-heading-2", active: current === 2, onSelect: () => h.setHeading(2) },
|
|
|
|
|
+ { label: "Heading 3", icon: "i-lucide-heading-3", active: current === 3, onSelect: () => h.setHeading(3) },
|
|
|
|
|
+ { label: "Heading 4", icon: "i-lucide-heading-4", active: current === 4, onSelect: () => h.setHeading(4) },
|
|
|
|
|
+ { label: "Heading 5", icon: "i-lucide-heading-5", active: current === 5, onSelect: () => h.setHeading(5) },
|
|
|
|
|
+ { label: "Heading 6", icon: "i-lucide-heading-6", active: current === 6, onSelect: () => h.setHeading(6) },
|
|
|
|
|
+ ]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function promptPageRef() {
|
|
|
|
|
+ const name = window.prompt("Enter page name:")
|
|
|
|
|
+ if (name) props.handlers?.insertPageRef(name)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function promptBlockRef() {
|
|
|
|
|
+ const id = window.prompt("Enter block ID:")
|
|
|
|
|
+ if (id) props.handlers?.insertBlockRef(id)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function promptTag() {
|
|
|
|
|
+ const tag = window.prompt("Enter tag name:")
|
|
|
|
|
+ if (tag) props.handlers?.insertTag(tag)
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div
|
|
|
|
|
+ role="toolbar"
|
|
|
|
|
+ aria-label="Formatting toolbar"
|
|
|
|
|
+ data-slot="base"
|
|
|
|
|
+ class="flex items-stretch gap-1.5"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- Block -->
|
|
|
|
|
+ <div role="group" aria-label="Block" class="flex items-center gap-0.5">
|
|
|
|
|
+ <UDropdownMenu :items="headingItems" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ :icon="headingIcon"
|
|
|
|
|
+ :label="headingLabel"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UDropdownMenu>
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-list-todo"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="currentTask !== null"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleTask()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <USeparator orientation="vertical" class="w-px self-stretch bg-border" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Inline -->
|
|
|
|
|
+ <div role="group" aria-label="Inline" class="flex items-center gap-0.5">
|
|
|
|
|
+ <UTooltip text="Bold (⌘B)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-bold"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.bold"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleBold()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Italic (⌘I)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-italic"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.italic"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleItalic()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Code (⌘`)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-code"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.code"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleCode()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Strikethrough (⌘⇧S)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-strikethrough"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.strikethrough"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleStrikethrough()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Highlight (⌘⇧H)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-highlighter"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.highlight"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.toggleHighlight()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <USeparator orientation="vertical" class="w-px self-stretch bg-border" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Insert -->
|
|
|
|
|
+ <div role="group" aria-label="Insert" class="flex items-center gap-0.5">
|
|
|
|
|
+ <UTooltip text="Link (⌘K)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-link"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.insertLink()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Inline math (⌘M)" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-sigma"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ active-color="primary"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ active-variant="subtle"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :active="active.inlineMath"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.insertInlineMath()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Block math" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-square-radical"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="handlers?.insertBlockMath()"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <USeparator orientation="vertical" class="w-px self-stretch bg-border" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- References -->
|
|
|
|
|
+ <div role="group" aria-label="References" class="flex items-center gap-0.5">
|
|
|
|
|
+ <UTooltip text="Page reference" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-book-marked"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="promptPageRef"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Block reference" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-arrow-up-from-dot"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="promptBlockRef"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ <UTooltip text="Tag" :disabled="!handlers">
|
|
|
|
|
+ <UButton
|
|
|
|
|
+ icon="i-lucide-hash"
|
|
|
|
|
+ color="neutral"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!handlers"
|
|
|
|
|
+ @click="promptTag"
|
|
|
|
|
+ />
|
|
|
|
|
+ </UTooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|