@cosmicdrift/kumiko-samples 0.363.0 → 0.365.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/package.json +1 -1
- package/packages/bundled-features/package.json +7 -1
- package/packages/bundled-features/src/audit/changes.json +18 -0
- package/packages/bundled-features/src/audit/feature.ts +1 -1
- package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
- package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +63 -11
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +30 -11
- package/packages/bundled-features/src/auth-email-password/changes.json +55 -0
- package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +4 -7
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +2 -3
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +6 -4
- package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +7 -3
- package/packages/bundled-features/src/auth-email-password/mail-i18n.ts +10 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
- package/packages/bundled-features/src/billing-foundation/changes.json +48 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +57 -5
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +4 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +9 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +18 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +35 -19
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +10 -4
- package/packages/bundled-features/src/billing-foundation/i18n.ts +9 -0
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +27 -7
- package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +20 -8
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +7 -2
- package/packages/bundled-features/src/cap-counter/changes.json +19 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +2 -0
- package/packages/bundled-features/src/cap-counter/index.ts +1 -0
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
- package/packages/bundled-features/src/cap-overview/changes.json +6 -0
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-options.query.ts +2 -1
- package/packages/bundled-features/src/channel-discord/discord-channel.ts +4 -2
- package/packages/bundled-features/src/channel-email/changes.json +18 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +47 -24
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +7 -1
- package/packages/bundled-features/src/channel-slack/slack-channel.ts +4 -2
- package/packages/bundled-features/src/channel-teams/teams-channel.ts +4 -2
- package/packages/bundled-features/src/config/changes.json +6 -0
- package/packages/bundled-features/src/config/handlers/set.write.ts +2 -2
- package/packages/bundled-features/src/config/resolver.ts +15 -12
- package/packages/bundled-features/src/config/write-helpers.ts +23 -0
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +54 -27
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/db/queries/retention.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +4 -5
- package/packages/bundled-features/src/delivery/attachment-ref.ts +39 -1
- package/packages/bundled-features/src/delivery/changes.json +30 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +31 -8
- package/packages/bundled-features/src/delivery/delivery-service.ts +93 -92
- package/packages/bundled-features/src/delivery/index.ts +2 -0
- package/packages/bundled-features/src/delivery/jobs.ts +51 -14
- package/packages/bundled-features/src/delivery/types.ts +7 -1
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/feature-toggles/changes.json +7 -0
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +26 -12
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-foundation/changes.json +6 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +6 -3
- package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
- package/packages/bundled-features/src/files-tenant-data/index.ts +3 -1
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +12 -3
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +3 -1
- package/packages/bundled-features/src/jobs/changes.json +31 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
- package/packages/bundled-features/src/jobs/feature.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +56 -18
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
- package/packages/bundled-features/src/ledger/changes.json +7 -0
- package/packages/bundled-features/src/ledger/entity.ts +3 -1
- package/packages/bundled-features/src/metrics/changes.json +6 -0
- package/packages/bundled-features/src/metrics/filters.ts +28 -12
- package/packages/bundled-features/src/personal-access-tokens/changes.json +6 -0
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +7 -8
- package/packages/bundled-features/src/renderer-foundation/types.ts +2 -0
- package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
- package/packages/bundled-features/src/secrets/changes.json +12 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -3
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +3 -1
- package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
- package/packages/bundled-features/src/secrets/write-gate.ts +8 -2
- package/packages/bundled-features/src/shared/changes.json +6 -0
- package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
- package/packages/bundled-features/src/shared/index.ts +8 -0
- package/packages/bundled-features/src/shared/mail-template-body.ts +17 -0
- package/packages/bundled-features/src/shared/resource-image-formats.ts +31 -0
- package/packages/bundled-features/src/shared/sharp-input-limits.ts +7 -8
- package/packages/bundled-features/src/step-dispatcher/webhook-runner.ts +2 -2
- package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
- package/packages/bundled-features/src/template-resolver/README.md +4 -2
- package/packages/bundled-features/src/template-resolver/api.ts +3 -2
- package/packages/bundled-features/src/template-resolver/changes.json +149 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +148 -18
- package/packages/bundled-features/src/template-resolver/compile-steps-block.ts +68 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +23 -3
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +3 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +33 -5
- package/packages/bundled-features/src/template-resolver/document-steps.ts +114 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +16 -1
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +81 -39
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +4 -3
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +121 -53
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +6 -44
- package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +56 -1
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +6 -3
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +25 -15
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +12 -1
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +73 -30
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +14 -11
- package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +102 -19
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +49 -10
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +39 -10
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +14 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +54 -4
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +96 -14
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +51 -24
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +16 -2
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +4 -1
- package/packages/bundled-features/src/template-resolver/index.ts +18 -2
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +56 -0
- package/packages/bundled-features/src/template-resolver/mail-constants.ts +4 -0
- package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +4 -0
- package/packages/bundled-features/src/template-resolver/resource-image.ts +9 -16
- package/packages/bundled-features/src/template-resolver/seeding.ts +27 -4
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +390 -124
- package/packages/bundled-features/src/template-resolver/table.ts +13 -1
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +4 -0
- package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +95 -24
- package/packages/bundled-features/src/template-resolver/template-preview.ts +160 -57
- package/packages/bundled-features/src/template-resolver/template-resources.ts +3 -4
- package/packages/bundled-features/src/template-resolver/template-usage.ts +31 -11
- package/packages/bundled-features/src/template-resolver/template-validation.ts +16 -9
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +12 -8
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +97 -10
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +15 -5
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +405 -145
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +17 -3
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +32 -4
- package/packages/bundled-features/src/template-resolver/web/index.ts +8 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +23 -7
- package/packages/bundled-features/src/tenant/changes.json +6 -0
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +3 -8
- package/packages/bundled-features/src/tenant/seeding.ts +33 -27
- package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +14 -3
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +9 -1
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +3 -2
- package/packages/bundled-features/src/tier-engine/changes.json +18 -0
- package/packages/bundled-features/src/tier-engine/constants.ts +9 -0
- package/packages/bundled-features/src/tier-engine/entity.ts +9 -4
- package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +8 -5
- package/packages/bundled-features/src/tier-engine/index.ts +2 -0
- package/packages/bundled-features/src/user/changes.json +6 -0
- package/packages/bundled-features/src/user/constants.ts +1 -0
- package/packages/bundled-features/src/user/invited-global-roles.ts +2 -4
- package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
- package/packages/bundled-features/src/user/seeding.ts +19 -5
- package/packages/bundled-features/src/user-data-rights/changes.json +12 -0
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +0 -1
- package/packages/bundled-features/src/user-data-rights/i18n.ts +5 -0
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +13 -2
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +7 -1
- package/packages/bundled-features/src/user-profile/changes.json +1 -1
- package/packages/bundled-features/src/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/feature.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +35 -3
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +2 -17
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +33 -9
- package/packages/bundled-features/src/waitlist/i18n.ts +9 -0
- package/packages/bundled-features/src/waitlist/lib.ts +20 -0
- package/packages/bundled-features/src/waitlist/options.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
- package/samples/apps/ui-walkthrough/package.json +9 -0
- package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +87 -2
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +246 -30
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +4 -0
- package/samples/apps/ui-walkthrough/src/run-config.ts +18 -1
- package/samples/apps/use-all-bundled/README.md +3 -13
- package/samples/apps/use-all-bundled/feature-manifest.json +7 -5
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +34 -118
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +1 -1
- package/samples/apps/use-all-bundled/src/run-config.ts +0 -3
- package/samples/recipes/auth-mfa/README.md +2 -1
- package/samples/recipes/auth-mfa/src/feature.ts +1 -0
- package/samples/recipes/custom-handlers/README.md +4 -3
- package/samples/recipes/custom-handlers/src/feature.ts +21 -12
- package/samples/recipes/dashboard-stats/README.md +38 -0
- package/samples/recipes/dashboard-stats/package.json +8 -0
- package/samples/recipes/dashboard-stats/src/feature.ts +104 -0
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/managed-pages/README.md +2 -2
- package/samples/recipes/pdf-audit-export/README.md +2 -1
- package/samples/recipes/pdf-audit-export/package.json +1 -1
- package/samples/recipes/pdf-audit-export/src/feature.ts +68 -48
- package/samples/recipes/workflow-engine/src/db/queries/suspended-runs.ts +2 -2
- package/samples/apps/use-all-bundled/schema/generate.ts +0 -617
- package/samples/apps/use-all-bundled/schema/migrations/meta/0000_snapshot.json +0 -3055
- package/samples/apps/use-all-bundled/schema/migrations/meta/_journal.json +0 -13
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +0 -94
- package/samples/apps/use-all-bundled/schema/schema.ts +0 -9
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// never as `{{name}}` text, so the sanitizer's schema keeps them.
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
type ActionMenuItemSpec,
|
|
9
10
|
TextareaContentEditor,
|
|
10
11
|
usePrimitives,
|
|
11
12
|
useTranslation,
|
|
@@ -15,6 +16,7 @@ import { cn } from "@cosmicdrift/kumiko-renderer-web";
|
|
|
15
16
|
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
16
17
|
import type { Editor, JSONContent } from "@tiptap/core";
|
|
17
18
|
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
|
|
19
|
+
import { BubbleMenu } from "@tiptap/react/menus";
|
|
18
20
|
import {
|
|
19
21
|
AlignCenter,
|
|
20
22
|
AlignJustify,
|
|
@@ -22,10 +24,12 @@ import {
|
|
|
22
24
|
AlignRight,
|
|
23
25
|
Bold as BoldIcon,
|
|
24
26
|
Braces,
|
|
27
|
+
ChevronDown,
|
|
25
28
|
Image as ImageIcon,
|
|
26
29
|
Italic as ItalicIcon,
|
|
27
30
|
Link as LinkIcon,
|
|
28
31
|
List,
|
|
32
|
+
ListChecks,
|
|
29
33
|
ListOrdered,
|
|
30
34
|
Minus,
|
|
31
35
|
MousePointerClick,
|
|
@@ -34,7 +38,8 @@ import {
|
|
|
34
38
|
Table as TableIcon,
|
|
35
39
|
Underline as UnderlineIcon,
|
|
36
40
|
} from "lucide-react";
|
|
37
|
-
import { type ReactNode, useEffect, useMemo, useState } from "react";
|
|
41
|
+
import { Fragment, type ReactNode, useEffect, useMemo, useState } from "react";
|
|
42
|
+
import { createPortal } from "react-dom";
|
|
38
43
|
import {
|
|
39
44
|
DOCUMENT_TEXT_ALIGNMENTS,
|
|
40
45
|
type DocumentTextAlignment,
|
|
@@ -43,7 +48,11 @@ import {
|
|
|
43
48
|
isDocumentReferenceName,
|
|
44
49
|
isDocumentResourceKey,
|
|
45
50
|
} from "../document-schema.js";
|
|
46
|
-
import {
|
|
51
|
+
import { parseDocumentStepsInput } from "../document-steps.js";
|
|
52
|
+
import {
|
|
53
|
+
createDocumentEditorExtensions,
|
|
54
|
+
DOCUMENT_BLOCK_ATTRIBUTE,
|
|
55
|
+
} from "./document-editor-extensions.js";
|
|
47
56
|
import {
|
|
48
57
|
type DocumentEditorProps,
|
|
49
58
|
type DocumentResourcePreview,
|
|
@@ -63,13 +72,17 @@ function previewsKey(previews: Readonly<Record<string, DocumentResourcePreview>>
|
|
|
63
72
|
.join("\n");
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
type PanelKind = "link" | "placeholder" | "image" | "button" | "repeat" | "align";
|
|
75
|
+
type PanelKind = "link" | "placeholder" | "image" | "button" | "repeat" | "align" | "steps";
|
|
76
|
+
|
|
77
|
+
// Blocks that stay in the canvas toolbar; the rest moves into the overflow menu.
|
|
78
|
+
const CANVAS_INLINE_BLOCKS: readonly DocumentToolbarBlock[] = ["image", "table"];
|
|
67
79
|
|
|
68
80
|
const BLOCK_PANEL: Partial<Record<DocumentToolbarBlock, PanelKind>> = {
|
|
69
81
|
image: "image",
|
|
70
82
|
button: "button",
|
|
71
83
|
repeat: "repeat",
|
|
72
84
|
align: "align",
|
|
85
|
+
steps: "steps",
|
|
73
86
|
};
|
|
74
87
|
|
|
75
88
|
const ALIGNMENT_ICONS: Record<DocumentTextAlignment, ReactNode> = {
|
|
@@ -137,6 +150,66 @@ type ParagraphStyle = (typeof PARAGRAPH_STYLES)[number];
|
|
|
137
150
|
|
|
138
151
|
const HEADING_LEVELS = { "1": 1, "2": 2, "3": 3 } as const;
|
|
139
152
|
|
|
153
|
+
const PLACEHOLDER_HREF = /^\{\{([^{}]+)\}\}$/;
|
|
154
|
+
|
|
155
|
+
function buttonTargetLabel(href: string, labels: Readonly<Record<string, string>>): string {
|
|
156
|
+
const name = PLACEHOLDER_HREF.exec(href)?.[1];
|
|
157
|
+
if (name === undefined) return href;
|
|
158
|
+
return Object.hasOwn(labels, name) ? (labels[name] ?? name) : name;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// The bubble menu registers its plugin once per mount, so a rebuilt editor needs a fresh mount.
|
|
162
|
+
const editorMountKeys = new WeakMap<Editor, number>();
|
|
163
|
+
let lastMountKey = 0;
|
|
164
|
+
function mountKeyOf(editor: Editor): number {
|
|
165
|
+
const known = editorMountKeys.get(editor);
|
|
166
|
+
if (known !== undefined) return known;
|
|
167
|
+
lastMountKey += 1;
|
|
168
|
+
const created = lastMountKey;
|
|
169
|
+
editorMountKeys.set(editor, created);
|
|
170
|
+
return created;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Board .hb: a small bar that straddles the block's top frame line on the right; above a table it sits fully
|
|
174
|
+
// outside so the header row stays readable.
|
|
175
|
+
const BUBBLE_BAR_CLASS =
|
|
176
|
+
"flex w-max items-center gap-1 rounded border border-input bg-popover p-px text-xs [&_button]:h-5 [&_button]:px-1.5 [&_button]:text-[10px]";
|
|
177
|
+
|
|
178
|
+
function bubbleOffset(editor: Editor): (args: { rects: { floating: { height: number } } }) => {
|
|
179
|
+
mainAxis: number;
|
|
180
|
+
crossAxis: number;
|
|
181
|
+
} {
|
|
182
|
+
return ({ rects }) => ({
|
|
183
|
+
mainAxis: findRepeatTable(editor) !== null ? 2 : -rects.floating.height / 2,
|
|
184
|
+
crossAxis: -8,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const BUBBLE_MENU_PLUGIN_KEY = "documentBlockOptions";
|
|
189
|
+
|
|
190
|
+
// The options bar belongs to the block frame, not to the caret inside it.
|
|
191
|
+
function selectedBlockReference(editor: Editor): {
|
|
192
|
+
getBoundingClientRect: () => DOMRect;
|
|
193
|
+
getClientRects: () => DOMRect[];
|
|
194
|
+
contextElement: Element;
|
|
195
|
+
} | null {
|
|
196
|
+
const { node } = editor.view.domAtPos(editor.state.selection.from);
|
|
197
|
+
const start = node instanceof Element ? node : node.parentElement;
|
|
198
|
+
const block = start?.closest(`[${DOCUMENT_BLOCK_ATTRIBUTE}], table`);
|
|
199
|
+
if (block === null || block === undefined) return null;
|
|
200
|
+
return {
|
|
201
|
+
getBoundingClientRect: () => block.getBoundingClientRect(),
|
|
202
|
+
getClientRects: () => [block.getBoundingClientRect()],
|
|
203
|
+
contextElement: block,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The bubble is first measured before React has filled it, so it sits at the wrong place until a second pass.
|
|
208
|
+
function repositionBubble(editor: Editor): void {
|
|
209
|
+
if (editor.isDestroyed) return;
|
|
210
|
+
editor.view.dispatch(editor.state.tr.setMeta(BUBBLE_MENU_PLUGIN_KEY, "updatePosition"));
|
|
211
|
+
}
|
|
212
|
+
|
|
140
213
|
function isParagraphStyle(value: string): value is ParagraphStyle {
|
|
141
214
|
return isOneOf(PARAGRAPH_STYLES, value);
|
|
142
215
|
}
|
|
@@ -245,6 +318,9 @@ function InsertPanel({ editor, editorId, kind, variables, onClose }: PanelProps)
|
|
|
245
318
|
const [buttonText, setButtonText] = useState(() =>
|
|
246
319
|
t("template-resolver.documentEditor.buttonDefaultText"),
|
|
247
320
|
);
|
|
321
|
+
const [stepsText, setStepsText] = useState(() =>
|
|
322
|
+
t("template-resolver.documentEditor.stepsDefault"),
|
|
323
|
+
);
|
|
248
324
|
const [chosenName, setChosenName] = useState("");
|
|
249
325
|
const [error, setError] = useState<string | null>(null);
|
|
250
326
|
|
|
@@ -266,6 +342,15 @@ function InsertPanel({ editor, editorId, kind, variables, onClose }: PanelProps)
|
|
|
266
342
|
const chain = editor.chain().focus();
|
|
267
343
|
if (!editor.state.selection.empty) chain.setLink({ href }).run();
|
|
268
344
|
else if (editor.isActive("link")) chain.extendMarkRange("link").setLink({ href }).run();
|
|
345
|
+
else if (isDocumentPlaceholderHref(href))
|
|
346
|
+
// Placeholders are never inserted as {{name}} text; the link shows the variable's value.
|
|
347
|
+
chain
|
|
348
|
+
.insertContent({
|
|
349
|
+
type: "variable",
|
|
350
|
+
attrs: { name: href.slice(2, -2) },
|
|
351
|
+
marks: [{ type: "link", attrs: { href } }],
|
|
352
|
+
})
|
|
353
|
+
.run();
|
|
269
354
|
else
|
|
270
355
|
chain
|
|
271
356
|
.insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] })
|
|
@@ -285,7 +370,17 @@ function InsertPanel({ editor, editorId, kind, variables, onClose }: PanelProps)
|
|
|
285
370
|
insertAndClose({ type: "resource", attrs: { resource, alt: alt || null } });
|
|
286
371
|
};
|
|
287
372
|
|
|
288
|
-
const
|
|
373
|
+
const submitSteps = (): void => {
|
|
374
|
+
const steps = parseDocumentStepsInput(stepsText);
|
|
375
|
+
if (steps === null) {
|
|
376
|
+
setError(t("template-resolver.documentEditor.invalidSteps"));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
insertAndClose({ type: "steps", attrs: { variable: selectedName, steps } });
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const needsVariables =
|
|
383
|
+
kind === "button" || kind === "repeat" || kind === "placeholder" || kind === "steps";
|
|
289
384
|
const noVariables = needsVariables && selectable.length === 0;
|
|
290
385
|
const idPrefix = `${editorId}-panel`;
|
|
291
386
|
|
|
@@ -388,6 +483,29 @@ function InsertPanel({ editor, editorId, kind, variables, onClose }: PanelProps)
|
|
|
388
483
|
onChange={setChosenName}
|
|
389
484
|
/>
|
|
390
485
|
)}
|
|
486
|
+
{kind === "steps" && !noVariables && (
|
|
487
|
+
<>
|
|
488
|
+
<NameSelect
|
|
489
|
+
id={`${idPrefix}-steps-variable`}
|
|
490
|
+
label={t("template-resolver.documentEditor.stepsVariable")}
|
|
491
|
+
names={selectable}
|
|
492
|
+
value={selectedName}
|
|
493
|
+
onChange={setChosenName}
|
|
494
|
+
/>
|
|
495
|
+
<Field
|
|
496
|
+
id={`${idPrefix}-steps-list`}
|
|
497
|
+
label={t("template-resolver.documentEditor.stepsList")}
|
|
498
|
+
>
|
|
499
|
+
<Input
|
|
500
|
+
kind="text"
|
|
501
|
+
id={`${idPrefix}-steps-list`}
|
|
502
|
+
name="steps"
|
|
503
|
+
value={stepsText}
|
|
504
|
+
onChange={setStepsText}
|
|
505
|
+
/>
|
|
506
|
+
</Field>
|
|
507
|
+
</>
|
|
508
|
+
)}
|
|
391
509
|
{error !== null && (
|
|
392
510
|
<p role="alert" className="text-sm text-destructive">
|
|
393
511
|
{error}
|
|
@@ -416,6 +534,7 @@ function InsertPanel({ editor, editorId, kind, variables, onClose }: PanelProps)
|
|
|
416
534
|
})}
|
|
417
535
|
{...(kind === "repeat" &&
|
|
418
536
|
!noVariables && { onInsert: () => insertAndClose(repeatTableContent(selectedName)) })}
|
|
537
|
+
{...(kind === "steps" && !noVariables && { onInsert: submitSteps })}
|
|
419
538
|
/>
|
|
420
539
|
</div>
|
|
421
540
|
</div>
|
|
@@ -434,19 +553,25 @@ export default function DocumentTiptapEditor({
|
|
|
434
553
|
resourcePreviews = NO_PREVIEWS,
|
|
435
554
|
chrome = "default",
|
|
436
555
|
toolbar = "full",
|
|
556
|
+
renderPaper,
|
|
557
|
+
toolbarHost,
|
|
437
558
|
onEditorReady,
|
|
438
559
|
}: DocumentEditorProps): ReactNode {
|
|
439
560
|
const t = useTranslation();
|
|
440
|
-
const { Button, Field, Input } = usePrimitives();
|
|
561
|
+
const { ActionOverflowMenu, Button, Field, Input } = usePrimitives();
|
|
441
562
|
const [openPanel, setOpenPanel] = useState<PanelKind | null>(null);
|
|
563
|
+
const [canvasScroller, setCanvasScroller] = useState<HTMLElement | null>(null);
|
|
442
564
|
const [showHtml, setShowHtml] = useState(false);
|
|
443
565
|
const canvas = chrome === "canvas";
|
|
444
566
|
const compact = toolbar === "compact" && !canvas;
|
|
567
|
+
const hosted = toolbarHost !== undefined;
|
|
445
568
|
|
|
446
569
|
const placeholderLabel = t("template-resolver.documentEditor.placeholder");
|
|
447
570
|
const imageLabel = t("template-resolver.documentEditor.image");
|
|
571
|
+
const imageBlockLabel = t("template-resolver.documentEditor.imageBlock");
|
|
448
572
|
const pageBreakLabel = t("template-resolver.documentEditor.pageBreak");
|
|
449
573
|
const buttonLabel = t("template-resolver.documentEditor.button");
|
|
574
|
+
const stepsLabel = t("template-resolver.documentEditor.steps");
|
|
450
575
|
const stableLabels = useStableByKey(variableLabels, JSON.stringify(variableLabels));
|
|
451
576
|
const stableSystem = useStableByKey(systemVariables, systemVariables.join("\n"));
|
|
452
577
|
const stablePreviews = useStableByKey(resourcePreviews, previewsKey(resourcePreviews));
|
|
@@ -457,6 +582,8 @@ export default function DocumentTiptapEditor({
|
|
|
457
582
|
image: imageLabel,
|
|
458
583
|
pageBreak: pageBreakLabel,
|
|
459
584
|
button: buttonLabel,
|
|
585
|
+
steps: stepsLabel,
|
|
586
|
+
imageBlock: imageBlockLabel,
|
|
460
587
|
variableLabels: stableLabels,
|
|
461
588
|
systemVariables: stableSystem,
|
|
462
589
|
resourcePreviews: stablePreviews,
|
|
@@ -465,8 +592,10 @@ export default function DocumentTiptapEditor({
|
|
|
465
592
|
[
|
|
466
593
|
placeholderLabel,
|
|
467
594
|
imageLabel,
|
|
595
|
+
imageBlockLabel,
|
|
468
596
|
pageBreakLabel,
|
|
469
597
|
buttonLabel,
|
|
598
|
+
stepsLabel,
|
|
470
599
|
stableLabels,
|
|
471
600
|
stableSystem,
|
|
472
601
|
stablePreviews,
|
|
@@ -487,6 +616,7 @@ export default function DocumentTiptapEditor({
|
|
|
487
616
|
immediatelyRender: false,
|
|
488
617
|
editorProps: { attributes: { id } },
|
|
489
618
|
onCreate: ({ editor: created }) => onEditorReady?.(createEditorHandle(created)),
|
|
619
|
+
onDestroy: () => onEditorReady?.(null),
|
|
490
620
|
onUpdate: ({ editor: e }) => onChange(e.getHTML()),
|
|
491
621
|
},
|
|
492
622
|
[extensions],
|
|
@@ -528,27 +658,27 @@ export default function DocumentTiptapEditor({
|
|
|
528
658
|
|
|
529
659
|
// The entry loads asynchronously, so `value` arrives after useEditor read
|
|
530
660
|
// its initial content; the guard keeps in-flight typing from being clobbered.
|
|
661
|
+
// When `extensions` change, useEditor's own effect destroys this render's
|
|
662
|
+
// editor earlier in the same commit (a late validate result in the layout
|
|
663
|
+
// editor does that); reading it then throws and unmounts the whole app. The
|
|
664
|
+
// replacement editor re-renders and syncs `value` itself.
|
|
531
665
|
// kumiko-lint-ignore no-raw-hooks DOM integration: syncs the controlled value into the imperative tiptap editor
|
|
532
666
|
useEffect(() => {
|
|
533
|
-
if (editor && value !== editor.getHTML()) {
|
|
667
|
+
if (editor && !editor.isDestroyed && value !== editor.getHTML()) {
|
|
534
668
|
editor.commands.setContent(value, { emitUpdate: false });
|
|
535
669
|
}
|
|
536
670
|
}, [value, editor]);
|
|
537
671
|
|
|
538
672
|
// kumiko-lint-ignore no-raw-hooks DOM integration: tiptap exposes editability only imperatively via setEditable
|
|
539
673
|
useEffect(() => {
|
|
540
|
-
|
|
674
|
+
// setEditable emits "update" by default, which would report the mount as an edit.
|
|
675
|
+
if (editor && !editor.isDestroyed) editor.setEditable(!readOnly, false);
|
|
541
676
|
}, [readOnly, editor]);
|
|
542
677
|
|
|
678
|
+
// Read-only without chips: a textarea would insert {{name}} text and plaintext into a document body.
|
|
543
679
|
if (!editor)
|
|
544
680
|
return (
|
|
545
|
-
<TextareaContentEditor
|
|
546
|
-
id={id}
|
|
547
|
-
value={value}
|
|
548
|
-
onChange={onChange}
|
|
549
|
-
variables={variables}
|
|
550
|
-
readOnly={readOnly}
|
|
551
|
-
/>
|
|
681
|
+
<TextareaContentEditor id={id} value={value} onChange={onChange} variables={[]} readOnly />
|
|
552
682
|
);
|
|
553
683
|
|
|
554
684
|
const togglePanel = (kind: PanelKind): void =>
|
|
@@ -584,6 +714,7 @@ export default function DocumentTiptapEditor({
|
|
|
584
714
|
repeat: { label: t("template-resolver.documentEditor.repeat"), icon: <Repeat size={16} /> },
|
|
585
715
|
pageBreak: { label: pageBreakLabel, icon: <Scissors size={16} /> },
|
|
586
716
|
align: { label: t("template-resolver.documentEditor.align"), icon: <AlignLeft size={16} /> },
|
|
717
|
+
steps: { label: stepsLabel, icon: <ListChecks size={16} /> },
|
|
587
718
|
};
|
|
588
719
|
|
|
589
720
|
const repeatTable = activeState?.repeatTable ?? null;
|
|
@@ -592,6 +723,11 @@ export default function DocumentTiptapEditor({
|
|
|
592
723
|
? (variableLabels[repeatTable.lists[0] ?? ""] ?? repeatTable.lists[0])
|
|
593
724
|
: undefined;
|
|
594
725
|
|
|
726
|
+
const repeatBarLabel =
|
|
727
|
+
repeatListLabel === undefined
|
|
728
|
+
? t("template-resolver.documentEditor.repeat")
|
|
729
|
+
: t("template-resolver.documentEditor.repeatBarList", { list: repeatListLabel });
|
|
730
|
+
|
|
595
731
|
const paragraphStyleLabel = t("template-resolver.documentEditor.paragraphStyle");
|
|
596
732
|
const styleOptions = [
|
|
597
733
|
{ value: "paragraph", label: t("template-resolver.documentEditor.paragraph") },
|
|
@@ -600,17 +736,18 @@ export default function DocumentTiptapEditor({
|
|
|
600
736
|
{ value: "3", label: t("template-resolver.documentEditor.heading3") },
|
|
601
737
|
];
|
|
602
738
|
|
|
603
|
-
// Canvas chrome
|
|
739
|
+
// Canvas chrome is one flat row: unframed ghost buttons, glyph plus text for the longer names.
|
|
604
740
|
const iconButton = (
|
|
605
741
|
label: string,
|
|
606
742
|
icon: ReactNode,
|
|
607
743
|
isActive: boolean,
|
|
608
744
|
onClick: () => void,
|
|
745
|
+
text?: string,
|
|
609
746
|
): ReactNode =>
|
|
610
747
|
canvas ? (
|
|
611
748
|
<Button
|
|
612
749
|
type="button"
|
|
613
|
-
variant=
|
|
750
|
+
variant="ghost"
|
|
614
751
|
size="sm"
|
|
615
752
|
pressed={isActive}
|
|
616
753
|
disabled={readOnly}
|
|
@@ -619,6 +756,7 @@ export default function DocumentTiptapEditor({
|
|
|
619
756
|
title={label}
|
|
620
757
|
>
|
|
621
758
|
{icon}
|
|
759
|
+
{text}
|
|
622
760
|
</Button>
|
|
623
761
|
) : (
|
|
624
762
|
<Button
|
|
@@ -646,6 +784,7 @@ export default function DocumentTiptapEditor({
|
|
|
646
784
|
readonly label: string;
|
|
647
785
|
readonly icon: ReactNode;
|
|
648
786
|
readonly showIcon: boolean;
|
|
787
|
+
readonly trailingIcon?: ReactNode;
|
|
649
788
|
readonly expanded?: boolean;
|
|
650
789
|
readonly onClick: () => void;
|
|
651
790
|
}): ReactNode => {
|
|
@@ -664,112 +803,205 @@ export default function DocumentTiptapEditor({
|
|
|
664
803
|
<Button key={spec.key} {...shared} size="sm">
|
|
665
804
|
{spec.showIcon && spec.icon}
|
|
666
805
|
{spec.label}
|
|
806
|
+
{spec.trailingIcon}
|
|
667
807
|
</Button>
|
|
668
808
|
);
|
|
669
809
|
};
|
|
670
810
|
|
|
671
|
-
|
|
811
|
+
const editorContent = (
|
|
812
|
+
<EditorContent
|
|
813
|
+
editor={editor}
|
|
814
|
+
className={cn(
|
|
815
|
+
!canvas && "min-h-40 px-3 py-2 text-base outline-none md:text-sm",
|
|
816
|
+
canvas && "text-sm",
|
|
817
|
+
"[&_.ProseMirror-focused_.ProseMirror-selectednode]:ring-2 [&_.ProseMirror-focused_.ProseMirror-selectednode]:ring-primary",
|
|
818
|
+
hosted
|
|
819
|
+
? "[&_.ProseMirror]:outline-none"
|
|
820
|
+
: "[&_.ProseMirror]:min-h-40 [&_.ProseMirror]:outline-none",
|
|
821
|
+
"[&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6",
|
|
822
|
+
"[&_h1]:text-xl [&_h1]:font-bold [&_h2]:text-lg [&_h2]:font-bold [&_h3]:text-base [&_h3]:font-bold",
|
|
823
|
+
"[&_a]:underline [&_a]:text-primary",
|
|
824
|
+
"[&_strong]:font-bold [&_em]:italic [&_u]:underline",
|
|
825
|
+
canvas
|
|
826
|
+
? "[&_table]:mb-2 [&_table]:mt-6 [&_.ProseMirror>:first-child_table]:mt-0 [&_.ProseMirror>table:first-child]:mt-0 [&_td:last-child>p[data-size=small][data-align=right]]:whitespace-nowrap"
|
|
827
|
+
: "[&_table]:my-2",
|
|
828
|
+
"[&_table]:w-full [&_table]:border-collapse",
|
|
829
|
+
"[&_td]:border-b [&_td]:border-border [&_td]:px-2 [&_td]:py-1.5 [&_td>p]:m-0 [&_th>p]:m-0",
|
|
830
|
+
"[&_th]:border-b [&_th]:border-border [&_th]:px-2 [&_th]:py-1.5 [&_th]:text-left [&_th]:text-xs [&_th]:font-medium [&_th]:text-muted-foreground",
|
|
831
|
+
"[&_tr[data-each]>td]:bg-muted/50",
|
|
832
|
+
"[&_[data-align=left]]:text-left [&_[data-align=center]]:text-center [&_[data-align=right]]:text-right [&_[data-align=justify]]:text-justify",
|
|
833
|
+
"[&_p[data-size=small]]:text-xs [&_p[data-size=small]]:text-muted-foreground",
|
|
834
|
+
"[&_a[data-button]]:inline-block [&_a[data-button]]:rounded-full [&_a[data-button]]:bg-primary [&_a[data-button]]:px-4 [&_a[data-button]]:py-1.5 [&_a[data-button]]:text-primary-foreground [&_a[data-button]]:no-underline",
|
|
835
|
+
"[&_hr]:my-3 [&_hr]:border-input",
|
|
836
|
+
"[&_.is-editor-empty]:before:pointer-events-none [&_.is-editor-empty]:before:float-left [&_.is-editor-empty]:before:h-0 [&_.is-editor-empty]:before:text-muted-foreground [&_.is-editor-empty]:before:content-[attr(data-placeholder)]",
|
|
837
|
+
)}
|
|
838
|
+
/>
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
// The canvas toolbar never scrolls sideways: the rarer blocks and the HTML view sit in the menu.
|
|
842
|
+
const menuBlocks =
|
|
843
|
+
canvas && ActionOverflowMenu !== undefined
|
|
844
|
+
? blocks.filter((block) => !CANVAS_INLINE_BLOCKS.includes(block))
|
|
845
|
+
: [];
|
|
846
|
+
const inlineBlocks = blocks.filter((block) => !menuBlocks.includes(block));
|
|
847
|
+
const overflowItems: readonly ActionMenuItemSpec[] = [
|
|
848
|
+
...menuBlocks.map((block) => ({
|
|
849
|
+
id: block,
|
|
850
|
+
label: blockButtons[block].label,
|
|
851
|
+
disabled: readOnly,
|
|
852
|
+
onSelect: () => runBlock(block),
|
|
853
|
+
})),
|
|
854
|
+
...(canvas && !hosted
|
|
855
|
+
? [
|
|
856
|
+
{
|
|
857
|
+
id: "html",
|
|
858
|
+
label: t("template-resolver.documentEditor.showHtml"),
|
|
859
|
+
onSelect: () => setShowHtml((current) => !current),
|
|
860
|
+
},
|
|
861
|
+
]
|
|
862
|
+
: []),
|
|
863
|
+
];
|
|
864
|
+
|
|
865
|
+
const toolbarBar = (
|
|
672
866
|
<div
|
|
673
|
-
className={cn(
|
|
867
|
+
className={cn(
|
|
868
|
+
"flex items-center border-b border-input",
|
|
869
|
+
canvas
|
|
870
|
+
? "min-h-9 flex-nowrap gap-0 overflow-hidden bg-background px-1.5 [&>*]:shrink-0 [&_button]:h-7 [&_button]:gap-1 [&_button]:px-1 [&_button]:text-xs"
|
|
871
|
+
: "flex-wrap gap-1 p-1",
|
|
872
|
+
)}
|
|
674
873
|
>
|
|
675
|
-
<div
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
name="paragraph-style"
|
|
682
|
-
value={activeState?.blockStyle ?? "paragraph"}
|
|
683
|
-
onChange={setBlockStyle}
|
|
684
|
-
options={styleOptions}
|
|
685
|
-
display="dropdown"
|
|
686
|
-
disabled={readOnly}
|
|
687
|
-
/>
|
|
688
|
-
</Field>
|
|
689
|
-
</div>
|
|
690
|
-
<Separator compact={compact} />
|
|
691
|
-
{iconButton(
|
|
692
|
-
t("kumiko.contentEditor.bold"),
|
|
693
|
-
markContent("B", <BoldIcon size={16} />, "font-bold"),
|
|
694
|
-
activeState?.bold ?? false,
|
|
695
|
-
() => editor.chain().focus().toggleBold().run(),
|
|
696
|
-
)}
|
|
697
|
-
{iconButton(
|
|
698
|
-
t("kumiko.contentEditor.italic"),
|
|
699
|
-
markContent("I", <ItalicIcon size={16} />, "italic"),
|
|
700
|
-
activeState?.italic ?? false,
|
|
701
|
-
() => editor.chain().focus().toggleItalic().run(),
|
|
702
|
-
)}
|
|
703
|
-
{iconButton(
|
|
704
|
-
t("template-resolver.documentEditor.underline"),
|
|
705
|
-
markContent("U", <UnderlineIcon size={16} />, "underline"),
|
|
706
|
-
activeState?.underline ?? false,
|
|
707
|
-
() => editor.chain().focus().toggleUnderline().run(),
|
|
874
|
+
<div
|
|
875
|
+
// kumiko-lint-ignore raw-classname the toolbar paragraph select sits flush in the editor toolbar; ComboboxInput has no flat trigger variant
|
|
876
|
+
className={cn(
|
|
877
|
+
!canvas && (compact ? "w-24" : "w-40"),
|
|
878
|
+
canvas &&
|
|
879
|
+
"w-auto shrink-0 [&_button[aria-haspopup=listbox]]:border-transparent [&_button[aria-haspopup=listbox]]:bg-transparent [&_button[aria-haspopup=listbox]]:shadow-none",
|
|
708
880
|
)}
|
|
709
|
-
|
|
710
|
-
{
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
881
|
+
>
|
|
882
|
+
<Field id={`${id}-paragraph-style`} label={paragraphStyleLabel} hideLabel>
|
|
883
|
+
<Input
|
|
884
|
+
kind="select"
|
|
885
|
+
id={`${id}-paragraph-style`}
|
|
886
|
+
name="paragraph-style"
|
|
887
|
+
value={activeState?.blockStyle ?? "paragraph"}
|
|
888
|
+
onChange={setBlockStyle}
|
|
889
|
+
options={styleOptions}
|
|
890
|
+
display="dropdown"
|
|
891
|
+
disabled={readOnly}
|
|
892
|
+
/>
|
|
893
|
+
</Field>
|
|
894
|
+
</div>
|
|
895
|
+
<Separator compact={compact || canvas} />
|
|
896
|
+
{iconButton(
|
|
897
|
+
t("kumiko.contentEditor.bold"),
|
|
898
|
+
markContent("B", <BoldIcon size={16} />, "font-bold"),
|
|
899
|
+
activeState?.bold ?? false,
|
|
900
|
+
() => editor.chain().focus().toggleBold().run(),
|
|
901
|
+
)}
|
|
902
|
+
{iconButton(
|
|
903
|
+
t("kumiko.contentEditor.italic"),
|
|
904
|
+
markContent("I", <ItalicIcon size={16} />, "italic"),
|
|
905
|
+
activeState?.italic ?? false,
|
|
906
|
+
() => editor.chain().focus().toggleItalic().run(),
|
|
907
|
+
)}
|
|
908
|
+
{iconButton(
|
|
909
|
+
t("template-resolver.documentEditor.underline"),
|
|
910
|
+
markContent("U", <UnderlineIcon size={16} />, "underline"),
|
|
911
|
+
activeState?.underline ?? false,
|
|
912
|
+
() => editor.chain().focus().toggleUnderline().run(),
|
|
913
|
+
)}
|
|
914
|
+
<Separator compact={compact || canvas} />
|
|
915
|
+
{iconButton(
|
|
916
|
+
t("kumiko.contentEditor.bulletList"),
|
|
917
|
+
<List size={16} />,
|
|
918
|
+
activeState?.bulletList ?? false,
|
|
919
|
+
() => editor.chain().focus().toggleBulletList().run(),
|
|
920
|
+
canvas ? undefined : t("template-resolver.documentEditor.list"),
|
|
921
|
+
)}
|
|
922
|
+
{iconButton(
|
|
923
|
+
t("kumiko.contentEditor.orderedList"),
|
|
924
|
+
<ListOrdered size={16} />,
|
|
925
|
+
activeState?.orderedList ?? false,
|
|
926
|
+
() => editor.chain().focus().toggleOrderedList().run(),
|
|
927
|
+
)}
|
|
928
|
+
{iconButton(
|
|
929
|
+
t("template-resolver.documentEditor.link"),
|
|
930
|
+
<LinkIcon size={16} />,
|
|
931
|
+
activeState?.link ?? false,
|
|
932
|
+
() => togglePanel("link"),
|
|
933
|
+
)}
|
|
934
|
+
<Separator compact={compact || canvas} />
|
|
935
|
+
{labelledButton({
|
|
936
|
+
label: t("template-resolver.documentEditor.insertPlaceholder"),
|
|
937
|
+
icon: <Braces size={16} />,
|
|
938
|
+
showIcon: true,
|
|
939
|
+
trailingIcon: canvas ? <ChevronDown size={12} /> : undefined,
|
|
940
|
+
expanded: openPanel === "placeholder",
|
|
941
|
+
onClick: () => togglePanel("placeholder"),
|
|
942
|
+
})}
|
|
943
|
+
{inlineBlocks.length > 0 && <Separator compact={compact || canvas} />}
|
|
944
|
+
{inlineBlocks.map((block) =>
|
|
945
|
+
canvas ? (
|
|
946
|
+
<Fragment key={block}>
|
|
947
|
+
{iconButton(blockButtons[block].label, blockButtons[block].icon, false, () =>
|
|
948
|
+
runBlock(block),
|
|
949
|
+
)}
|
|
950
|
+
</Fragment>
|
|
951
|
+
) : (
|
|
738
952
|
labelledButton({
|
|
739
953
|
key: block,
|
|
740
954
|
label: blockButtons[block].label,
|
|
741
955
|
icon: blockButtons[block].icon,
|
|
742
|
-
showIcon:
|
|
956
|
+
showIcon: true,
|
|
743
957
|
onClick: () => runBlock(block),
|
|
744
|
-
})
|
|
745
|
-
)
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
{
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
958
|
+
})
|
|
959
|
+
),
|
|
960
|
+
)}
|
|
961
|
+
{overflowItems.length > 0 && ActionOverflowMenu !== undefined && (
|
|
962
|
+
<span className="ml-auto shrink-0">
|
|
963
|
+
<ActionOverflowMenu
|
|
964
|
+
items={overflowItems}
|
|
965
|
+
label={t("template-resolver.documentEditor.more")}
|
|
966
|
+
testId={`${id}-toolbar-more`}
|
|
967
|
+
/>
|
|
968
|
+
</span>
|
|
969
|
+
)}
|
|
970
|
+
</div>
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
const bubbleMenu = (
|
|
974
|
+
<BubbleMenu
|
|
975
|
+
key={mountKeyOf(editor)}
|
|
976
|
+
pluginKey={BUBBLE_MENU_PLUGIN_KEY}
|
|
977
|
+
editor={editor}
|
|
978
|
+
options={{
|
|
979
|
+
strategy: "fixed",
|
|
980
|
+
placement: "top-end",
|
|
981
|
+
offset: bubbleOffset(editor),
|
|
982
|
+
scrollTarget: canvasScroller ?? window,
|
|
983
|
+
onShow: () => requestAnimationFrame(() => repositionBubble(editor)),
|
|
984
|
+
}}
|
|
985
|
+
getReferencedVirtualElement={() => selectedBlockReference(editor)}
|
|
986
|
+
shouldShow={({ editor: e, element }) =>
|
|
987
|
+
// A leading image is selected by default; the bubble belongs to a deliberate selection.
|
|
988
|
+
(e.isFocused || element.contains(document.activeElement)) &&
|
|
989
|
+
(e.isActive("resource") || e.isActive("button") || findRepeatTable(e) !== null)
|
|
990
|
+
}
|
|
991
|
+
>
|
|
992
|
+
{selectedBlock !== null ? (
|
|
993
|
+
<div className={BUBBLE_BAR_CLASS} data-testid="document-block-options">
|
|
994
|
+
<span className="px-1.5 text-[10px] text-muted-foreground">
|
|
995
|
+
{selectedBlock.type === "resource"
|
|
996
|
+
? t("template-resolver.documentEditor.resourceKey")
|
|
997
|
+
: t("template-resolver.documentEditor.buttonTarget")}
|
|
998
|
+
{": "}
|
|
999
|
+
{selectedBlock.type === "button" ? (
|
|
1000
|
+
<strong>{buttonTargetLabel(selectedBlock.detail, variableLabels)}</strong>
|
|
1001
|
+
) : (
|
|
1002
|
+
<code>{selectedBlock.detail}</code>
|
|
1003
|
+
)}
|
|
770
1004
|
</span>
|
|
771
|
-
<code className="text-xs text-muted-foreground">{selectedBlock.detail}</code>
|
|
772
|
-
<span className="flex-1" />
|
|
773
1005
|
<Button
|
|
774
1006
|
type="button"
|
|
775
1007
|
size="sm"
|
|
@@ -781,25 +1013,72 @@ export default function DocumentTiptapEditor({
|
|
|
781
1013
|
{t("template-resolver.documentEditor.removeBlock")}
|
|
782
1014
|
</Button>
|
|
783
1015
|
</div>
|
|
1016
|
+
) : (
|
|
1017
|
+
repeatTable !== null && (
|
|
1018
|
+
<div data-testid="document-repeat-bar" className={BUBBLE_BAR_CLASS}>
|
|
1019
|
+
<span className="px-1.5 text-[10px] uppercase tracking-wide text-muted-foreground">
|
|
1020
|
+
{repeatBarLabel}
|
|
1021
|
+
</span>
|
|
1022
|
+
<Button
|
|
1023
|
+
type="button"
|
|
1024
|
+
variant="secondary"
|
|
1025
|
+
size="sm"
|
|
1026
|
+
pressed={repeatTable.headerRepeats}
|
|
1027
|
+
onClick={() => toggleRepeatHeader(editor)}
|
|
1028
|
+
>
|
|
1029
|
+
{t("template-resolver.documentEditor.repeatHeader")}
|
|
1030
|
+
</Button>
|
|
1031
|
+
</div>
|
|
1032
|
+
)
|
|
784
1033
|
)}
|
|
785
|
-
|
|
1034
|
+
</BubbleMenu>
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
const insertPanel = openPanel !== null && !readOnly && (
|
|
1038
|
+
<InsertPanel
|
|
1039
|
+
key={openPanel}
|
|
1040
|
+
editor={editor}
|
|
1041
|
+
editorId={id}
|
|
1042
|
+
kind={openPanel}
|
|
1043
|
+
variables={variables}
|
|
1044
|
+
onClose={() => setOpenPanel(null)}
|
|
1045
|
+
/>
|
|
1046
|
+
);
|
|
1047
|
+
|
|
1048
|
+
if (hosted) {
|
|
1049
|
+
return (
|
|
1050
|
+
<>
|
|
1051
|
+
{toolbarHost !== null &&
|
|
1052
|
+
createPortal(
|
|
1053
|
+
<>
|
|
1054
|
+
{toolbarBar}
|
|
1055
|
+
{insertPanel}
|
|
1056
|
+
</>,
|
|
1057
|
+
toolbarHost,
|
|
1058
|
+
)}
|
|
1059
|
+
{!readOnly && bubbleMenu}
|
|
1060
|
+
{editorContent}
|
|
1061
|
+
</>
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return (
|
|
1066
|
+
<div
|
|
1067
|
+
className={cn(
|
|
1068
|
+
canvas ? "flex min-h-0 flex-1 flex-col" : "rounded-md border border-input bg-transparent",
|
|
1069
|
+
)}
|
|
1070
|
+
>
|
|
1071
|
+
{!hosted && toolbarBar}
|
|
1072
|
+
{canvas && !readOnly && bubbleMenu}
|
|
1073
|
+
{canvas && !hosted && showHtml && (
|
|
786
1074
|
<section aria-label={t("template-resolver.documentEditor.htmlSource")}>
|
|
787
1075
|
<pre className="h-40 overflow-auto whitespace-pre-wrap break-all border-b border-input bg-muted p-2 font-mono text-xs">
|
|
788
1076
|
{activeState?.html ?? ""}
|
|
789
1077
|
</pre>
|
|
790
1078
|
</section>
|
|
791
1079
|
)}
|
|
792
|
-
{
|
|
793
|
-
|
|
794
|
-
key={openPanel}
|
|
795
|
-
editor={editor}
|
|
796
|
-
editorId={id}
|
|
797
|
-
kind={openPanel}
|
|
798
|
-
variables={variables}
|
|
799
|
-
onClose={() => setOpenPanel(null)}
|
|
800
|
-
/>
|
|
801
|
-
)}
|
|
802
|
-
{repeatTable !== null && !readOnly && (
|
|
1080
|
+
{!hosted && insertPanel}
|
|
1081
|
+
{repeatTable !== null && !readOnly && !canvas && (
|
|
803
1082
|
<div
|
|
804
1083
|
data-testid="document-repeat-bar"
|
|
805
1084
|
className="flex flex-wrap items-center gap-3 border-b border-input bg-muted/40 px-2 py-1 text-xs"
|
|
@@ -818,32 +1097,13 @@ export default function DocumentTiptapEditor({
|
|
|
818
1097
|
>
|
|
819
1098
|
{t("template-resolver.documentEditor.repeatHeader")}
|
|
820
1099
|
</Button>
|
|
821
|
-
<span className="text-muted-foreground">
|
|
822
|
-
{t("template-resolver.documentEditor.keepRowsTogether")}
|
|
823
|
-
</span>
|
|
824
1100
|
</div>
|
|
825
1101
|
)}
|
|
826
|
-
<div
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
? "w-[600px] max-w-full bg-background px-10 py-7 text-sm border border-input"
|
|
832
|
-
: "min-h-40 px-3 py-2 text-base outline-none md:text-sm",
|
|
833
|
-
"[&_.ProseMirror-selectednode]:ring-2 [&_.ProseMirror-selectednode]:ring-primary",
|
|
834
|
-
"[&_.ProseMirror]:min-h-40 [&_.ProseMirror]:outline-none",
|
|
835
|
-
"[&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6",
|
|
836
|
-
"[&_h1]:text-xl [&_h1]:font-bold [&_h2]:text-lg [&_h2]:font-bold [&_h3]:text-base [&_h3]:font-bold",
|
|
837
|
-
"[&_a]:underline [&_a]:text-primary",
|
|
838
|
-
"[&_strong]:font-bold [&_em]:italic [&_u]:underline",
|
|
839
|
-
"[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse",
|
|
840
|
-
"[&_td]:border [&_td]:border-input [&_td]:p-1 [&_th]:border [&_th]:border-input [&_th]:bg-muted [&_th]:p-1 [&_th]:text-left",
|
|
841
|
-
"[&_tr[data-each]]:bg-muted",
|
|
842
|
-
"[&_a[data-button]]:inline-block [&_a[data-button]]:rounded-full [&_a[data-button]]:bg-primary [&_a[data-button]]:px-4 [&_a[data-button]]:py-1.5 [&_a[data-button]]:text-primary-foreground [&_a[data-button]]:no-underline",
|
|
843
|
-
"[&_hr]:my-3 [&_hr]:border-input",
|
|
844
|
-
"[&_.is-editor-empty]:before:pointer-events-none [&_.is-editor-empty]:before:float-left [&_.is-editor-empty]:before:h-0 [&_.is-editor-empty]:before:text-muted-foreground [&_.is-editor-empty]:before:content-[attr(data-placeholder)]",
|
|
845
|
-
)}
|
|
846
|
-
/>
|
|
1102
|
+
<div
|
|
1103
|
+
ref={setCanvasScroller}
|
|
1104
|
+
className={cn(canvas && "min-h-0 flex-1 overflow-auto bg-muted px-8 py-6")}
|
|
1105
|
+
>
|
|
1106
|
+
{canvas && renderPaper !== undefined ? renderPaper(editorContent) : editorContent}
|
|
847
1107
|
</div>
|
|
848
1108
|
</div>
|
|
849
1109
|
);
|