@riducms/plugin-seo 0.16.2 → 0.18.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.
Files changed (37) hide show
  1. package/dist/generation-controller.svelte.d.ts +7 -0
  2. package/dist/generation-controller.svelte.js +68 -0
  3. package/dist/generation-snapshot.d.ts +3 -0
  4. package/dist/generation-snapshot.js +11 -0
  5. package/dist/index.d.ts +59 -0
  6. package/dist/index.js +48 -0
  7. package/dist/length-indicator.d.ts +8 -0
  8. package/dist/length-indicator.js +24 -0
  9. package/{src → dist}/length-indicator.svelte +2 -2
  10. package/dist/length-indicator.svelte.d.ts +11 -0
  11. package/dist/messages.d.ts +33 -0
  12. package/dist/messages.js +104 -0
  13. package/{src → dist}/meta-description-field.svelte +4 -4
  14. package/dist/meta-description-field.svelte.d.ts +6 -0
  15. package/{src → dist}/meta-image-field.svelte +5 -5
  16. package/dist/meta-image-field.svelte.d.ts +7 -0
  17. package/{src → dist}/meta-title-field.svelte +4 -4
  18. package/dist/meta-title-field.svelte.d.ts +6 -0
  19. package/{src → dist}/overview-field.svelte +2 -2
  20. package/dist/overview-field.svelte.d.ts +6 -0
  21. package/{src → dist}/preview-field.svelte +3 -3
  22. package/dist/preview-field.svelte.d.ts +6 -0
  23. package/dist/seo-config.d.ts +27 -0
  24. package/dist/seo-config.js +74 -0
  25. package/dist/seo.css +308 -0
  26. package/dist/~icons/lucide/image.svelte +2 -0
  27. package/dist/~icons/lucide/pencil.svelte +2 -0
  28. package/dist/~icons/lucide/x.svelte +2 -0
  29. package/package.json +32 -13
  30. package/src/generation-controller.svelte.ts +0 -84
  31. package/src/generation-snapshot.ts +0 -17
  32. package/src/index.ts +0 -59
  33. package/src/length-indicator.ts +0 -32
  34. package/src/messages.ts +0 -113
  35. package/src/seo-config.ts +0 -96
  36. package/src/vite-env.d.ts +0 -7
  37. /package/{src → dist}/seo.scss +0 -0
