Remove unused components and models for improved maintainability
- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files. - Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter. - Updated project structure and references to reflect these removals.
This commit is contained in:
28
lib/utils/book.ts
Normal file
28
lib/utils/book.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
import {SyncedBook} from "@/lib/types/synced-book";
|
||||
|
||||
export function booksToSelectBox(books: SyncedBook[]): SelectBoxProps[] {
|
||||
return books.map((book: SyncedBook): SelectBoxProps => {
|
||||
return {
|
||||
label: book.title,
|
||||
value: book.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getBookTypeLabel(value: string): string {
|
||||
switch (value) {
|
||||
case 'short':
|
||||
return 'bookTypes.short';
|
||||
case 'novelette':
|
||||
return 'bookTypes.novelette';
|
||||
case 'long':
|
||||
return 'bookTypes.novella';
|
||||
case 'chapbook':
|
||||
return 'bookTypes.chapbook';
|
||||
case 'novel':
|
||||
return 'bookTypes.novel';
|
||||
default:
|
||||
return 'bookTypes.novel';
|
||||
}
|
||||
}
|
||||
33
lib/utils/cookies.ts
Normal file
33
lib/utils/cookies.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function getCookie(name: string): string | null {
|
||||
const nameEQ: string = `${name}=`;
|
||||
const allCookies: string[] = document.cookie.split(';');
|
||||
for (let i: number = 0; i < allCookies.length; i++) {
|
||||
let cookie: string = allCookies[i];
|
||||
while (cookie.charAt(0) === ' ') cookie = cookie.substring(1, cookie.length);
|
||||
if (cookie.indexOf(nameEQ) === 0) return cookie.substring(nameEQ.length, cookie.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setCookie(name: string, value: string, days: number): void {
|
||||
const date: Date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires: string = `expires=${date.toUTCString()}`;
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure: string = 'Secure;';
|
||||
const sameSite: string = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=${value}; ${expires}; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
|
||||
export function removeCookie(name: string): void {
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure: string = 'Secure;';
|
||||
const sameSite: string = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
75
lib/utils/dynamicStyles.ts
Normal file
75
lib/utils/dynamicStyles.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
const styleMap: Map<string, string> = new Map();
|
||||
let sheet: CSSStyleSheet | null = null;
|
||||
|
||||
function getSheet(): CSSStyleSheet {
|
||||
if (!sheet) {
|
||||
sheet = new CSSStyleSheet();
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function sanitizeForClassName(value: string): string {
|
||||
return value.replace(/[^a-zA-Z0-9]/g, '');
|
||||
}
|
||||
|
||||
export function dynamicBg(color: string): string {
|
||||
const key: string = `bg-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bg-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { background-color: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicText(color: string): string {
|
||||
const key: string = `text-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-text-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { color: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorder(color: string, side: string = ''): string {
|
||||
const prop: string = side ? `border-${side}-color` : 'border-color';
|
||||
const key: string = `border-${side}-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-border-${side ? side + '-' : ''}${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { ${prop}: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBgWithOpacity(hexColor: string, opacityHex: string): string {
|
||||
const key: string = `bg-${hexColor}-${opacityHex}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bg-${sanitizeForClassName(hexColor)}-${opacityHex}`;
|
||||
getSheet().insertRule(`.${className} { background-color: ${hexColor}${opacityHex}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorderWithOpacity(hexColor: string, opacityHex: string): string {
|
||||
const key: string = `border-${hexColor}-${opacityHex}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-border-${sanitizeForClassName(hexColor)}-${opacityHex}`;
|
||||
getSheet().insertRule(`.${className} { border-color: ${hexColor}${opacityHex}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorderLeft(color: string, width: string = '3px'): string {
|
||||
const key: string = `bl-${width}-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bl-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { border-left: ${width} solid ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
6
lib/utils/editor.ts
Normal file
6
lib/utils/editor.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function convertToHtml(text: string): string {
|
||||
return text
|
||||
.split(/\n\s*\n/)
|
||||
.map((paragraph: string): string => `<p>${paragraph.trim()}</p>`)
|
||||
.join('');
|
||||
}
|
||||
27
lib/utils/html.ts
Normal file
27
lib/utils/html.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export function formatHTMLContent(htmlContent: string): string {
|
||||
return htmlContent
|
||||
.replace(/<h1>/g, '<h1 style="color: var(--color-text-primary); text-indent: 5px; font-size: 28px; font-weight: bold; text-align: left; margin-vertical: 10px;">')
|
||||
.replace(/<p>/g, '<p style="color: var(--color-editor-text); text-indent: 30px; font-size: 16px; line-height: 22px; margin-vertical: 5px;">')
|
||||
.replace(/<blockquote>/g, '<blockquote style="border-left-width: 4px; border-left-color: var(--color-gray-light); padding-left: 10px; font-style: italic; color: var(--color-text-dimmed);">');
|
||||
}
|
||||
|
||||
export function textContentToHtml(content: string): string {
|
||||
const paragraphs: string[] = content
|
||||
.split(/\n+/)
|
||||
.map((paragraph: string) => paragraph.trim())
|
||||
.filter((paragraph: string) => paragraph.length > 0);
|
||||
|
||||
return paragraphs
|
||||
.map((paragraph: string) => `<p>${paragraph}</p>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
export function htmlToText(html: string): string {
|
||||
return html
|
||||
.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();
|
||||
}
|
||||
41
lib/utils/quillsense.ts
Normal file
41
lib/utils/quillsense.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {SessionProps} from "@/lib/types/session";
|
||||
import {Subscription} from "@/lib/types/user";
|
||||
import {getCurrentSubscription} from "@/lib/utils/user";
|
||||
|
||||
export function getSubLevel(session: SessionProps): number {
|
||||
let currentSub: Subscription | null = getCurrentSubscription(session?.user, 'quill-sense');
|
||||
if (!currentSub) {
|
||||
currentSub = getCurrentSubscription(session?.user, 'quill-trial');
|
||||
if (!currentSub) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
switch (currentSub?.subTier) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function isBringYourKeys(session: SessionProps): boolean {
|
||||
if (!session?.user) return false;
|
||||
const currentSub: Subscription | null = getCurrentSubscription(session?.user, 'use-your-keys');
|
||||
return currentSub?.status || session.user.groupId <= 4;
|
||||
}
|
||||
|
||||
export function isGeminiEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.gemini || false;
|
||||
}
|
||||
|
||||
export function isAnthropicEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.anthropic || false;
|
||||
}
|
||||
|
||||
export function isOpenAIEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.openai || false;
|
||||
}
|
||||
430
lib/utils/story.ts
Normal file
430
lib/utils/story.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
import {Dispatch, SetStateAction} from "react";
|
||||
import {VerbalTimeProps} from "@/lib/types/story";
|
||||
|
||||
export function getVerbesStyle(verbalTimeValue: number, level: number): VerbalTimeProps {
|
||||
switch (verbalTimeValue) {
|
||||
case 1:
|
||||
return {
|
||||
actions: level === 1 ? 'Passé composé' : 'Passé simple',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Passé composé',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait',
|
||||
summary: '→ Narrations épurées, style classique',
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
actions: 'Passé composé',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Imparfait + infinitifs' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Présent + impératif' : 'Impératif',
|
||||
thoughts: level === 1 ? 'Futur proche' : level === 2 ? 'Conditionnel présent' : 'Subjonctif présent',
|
||||
summary: '→ Témoignages, récits autobiographiques',
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
actions: level === 1 ? 'Plus-que-parfait' : 'Passé antérieur',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Plus-que-parfait' : 'Conditionnel passé',
|
||||
dialogues: level === 3 ? 'Passé antérieur' : 'Passé simple',
|
||||
thoughts: level === 1 ? 'Plus-que-parfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Flashbacks littéraires, tragédies',
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent' : 'Présent + participe présent',
|
||||
descriptions: level === 1 ? 'Participe présent' : level === 2 ? 'Participe présent + infinitifs' : 'Participes présents enchaînés',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Impératif' : 'Impératif + infinitifs',
|
||||
thoughts: level === 1 ? 'Futur proche' : level === 2 ? 'Futur simple' : 'Futur antérieur',
|
||||
summary: '→ Urgence, immersion totale',
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Gérondif' : level === 2 ? 'Gérondif + infinitifs' : 'Gérondif + conditionnel',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Conditionnel présent' : 'Infinitif',
|
||||
thoughts: level === 1 ? 'Infinitif' : 'Infinitif passé',
|
||||
summary: '→ Méditations philosophiques',
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : 'Futur antérieur',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Futur antérieur',
|
||||
dialogues: level === 1 ? 'Futur simple' : 'Futur proche',
|
||||
thoughts: level === 1 ? 'Futur proche' : 'Futur antérieur',
|
||||
summary: '→ Prophéties, plans stratégiques',
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : 'Futur antérieur',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur simple + conditionnel' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Futur simple',
|
||||
thoughts: level === 1 ? 'Futur simple' : level === 2 ? 'Conditionnel passé' : 'Futur antérieur',
|
||||
summary: '→ Dystopies, récits post-apocalyptiques',
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
actions: 'Imparfait',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Conditionnel présent' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Infinitif' : 'Infinitifs',
|
||||
thoughts: level === 1 ? 'Subjonctif présent' : level === 2 ? 'Subjonctif imparfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Rêves, souvenirs déformés',
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
actions: 'Conditionnel présent',
|
||||
descriptions: 'Conditionnel passé',
|
||||
dialogues: 'Subjonctif imparfait',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait',
|
||||
summary: '→ Uchronies, réalités alternatives',
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
actions: level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait',
|
||||
descriptions: level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait',
|
||||
dialogues: 'Impératif',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Conditionnel passé',
|
||||
summary: '→ Drames psychologiques, dilemmes',
|
||||
};
|
||||
case 11:
|
||||
return {
|
||||
actions: 'Passé composé',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Plus-que-parfait',
|
||||
thoughts: 'Infinitif passé',
|
||||
summary: '→ Regrets, introspection nostalgique',
|
||||
};
|
||||
case 12:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Passé composé' : 'Passé composé + futur antérieur',
|
||||
dialogues: level === 1 ? 'Futur proche' : 'Futur antérieur',
|
||||
thoughts: 'Participe présent',
|
||||
summary: '→ Crise en cours, compte à rebours',
|
||||
};
|
||||
case 13:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent + participe présent' : 'Participes présents enchaînés',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent + adjectifs' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Conditionnel présent' : 'Subjonctif présent',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Émotions intenses, introspections vives (romances, drames psychologiques)',
|
||||
};
|
||||
case 14:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Gérondif' : level === 2 ? 'Gérondif + infinitifs' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Impératif' : 'Infinitif',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Réflexions profondes, analyse des émotions (nouvelles philosophiques, récits introspectifs)',
|
||||
};
|
||||
case 15:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent + passé simple' : 'Présent + passé antérieur',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Passé composé' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Passé simple' : 'Futur antérieur',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Plus-que-parfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Histoires historiques avec une intensité immédiate (batailles, moments décisifs)',
|
||||
};
|
||||
case 16:
|
||||
return {
|
||||
actions: level === 1 ? 'Passé composé' : level === 2 ? 'Imparfait + passé simple' : 'Plus-que-parfait',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe passé' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Subjonctif imparfait',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif présent',
|
||||
summary: '→ Récits introspectifs, auto-analyse (autofictions, récits de croissance personnelle)',
|
||||
};
|
||||
case 17:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : level === 2 ? 'Futur antérieur' : 'Conditionnel passé',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Futur simple' : level === 2 ? 'Futur antérieur' : 'Subjonctif présent',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Futur antérieur' : 'Conditionnel passé',
|
||||
summary: '→ Prophéties, visions apocalyptiques (récits mystiques, romans de science-fiction)',
|
||||
};
|
||||
case 18:
|
||||
return {
|
||||
actions: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Conditionnel passé' : 'Subjonctif imparfait',
|
||||
descriptions: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Conditionnel passé' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Subjonctif imparfait' : 'Impératif',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Mondes parallèles, uchronies (romans alternatifs, récits de fantasy)',
|
||||
};
|
||||
case 19:
|
||||
return {
|
||||
actions: level === 1 ? 'Imparfait' : level === 2 ? 'Imparfait + participe présent' : 'Participes présents enchaînés',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent + adjectifs' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Subjonctif imparfait',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif présent',
|
||||
summary: '→ Lyrisme, poésie narrative (récits oniriques, nouvelles littéraires)',
|
||||
};
|
||||
case 20:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Imparfait' : 'Futur simple',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Futur proche',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Plus-que-parfait' : 'Subjonctif présent',
|
||||
summary: '→ Immersion totale (récits interactifs, jeux de rôle, romans à choix multiples)',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
actions: 'Passé simple',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Passé composé',
|
||||
thoughts: 'Plus-que-parfait',
|
||||
summary: '→ Narrations épurées, style classique',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function presetStoryType(
|
||||
presetType: string,
|
||||
setTone: Dispatch<SetStateAction<string>>,
|
||||
setAtmosphere: Dispatch<SetStateAction<string>>,
|
||||
setVerbTense: Dispatch<SetStateAction<string>>,
|
||||
setPerson: Dispatch<SetStateAction<string>>,
|
||||
setDialogueType: Dispatch<SetStateAction<string>>,
|
||||
setIsExplicit: Dispatch<SetStateAction<boolean>>,
|
||||
): void {
|
||||
switch (presetType) {
|
||||
case '1':
|
||||
setTone('Suspense angoissant, mystère troublant');
|
||||
setAtmosphere('Tension oppressante, ombres menaçantes');
|
||||
setVerbTense('3');
|
||||
setPerson('1');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '2':
|
||||
setTone('Brutalité crue, terreur psychologique');
|
||||
setAtmosphere('Claustrophobie, clair-obscur sinistre');
|
||||
setVerbTense('10');
|
||||
setPerson('4');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '3':
|
||||
setTone('Magie envoûtante, innocence poétique');
|
||||
setAtmosphere('Forêt luminescente, brume enchantée');
|
||||
setVerbTense('19');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '4':
|
||||
setTone('Froidure technologique, désespoir systémique');
|
||||
setAtmosphere('Métal rouillé, lumières néon vacillantes');
|
||||
setVerbTense('7');
|
||||
setPerson('5');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '5':
|
||||
setTone('Passion tourmentée, mélancolie sensuelle');
|
||||
setAtmosphere('Pluie fine, chambres aux rideaux lourds');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '6':
|
||||
setTone('Héroïsme grandiose, dangers exaltants');
|
||||
setAtmosphere('Vastes paysages, ruines anciennes');
|
||||
setVerbTense('4');
|
||||
setPerson('6');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '7':
|
||||
setTone('Méditation existentielle, questions sans réponses');
|
||||
setAtmosphere('Bibliothèque poussiéreuse, nuit silencieuse');
|
||||
setVerbTense('5');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '8':
|
||||
setTone('Tension psychologique, suspense mental');
|
||||
setAtmosphere('Isolation, paranoïa croissante');
|
||||
setVerbTense('10');
|
||||
setPerson('4');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '9':
|
||||
setTone('Mystère obscur, surnaturel inquiétant');
|
||||
setAtmosphere('Forêts sombres, créatures cachées');
|
||||
setVerbTense('3');
|
||||
setPerson('1');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '10':
|
||||
setTone('Amour interdit, passion à travers les âges');
|
||||
setAtmosphere('Châteaux majestueux, bals somptueux');
|
||||
setVerbTense('1');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '11':
|
||||
setTone('Dure réalité, enquête sombre');
|
||||
setAtmosphere('Rues sombres, ambiance de crime');
|
||||
setVerbTense('16');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '12':
|
||||
setTone('Espoir futuriste, société idéale');
|
||||
setAtmosphere('Villes lumineuses, technologie avancée');
|
||||
setVerbTense('6');
|
||||
setPerson('4');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '13':
|
||||
setTone('Magie contemporaine, réalisme enchanté');
|
||||
setAtmosphere('Ville moderne, éléments féeriques');
|
||||
setVerbTense('4');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '14':
|
||||
setTone('Conflits émotionnels, relations complexes');
|
||||
setAtmosphere('Intérieur chaleureux, tensions sous-jacentes');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '15':
|
||||
setTone('Exploration audacieuse, dangers marins');
|
||||
setAtmosphere('Océan infini, navires anciens');
|
||||
setVerbTense('4');
|
||||
setPerson('6');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '16':
|
||||
setTone('Quête héroïque, magie puissante');
|
||||
setAtmosphere('Mondes imaginaires, créatures mythiques');
|
||||
setVerbTense('19');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '17':
|
||||
setTone('Amour moderne, relations actuelles');
|
||||
setAtmosphere('Ville animée, cafés cosy');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '18':
|
||||
setTone("Intrigue internationale, secrets d'État");
|
||||
setAtmosphere('Villes étrangères, tensions diplomatiques');
|
||||
setVerbTense('16');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '19':
|
||||
setTone('Survie désespérée, monde en ruines');
|
||||
setAtmosphere('Paysages dévastés, ressources rares');
|
||||
setVerbTense('7');
|
||||
setPerson('4');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '20':
|
||||
setTone('Leçons de vie, valeurs profondes');
|
||||
setAtmosphere('Village paisible, nature environnante');
|
||||
setVerbTense('1');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNarrativePerson(value: number, level: number): string {
|
||||
if (level === 1) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur) - Parfait pour les débuts (ex: Je marchais)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente - Narration globale (ex: Il marchait)';
|
||||
default:
|
||||
return 'Première personne';
|
||||
}
|
||||
} else if (level === 2) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur)';
|
||||
case 2:
|
||||
return 'Première personne (Je témoin) - Observateur (ex: Je le regardais marcher)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente';
|
||||
case 4:
|
||||
return 'Troisième limitée - Focus sur un personnage (ex: Il marchait, ignorant le danger)';
|
||||
default:
|
||||
return 'Première personne';
|
||||
}
|
||||
} else if (level === 3) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur)';
|
||||
case 2:
|
||||
return 'Première personne (Je témoin)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente';
|
||||
case 4:
|
||||
return 'Troisième limitée';
|
||||
case 5:
|
||||
return 'Deuxième personne (Tu) - Immersion forte (ex: Tu marches vers la mort)';
|
||||
case 6:
|
||||
return 'Nous collectif - Voix chorale (ex: Nous marchions, unis par le destin)';
|
||||
default:
|
||||
return 'Troisième omnisciente';
|
||||
}
|
||||
}
|
||||
return 'Première personne';
|
||||
}
|
||||
|
||||
export function getDialogueType(value: number, level: number): string {
|
||||
if (level === 1) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct - Paroles exactes (ex: "Je t\'aime !")';
|
||||
case 2:
|
||||
return 'Dialogue indirect - Résumé par le narrateur (ex: Il dit qu\'il m\'aime)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
} else if (level === 2) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct';
|
||||
case 2:
|
||||
return 'Dialogue indirect';
|
||||
case 3:
|
||||
return 'Dialogue mixte (ex: "Je t\'aime" dit-il, puis explique ses sentiments)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
} else if (level === 3) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct';
|
||||
case 2:
|
||||
return 'Dialogue indirect';
|
||||
case 3:
|
||||
return 'Dialogue mixte';
|
||||
case 4:
|
||||
return 'Monologue intérieur (ex: *Je ne peux pas le perdre...*)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
}
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
SyncedActSummary,
|
||||
SyncedGuideLine,
|
||||
SyncedAIGuideLine
|
||||
} from "@/lib/models/SyncedBook";
|
||||
} from "@/lib/types/synced-book";
|
||||
|
||||
/**
|
||||
* Résultat de comparaison pour un livre
|
||||
|
||||
3
lib/utils/time.ts
Normal file
3
lib/utils/time.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function timeStampInSeconds(): number {
|
||||
return Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
140
lib/utils/tiptap.ts
Normal file
140
lib/utils/tiptap.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import {TiptapAttrValue, TiptapLinkAttrs, TiptapNode} from "@/lib/types/chapter";
|
||||
|
||||
function isTiptapLinkAttrs(value: TiptapAttrValue): value is TiptapLinkAttrs {
|
||||
return typeof value === 'object' && value !== null && 'href' in value;
|
||||
}
|
||||
|
||||
export function getPageCount(text: string): number {
|
||||
const charactersPerLine: number = 90;
|
||||
const linesPerPage: number = 40;
|
||||
|
||||
const lines: string[] = text.split('\n');
|
||||
let totalLines: number = 0;
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const lineLength: number = line.length;
|
||||
const estimatedLines: number = Math.ceil(lineLength / charactersPerLine);
|
||||
totalLines += estimatedLines;
|
||||
});
|
||||
|
||||
return Math.ceil(totalLines / linesPerPage);
|
||||
}
|
||||
|
||||
export function convertTiptapToHTML(node: TiptapNode): string {
|
||||
let html: string = '';
|
||||
|
||||
switch (node.type) {
|
||||
case 'doc':
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'paragraph':
|
||||
html += '<p>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</p>';
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
let textContent: string = node.text || '';
|
||||
|
||||
if (node.attrs) {
|
||||
if (node.attrs.bold) {
|
||||
textContent = `<strong>${textContent}</strong>`;
|
||||
}
|
||||
if (node.attrs.italic) {
|
||||
textContent = `<em>${textContent}</em>`;
|
||||
}
|
||||
if (node.attrs.underline) {
|
||||
textContent = `<u>${textContent}</u>`;
|
||||
}
|
||||
if (node.attrs.strike) {
|
||||
textContent = `<s>${textContent}</s>`;
|
||||
}
|
||||
if (node.attrs.link && isTiptapLinkAttrs(node.attrs.link)) {
|
||||
textContent = `<a href="${node.attrs.link.href}">${textContent}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += textContent;
|
||||
break;
|
||||
|
||||
case 'heading':
|
||||
const level: number = typeof node.attrs?.level === 'number' ? node.attrs.level : 1;
|
||||
html += `<h${level}>`;
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += `</h${level}>`;
|
||||
break;
|
||||
|
||||
case 'bulletList':
|
||||
html += '<ul>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ul>';
|
||||
break;
|
||||
|
||||
case 'orderedList':
|
||||
html += '<ol>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ol>';
|
||||
break;
|
||||
|
||||
case 'listItem':
|
||||
html += '<li>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</li>';
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
html += '<blockquote>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</blockquote>';
|
||||
break;
|
||||
|
||||
case 'codeBlock':
|
||||
html += '<pre><code>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</code></pre>';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
44
lib/utils/user.ts
Normal file
44
lib/utils/user.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {GuideTour, Subscription, UserProps} from "@/lib/types/user";
|
||||
import {SessionProps} from "@/lib/types/session";
|
||||
|
||||
export function getCurrentSubscription(user: UserProps | null, type: "quill-sense" | "use-your-keys" | "quill-trial"): Subscription | null {
|
||||
if (!user || !user.subscription || user.subscription.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return user.subscription.find((sub: Subscription): boolean => {
|
||||
return sub.subType === type && sub.status;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
export function getWritingLevel(level: number): string {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 'Débutant';
|
||||
case 2:
|
||||
return 'Intermédiaire';
|
||||
case 3:
|
||||
return 'Avancé';
|
||||
default:
|
||||
return 'Débutant';
|
||||
}
|
||||
}
|
||||
|
||||
export function guideTourDone(guide: GuideTour[], tour: string): boolean {
|
||||
if (!guide || !tour) return false;
|
||||
return guide.find((guide: GuideTour): boolean => guide[tour]) === undefined;
|
||||
}
|
||||
|
||||
export function setNewGuideTour(session: SessionProps, tour: string): SessionProps {
|
||||
if (!session.user) return session;
|
||||
const newGuideTour: GuideTour[] = [
|
||||
...(session.user.guideTour ?? []),
|
||||
{[tour]: true}
|
||||
];
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
guideTour: newGuideTour
|
||||
}
|
||||
};
|
||||
}
|
||||
4
lib/utils/validation.ts
Normal file
4
lib/utils/validation.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function verifyInput(input: string): boolean {
|
||||
const pattern: RegExp = new RegExp('(<.*?>)|(&.*?;)|({.*?})', 'gmi');
|
||||
return pattern.test(input);
|
||||
}
|
||||
Reference in New Issue
Block a user