@twelvemonday/blog-editor 1.0.3
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/README.md +104 -0
- package/dist/index.cjs +2228 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +2226 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +36 -0
- package/package.json +62 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface EditorDocument {
|
|
5
|
+
id: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
excerpt: string;
|
|
9
|
+
content: string;
|
|
10
|
+
featuredImage: string;
|
|
11
|
+
author: string;
|
|
12
|
+
publishedAt: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
status: 'draft' | 'published';
|
|
15
|
+
metaTitle?: string;
|
|
16
|
+
metaDescription?: string;
|
|
17
|
+
focusKeyword?: string;
|
|
18
|
+
}
|
|
19
|
+
type OnUploadImage = (file: File) => Promise<string>;
|
|
20
|
+
interface EditorTheme {
|
|
21
|
+
primary: string;
|
|
22
|
+
primaryHover: string;
|
|
23
|
+
primaryMuted: string;
|
|
24
|
+
primaryBorder: string;
|
|
25
|
+
background: string;
|
|
26
|
+
surface: string;
|
|
27
|
+
successText: string;
|
|
28
|
+
}
|
|
29
|
+
interface BlogEditorProps {
|
|
30
|
+
initial: EditorDocument;
|
|
31
|
+
originalSlug?: string;
|
|
32
|
+
/** Called when user saves or publishes. Return optional slug if it changed. */
|
|
33
|
+
onSave: (document: EditorDocument, options: {
|
|
34
|
+
publish?: boolean;
|
|
35
|
+
contentHtml: string;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
slug?: string;
|
|
38
|
+
} | void>;
|
|
39
|
+
/** Fired after a successful save (e.g. for client-side navigation). */
|
|
40
|
+
onNavigate?: (path: string) => void;
|
|
41
|
+
backHref?: string;
|
|
42
|
+
previewUrl?: (slug: string) => string;
|
|
43
|
+
/**
|
|
44
|
+
* Handle image uploads (featured image + image blocks).
|
|
45
|
+
* You receive the `File` — upload anywhere (S3, Cloudinary, your API) and return the public URL string.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* onUploadImage={async (file) => {
|
|
49
|
+
* const form = new FormData();
|
|
50
|
+
* form.append('file', file);
|
|
51
|
+
* const res = await fetch('/api/upload', { method: 'POST', body: form });
|
|
52
|
+
* const { url } = await res.json();
|
|
53
|
+
* return url;
|
|
54
|
+
* }}
|
|
55
|
+
*/
|
|
56
|
+
onUploadImage?: OnUploadImage;
|
|
57
|
+
/** @deprecated Use `onUploadImage` instead */
|
|
58
|
+
uploadFile?: OnUploadImage;
|
|
59
|
+
siteName?: string;
|
|
60
|
+
baseUrl?: string;
|
|
61
|
+
theme?: Partial<EditorTheme>;
|
|
62
|
+
newPostSlug?: string;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
type SaveResult = {
|
|
66
|
+
slug?: string;
|
|
67
|
+
} | void;
|
|
68
|
+
|
|
69
|
+
declare function BlogEditor({ theme, onUploadImage, uploadFile, siteName, baseUrl, ...props }: BlogEditorProps): react_jsx_runtime.JSX.Element;
|
|
70
|
+
|
|
71
|
+
interface EditorContextValue {
|
|
72
|
+
onUploadImage?: OnUploadImage;
|
|
73
|
+
theme: EditorTheme;
|
|
74
|
+
siteName: string;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
}
|
|
77
|
+
declare function EditorProvider({ children, onUploadImage, theme, siteName, baseUrl, }: {
|
|
78
|
+
children: React.ReactNode;
|
|
79
|
+
onUploadImage?: OnUploadImage;
|
|
80
|
+
theme?: Partial<EditorTheme>;
|
|
81
|
+
siteName?: string;
|
|
82
|
+
baseUrl?: string;
|
|
83
|
+
}): react_jsx_runtime.JSX.Element;
|
|
84
|
+
declare function useEditorContext(): EditorContextValue;
|
|
85
|
+
|
|
86
|
+
declare const defaultTheme: EditorTheme;
|
|
87
|
+
declare function resolveTheme(partial?: Partial<EditorTheme>): EditorTheme;
|
|
88
|
+
declare function themeToCssVars(theme: EditorTheme): CSSProperties;
|
|
89
|
+
|
|
90
|
+
type BlogBlockType = 'paragraph' | 'heading' | 'list' | 'quote' | 'image' | 'embed' | 'html' | 'code' | 'separator';
|
|
91
|
+
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
92
|
+
interface BlogBlock {
|
|
93
|
+
id: string;
|
|
94
|
+
type: BlogBlockType;
|
|
95
|
+
html?: string;
|
|
96
|
+
level?: HeadingLevel;
|
|
97
|
+
items?: string[];
|
|
98
|
+
ordered?: boolean;
|
|
99
|
+
src?: string;
|
|
100
|
+
alt?: string;
|
|
101
|
+
caption?: string;
|
|
102
|
+
imageWidth?: number;
|
|
103
|
+
imageHeight?: number;
|
|
104
|
+
imageWidthPercent?: number;
|
|
105
|
+
embedUrl?: string;
|
|
106
|
+
embedHeight?: number;
|
|
107
|
+
rawHtml?: string;
|
|
108
|
+
}
|
|
109
|
+
declare const BLOCK_LABELS: Record<BlogBlockType, string>;
|
|
110
|
+
declare const HEADING_LABELS: Record<HeadingLevel, string>;
|
|
111
|
+
declare function createBlock(type: BlogBlockType): BlogBlock;
|
|
112
|
+
declare function htmlToBlocks(html: string): BlogBlock[];
|
|
113
|
+
declare function blocksToHtml(blocks: BlogBlock[]): string;
|
|
114
|
+
declare function slugifyTitle(title: string): string;
|
|
115
|
+
declare const HEADING_CLASSES: Record<HeadingLevel, string>;
|
|
116
|
+
|
|
117
|
+
/** Strip Word/Google Docs junk and keep semantic formatting. */
|
|
118
|
+
declare function cleanPastedHtml(raw: string): string;
|
|
119
|
+
declare function insertHtmlAtCursor(html: string): void;
|
|
120
|
+
declare function pastedHtmlToBlocks(raw: string): BlogBlock[];
|
|
121
|
+
declare function isMultiBlockPaste(blocks: BlogBlock[]): boolean;
|
|
122
|
+
declare function inlineHtmlFromBlocks(blocks: BlogBlock[]): string;
|
|
123
|
+
|
|
124
|
+
interface SeoCheck {
|
|
125
|
+
id: string;
|
|
126
|
+
label: string;
|
|
127
|
+
passed: boolean;
|
|
128
|
+
}
|
|
129
|
+
interface SeoScore {
|
|
130
|
+
score: number;
|
|
131
|
+
checks: SeoCheck[];
|
|
132
|
+
}
|
|
133
|
+
declare function getSeoPreview(post: EditorDocument, siteName?: string, baseUrl?: string): {
|
|
134
|
+
title: string;
|
|
135
|
+
description: string;
|
|
136
|
+
url: string;
|
|
137
|
+
siteName: string;
|
|
138
|
+
};
|
|
139
|
+
declare function computeSeoScore(post: EditorDocument, contentHtml?: string): SeoScore;
|
|
140
|
+
declare function titleLengthColor(len: number): "bg-gray-200" | "bg-amber-400" | "bg-green-500";
|
|
141
|
+
declare function descriptionLengthColor(len: number): "bg-gray-200" | "bg-amber-400" | "bg-green-500";
|
|
142
|
+
|
|
143
|
+
export { BLOCK_LABELS, type BlogEditorProps as BlockEditorProps, type BlogBlock, type BlogBlockType, BlogEditor, type BlogEditorProps, type EditorDocument, EditorProvider, type EditorTheme, HEADING_CLASSES, HEADING_LABELS, type HeadingLevel, type OnUploadImage, type SaveResult, type SeoCheck, type SeoScore, blocksToHtml, cleanPastedHtml, computeSeoScore, createBlock, defaultTheme, descriptionLengthColor, getSeoPreview, htmlToBlocks, inlineHtmlFromBlocks, insertHtmlAtCursor, isMultiBlockPaste, pastedHtmlToBlocks, resolveTheme, slugifyTitle, themeToCssVars, titleLengthColor, useEditorContext };
|