|
@@ -1,74 +1,116 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, nextTick, ref } from "vue"
|
|
|
|
|
|
|
+import { nextTick, ref, watch, computed } from "vue"
|
|
|
|
|
+import { useFloating, offset, flip, shift } from "@floating-ui/vue"
|
|
|
import type { FormattingHandlers } from "@/lib/formatting"
|
|
import type { FormattingHandlers } from "@/lib/formatting"
|
|
|
|
|
+import EditorToolbarContent from "./EditorToolbarContent.vue"
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Layout mode for the toolbar:
|
|
|
|
|
+ * - `fixed`: Sticky bar at the editor top.
|
|
|
|
|
+ * - `bubble`: Floating popover near text selection.
|
|
|
|
|
+ * - `floating`: Floating popover at the cursor on empty lines.
|
|
|
|
|
+ */
|
|
|
|
|
+export type ToolbarMode = "fixed" | "bubble" | "floating"
|
|
|
|
|
|
|
|
type PromptKind = "page" | "block" | "tag"
|
|
type PromptKind = "page" | "block" | "tag"
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
|
|
+ /** Toolbar layout mode. Defaults to `fixed`. */
|
|
|
|
|
+ mode?: ToolbarMode
|
|
|
|
|
+ /** Formatting handlers from the focused block, or null when no block is focused. */
|
|
|
handlers: FormattingHandlers | null
|
|
handlers: FormattingHandlers | null
|
|
|
|
|
+ /** Bumped on every cursor move — forces reactivity on active state checks. */
|
|
|
selectionVersion?: number
|
|
selectionVersion?: number
|
|
|
|
|
+ /** Whether undo is available in the operation history. */
|
|
|
canUndo?: boolean
|
|
canUndo?: boolean
|
|
|
|
|
+ /** Whether redo is available in the operation history. */
|
|
|
canRedo?: boolean
|
|
canRedo?: boolean
|
|
|
|
|
+ /** Undo the last operation. */
|
|
|
undo?: () => void
|
|
undo?: () => void
|
|
|
|
|
+ /** Redo the last undone operation. */
|
|
|
redo?: () => void
|
|
redo?: () => void
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
|
-const active = computed(() => {
|
|
|
|
|
- if (!props.handlers) return {}
|
|
|
|
|
- // Force reactivity: selectionVersion is bumped on every cursor move,
|
|
|
|
|
- // but handlers.isActive() reads PM state imperatively and is not
|
|
|
|
|
- // reactive. Tapping the prop here makes this computed re-evaluate
|
|
|
|
|
- // when selection changes.
|
|
|
|
|
- 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
|
|
|
|
|
- // Force reactivity — see active computed above.
|
|
|
|
|
- void props.selectionVersion
|
|
|
|
|
- return props.handlers.getActiveHeading()
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const currentTask = computed(() => {
|
|
|
|
|
- if (!props.handlers) return null
|
|
|
|
|
- // Force reactivity — see active computed above.
|
|
|
|
|
- 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 floatingRef = ref<HTMLElement | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Target element for the bubble/floating toolbar teleport.
|
|
|
|
|
+ * Teleports into the nearest `.editor-shell` ancestor to keep the toolbar
|
|
|
|
|
+ * in the editor's stacking context (below app-level overlays).
|
|
|
|
|
+ * Falls back to `document.body`.
|
|
|
|
|
+ *
|
|
|
|
|
+ * In `fixed` mode no teleport is used (the toolbar renders inline).
|
|
|
|
|
+ */
|
|
|
|
|
+const teleportTarget = computed(() => {
|
|
|
|
|
+ if (props.mode === "fixed" || props.mode === undefined) return undefined
|
|
|
|
|
+ return document.querySelector(".editor-shell") ?? "body"
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-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) },
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Virtual reference element for Floating UI positioning.
|
|
|
|
|
+ * Provides a `getBoundingClientRect` that tracks the current selection's
|
|
|
|
|
+ * screen coordinates. Updated reactively when `selectionVersion` changes.
|
|
|
|
|
+ * Set to `null` to hide the bubble/floating toolbar.
|
|
|
|
|
+ */
|
|
|
|
|
+const virtualRef = ref<{
|
|
|
|
|
+ getBoundingClientRect: () => DOMRect
|
|
|
|
|
+} | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Track selection changes and update the floating toolbar position.
|
|
|
|
|
+ * - In `bubble` mode: show when text is selected, hide when collapsed.
|
|
|
|
|
+ * - In `floating` mode: show at cursor position regardless of selection state.
|
|
|
|
|
+ * - Hides when there is no selection (window.getSelection is empty).
|
|
|
|
|
+ */
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => props.selectionVersion,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ if (props.mode === "fixed" || props.mode === undefined) return
|
|
|
|
|
+
|
|
|
|
|
+ const sel = window.getSelection()
|
|
|
|
|
+ if (!sel || !sel.rangeCount) {
|
|
|
|
|
+ virtualRef.value = null
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (props.mode === "bubble" && sel.isCollapsed) {
|
|
|
|
|
+ virtualRef.value = null
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rect = sel.getRangeAt(0).getBoundingClientRect()
|
|
|
|
|
+ virtualRef.value =
|
|
|
|
|
+ rect.width > 0 || rect.height > 0
|
|
|
|
|
+ ? { getBoundingClientRect: () => rect }
|
|
|
|
|
+ : null
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true },
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Close bubble/floating toolbar when the focused block loses focus
|
|
|
|
|
+ * (handlers becomes null). This covers the case where the insertion zone
|
|
|
|
|
+ * is clicked and no selectionVersion change fires.
|
|
|
|
|
+ */
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => props.handlers,
|
|
|
|
|
+ (h) => {
|
|
|
|
|
+ if (!h && props.mode !== "fixed" && props.mode !== undefined) {
|
|
|
|
|
+ virtualRef.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Floating UI positioning for bubble/floating modes.
|
|
|
|
|
+ * Uses the virtual element as the anchor and auto-updates on scroll/resize.
|
|
|
|
|
+ * - `offset(8)`: 8px gap between toolbar and selection.
|
|
|
|
|
+ * - `flip()`: flip to bottom when there's no space above.
|
|
|
|
|
+ * - `shift()`: keep within viewport horizontally.
|
|
|
|
|
+ */
|
|
|
|
|
+const { floatingStyles } = useFloating(virtualRef, floatingRef, {
|
|
|
|
|
+ placement: "top",
|
|
|
|
|
+ middleware: [offset(8), flip(), shift({ crossAxis: true })],
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const promptKind = ref<PromptKind | null>(null)
|
|
const promptKind = ref<PromptKind | null>(null)
|
|
@@ -98,7 +140,6 @@ const promptLabels = computed(() => ({
|
|
|
function openPrompt(kind: PromptKind) {
|
|
function openPrompt(kind: PromptKind) {
|
|
|
promptValue.value = ""
|
|
promptValue.value = ""
|
|
|
promptKind.value = kind
|
|
promptKind.value = kind
|
|
|
- // UInput exposes { inputRef } via defineExpose — use the public API.
|
|
|
|
|
nextTick(() => inputRef.value?.inputRef?.focus())
|
|
nextTick(() => inputRef.value?.inputRef?.focus())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -122,254 +163,53 @@ function cancelPrompt() {
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
<div
|
|
<div
|
|
|
|
|
+ v-if="mode === 'fixed' || mode === undefined"
|
|
|
role="toolbar"
|
|
role="toolbar"
|
|
|
aria-label="Formatting toolbar"
|
|
aria-label="Formatting toolbar"
|
|
|
data-slot="base"
|
|
data-slot="base"
|
|
|
class="flex items-stretch gap-1.5"
|
|
class="flex items-stretch gap-1.5"
|
|
|
|
|
+ @mousedown.prevent
|
|
|
>
|
|
>
|
|
|
- <!-- Undo / Redo -->
|
|
|
|
|
- <div role="group" aria-label="History" class="flex items-center gap-0.5">
|
|
|
|
|
- <UTooltip text="Undo (⌘Z)">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-undo-2"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :disabled="!canUndo"
|
|
|
|
|
- data-toolbar-action="undo"
|
|
|
|
|
- @click="undo?.()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Redo (⌘⇧Z)">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-redo-2"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :disabled="!canRedo"
|
|
|
|
|
- data-toolbar-action="redo"
|
|
|
|
|
- @click="redo?.()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <USeparator orientation="vertical" class="w-px self-stretch bg-border" />
|
|
|
|
|
|
|
+ <EditorToolbarContent
|
|
|
|
|
+ :handlers="handlers"
|
|
|
|
|
+ :selection-version="selectionVersion"
|
|
|
|
|
+ :can-undo="canUndo"
|
|
|
|
|
+ :can-redo="canRedo"
|
|
|
|
|
+ :undo="undo"
|
|
|
|
|
+ :redo="redo"
|
|
|
|
|
+ @open-prompt="openPrompt"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <!-- 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"
|
|
|
|
|
- data-toolbar-action="heading"
|
|
|
|
|
- />
|
|
|
|
|
- </UDropdownMenu>
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-list-todo"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="currentTask !== null"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="currentTask !== null ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="task"
|
|
|
|
|
- @click="handlers?.toggleTask()"
|
|
|
|
|
|
|
+ <Teleport v-else :to="teleportTarget">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="virtualRef"
|
|
|
|
|
+ ref="floatingRef"
|
|
|
|
|
+ role="toolbar"
|
|
|
|
|
+ aria-label="Formatting toolbar"
|
|
|
|
|
+ data-slot="base"
|
|
|
|
|
+ class="flex items-stretch gap-1.5 rounded-lg border border-default bg-white dark:bg-neutral-950 shadow-lg p-1"
|
|
|
|
|
+ :style="floatingStyles"
|
|
|
|
|
+ @mousedown.prevent
|
|
|
|
|
+ >
|
|
|
|
|
+ <EditorToolbarContent
|
|
|
|
|
+ :handlers="handlers"
|
|
|
|
|
+ :selection-version="selectionVersion"
|
|
|
|
|
+ :can-undo="canUndo"
|
|
|
|
|
+ :can-redo="canRedo"
|
|
|
|
|
+ :undo="undo"
|
|
|
|
|
+ :redo="redo"
|
|
|
|
|
+ @open-prompt="openPrompt"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</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="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.bold"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.bold ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="bold"
|
|
|
|
|
- @click="handlers?.toggleBold()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Italic (⌘I)" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-italic"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.italic"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.italic ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="italic"
|
|
|
|
|
- @click="handlers?.toggleItalic()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Code (⌘`)" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-code"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.code"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.code ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="code"
|
|
|
|
|
- @click="handlers?.toggleCode()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Strikethrough (⌘⇧S)" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-strikethrough"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.strikethrough"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.strikethrough ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="strikethrough"
|
|
|
|
|
- @click="handlers?.toggleStrikethrough()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Highlight (⌘⇧H)" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-highlighter"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.highlight"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.highlight ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="highlight"
|
|
|
|
|
- @click="handlers?.toggleHighlight()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Inline math (⌘M)" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-sigma"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- active-color="primary"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- active-variant="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.inlineMath"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- :aria-pressed="active.inlineMath ? 'true' : 'false'"
|
|
|
|
|
- data-toolbar-action="inline-math"
|
|
|
|
|
- @click="handlers?.insertInlineMath()"
|
|
|
|
|
- />
|
|
|
|
|
- </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"
|
|
|
|
|
- data-toolbar-action="link"
|
|
|
|
|
- @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="soft"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :active="active.inlineMath"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- data-toolbar-action="inline-math-insert"
|
|
|
|
|
- @click="handlers?.insertInlineMath()"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Block math" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-square-radical"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- data-toolbar-action="block-math"
|
|
|
|
|
- @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"
|
|
|
|
|
- data-toolbar-action="page-ref"
|
|
|
|
|
- @click="openPrompt('page')"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Block reference" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-arrow-up-from-dot"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- data-toolbar-action="block-ref"
|
|
|
|
|
- @click="openPrompt('block')"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- <UTooltip text="Tag" :disabled="!handlers">
|
|
|
|
|
- <UButton
|
|
|
|
|
- icon="i-lucide-hash"
|
|
|
|
|
- color="neutral"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- :disabled="!handlers"
|
|
|
|
|
- data-toolbar-action="tag"
|
|
|
|
|
- @click="openPrompt('tag')"
|
|
|
|
|
- />
|
|
|
|
|
- </UTooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </Teleport>
|
|
|
|
|
|
|
|
<UModal v-model:open="promptOpen">
|
|
<UModal v-model:open="promptOpen">
|
|
|
<template #content>
|
|
<template #content>
|
|
|
<UCard>
|
|
<UCard>
|
|
|
<template #header>
|
|
<template #header>
|
|
|
- <h3 class="text-sm font-semibold">
|
|
|
|
|
- {{ promptLabels.title }}
|
|
|
|
|
- </h3>
|
|
|
|
|
|
|
+ <h3 class="text-sm font-semibold">{{ promptLabels.title }}</h3>
|
|
|
</template>
|
|
</template>
|
|
|
-
|
|
|
|
|
<UInput
|
|
<UInput
|
|
|
ref="inputRef"
|
|
ref="inputRef"
|
|
|
v-model="promptValue"
|
|
v-model="promptValue"
|
|
@@ -377,7 +217,6 @@ function cancelPrompt() {
|
|
|
@keydown.enter="confirmPrompt"
|
|
@keydown.enter="confirmPrompt"
|
|
|
@keydown.escape="cancelPrompt"
|
|
@keydown.escape="cancelPrompt"
|
|
|
/>
|
|
/>
|
|
|
-
|
|
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div class="flex justify-end gap-2">
|
|
<div class="flex justify-end gap-2">
|
|
|
<UButton label="Cancel" color="neutral" variant="ghost" size="sm" @click="cancelPrompt" />
|
|
<UButton label="Cancel" color="neutral" variant="ghost" size="sm" @click="cancelPrompt" />
|