refactor: remove Tiptap editor dependency and legacy JSON format support from wiki components
This commit is contained in:
@@ -20,24 +20,6 @@ type Props = {
|
||||
onWikiLinkRequest: (request: { slug: string; rect: DOMRect }) => void;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function tiptapJsonToPlainText(node: unknown): string {
|
||||
if (node == null) return "";
|
||||
if (typeof node === "string") return node;
|
||||
if (Array.isArray(node)) return node.map(tiptapJsonToPlainText).join("");
|
||||
|
||||
if (isRecord(node)) {
|
||||
if (node.type === "text" && typeof node.text === "string") return node.text;
|
||||
if (node.type === "hardBreak") return "\n";
|
||||
if ("content" in node) return tiptapJsonToPlainText(node.content);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function escapeHtml(input: string): string {
|
||||
return input
|
||||
.replaceAll("&", "&")
|
||||
@@ -53,17 +35,6 @@ function normalizeWikiContentToHtml(raw: string | null | undefined): string {
|
||||
|
||||
if (value[0] === "<") return value;
|
||||
|
||||
if (value[0] === "{") {
|
||||
try {
|
||||
const json: unknown = JSON.parse(value);
|
||||
const text = tiptapJsonToPlainText(json).trim();
|
||||
if (!text.length) return "";
|
||||
return `<p>${escapeHtml(text).replace(/\n/g, "<br/>")}</p>`;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
return `<p>${escapeHtml(value).replace(/\n/g, "<br/>")}</p>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ type QuillLinkFormat = {
|
||||
__uhmAllowSlugHref?: boolean;
|
||||
__uhmOriginalSanitize?: unknown;
|
||||
};
|
||||
type QuillImageFormatCtor = {
|
||||
new (): {
|
||||
domNode: Element;
|
||||
format: (name: string, value: string) => void;
|
||||
};
|
||||
formats: (domNode: Element) => Record<string, string>;
|
||||
};
|
||||
|
||||
const ReactQuillEditor = dynamic<ReactQuillProps>(() => import("react-quill-new"), {
|
||||
ssr: false,
|
||||
@@ -113,7 +120,7 @@ export default function WikiSidebarPanel({ projectId, wikis, setWikis, requested
|
||||
console.error("Failed to load quill-blot-formatter", err);
|
||||
}
|
||||
|
||||
const ImageFormat = Quill.import?.("formats/image") as any;
|
||||
const ImageFormat = Quill.import?.("formats/image") as QuillImageFormatCtor | undefined;
|
||||
if (ImageFormat) {
|
||||
class CustomImage extends ImageFormat {
|
||||
static formats(domNode: Element) {
|
||||
@@ -1059,22 +1066,8 @@ function normalizeWikiDocForQuill(doc: string | null): string {
|
||||
const raw = (doc || "").trim();
|
||||
if (!raw.length) return "";
|
||||
|
||||
// New format (Quill): HTML string.
|
||||
if (raw[0] === "<") return raw;
|
||||
|
||||
// Legacy format (Tiptap): JSON string.
|
||||
if (raw[0] === "{") {
|
||||
try {
|
||||
const json: unknown = JSON.parse(raw);
|
||||
const text = tiptapJsonToPlainText(json).trim();
|
||||
if (!text.length) return "";
|
||||
return `<p>${escapeHtml(text).replace(/\n/g, "<br/>")}</p>`;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown plaintext: treat as plain text.
|
||||
return `<p>${escapeHtml(raw).replace(/\n/g, "<br/>")}</p>`;
|
||||
}
|
||||
|
||||
@@ -1096,24 +1089,6 @@ function slugifyWikiTitle(raw: string): string {
|
||||
.slice(0, 80);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function tiptapJsonToPlainText(node: unknown): string {
|
||||
if (node == null) return "";
|
||||
if (typeof node === "string") return node;
|
||||
if (Array.isArray(node)) return node.map(tiptapJsonToPlainText).join("");
|
||||
|
||||
if (isRecord(node)) {
|
||||
if (node.type === "text" && typeof node.text === "string") return node.text;
|
||||
if (node.type === "hardBreak") return "\n";
|
||||
if ("content" in node) return tiptapJsonToPlainText(node.content);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function escapeHtml(input: string): string {
|
||||
return input
|
||||
.replaceAll("&", "&")
|
||||
@@ -1123,15 +1098,12 @@ function escapeHtml(input: string): string {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
type WikiDocStorageFormat = "html" | "json" | "text";
|
||||
type WikiDocStorageFormat = "html" | "text";
|
||||
|
||||
function detectWikiDocStorageFormat(doc: string): WikiDocStorageFormat {
|
||||
const raw = String(doc || "").trim();
|
||||
if (!raw.length) return "html";
|
||||
const first = raw[0];
|
||||
if (first === "<") return "html";
|
||||
if (first === "{" || first === "[") return "json";
|
||||
return "text";
|
||||
return raw[0] === "<" ? "html" : "text";
|
||||
}
|
||||
|
||||
function downloadTextFile(filename: string, contents: string, mime: string): void {
|
||||
|
||||
@@ -113,7 +113,7 @@ Các file nên đọc trước:
|
||||
Các điểm dễ làm hỏng:
|
||||
|
||||
- sanitize link của Quill
|
||||
- compatibility với doc dạng HTML/Tiptap JSON/plain text
|
||||
- compatibility với doc dạng HTML/plain text
|
||||
- slug links nội bộ
|
||||
- sentinel `__missing__`
|
||||
|
||||
|
||||
@@ -218,7 +218,6 @@ Các khả năng đang có:
|
||||
Storage thực tế của `doc`:
|
||||
|
||||
- format mới: HTML string
|
||||
- format cũ tương thích: Tiptap JSON string
|
||||
- plaintext fallback
|
||||
|
||||
### Internal wiki link
|
||||
|
||||
@@ -8,18 +8,16 @@ Wiki trong UHM editor hiện chạy qua hai phần:
|
||||
## 1. Storage format của wiki doc
|
||||
|
||||
Field `doc` trong `WikiSnapshot` hiện là `string | null`.
|
||||
Frontend đang hỗ trợ ba dạng:
|
||||
Frontend hiện hỗ trợ hai dạng:
|
||||
|
||||
- HTML string
|
||||
- JSON string kiểu Tiptap cũ
|
||||
- plain text fallback
|
||||
|
||||
Quy ước hiện tại:
|
||||
|
||||
- format ghi mới từ editor Quill là HTML
|
||||
- Tiptap JSON chỉ còn để tương thích dữ liệu cũ
|
||||
|
||||
`normalizeWikiDocForQuill()` và `normalizeWikiContentToHtml()` là hai hàm quan trọng cho compatibility này.
|
||||
`normalizeWikiDocForQuill()` và `normalizeWikiContentToHtml()` hiện chỉ xử lý HTML hoặc plain text.
|
||||
|
||||
## 2. Editor hiện dùng Quill, không dùng Tiptap
|
||||
|
||||
@@ -42,8 +40,6 @@ Toolbar hiện có:
|
||||
- `image`
|
||||
- `clean`
|
||||
|
||||
Trang `app/user/wikieditor/page.tsx` vẫn dùng Tiptap, nhưng đó là trang riêng và không phải wiki editor chính của project UHM.
|
||||
|
||||
## 3. Tạo, sửa và xóa wiki trong project editor
|
||||
|
||||
`WikiSidebarPanel` hỗ trợ:
|
||||
@@ -89,7 +85,6 @@ Export hiện chỉ là download text từ `wikiDocHtml`.
|
||||
Định dạng file được đoán từ nội dung hiện tại:
|
||||
|
||||
- bắt đầu bằng `<` -> `html`
|
||||
- bắt đầu bằng `{` hoặc `[` -> `json`
|
||||
- còn lại -> `txt`
|
||||
|
||||
Đây là export client-side, không có API export chuyên biệt.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// BackEndGo snapshot expects wiki doc as a string (stored in DB as TEXT).
|
||||
// FE stores Tiptap JSON as a JSON-stringified payload.
|
||||
// FE wiki runtime now stores HTML or plain text in this string field.
|
||||
export type WikiDoc = string | null;
|
||||
|
||||
export type WikiContentSample = {
|
||||
@@ -20,4 +20,3 @@ export type WikiSnapshot = {
|
||||
content_sample?: WikiContentSample[];
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user