@@ -0,0 +1,7 @@
1
+ import type { FieldAuthoringHost, FieldDocumentForm } from "@riducms/plugin";
2
+ export declare class GenerationController {
3
+ #private;
4
+ status: "idle" | "pending" | "error";
5
+ run(authoring: FieldAuthoringHost | undefined, form: FieldDocumentForm, path: string): Promise<string | undefined>;
6
+ cancel(): void;
7
+ }
@@ -0,0 +1,68 @@
1
+ import { generationScopeToken, generationSnapshotToken } from "./generation-snapshot.js";
2
+ export class GenerationController {
3
+ status = $state("idle");
4
+ #generation = 0;
5
+ #request;
6
+ async run(authoring, form, path) {
7
+ const requestPlugin = authoring?.requestPlugin;
8
+ const snapshot = form.snapshot;
9
+ const resource = form.resource;
10
+ if (requestPlugin === undefined || snapshot === undefined || resource === undefined) {
11
+ this.status = "error";
12
+ return undefined;
13
+ }
14
+ this.#request?.abort();
15
+ const request = new AbortController();
16
+ this.#request = request;
17
+ const generation = ++this.#generation;
18
+ this.status = "pending";
19
+ const document = snapshot.call(form);
20
+ const documentRevision = generationSnapshotToken(document);
21
+ const scope = generationScopeToken(resource, form.contentLocale);
22
+ try {
23
+ const response = await requestPlugin(path, {
24
+ ...(resource.global
25
+ ? { global: resource.collection }
26
+ : { collection: resource.collection }),
27
+ id: resource.id,
28
+ locale: form.contentLocale,
29
+ document,
30
+ }, request.signal);
31
+ if (generation !== this.#generation ||
32
+ request.signal.aborted ||
33
+ generationSnapshotToken(snapshot.call(form)) !== documentRevision ||
34
+ generationScopeToken(form.resource, form.contentLocale) !== scope) {
35
+ if (generation === this.#generation)
36
+ this.status = "idle";
37
+ return undefined;
38
+ }
39
+ if (!isGenerationResponse(response))
40
+ throw new Error("SEO generation returned an invalid response");
41
+ this.status = "idle";
42
+ return response.result;
43
+ }
44
+ catch {
45
+ if (generation !== this.#generation || request.signal.aborted)
46
+ return undefined;
47
+ this.status = "error";
48
+ return undefined;
49
+ }
50
+ finally {
51
+ if (this.#request === request)
52
+ this.#request = undefined;
53
+ }
54
+ }
55
+ cancel() {
56
+ this.#generation += 1;
57
+ this.#request?.abort();
58
+ this.#request = undefined;
59
+ if (this.status === "pending")
60
+ this.status = "idle";
61
+ }
62
+ }
63
+ function isGenerationResponse(value) {
64
+ return (typeof value === "object" &&
65
+ value !== null &&
66
+ "result" in value &&
67
+ typeof value.result === "string");
68
+ }
@@ -0,0 +1,3 @@
1
+ import type { FieldFormResource } from "@riducms/plugin";
2
+ export declare function generationSnapshotToken(document: Readonly<Record<string, unknown>>): string;
3
+ export declare function generationScopeToken(resource: FieldFormResource | undefined, locale: string | undefined): string;
@@ -0,0 +1,11 @@
1
+ export function generationSnapshotToken(document) {
2
+ return JSON.stringify(document);
3
+ }
4
+ export function generationScopeToken(resource, locale) {
5
+ return JSON.stringify({
6
+ collection: resource?.collection ?? "",
7
+ global: resource?.global === true,
8
+ id: resource?.id ?? "",
9
+ locale: locale ?? "",
10
+ });
11
+ }
@@ -0,0 +1,59 @@
1
+ export declare const seoAdminPlugin: {
2
+ readonly key: "seo";
3
+ readonly pairingVersion: 1;
4
+ readonly fieldEditors: {
5
+ readonly overview: import("@riducms/plugin").PluginFieldRegistration<undefined, undefined, "ui"> & {
6
+ readonly fieldType?: never;
7
+ };
8
+ readonly title: import("@riducms/plugin").PluginFieldRegistration<string, string, "text"> & {
9
+ readonly fieldType?: never;
10
+ };
11
+ readonly description: import("@riducms/plugin").PluginFieldRegistration<string, string, "textarea"> & {
12
+ readonly fieldType?: never;
13
+ };
14
+ readonly image: import("@riducms/plugin").PluginFieldRegistration<string, string, "upload"> & {
15
+ readonly fieldType?: never;
16
+ };
17
+ readonly preview: import("@riducms/plugin").PluginFieldRegistration<undefined, undefined, "ui"> & {
18
+ readonly fieldType?: never;
19
+ };
20
+ };
21
+ readonly messages: import("@riducms/plugin").DefineAdminMessagesInput<{
22
+ readonly almostThere: "Almost there";
23
+ readonly autoGenerate: "Auto-generate";
24
+ readonly bestPractices: "best practices";
25
+ readonly cancelled: "Generation was cancelled.";
26
+ readonly characterCount: "{current}/{minLength}-{maxLength} chars";
27
+ readonly charactersLeft: ", {characters} left over";
28
+ readonly charactersToGo: ", {characters} to go";
29
+ readonly charactersTooMany: ", {characters} too many";
30
+ readonly checksPassing: "{current}/{max} checks are passing";
31
+ readonly chooseExistingImage: "Choose existing";
32
+ readonly createNewImage: "Create new";
33
+ readonly generationFailed: "Could not generate this value. Try again.";
34
+ readonly good: "Good";
35
+ readonly image: "Image";
36
+ readonly imageAutoGenerationTip: "Generate an image from the current document, or choose one manually.";
37
+ readonly imageSourceTip: "or drag and drop a file.";
38
+ readonly imageUnavailable: "Referenced image is unavailable.";
39
+ readonly inspectImage: "Inspect image";
40
+ readonly lengthTipDescription: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta descriptions, see ";
41
+ readonly lengthTipTitle: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta titles, see ";
42
+ readonly missing: "Missing";
43
+ readonly noImage: "No image";
44
+ readonly or: "or";
45
+ readonly preview: "Preview";
46
+ readonly previewDescription: "Exact result listings may vary based on content and search relevancy.";
47
+ readonly removeImage: "Remove image";
48
+ readonly selectImage: "Select image";
49
+ readonly tooLong: "Too long";
50
+ readonly tooShort: "Too short";
51
+ readonly title: "Title";
52
+ readonly description: "Description";
53
+ }>;
54
+ } & {
55
+ readonly apiVersion: 1;
56
+ };
57
+ export { lengthState, type LengthState, type LengthStatus } from "./length-indicator.js";
58
+ export { generationScopeToken, generationSnapshotToken } from "./generation-snapshot.js";
59
+ export { seoMessages } from "./messages.js";
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import { defineAdminPlugin, defineFieldComponent } from "@riducms/plugin/authoring/v1";
2
+ import { decodeString, decodeUI, decodeLengthConfig, decodeOverviewConfig, decodeImageConfig, decodePreviewConfig, } from "./seo-config.js";
3
+ import { seoMessages } from "./messages.js";
4
+ import MetaDescriptionField from "./meta-description-field.svelte";
5
+ import MetaImageField from "./meta-image-field.svelte";
6
+ import MetaTitleField from "./meta-title-field.svelte";
7
+ import OverviewField from "./overview-field.svelte";
8
+ import PreviewField from "./preview-field.svelte";
9
+ export const seoAdminPlugin = defineAdminPlugin({
10
+ key: "seo",
11
+ pairingVersion: 1,
12
+ fieldEditors: {
13
+ overview: defineFieldComponent({
14
+ type: "ui",
15
+ component: OverviewField,
16
+ decodeValue: decodeUI,
17
+ decodeConfig: decodeOverviewConfig,
18
+ }),
19
+ title: defineFieldComponent({
20
+ type: "text",
21
+ component: MetaTitleField,
22
+ decodeValue: decodeString,
23
+ decodeConfig: (raw) => decodeLengthConfig(raw, "text"),
24
+ }),
25
+ description: defineFieldComponent({
26
+ type: "textarea",
27
+ component: MetaDescriptionField,
28
+ decodeValue: decodeString,
29
+ decodeConfig: (raw) => decodeLengthConfig(raw, "textarea"),
30
+ }),
31
+ image: defineFieldComponent({
32
+ type: "upload",
33
+ component: MetaImageField,
34
+ decodeValue: decodeString,
35
+ decodeConfig: decodeImageConfig,
36
+ }),
37
+ preview: defineFieldComponent({
38
+ type: "ui",
39
+ component: PreviewField,
40
+ decodeValue: decodeUI,
41
+ decodeConfig: decodePreviewConfig,
42
+ }),
43
+ },
44
+ messages: seoMessages,
45
+ });
46
+ export { lengthState } from "./length-indicator.js";
47
+ export { generationScopeToken, generationSnapshotToken } from "./generation-snapshot.js";
48
+ export { seoMessages } from "./messages.js";
@@ -0,0 +1,8 @@
1
+ export type LengthStatus = "missing" | "tooShort" | "almostThere" | "good" | "tooLong";
2
+ export interface LengthState {
3
+ status: LengthStatus;
4
+ length: number;
5
+ progress: number;
6
+ remaining: number;
7
+ }
8
+ export declare function lengthState(text: string, minLength: number, maxLength: number): LengthState;
@@ -0,0 +1,24 @@
1
+ export function lengthState(text, minLength, maxLength) {
2
+ const length = text.length;
3
+ if (length === 0)
4
+ return { status: "missing", length, progress: 0, remaining: minLength };
5
+ if (length < minLength) {
6
+ const progress = Math.min(1, length / minLength);
7
+ return {
8
+ status: progress > 0.9 ? "almostThere" : "tooShort",
9
+ length,
10
+ progress,
11
+ remaining: minLength - length,
12
+ };
13
+ }
14
+ if (length <= maxLength) {
15
+ const range = Math.max(1, maxLength - minLength);
16
+ return {
17
+ status: "good",
18
+ length,
19
+ progress: Math.min(1, (length - minLength) / range),
20
+ remaining: maxLength - length,
21
+ };
22
+ }
23
+ return { status: "tooLong", length, progress: 1, remaining: length - maxLength };
24
+ }
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { AdminI18n } from "@riducms/plugin";
3
3
 
4
- import { lengthState } from "@plugin-seo/length-indicator";
5
- import "@plugin-seo/seo.scss";
4
+ import { lengthState } from "./length-indicator.js";
5
+ import "./seo.css";
6
6
 
7
7
  let {
8
8
  text,
@@ -0,0 +1,11 @@
1
+ import type { AdminI18n } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ type $$ComponentProps = {
4
+ text: string;
5
+ minLength: number;
6
+ maxLength: number;
7
+ i18n: AdminI18n;
8
+ };
9
+ declare const LengthIndicator: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type LengthIndicator = ReturnType<typeof LengthIndicator>;
11
+ export default LengthIndicator;
@@ -0,0 +1,33 @@
1
+ export declare const seoMessages: import("@riducms/plugin").DefineAdminMessagesInput<{
2
+ readonly almostThere: "Almost there";
3
+ readonly autoGenerate: "Auto-generate";
4
+ readonly bestPractices: "best practices";
5
+ readonly cancelled: "Generation was cancelled.";
6
+ readonly characterCount: "{current}/{minLength}-{maxLength} chars";
7
+ readonly charactersLeft: ", {characters} left over";
8
+ readonly charactersToGo: ", {characters} to go";
9
+ readonly charactersTooMany: ", {characters} too many";
10
+ readonly checksPassing: "{current}/{max} checks are passing";
11
+ readonly chooseExistingImage: "Choose existing";
12
+ readonly createNewImage: "Create new";
13
+ readonly generationFailed: "Could not generate this value. Try again.";
14
+ readonly good: "Good";
15
+ readonly image: "Image";
16
+ readonly imageAutoGenerationTip: "Generate an image from the current document, or choose one manually.";
17
+ readonly imageSourceTip: "or drag and drop a file.";
18
+ readonly imageUnavailable: "Referenced image is unavailable.";
19
+ readonly inspectImage: "Inspect image";
20
+ readonly lengthTipDescription: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta descriptions, see ";
21
+ readonly lengthTipTitle: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta titles, see ";
22
+ readonly missing: "Missing";
23
+ readonly noImage: "No image";
24
+ readonly or: "or";
25
+ readonly preview: "Preview";
26
+ readonly previewDescription: "Exact result listings may vary based on content and search relevancy.";
27
+ readonly removeImage: "Remove image";
28
+ readonly selectImage: "Select image";
29
+ readonly tooLong: "Too long";
30
+ readonly tooShort: "Too short";
31
+ readonly title: "Title";
32
+ readonly description: "Description";
33
+ }>;
@@ -0,0 +1,104 @@
1
+ import { defineAdminMessages } from "@riducms/plugin";
2
+ export const seoMessages = defineAdminMessages({
3
+ fallback: {
4
+ almostThere: "Almost there",
5
+ autoGenerate: "Auto-generate",
6
+ bestPractices: "best practices",
7
+ cancelled: "Generation was cancelled.",
8
+ characterCount: "{current}/{minLength}-{maxLength} chars",
9
+ charactersLeft: ", {characters} left over",
10
+ charactersToGo: ", {characters} to go",
11
+ charactersTooMany: ", {characters} too many",
12
+ checksPassing: "{current}/{max} checks are passing",
13
+ chooseExistingImage: "Choose existing",
14
+ createNewImage: "Create new",
15
+ generationFailed: "Could not generate this value. Try again.",
16
+ good: "Good",
17
+ image: "Image",
18
+ imageAutoGenerationTip: "Generate an image from the current document, or choose one manually.",
19
+ imageSourceTip: "or drag and drop a file.",
20
+ imageUnavailable: "Referenced image is unavailable.",
21
+ inspectImage: "Inspect image",
22
+ lengthTipDescription: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta descriptions, see ",
23
+ lengthTipTitle: "This should be between {minLength} and {maxLength} characters. For help in writing quality meta titles, see ",
24
+ missing: "Missing",
25
+ noImage: "No image",
26
+ or: "or",
27
+ preview: "Preview",
28
+ previewDescription: "Exact result listings may vary based on content and search relevancy.",
29
+ removeImage: "Remove image",
30
+ selectImage: "Select image",
31
+ tooLong: "Too long",
32
+ tooShort: "Too short",
33
+ title: "Title",
34
+ description: "Description",
35
+ },
36
+ translations: {
37
+ fr: {
38
+ almostThere: "Presque terminé",
39
+ autoGenerate: "Générer automatiquement",
40
+ bestPractices: "bonnes pratiques",
41
+ cancelled: "La génération a été annulée.",
42
+ characterCount: "{current}/{minLength}-{maxLength} caractères",
43
+ charactersLeft: ", {characters} restants",
44
+ charactersToGo: ", encore {characters}",
45
+ charactersTooMany: ", {characters} en trop",
46
+ checksPassing: "{current}/{max} vérifications réussies",
47
+ chooseExistingImage: "Choisir un élément existant",
48
+ createNewImage: "Créer un élément",
49
+ generationFailed: "Impossible de générer cette valeur. Réessayez.",
50
+ good: "Bon",
51
+ image: "Image",
52
+ imageAutoGenerationTip: "Générez une image à partir du document actuel ou choisissez-en une manuellement.",
53
+ imageSourceTip: "ou glissez-déposez un fichier.",
54
+ imageUnavailable: "L’image référencée n’est pas disponible.",
55
+ inspectImage: "Inspecter l’image",
56
+ lengthTipDescription: "Ceci devrait contenir entre {minLength} et {maxLength} caractères. Pour obtenir de l’aide pour rédiger des descriptions meta de qualité, consultez les ",
57
+ lengthTipTitle: "Ceci devrait contenir entre {minLength} et {maxLength} caractères. Pour obtenir de l’aide pour rédiger des titres meta de qualité, consultez les ",
58
+ missing: "Manquant",
59
+ noImage: "Aucune image",
60
+ or: "ou",
61
+ preview: "Aperçu",
62
+ previewDescription: "Les résultats exacts peuvent varier en fonction du contenu et de la pertinence de la recherche.",
63
+ removeImage: "Supprimer l’image",
64
+ selectImage: "Sélectionner une image",
65
+ tooLong: "Trop long",
66
+ tooShort: "Trop court",
67
+ title: "Titre",
68
+ description: "Description",
69
+ },
70
+ ar: {
71
+ almostThere: "أوشكت على الانتهاء",
72
+ autoGenerate: "إنشاء تلقائي",
73
+ bestPractices: "أفضل الممارسات",
74
+ cancelled: "تم إلغاء الإنشاء.",
75
+ characterCount: "{current}/{minLength}-{maxLength} حرفًا",
76
+ charactersLeft: "، متبقٍ {characters}",
77
+ charactersToGo: "، يلزم {characters}",
78
+ charactersTooMany: "، زائد {characters}",
79
+ checksPassing: "تم اجتياز {current}/{max} من الفحوصات",
80
+ chooseExistingImage: "اختيار صورة موجودة",
81
+ createNewImage: "إنشاء صورة جديدة",
82
+ description: "الوصف",
83
+ generationFailed: "تعذر إنشاء هذه القيمة. حاول مرة أخرى.",
84
+ good: "جيد",
85
+ image: "الصورة",
86
+ imageAutoGenerationTip: "أنشئ صورة من المستند الحالي أو اختر صورة يدويًا.",
87
+ imageSourceTip: "أو اسحب ملفًا وأفلته.",
88
+ imageUnavailable: "الصورة المشار إليها غير متاحة.",
89
+ inspectImage: "فحص الصورة",
90
+ lengthTipDescription: "يجب أن يتراوح هذا بين {minLength} و{maxLength} حرفًا. للحصول على مساعدة في كتابة أوصاف ميتا ذات جودة، راجع ",
91
+ lengthTipTitle: "يجب أن يتراوح هذا بين {minLength} و{maxLength} حرفًا. للحصول على مساعدة في كتابة عناوين ميتا ذات جودة، راجع ",
92
+ missing: "مفقود",
93
+ noImage: "لا توجد صورة",
94
+ or: "أو",
95
+ preview: "معاينة",
96
+ previewDescription: "قد تختلف النتائج الدقيقة بناءً على المحتوى وملاءمة البحث.",
97
+ removeImage: "إزالة الصورة",
98
+ selectImage: "اختيار صورة",
99
+ title: "العنوان",
100
+ tooLong: "طويل جدًا",
101
+ tooShort: "قصير جدًا",
102
+ },
103
+ },
104
+ });
@@ -2,10 +2,10 @@
2
2
  import type { PluginFieldProps } from "@riducms/plugin";
3
3
  import { Button, FieldFrame, Textarea, fieldControlARIA } from "@riducms/ui";
4
4
 
5
- import { GenerationController } from "@plugin-seo/generation-controller.svelte";
6
- import LengthIndicator from "@plugin-seo/length-indicator.svelte";
7
- import "@plugin-seo/seo.scss";
8
- import type { LengthConfig } from "@plugin-seo/seo-config";
5
+ import { GenerationController } from "./generation-controller.svelte.js";
6
+ import LengthIndicator from "./length-indicator.svelte";
7
+ import "./seo.css";
8
+ import type { LengthConfig } from "./seo-config.js";
9
9
 
10
10
  let {
11
11
  field: binding,
@@ -0,0 +1,6 @@
1
+ import type { PluginFieldProps } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ import type { LengthConfig } from "./seo-config.js";
4
+ declare const MetaDescriptionField: import("svelte").Component<PluginFieldProps<string, LengthConfig, "textarea">, {}, "">;
5
+ type MetaDescriptionField = ReturnType<typeof MetaDescriptionField>;
6
+ export default MetaDescriptionField;
@@ -1,12 +1,12 @@
1
1
  <script lang="ts">
2
2
  import type { PluginFieldProps, FieldDocument } from "@riducms/plugin";
3
3
  import { Button, FieldFrame, fieldControlARIA } from "@riducms/ui";
4
- import ImageIcon from "~icons/lucide/image";
5
- import PencilIcon from "~icons/lucide/pencil";
6
- import XIcon from "~icons/lucide/x";
4
+ import ImageIcon from "./~icons/lucide/image.svelte";
5
+ import PencilIcon from "./~icons/lucide/pencil.svelte";
6
+ import XIcon from "./~icons/lucide/x.svelte";
7
7
 
8
- import { GenerationController } from "@plugin-seo/generation-controller.svelte";
9
- import "@plugin-seo/seo.scss";
8
+ import { GenerationController } from "./generation-controller.svelte.js";
9
+ import "./seo.css";
10
10
 
11
11
  let {
12
12
  field: binding,
@@ -0,0 +1,7 @@
1
+ import type { PluginFieldProps } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ declare const MetaImageField: import("svelte").Component<PluginFieldProps<string, {
4
+ generate: boolean;
5
+ }, "upload">, {}, "">;
6
+ type MetaImageField = ReturnType<typeof MetaImageField>;
7
+ export default MetaImageField;
@@ -2,10 +2,10 @@
2
2
  import type { PluginFieldProps } from "@riducms/plugin";
3
3
  import { Button, FieldFrame, Input, fieldControlARIA } from "@riducms/ui";
4
4
 
5
- import { GenerationController } from "@plugin-seo/generation-controller.svelte";
6
- import LengthIndicator from "@plugin-seo/length-indicator.svelte";
7
- import "@plugin-seo/seo.scss";
8
- import type { LengthConfig } from "@plugin-seo/seo-config";
5
+ import { GenerationController } from "./generation-controller.svelte.js";
6
+ import LengthIndicator from "./length-indicator.svelte";
7
+ import "./seo.css";
8
+ import type { LengthConfig } from "./seo-config.js";
9
9
 
10
10
  let {
11
11
  field: binding,
@@ -0,0 +1,6 @@
1
+ import type { PluginFieldProps } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ import type { LengthConfig } from "./seo-config.js";
4
+ declare const MetaTitleField: import("svelte").Component<PluginFieldProps<string, LengthConfig, "text">, {}, "">;
5
+ type MetaTitleField = ReturnType<typeof MetaTitleField>;
6
+ export default MetaTitleField;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { PluginFieldProps } from "@riducms/plugin";
3
- import "@plugin-seo/seo.scss";
3
+ import "./seo.css";
4
4
 
5
- import type { OverviewConfig } from "@plugin-seo/seo-config";
5
+ import type { OverviewConfig } from "./seo-config.js";
6
6
 
7
7
  let {
8
8
  field: binding,
@@ -0,0 +1,6 @@
1
+ import type { PluginFieldProps } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ import type { OverviewConfig } from "./seo-config.js";
4
+ declare const OverviewField: import("svelte").Component<PluginFieldProps<undefined, OverviewConfig, "ui">, {}, "">;
5
+ type OverviewField = ReturnType<typeof OverviewField>;
6
+ export default OverviewField;
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { PluginFieldProps } from "@riducms/plugin";
3
3
 
4
- import { GenerationController } from "@plugin-seo/generation-controller.svelte";
5
- import "@plugin-seo/seo.scss";
6
- import type { PreviewConfig } from "@plugin-seo/seo-config";
4
+ import { GenerationController } from "./generation-controller.svelte.js";
5
+ import "./seo.css";
6
+ import type { PreviewConfig } from "./seo-config.js";
7
7
 
8
8
  let {
9
9
  field: binding,
@@ -0,0 +1,6 @@
1
+ import type { PluginFieldProps } from "@riducms/plugin";
2
+ import "./seo.css";
3
+ import type { PreviewConfig } from "./seo-config.js";
4
+ declare const PreviewField: import("svelte").Component<PluginFieldProps<undefined, PreviewConfig, "ui">, {}, "">;
5
+ type PreviewField = ReturnType<typeof PreviewField>;
6
+ export default PreviewField;
@@ -0,0 +1,27 @@
1
+ export interface LengthConfig {
2
+ generate: boolean;
3
+ minLength: number;
4
+ maxLength: number;
5
+ }
6
+ export interface OverviewConfig {
7
+ titlePath: string;
8
+ descriptionPath: string;
9
+ imagePath: string;
10
+ titleMin: number;
11
+ titleMax: number;
12
+ descriptionMin: number;
13
+ descriptionMax: number;
14
+ }
15
+ export interface PreviewConfig {
16
+ generate: boolean;
17
+ titlePath: string;
18
+ descriptionPath: string;
19
+ }
20
+ export declare function decodeLengthConfig(raw: unknown, type: "text" | "textarea"): LengthConfig;
21
+ export declare function decodeOverviewConfig(raw: unknown): OverviewConfig;
22
+ export declare function decodePreviewConfig(raw: unknown): PreviewConfig;
23
+ export declare function decodeImageConfig(raw: unknown): {
24
+ generate: boolean;
25
+ };
26
+ export declare function decodeString(value: unknown): string;
27
+ export declare function decodeUI(value: unknown): undefined;
@@ -0,0 +1,74 @@
1
+ export function decodeLengthConfig(raw, type) {
2
+ const value = componentConfig(raw, ["generate", "minLength", "maxLength"]);
3
+ return {
4
+ generate: value.generate === true,
5
+ minLength: positiveInteger(value.minLength, type === "textarea" ? 100 : 50),
6
+ maxLength: positiveInteger(value.maxLength, type === "textarea" ? 150 : 60),
7
+ };
8
+ }
9
+ export function decodeOverviewConfig(raw) {
10
+ const value = componentConfig(raw, [
11
+ "titlePath",
12
+ "descriptionPath",
13
+ "imagePath",
14
+ "titleMin",
15
+ "titleMax",
16
+ "descriptionMin",
17
+ "descriptionMax",
18
+ ]);
19
+ return {
20
+ titlePath: stringValue(value.titlePath, "meta.title"),
21
+ descriptionPath: stringValue(value.descriptionPath, "meta.description"),
22
+ imagePath: stringValue(value.imagePath, "meta.image"),
23
+ titleMin: positiveInteger(value.titleMin, 50),
24
+ titleMax: positiveInteger(value.titleMax, 60),
25
+ descriptionMin: positiveInteger(value.descriptionMin, 100),
26
+ descriptionMax: positiveInteger(value.descriptionMax, 150),
27
+ };
28
+ }
29
+ export function decodePreviewConfig(raw) {
30
+ const value = componentConfig(raw, ["generate", "titlePath", "descriptionPath"]);
31
+ return {
32
+ generate: value.generate === true,
33
+ titlePath: stringValue(value.titlePath, "meta.title"),
34
+ descriptionPath: stringValue(value.descriptionPath, "meta.description"),
35
+ };
36
+ }
37
+ export function decodeImageConfig(raw) {
38
+ return { generate: componentConfig(raw, ["generate"]).generate === true };
39
+ }
40
+ function componentConfig(raw, allowed) {
41
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
42
+ throw new Error("SEO config must be an object.");
43
+ const value = raw;
44
+ for (const key of Object.keys(value))
45
+ if (!allowed.includes(key))
46
+ throw new Error(`Unsupported SEO config key ${key}.`);
47
+ if (value.generate !== undefined && typeof value.generate !== "boolean")
48
+ throw new Error("generate must be a boolean.");
49
+ return value;
50
+ }
51
+ export function decodeString(value) {
52
+ if (typeof value !== "string")
53
+ throw new Error("Expected a string value.");
54
+ return value;
55
+ }
56
+ export function decodeUI(value) {
57
+ if (value !== undefined)
58
+ throw new Error("UI fields have no stored value.");
59
+ return undefined;
60
+ }
61
+ function stringValue(value, fallback) {
62
+ if (value === undefined)
63
+ return fallback;
64
+ if (typeof value !== "string" || !value.length)
65
+ throw new Error("Expected a nonempty string config value.");
66
+ return value;
67
+ }
68
+ function positiveInteger(value, fallback) {
69
+ if (value === undefined)
70
+ return fallback;
71
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0)
72
+ throw new Error("Expected a positive integer config value.");
73
+ return value;
74
+ }