@cosmicdrift/kumiko-samples 0.363.0 → 0.364.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/package.json +1 -1
- package/packages/bundled-features/package.json +7 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
- package/packages/bundled-features/src/auth-email-password/changes.json +37 -0
- package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
- 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/token-request-handler.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
- package/packages/bundled-features/src/billing-foundation/changes.json +36 -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 +10 -1
- 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 +3 -0
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +17 -5
- package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
- package/packages/bundled-features/src/cap-counter/changes.json +6 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
- 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/channel-email/email-channel.ts +17 -4
- 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 +48 -27
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
- package/packages/bundled-features/src/delivery/attachment-ref.ts +21 -0
- package/packages/bundled-features/src/delivery/changes.json +18 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +31 -8
- 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/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/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 +19 -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 +52 -17
- 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/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 +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
- 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/mail-template-body.ts +17 -0
- 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 +68 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +77 -13
- package/packages/bundled-features/src/template-resolver/constants.ts +12 -2
- package/packages/bundled-features/src/template-resolver/document-schema.ts +26 -4
- package/packages/bundled-features/src/template-resolver/feature.ts +4 -1
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +31 -13
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +5 -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/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 +90 -17
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +1 -1
- 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/index.ts +12 -1
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -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/seeding.ts +25 -4
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +2 -2
- package/packages/bundled-features/src/template-resolver/table.ts +13 -1
- package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +87 -24
- package/packages/bundled-features/src/template-resolver/template-preview.ts +159 -56
- package/packages/bundled-features/src/template-resolver/template-resources.ts +44 -4
- package/packages/bundled-features/src/template-resolver/template-usage.ts +31 -11
- package/packages/bundled-features/src/template-resolver/template-validation.ts +1 -0
- 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 +63 -10
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +12 -3
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +349 -134
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +11 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +25 -4
- package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +10 -3
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
- package/packages/bundled-features/src/user/changes.json +6 -0
- 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 +6 -0
- package/packages/bundled-features/src/user-profile/changes.json +1 -1
- 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/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 +4 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +5 -3
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type NotificationDefinition,
|
|
3
|
+
parseQn,
|
|
4
|
+
type Registry,
|
|
5
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
2
6
|
import { DEFAULT_MAIL_LAYOUT_SLUG, type TemplateKind } from "./constants.js";
|
|
3
7
|
|
|
4
8
|
type UsageRole = "template" | "layout";
|
|
@@ -11,27 +15,43 @@ export type TemplateUsageEntry = {
|
|
|
11
15
|
readonly role: UsageRole;
|
|
12
16
|
};
|
|
13
17
|
|
|
18
|
+
/** Layout slug of the effective stored mail template, by notification qualified
|
|
19
|
+
* name and locale. The renderer reads the layout from the stored row's meta,
|
|
20
|
+
* so it wins over the code-registered notification meta. */
|
|
21
|
+
export type EffectiveMailLayoutSlugs = ReadonlyMap<string, ReadonlyMap<string, string>>;
|
|
22
|
+
|
|
23
|
+
function layoutSlugsOf(
|
|
24
|
+
qualifiedName: string,
|
|
25
|
+
notification: NotificationDefinition,
|
|
26
|
+
effectiveLayouts: EffectiveMailLayoutSlugs,
|
|
27
|
+
): string[] {
|
|
28
|
+
const stored = effectiveLayouts.get(qualifiedName);
|
|
29
|
+
const locales = new Set([
|
|
30
|
+
...Object.keys(notification.template ?? {}),
|
|
31
|
+
...Object.keys(notification.meta ?? {}),
|
|
32
|
+
...(stored?.keys() ?? []),
|
|
33
|
+
]);
|
|
34
|
+
return [...locales].map(
|
|
35
|
+
(locale) =>
|
|
36
|
+
stored?.get(locale) ?? notification.meta?.[locale]?.layoutSlug ?? DEFAULT_MAIL_LAYOUT_SLUG,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
14
40
|
export function collectTemplateUsage(
|
|
15
41
|
registry: Registry,
|
|
16
42
|
slug: string,
|
|
17
43
|
kind: TemplateKind,
|
|
44
|
+
effectiveLayouts: EffectiveMailLayoutSlugs = new Map(),
|
|
18
45
|
): TemplateUsageEntry[] {
|
|
19
46
|
const usedBy: TemplateUsageEntry[] = [];
|
|
20
47
|
if (kind === "mail" || kind === "mail-layout") {
|
|
21
48
|
for (const [qualifiedName, notification] of registry.getAllNotifications()) {
|
|
22
49
|
const role: UsageRole = kind === "mail" ? "template" : "layout";
|
|
50
|
+
// Notifications without a code template are not rendered through the resolver.
|
|
23
51
|
const isUsed =
|
|
24
52
|
kind === "mail"
|
|
25
|
-
? qualifiedName === slug
|
|
26
|
-
:
|
|
27
|
-
...new Set([
|
|
28
|
-
...Object.keys(notification.template ?? {}),
|
|
29
|
-
...Object.keys(notification.meta ?? {}),
|
|
30
|
-
]),
|
|
31
|
-
].some(
|
|
32
|
-
(locale) =>
|
|
33
|
-
(notification.meta?.[locale]?.layoutSlug ?? DEFAULT_MAIL_LAYOUT_SLUG) === slug,
|
|
34
|
-
);
|
|
53
|
+
? qualifiedName === slug && notification.template !== undefined
|
|
54
|
+
: layoutSlugsOf(qualifiedName, notification, effectiveLayouts).includes(slug);
|
|
35
55
|
if (!isUsed) continue;
|
|
36
56
|
const parsed = parseQn(qualifiedName);
|
|
37
57
|
usedBy.push({
|
|
@@ -47,6 +47,7 @@ const textLikeExample = z.string().optional();
|
|
|
47
47
|
const scalarVariableSchema = z.discriminatedUnion("type", [
|
|
48
48
|
z.object({ ...variableCommon, type: z.literal("text"), example: textLikeExample }),
|
|
49
49
|
z.object({ ...variableCommon, type: z.literal("link"), example: textLikeExample }),
|
|
50
|
+
z.object({ ...variableCommon, type: z.literal("richText"), example: textLikeExample }),
|
|
50
51
|
z.object({
|
|
51
52
|
...variableCommon,
|
|
52
53
|
type: z.literal("money"),
|
|
@@ -40,7 +40,8 @@ import {
|
|
|
40
40
|
TemplateResolverHandlers,
|
|
41
41
|
TemplateResolverQueries,
|
|
42
42
|
} from "../qualified-names.js";
|
|
43
|
-
import { DocumentContentEditor } from "./document-editor.js";
|
|
43
|
+
import { DocumentBlocksContext, DocumentContentEditor } from "./document-editor.js";
|
|
44
|
+
import { documentBlocksForKind } from "./document-toolbar-blocks.js";
|
|
44
45
|
import { defaultTranslations } from "./i18n.js";
|
|
45
46
|
|
|
46
47
|
// Exported for the unit test — groupBlocksByFolder is a pure function.
|
|
@@ -311,6 +312,7 @@ function TextBlockEditor({
|
|
|
311
312
|
const contentFormat = findContentFormat(features, collectionId);
|
|
312
313
|
const variableExamples = findVariableExamples(features, collectionId);
|
|
313
314
|
const variables = Object.keys(variableExamples);
|
|
315
|
+
const documentBlocks = documentBlocksForKind(findCollection(features, collectionId)?.kind);
|
|
314
316
|
const ContentEditor = useContentEditor(contentFormat);
|
|
315
317
|
const canWrite =
|
|
316
318
|
user?.roles.includes("TenantAdmin") === true || user?.roles.includes("SystemAdmin") === true;
|
|
@@ -448,13 +450,15 @@ function TextBlockEditor({
|
|
|
448
450
|
contentFormat={contentFormat}
|
|
449
451
|
/>
|
|
450
452
|
) : (
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
453
|
+
<DocumentBlocksContext.Provider value={documentBlocks}>
|
|
454
|
+
<ContentEditor
|
|
455
|
+
id={contentEditorId}
|
|
456
|
+
value={content}
|
|
457
|
+
onChange={setContent}
|
|
458
|
+
variables={variables}
|
|
459
|
+
readOnly={disabled}
|
|
460
|
+
/>
|
|
461
|
+
</DocumentBlocksContext.Provider>
|
|
458
462
|
)}
|
|
459
463
|
</Field>
|
|
460
464
|
{saveError !== null && <Banner variant="error">{saveError}</Banner>}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Extensions, NodeViewRenderer } from "@tiptap/core";
|
|
3
3
|
import { ListKeymap } from "@tiptap/extension-list";
|
|
4
4
|
import { Dropcursor, Gapcursor, Placeholder, UndoRedo } from "@tiptap/extensions";
|
|
5
|
+
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
|
5
6
|
import {
|
|
6
7
|
DocumentButton,
|
|
7
8
|
DocumentPageBreak,
|
|
@@ -11,11 +12,16 @@ import {
|
|
|
11
12
|
} from "../document-schema.js";
|
|
12
13
|
import type { DocumentResourcePreview } from "./document-toolbar-blocks.js";
|
|
13
14
|
|
|
15
|
+
const DOCUMENT_RESOURCE_SIZE_TOKEN = "{size}";
|
|
16
|
+
export const DOCUMENT_BLOCK_ATTRIBUTE = "data-document-block";
|
|
17
|
+
|
|
14
18
|
export type DocumentEditorLabels = {
|
|
15
19
|
readonly placeholder: string;
|
|
16
20
|
readonly image: string;
|
|
17
21
|
readonly pageBreak: string;
|
|
18
22
|
readonly button?: string;
|
|
23
|
+
/** Canvas block label of an image, e.g. "Image · inline". */
|
|
24
|
+
readonly imageBlock?: string;
|
|
19
25
|
readonly variableLabels?: Readonly<Record<string, string>>;
|
|
20
26
|
readonly systemVariables?: readonly string[];
|
|
21
27
|
readonly resourcePreviews?: Readonly<Record<string, DocumentResourcePreview>>;
|
|
@@ -38,22 +44,36 @@ const CHIP_CLASS = "mx-0.5 inline-flex items-center gap-1 rounded border px-1.5
|
|
|
38
44
|
const USER_CHIP_CLASS = `${CHIP_CLASS} border-primary/30 bg-primary/10 text-primary`;
|
|
39
45
|
const SYSTEM_CHIP_CLASS = `${CHIP_CLASS} border-input bg-muted text-muted-foreground`;
|
|
40
46
|
|
|
47
|
+
// A chip inside a repeat row names an item field; its label is registered as `list.field`.
|
|
48
|
+
function repeatRowListAt(doc: ProseMirrorNode, position: number | undefined): string | null {
|
|
49
|
+
if (position === undefined) return null;
|
|
50
|
+
const resolved = doc.resolve(position);
|
|
51
|
+
for (let depth = resolved.depth; depth > 0; depth--) {
|
|
52
|
+
const each: unknown = resolved.node(depth).attrs["each"];
|
|
53
|
+
if (typeof each === "string" && each !== "") return each;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
// The chip is display only: the stored node carries just the name, so a
|
|
42
59
|
// label never reaches the document.
|
|
43
60
|
export function createVariableView(
|
|
44
61
|
variableLabels: Readonly<Record<string, string>>,
|
|
45
62
|
systemVariables: readonly string[],
|
|
63
|
+
labelOnly = false,
|
|
46
64
|
): NodeViewRenderer {
|
|
47
|
-
return ({ node }) => {
|
|
65
|
+
return ({ node, editor, getPos }) => {
|
|
48
66
|
const name = String(node.attrs["name"]);
|
|
49
|
-
const
|
|
67
|
+
const listName = repeatRowListAt(editor.state.doc, getPos());
|
|
68
|
+
const labelKey = listName === null ? name : `${listName}.${name}`;
|
|
69
|
+
const label = Object.hasOwn(variableLabels, labelKey) ? variableLabels[labelKey] : undefined;
|
|
50
70
|
const dom = createElementWithClass(
|
|
51
71
|
"span",
|
|
52
72
|
systemVariables.includes(name) ? SYSTEM_CHIP_CLASS : USER_CHIP_CLASS,
|
|
53
73
|
);
|
|
54
74
|
dom.contentEditable = "false";
|
|
55
75
|
dom.append(document.createTextNode(label !== undefined && label !== "" ? label : name));
|
|
56
|
-
if (label !== undefined && label !== "" && label !== name) {
|
|
76
|
+
if (!labelOnly && label !== undefined && label !== "" && label !== name) {
|
|
57
77
|
dom.append(createElementWithClass("small", "font-mono text-[10px] opacity-75", name));
|
|
58
78
|
}
|
|
59
79
|
return { dom };
|
|
@@ -79,18 +99,46 @@ function createResourceView(labels: DocumentEditorLabels): NodeViewRenderer {
|
|
|
79
99
|
const key = String(node.attrs["resource"]);
|
|
80
100
|
const previews = labels.resourcePreviews ?? {};
|
|
81
101
|
const preview = Object.hasOwn(previews, key) ? previews[key] : undefined;
|
|
102
|
+
const hasImage = preview !== undefined && PREVIEW_SRC.test(preview.src);
|
|
103
|
+
// A rendered image needs no frame until the node is selected (ProseMirror marks it).
|
|
82
104
|
const dom = createElementWithClass(
|
|
83
105
|
"div",
|
|
84
|
-
|
|
106
|
+
hasImage
|
|
107
|
+
? "relative my-1 flex items-center gap-2 rounded border border-transparent px-2 py-1 text-sm text-muted-foreground [.ProseMirror-focused_&.ProseMirror-selectednode]:border-dashed [.ProseMirror-focused_&.ProseMirror-selectednode]:border-input"
|
|
108
|
+
: "relative my-3 flex items-center gap-3 rounded border border-dashed border-input px-3 py-3 text-sm text-muted-foreground",
|
|
85
109
|
);
|
|
110
|
+
dom.setAttribute(DOCUMENT_BLOCK_ATTRIBUTE, "");
|
|
86
111
|
dom.contentEditable = "false";
|
|
87
|
-
if (labels.blockLabels)
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
if (labels.blockLabels) {
|
|
113
|
+
const label = createBlockLabel(labels.imageBlock ?? labels.image);
|
|
114
|
+
if (hasImage)
|
|
115
|
+
label.classList.add("hidden", "[.ProseMirror-focused_.ProseMirror-selectednode_&]:block");
|
|
116
|
+
dom.append(label);
|
|
117
|
+
}
|
|
118
|
+
if (hasImage) {
|
|
119
|
+
const image = createElementWithClass("img", "max-w-[180px] rounded");
|
|
90
120
|
image.src = preview.src;
|
|
91
121
|
image.alt = typeof node.attrs["alt"] === "string" ? node.attrs["alt"] : key;
|
|
92
122
|
dom.append(image);
|
|
93
|
-
|
|
123
|
+
// An empty caption means "no caption", not the key.
|
|
124
|
+
const caption = preview.caption ?? key;
|
|
125
|
+
if (caption !== "") {
|
|
126
|
+
const captionElement = createElementWithClass(
|
|
127
|
+
"span",
|
|
128
|
+
"whitespace-nowrap text-[11px]",
|
|
129
|
+
caption.replace(` · ${DOCUMENT_RESOURCE_SIZE_TOKEN}`, ""),
|
|
130
|
+
);
|
|
131
|
+
// The size is only known once the browser decoded the image (possibly already, from cache).
|
|
132
|
+
const showSize = (): void => {
|
|
133
|
+
captionElement.textContent = caption.replace(
|
|
134
|
+
DOCUMENT_RESOURCE_SIZE_TOKEN,
|
|
135
|
+
`${image.naturalWidth} × ${image.naturalHeight} px`,
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
image.addEventListener("load", showSize);
|
|
139
|
+
if (image.complete && image.naturalWidth > 0) showSize();
|
|
140
|
+
dom.append(captionElement);
|
|
141
|
+
}
|
|
94
142
|
} else {
|
|
95
143
|
dom.append(
|
|
96
144
|
createElementWithClass("span", "font-medium", labels.image),
|
|
@@ -107,6 +155,7 @@ function createButtonView(buttonLabel: string): NodeViewRenderer {
|
|
|
107
155
|
"div",
|
|
108
156
|
"relative my-3 rounded border border-dashed border-input p-3",
|
|
109
157
|
);
|
|
158
|
+
dom.setAttribute(DOCUMENT_BLOCK_ATTRIBUTE, "");
|
|
110
159
|
const button = document.createElement("a");
|
|
111
160
|
button.setAttribute("data-button", "");
|
|
112
161
|
dom.append(createBlockLabel(buttonLabel), button);
|
|
@@ -118,7 +167,7 @@ function createPageBreakView(pageBreakLabel: string): NodeViewRenderer {
|
|
|
118
167
|
return () => {
|
|
119
168
|
const dom = createElementWithClass(
|
|
120
169
|
"div",
|
|
121
|
-
"my-3 border-t border-dashed border-input pt-1 text-center text-xs text-muted-foreground",
|
|
170
|
+
"my-3 border-t border-dashed border-input pt-1 text-center text-xs uppercase tracking-wide text-muted-foreground",
|
|
122
171
|
pageBreakLabel,
|
|
123
172
|
);
|
|
124
173
|
dom.contentEditable = "false";
|
|
@@ -134,7 +183,11 @@ export function createDocumentEditorExtensions(labels: DocumentEditorLabels): Ex
|
|
|
134
183
|
case DocumentVariable.name:
|
|
135
184
|
return DocumentVariable.extend({
|
|
136
185
|
addNodeView: () =>
|
|
137
|
-
createVariableView(
|
|
186
|
+
createVariableView(
|
|
187
|
+
labels.variableLabels ?? {},
|
|
188
|
+
labels.systemVariables ?? [],
|
|
189
|
+
labels.blockLabels === true,
|
|
190
|
+
),
|
|
138
191
|
});
|
|
139
192
|
case DocumentResource.name:
|
|
140
193
|
return DocumentResource.extend({ addNodeView: () => createResourceView(labels) });
|
|
@@ -4,12 +4,21 @@
|
|
|
4
4
|
// Never import document-tiptap-editor or document-editor-extensions statically here.
|
|
5
5
|
|
|
6
6
|
import { TextareaContentEditor } from "@cosmicdrift/kumiko-renderer";
|
|
7
|
-
import { lazy, type ReactNode, Suspense } from "react";
|
|
8
|
-
import type { DocumentEditorProps } from "./document-toolbar-blocks.js";
|
|
7
|
+
import { createContext, lazy, type ReactNode, Suspense, useContext } from "react";
|
|
8
|
+
import type { DocumentEditorProps, DocumentToolbarBlock } from "./document-toolbar-blocks.js";
|
|
9
9
|
|
|
10
10
|
const LazyDocumentTiptapEditor = lazy(() => import("./document-tiptap-editor.js"));
|
|
11
11
|
|
|
12
|
+
// The editor registry only hands ContentEditorProps to an editor, so the
|
|
13
|
+
// collection's block set (mail vs. PDF) reaches it through this context. An
|
|
14
|
+
// explicit `blocks` prop still wins.
|
|
15
|
+
export const DocumentBlocksContext = createContext<readonly DocumentToolbarBlock[] | undefined>(
|
|
16
|
+
undefined,
|
|
17
|
+
);
|
|
18
|
+
|
|
12
19
|
export function DocumentContentEditor(props: DocumentEditorProps): ReactNode {
|
|
20
|
+
const contextBlocks = useContext(DocumentBlocksContext);
|
|
21
|
+
const editorProps = props.blocks === undefined ? { ...props, blocks: contextBlocks } : props;
|
|
13
22
|
return (
|
|
14
23
|
<Suspense
|
|
15
24
|
fallback={
|
|
@@ -22,7 +31,7 @@ export function DocumentContentEditor(props: DocumentEditorProps): ReactNode {
|
|
|
22
31
|
/>
|
|
23
32
|
}
|
|
24
33
|
>
|
|
25
|
-
<LazyDocumentTiptapEditor {...
|
|
34
|
+
<LazyDocumentTiptapEditor {...editorProps} />
|
|
26
35
|
</Suspense>
|
|
27
36
|
);
|
|
28
37
|
}
|