Add models for guidelines, incidents, plot points, issues, acts, and world data

- Introduced new models: `GuideLine`, `Incident`, `PlotPoint`, `Issue`, `Act`, and `World` for managing book-related entities.
- Integrated encryption/decryption for sensitive properties in all models using user-specific keys.
- Added methods for CRUD operations and synchronization workflows with error handling and multilingual support.
- Improved maintainability with JSDoc comments and streamlined queries.
This commit is contained in:
natreex
2026-01-12 13:38:10 -05:00
parent d9bf089e32
commit cf6fb97bf0
19 changed files with 3643 additions and 2509 deletions

View File

@@ -1,3 +1,6 @@
/**
* Represents a TipTap editor node structure.
*/
export interface TiptapNode {
type: string;
content?: TiptapNode[];
@@ -7,43 +10,74 @@ export interface TiptapNode {
};
}
/**
* Utility class for handling TipTap content conversions.
* Provides methods to convert TipTap JSON content to HTML and plain text.
*/
export default class Content {
/**
* Converts TipTap raw JSON string content to plain text.
* First converts to HTML, then strips HTML tags to produce plain text.
*
* @param content - The TipTap JSON string to convert
* @returns The plain text representation of the content
*/
static convertTipTapRawToText(content: string): string {
const text: string = this.convertTiptapToHTMLFromString(content);
return this.htmlToText(text);
const htmlContent: string = this.convertTiptapToHTMLFromString(content);
return this.htmlToText(htmlContent);
}
static htmlToText(html: string) {
/**
* Converts HTML string to plain text by removing tags and normalizing whitespace.
* Preserves paragraph structure by converting block elements to newlines.
*
* @param html - The HTML string to convert
* @returns The plain text representation with preserved paragraph structure
*/
static htmlToText(html: string): string {
return html
.replace(/<br\s*\/?>/gi, '\n') // Gérer les <br> d'abord
.replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n') // Balises bloc
.replace(/<\/?[^>]+(>|$)/g, '') // Supprimer toutes les balises restantes
.replace(/(\n\s*){2,}/g, '\n\n') // Préserver les paragraphes
.replace(/^\s+|\s+$|(?<=\s)\s+/g, '') // Nettoyer les espaces
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n')
.replace(/<\/?[^>]+(>|$)/g, '')
.replace(/(\n\s*){2,}/g, '\n\n')
.replace(/^\s+|\s+$|(?<=\s)\s+/g, '')
.trim();
}
/**
* Converts a TipTap JSON string to HTML.
* Parses the JSON string and delegates to the node-based conversion method.
*
* @param jsonString - The TipTap JSON string to convert
* @returns The HTML representation, or empty string if JSON is invalid
*/
static convertTiptapToHTMLFromString(jsonString: string): string {
// Convert the JSON string to an object
let jsonObject: TiptapNode;
let tiptapNode: TiptapNode;
try {
jsonObject = JSON.parse(jsonString);
tiptapNode = JSON.parse(jsonString);
} catch (error) {
console.error('Invalid JSON string:', error);
return '';
}
// Use the existing conversion function
return this.convertTiptapToHTML(jsonObject);
return this.convertTiptapToHTML(tiptapNode);
}
/**
* Recursively converts a TipTap node structure to HTML.
* Handles various node types including documents, paragraphs, headings, lists,
* blockquotes, code blocks, and text with formatting attributes.
*
* @param node - The TipTap node to convert
* @returns The HTML representation of the node and its children
*/
static convertTiptapToHTML(node: TiptapNode): string {
let html = '';
switch (node.type) {
case 'doc':
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -52,7 +86,7 @@ export default class Content {
case 'paragraph':
html += '<p>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -60,45 +94,44 @@ export default class Content {
break;
case 'text':
let textContent = node.text || '';
let formattedText = node.text || '';
// Apply attributes like bold, italic, etc.
if (node.attrs) {
if (node.attrs.bold) {
textContent = `<strong>${textContent}</strong>`;
formattedText = `<strong>${formattedText}</strong>`;
}
if (node.attrs.italic) {
textContent = `<em>${textContent}</em>`;
formattedText = `<em>${formattedText}</em>`;
}
if (node.attrs.underline) {
textContent = `<u>${textContent}</u>`;
formattedText = `<u>${formattedText}</u>`;
}
if (node.attrs.strike) {
textContent = `<s>${textContent}</s>`;
formattedText = `<s>${formattedText}</s>`;
}
if (node.attrs.link) {
textContent = `<a href="${node.attrs.link.href}">${textContent}</a>`;
formattedText = `<a href="${node.attrs.link.href}">${formattedText}</a>`;
}
}
html += textContent;
html += formattedText;
break;
case 'heading':
const level = node.attrs?.level || 1;
html += `<h${level}>`;
const headingLevel = node.attrs?.level || 1;
html += `<h${headingLevel}>`;
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
html += `</h${level}>`;
html += `</h${headingLevel}>`;
break;
case 'bulletList':
html += '<ul>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -108,7 +141,7 @@ export default class Content {
case 'orderedList':
html += '<ol>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -118,7 +151,7 @@ export default class Content {
case 'listItem':
html += '<li>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -128,7 +161,7 @@ export default class Content {
case 'blockquote':
html += '<blockquote>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -138,7 +171,7 @@ export default class Content {
case 'codeBlock':
html += '<pre><code>';
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}
@@ -148,7 +181,7 @@ export default class Content {
default:
console.warn(`Unhandled node type: ${node.type}`);
if (node.content) {
node.content.forEach(childNode => {
node.content.forEach((childNode: TiptapNode) => {
html += this.convertTiptapToHTML(childNode);
});
}