Multi lingua, hotfixes and design

This commit is contained in:
Ichitux
2026-04-05 03:56:31 +02:00
parent 594b50b77f
commit 6cd1bf305d
29 changed files with 3157 additions and 562 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Pacifico&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Pacifico&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-9IOnhCkb.js"></script> <script type="module" crossorigin src="/assets/index-D3_QgWye.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DS0xE7Ko.css"> <link rel="stylesheet" crossorigin href="/assets/index-CZFZrYeL.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+64 -22
View File
@@ -1,100 +1,145 @@
{ {
"hash": "deb88d1e", "hash": "e20034a6",
"configHash": "e64eb32e", "configHash": "e64eb32e",
"lockfileHash": "fe4eb8a2", "lockfileHash": "b223a39a",
"browserHash": "c9f0ad5b", "browserHash": "4e2530e7",
"optimized": { "optimized": {
"react": { "react": {
"src": "../../../../node_modules/react/index.js", "src": "../../../../node_modules/react/index.js",
"file": "react.js", "file": "react.js",
"fileHash": "80d72dea", "fileHash": "bc47155f",
"needsInterop": true "needsInterop": true
}, },
"react-dom": { "react-dom": {
"src": "../../../../node_modules/react-dom/index.js", "src": "../../../../node_modules/react-dom/index.js",
"file": "react-dom.js", "file": "react-dom.js",
"fileHash": "3f8ff889", "fileHash": "f3537af4",
"needsInterop": true "needsInterop": true
}, },
"react/jsx-dev-runtime": { "react/jsx-dev-runtime": {
"src": "../../../../node_modules/react/jsx-dev-runtime.js", "src": "../../../../node_modules/react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js", "file": "react_jsx-dev-runtime.js",
"fileHash": "05119472", "fileHash": "dd4d0058",
"needsInterop": true "needsInterop": true
}, },
"react/jsx-runtime": { "react/jsx-runtime": {
"src": "../../../../node_modules/react/jsx-runtime.js", "src": "../../../../node_modules/react/jsx-runtime.js",
"file": "react_jsx-runtime.js", "file": "react_jsx-runtime.js",
"fileHash": "e0024c91", "fileHash": "49f980be",
"needsInterop": true "needsInterop": true
}, },
"@tanstack/react-query": { "@tanstack/react-query": {
"src": "../../../../node_modules/@tanstack/react-query/build/modern/index.js", "src": "../../../../node_modules/@tanstack/react-query/build/modern/index.js",
"file": "@tanstack_react-query.js", "file": "@tanstack_react-query.js",
"fileHash": "4d070117", "fileHash": "cb583fa8",
"needsInterop": false "needsInterop": false
}, },
"axios": { "axios": {
"src": "../../../../node_modules/axios/index.js", "src": "../../../../node_modules/axios/index.js",
"file": "axios.js", "file": "axios.js",
"fileHash": "a4419c2e", "fileHash": "b1a573d1",
"needsInterop": false "needsInterop": false
}, },
"lucide-react": { "lucide-react": {
"src": "../../../../node_modules/lucide-react/dist/esm/lucide-react.js", "src": "../../../../node_modules/lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js", "file": "lucide-react.js",
"fileHash": "92a1456b", "fileHash": "14fb8e72",
"needsInterop": false "needsInterop": false
}, },
"react-dom/client": { "react-dom/client": {
"src": "../../../../node_modules/react-dom/client.js", "src": "../../../../node_modules/react-dom/client.js",
"file": "react-dom_client.js", "file": "react-dom_client.js",
"fileHash": "86bf4335", "fileHash": "5d2ebd48",
"needsInterop": true "needsInterop": true
}, },
"react-konva": { "react-konva": {
"src": "../../../../node_modules/react-konva/es/ReactKonva.js", "src": "../../../../node_modules/react-konva/es/ReactKonva.js",
"file": "react-konva.js", "file": "react-konva.js",
"fileHash": "56221f90", "fileHash": "a0e0f67f",
"needsInterop": false "needsInterop": false
}, },
"react-router-dom": { "react-router-dom": {
"src": "../../../../node_modules/react-router-dom/dist/index.js", "src": "../../../../node_modules/react-router-dom/dist/index.js",
"file": "react-router-dom.js", "file": "react-router-dom.js",
"fileHash": "457f2587", "fileHash": "fec8af05",
"needsInterop": false "needsInterop": false
}, },
"zustand": { "zustand": {
"src": "../../../../node_modules/zustand/esm/index.mjs", "src": "../../../../node_modules/zustand/esm/index.mjs",
"file": "zustand.js", "file": "zustand.js",
"fileHash": "848a6c38", "fileHash": "35ad42af",
"needsInterop": false "needsInterop": false
}, },
"i18next": { "i18next": {
"src": "../../../../node_modules/i18next/dist/esm/i18next.js", "src": "../../../../node_modules/i18next/dist/esm/i18next.js",
"file": "i18next.js", "file": "i18next.js",
"fileHash": "3d2d9026", "fileHash": "9acaef61",
"needsInterop": false "needsInterop": false
}, },
"react-i18next": { "react-i18next": {
"src": "../../../../node_modules/react-i18next/dist/es/index.js", "src": "../../../../node_modules/react-i18next/dist/es/index.js",
"file": "react-i18next.js", "file": "react-i18next.js",
"fileHash": "78d9a217", "fileHash": "b0e3e0fb",
"needsInterop": false "needsInterop": false
}, },
"i18next-browser-languagedetector": { "i18next-browser-languagedetector": {
"src": "../../../../node_modules/i18next-browser-languagedetector/dist/esm/i18nextBrowserLanguageDetector.js", "src": "../../../../node_modules/i18next-browser-languagedetector/dist/esm/i18nextBrowserLanguageDetector.js",
"file": "i18next-browser-languagedetector.js", "file": "i18next-browser-languagedetector.js",
"fileHash": "09be5c13", "fileHash": "4cc94160",
"needsInterop": false
},
"@tiptap/react": {
"src": "../../../../node_modules/@tiptap/react/dist/index.js",
"file": "@tiptap_react.js",
"fileHash": "b3a13a61",
"needsInterop": false
},
"@tiptap/starter-kit": {
"src": "../../../../node_modules/@tiptap/starter-kit/dist/index.js",
"file": "@tiptap_starter-kit.js",
"fileHash": "a44103b6",
"needsInterop": false
},
"@tiptap/extension-underline": {
"src": "../../../../node_modules/@tiptap/extension-underline/dist/index.js",
"file": "@tiptap_extension-underline.js",
"fileHash": "1e7a61a1",
"needsInterop": false
},
"@tiptap/extension-text-align": {
"src": "../../../../node_modules/@tiptap/extension-text-align/dist/index.js",
"file": "@tiptap_extension-text-align.js",
"fileHash": "cd5adcf1",
"needsInterop": false
},
"@tiptap/extension-placeholder": {
"src": "../../../../node_modules/@tiptap/extension-placeholder/dist/index.js",
"file": "@tiptap_extension-placeholder.js",
"fileHash": "8fd72abc",
"needsInterop": false "needsInterop": false
} }
}, },
"chunks": { "chunks": {
"chunk-DRQXKEUS": {
"file": "chunk-DRQXKEUS.js"
},
"chunk-JYSI5OBP": {
"file": "chunk-JYSI5OBP.js"
},
"chunk-6FQSNAE3": {
"file": "chunk-6FQSNAE3.js"
},
"chunk-CITXGXPZ": { "chunk-CITXGXPZ": {
"file": "chunk-CITXGXPZ.js" "file": "chunk-CITXGXPZ.js"
}, },
"chunk-DRQXKEUS": { "chunk-KMJOTAPQ": {
"file": "chunk-DRQXKEUS.js" "file": "chunk-KMJOTAPQ.js"
},
"chunk-N2AN2IIV": {
"file": "chunk-N2AN2IIV.js"
},
"chunk-DBGWLXJ5": {
"file": "chunk-DBGWLXJ5.js"
}, },
"chunk-TJ6I66L4": { "chunk-TJ6I66L4": {
"file": "chunk-TJ6I66L4.js" "file": "chunk-TJ6I66L4.js"
@@ -102,9 +147,6 @@
"chunk-RMSUKPN2": { "chunk-RMSUKPN2": {
"file": "chunk-RMSUKPN2.js" "file": "chunk-RMSUKPN2.js"
}, },
"chunk-JYSI5OBP": {
"file": "chunk-JYSI5OBP.js"
},
"chunk-7URR3GLA": { "chunk-7URR3GLA": {
"file": "chunk-7URR3GLA.js" "file": "chunk-7URR3GLA.js"
}, },
+3 -3
View File
@@ -1,9 +1,9 @@
import {
require_shim
} from "./chunk-CITXGXPZ.js";
import { import {
keysFromSelector keysFromSelector
} from "./chunk-DRQXKEUS.js"; } from "./chunk-DRQXKEUS.js";
import {
require_shim
} from "./chunk-CITXGXPZ.js";
import { import {
require_react require_react
} from "./chunk-7URR3GLA.js"; } from "./chunk-7URR3GLA.js";
+3 -95
View File
@@ -1,93 +1,14 @@
import { import {
require_shim require_with_selector
} from "./chunk-CITXGXPZ.js"; } from "./chunk-6FQSNAE3.js";
import "./chunk-CITXGXPZ.js";
import { import {
require_react require_react
} from "./chunk-7URR3GLA.js"; } from "./chunk-7URR3GLA.js";
import { import {
__commonJS,
__toESM __toESM
} from "./chunk-4MBMRILA.js"; } from "./chunk-4MBMRILA.js";
// ../node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js
var require_with_selector_development = __commonJS({
"../node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js"(exports) {
"use strict";
(function() {
function is(x, y) {
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React = require_react(), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is, useSyncExternalStore = shim.useSyncExternalStore, useRef = React.useRef, useEffect = React.useEffect, useMemo = React.useMemo, useDebugValue2 = React.useDebugValue;
exports.useSyncExternalStoreWithSelector = function(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
var instRef = useRef(null);
if (null === instRef.current) {
var inst = { hasValue: false, value: null };
instRef.current = inst;
} else inst = instRef.current;
instRef = useMemo(
function() {
function memoizedSelector(nextSnapshot) {
if (!hasMemo) {
hasMemo = true;
memoizedSnapshot = nextSnapshot;
nextSnapshot = selector(nextSnapshot);
if (void 0 !== isEqual && inst.hasValue) {
var currentSelection = inst.value;
if (isEqual(currentSelection, nextSnapshot))
return memoizedSelection = currentSelection;
}
return memoizedSelection = nextSnapshot;
}
currentSelection = memoizedSelection;
if (objectIs(memoizedSnapshot, nextSnapshot))
return currentSelection;
var nextSelection = selector(nextSnapshot);
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return memoizedSnapshot = nextSnapshot, currentSelection;
memoizedSnapshot = nextSnapshot;
return memoizedSelection = nextSelection;
}
var hasMemo = false, memoizedSnapshot, memoizedSelection, maybeGetServerSnapshot = void 0 === getServerSnapshot ? null : getServerSnapshot;
return [
function() {
return memoizedSelector(getSnapshot());
},
null === maybeGetServerSnapshot ? void 0 : function() {
return memoizedSelector(maybeGetServerSnapshot());
}
];
},
[getSnapshot, getServerSnapshot, selector, isEqual]
);
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function() {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue2(value);
return value;
};
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// ../node_modules/use-sync-external-store/shim/with-selector.js
var require_with_selector = __commonJS({
"../node_modules/use-sync-external-store/shim/with-selector.js"(exports, module) {
"use strict";
if (false) {
module.exports = null;
} else {
module.exports = require_with_selector_development();
}
}
});
// ../node_modules/zustand/esm/vanilla.mjs // ../node_modules/zustand/esm/vanilla.mjs
var createStoreImpl = (createState) => { var createStoreImpl = (createState) => {
let state; let state;
@@ -170,17 +91,4 @@ export {
react as default, react as default,
useStore useStore
}; };
/*! Bundled license information:
use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js:
(**
* @license React
* use-sync-external-store-shim/with-selector.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=zustand.js.map //# sourceMappingURL=zustand.js.map
+4 -4
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -14,6 +14,12 @@
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0", "@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5", "axios": "^1.6.5",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"i18next": "^26.0.3", "i18next": "^26.0.3",
+5 -4
View File
@@ -1,4 +1,4 @@
import { Routes, Route, Navigate } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';
import { BookOpen, Image, Settings } from 'lucide-react'; import { BookOpen, Image, Settings } from 'lucide-react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -7,6 +7,7 @@ import AuthModal from './components/auth/AuthModal';
import CoverDesigner from './components/cover-designer/CoverDesigner'; import CoverDesigner from './components/cover-designer/CoverDesigner';
import BookGenerator from './components/book-generator/BookGenerator'; import BookGenerator from './components/book-generator/BookGenerator';
import SettingsPage from './components/SettingsPage'; import SettingsPage from './components/SettingsPage';
import OnboardingPage from './components/OnboardingPage';
function App() { function App() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -23,12 +24,12 @@ function App() {
<header className="bg-white border-b border-gray-200 sticky top-0 z-50"> <header className="bg-white border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
<div className="flex items-center gap-2"> <a href="/" className="flex items-center gap-2 no-underline">
<div className="w-8 h-8 bg-gradient-to-br from-primary-500 to-accent-500 rounded-lg flex items-center justify-center"> <div className="w-8 h-8 bg-gradient-to-br from-primary-500 to-accent-500 rounded-lg flex items-center justify-center">
<BookOpen className="w-5 h-5 text-white" /> <BookOpen className="w-5 h-5 text-white" />
</div> </div>
<h1 className="text-xl font-bold text-gray-900">CreaBook</h1> <h1 className="text-xl font-bold text-gray-900">CreaBook</h1>
</div> </a>
{/* Navigation */} {/* Navigation */}
<nav className="flex items-center gap-1"> <nav className="flex items-center gap-1">
@@ -84,7 +85,7 @@ function App() {
{/* Main Content */} {/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes> <Routes>
<Route path="/" element={<Navigate to="/covers" replace />} /> <Route path="/" element={<OnboardingPage />} />
<Route path="/covers" element={<CoverDesigner />} /> <Route path="/covers" element={<CoverDesigner />} />
<Route path="/books" element={<BookGenerator />} /> <Route path="/books" element={<BookGenerator />} />
<Route path="/settings" element={<SettingsPage />} /> <Route path="/settings" element={<SettingsPage />} />
+124
View File
@@ -0,0 +1,124 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Globe, BookOpen, Wand2, Edit3 } from 'lucide-react';
import { booksApi } from '../services/api';
import { useQuery } from '@tanstack/react-query';
export default function OnboardingPage() {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const [idea, setIdea] = useState('');
const [genre, setGenre] = useState('fiction');
const { data: genresData } = useQuery({
queryKey: ['genres'],
queryFn: async () => {
const response = await booksApi.getGenres();
return response.data.genres;
},
});
const handleLanguageChange = (lang: string) => {
i18n.changeLanguage(lang);
};
const startJourney = (useAI: boolean) => {
navigate('/books', { state: { autoBook: { idea, genre, useAI } } });
};
return (
<div className="max-w-3xl mx-auto py-12 px-6">
<div className="text-center mb-12">
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">{t('onboarding.title')}</h1>
<p className="text-lg text-gray-600">{t('onboarding.subtitle')}</p>
</div>
<div className="space-y-12">
{/* Step 1: Language */}
<section className="card">
<h2 className="text-xl font-bold flex items-center gap-2 mb-4">
<Globe className="text-primary-500 w-6 h-6" />
{t('onboarding.step1')}
</h2>
<div className="flex gap-4">
<button
onClick={() => handleLanguageChange('en')}
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
i18n.language === 'en'
? 'bg-primary-500 text-white shadow-md'
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
}`}
>
English
</button>
<button
onClick={() => handleLanguageChange('es')}
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
i18n.language === 'es'
? 'bg-primary-500 text-white shadow-md'
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
}`}
>
Español
</button>
</div>
</section>
{/* Step 2: Book Idea */}
<section className="card">
<h2 className="text-xl font-bold flex items-center gap-2 mb-4">
<BookOpen className="text-primary-500 w-6 h-6" />
{t('onboarding.step2')}
</h2>
<div className="space-y-4">
<div>
<label className="label">{t('onboarding.genreLabel')}</label>
<select
value={genre}
onChange={(e) => setGenre(e.target.value)}
className="input bg-white"
>
{genresData?.map((g: any) => (
<option key={g.id} value={g.id}>
{String(t(`genres.${g.id}.name`, g.name))}
</option>
))}
</select>
</div>
<div>
<textarea
rows={4}
value={idea}
onChange={(e) => setIdea(e.target.value)}
placeholder={t('onboarding.ideaPlaceholder')}
className="input text-lg resize-none"
/>
</div>
</div>
</section>
{/* Step 3: Progression */}
<section className="text-center pt-4">
<h2 className="text-xl font-bold mb-6">{t('onboarding.step3')}</h2>
<div className="grid sm:grid-cols-2 gap-4">
<button
onClick={() => startJourney(false)}
className="flex flex-col items-center justify-center p-6 bg-white border-2 border-gray-200 hover:border-gray-400 rounded-xl transition-all"
>
<Edit3 className="w-10 h-10 text-gray-500 mb-3" />
<span className="font-semibold text-gray-800 text-lg">{t('onboarding.btnFree')}</span>
</button>
<button
onClick={() => startJourney(true)}
className="flex flex-col items-center justify-center p-6 bg-primary-50 border-2 border-primary-500 hover:bg-primary-100 rounded-xl transition-all transform hover:scale-[1.02]"
>
<Wand2 className="w-10 h-10 text-primary-600 mb-3" />
<span className="font-semibold text-primary-800 text-lg">{t('onboarding.btnAI')}</span>
</button>
</div>
</section>
</div>
</div>
);
}
+219
View File
@@ -0,0 +1,219 @@
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Placeholder from '@tiptap/extension-placeholder';
import {
Bold, Italic, Underline as UnderlineIcon, Strikethrough,
List, ListOrdered, AlignLeft, AlignCenter, AlignRight,
Undo2, Redo2, Heading1, Heading2, Heading3, Quote, Minus,
} from 'lucide-react';
import { useEffect } from 'react';
interface RichTextEditorProps {
content: string;
onChange: (html: string) => void;
placeholder?: string;
}
function ToolbarButton({
onClick,
active,
children,
title,
}: {
onClick: () => void;
active?: boolean;
children: React.ReactNode;
title: string;
}) {
return (
<button
type="button"
onClick={onClick}
title={title}
className={`p-1.5 rounded transition-colors ${
active
? 'bg-primary-100 text-primary-700'
: 'text-gray-500 hover:bg-gray-100 hover:text-gray-700'
}`}
>
{children}
</button>
);
}
export default function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3] },
}),
Underline,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
Placeholder.configure({
placeholder: placeholder || 'Start writing...',
}),
],
content,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
editorProps: {
attributes: {
class:
'prose prose-lg max-w-none min-h-[500px] focus:outline-none p-6 font-serif leading-relaxed text-center',
},
},
onCreate: ({ editor }) => {
// Set default text alignment to center for completely new/empty content
if (!content || content.trim() === '' || content.trim() === '<p></p>') {
editor.chain().focus().setTextAlign('center').run();
}
},
});
// Sync external content changes (e.g. AI-generated content)
useEffect(() => {
if (editor && content !== editor.getHTML()) {
editor.commands.setContent(content);
}
}, [content]);
if (!editor) return null;
return (
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* Toolbar */}
<div className="flex flex-wrap items-center gap-0.5 px-3 py-2 bg-gray-50 border-b border-gray-200">
{/* Undo / Redo */}
<ToolbarButton onClick={() => editor.chain().focus().undo().run()} title="Undo">
<Undo2 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton onClick={() => editor.chain().focus().redo().run()} title="Redo">
<Redo2 className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Headings */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
active={editor.isActive('heading', { level: 1 })}
title="Heading 1"
>
<Heading1 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
active={editor.isActive('heading', { level: 2 })}
title="Heading 2"
>
<Heading2 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
active={editor.isActive('heading', { level: 3 })}
title="Heading 3"
>
<Heading3 className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Inline formatting */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBold().run()}
active={editor.isActive('bold')}
title="Bold"
>
<Bold className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleItalic().run()}
active={editor.isActive('italic')}
title="Italic"
>
<Italic className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleUnderline().run()}
active={editor.isActive('underline')}
title="Underline"
>
<UnderlineIcon className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleStrike().run()}
active={editor.isActive('strike')}
title="Strikethrough"
>
<Strikethrough className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Lists */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBulletList().run()}
active={editor.isActive('bulletList')}
title="Bullet List"
>
<List className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleOrderedList().run()}
active={editor.isActive('orderedList')}
title="Ordered List"
>
<ListOrdered className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Alignment */}
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('left').run()}
active={editor.isActive({ textAlign: 'left' })}
title="Align Left"
>
<AlignLeft className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('center').run()}
active={editor.isActive({ textAlign: 'center' })}
title="Align Center"
>
<AlignCenter className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('right').run()}
active={editor.isActive({ textAlign: 'right' })}
title="Align Right"
>
<AlignRight className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Block elements */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBlockquote().run()}
active={editor.isActive('blockquote')}
title="Blockquote"
>
<Quote className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setHorizontalRule().run()}
title="Horizontal Rule"
>
<Minus className="w-4 h-4" />
</ToolbarButton>
</div>
{/* Editor content */}
<EditorContent editor={editor} />
</div>
);
}
@@ -1,18 +1,21 @@
import { useState } from 'react'; import { useState } from 'react';
import { Wand2, ChevronLeft, ChevronRight, Sparkles } from 'lucide-react'; import { Wand2, ChevronLeft, ChevronRight, Sparkles } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useBookStore } from '../../stores/bookStore'; import { useBookStore } from '../../stores/bookStore';
import { booksApi } from '../../services/api'; import { booksApi } from '../../services/api';
import RichTextEditor from '../RichTextEditor';
export default function BookEditor() { export default function BookEditor() {
const { activeBook, updateChapterContent, setCurrentChapter } = useBookStore(); const { activeBook, updateChapterContent, setCurrentChapter } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [showAIHelp, setShowAIHelp] = useState(false); const [showAIHelp, setShowAIHelp] = useState(false);
const { t } = useTranslation();
if (!activeBook || !activeBook.outline) { if (!activeBook || !activeBook.outline) {
return ( return (
<div className="card text-center py-12"> <div className="card text-center py-12">
<p className="text-gray-500"> <p className="text-gray-500">
Generate an outline first to start writing your book. {t('bookEditor.noOutline')}
</p> </p>
</div> </div>
); );
@@ -51,6 +54,7 @@ export default function BookEditor() {
previousChapter?.content previousChapter?.content
); );
console.log('Chapter generation response:', response.data);
updateChapterContent(activeBook.currentChapter, response.data.content); updateChapterContent(activeBook.currentChapter, response.data.content);
} catch (error) { } catch (error) {
console.error('Failed to generate chapter:', error); console.error('Failed to generate chapter:', error);
@@ -69,7 +73,7 @@ export default function BookEditor() {
{/* Chapter Navigation */} {/* Chapter Navigation */}
<div className="space-y-4"> <div className="space-y-4">
<div className="card"> <div className="card">
<h3 className="font-semibold text-gray-900 mb-4">Chapters</h3> <h3 className="font-semibold text-gray-900 mb-4">{t('bookEditor.chapters')}</h3>
<div className="space-y-2"> <div className="space-y-2">
{activeBook.outline.chapters.map((chapter) => ( {activeBook.outline.chapters.map((chapter) => (
<button <button
@@ -88,7 +92,7 @@ export default function BookEditor() {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{chapter.title}</p> <p className="text-sm font-medium truncate">{chapter.title}</p>
{chapter.content && ( {chapter.content && (
<span className="text-xs text-green-600">✓ Written</span> <span className="text-xs text-green-600">✓ {t('bookEditor.written')}</span>
)} )}
</div> </div>
</div> </div>
@@ -99,7 +103,7 @@ export default function BookEditor() {
{/* Chapter Info */} {/* Chapter Info */}
<div className="card"> <div className="card">
<h4 className="font-medium text-gray-900 mb-2">Chapter Summary</h4> <h4 className="font-medium text-gray-900 mb-2">{t('bookEditor.chapterSummary')}</h4>
<p className="text-sm text-gray-600">{currentChapter?.summary}</p> <p className="text-sm text-gray-600">{currentChapter?.summary}</p>
</div> </div>
</div> </div>
@@ -122,7 +126,7 @@ export default function BookEditor() {
{currentChapter?.title} {currentChapter?.title}
</h2> </h2>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Chapter {activeBook.currentChapter} of {activeBook.outline.chapters.length} {t('bookEditor.chapterOf', { current: activeBook.currentChapter, total: activeBook.outline.chapters.length })}
</p> </p>
</div> </div>
<button <button
@@ -140,7 +144,7 @@ export default function BookEditor() {
className="btn-secondary flex items-center gap-2" className="btn-secondary flex items-center gap-2"
> >
<Sparkles className="w-4 h-4" /> <Sparkles className="w-4 h-4" />
AI Assist {t('bookEditor.aiAssist')}
</button> </button>
<button <button
onClick={handleGenerateChapter} onClick={handleGenerateChapter}
@@ -148,7 +152,7 @@ export default function BookEditor() {
className="btn-primary flex items-center gap-2" className="btn-primary flex items-center gap-2"
> >
<Wand2 className="w-4 h-4" /> <Wand2 className="w-4 h-4" />
{isGenerating ? 'Generating...' : 'Generate Chapter'} {isGenerating ? t('bookEditor.generating') : t('bookEditor.generateChapter')}
</button> </button>
</div> </div>
</div> </div>
@@ -156,22 +160,22 @@ export default function BookEditor() {
{/* AI Help Panel */} {/* AI Help Panel */}
{showAIHelp && ( {showAIHelp && (
<div className="mb-6 bg-gradient-to-r from-accent-50 to-primary-50 rounded-lg p-4 border border-accent-200"> <div className="mb-6 bg-gradient-to-r from-accent-50 to-primary-50 rounded-lg p-4 border border-accent-200">
<h4 className="font-medium text-gray-900 mb-3">AI Writing Assistant</h4> <h4 className="font-medium text-gray-900 mb-3">{t('bookEditor.aiWritingAssistant')}</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<button <button
onClick={handleExpandText} onClick={handleExpandText}
className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors" className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors"
> >
✨ Expand this section ✨ {t('bookEditor.expandSection')}
</button> </button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors"> <button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
📝 Improve prose 📝 {t('bookEditor.improveProse')}
</button> </button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors"> <button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
💡 Add description 💡 {t('bookEditor.addDescription')}
</button> </button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors"> <button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
🔄 Rewrite paragraph 🔄 {t('bookEditor.rewriteParagraph')}
</button> </button>
</div> </div>
</div> </div>
@@ -179,24 +183,13 @@ export default function BookEditor() {
{/* Editor */} {/* Editor */}
<div className="min-h-[500px]"> <div className="min-h-[500px]">
{currentChapter?.content ? ( <RichTextEditor
<textarea content={currentChapter?.content || ''}
value={currentChapter.content} onChange={(html) =>
onChange={(e) => updateChapterContent(activeBook.currentChapter, html)
updateChapterContent(activeBook.currentChapter, e.target.value)
} }
className="w-full h-[600px] p-6 font-serif text-lg leading-relaxed border-0 focus:ring-0 resize-y outline-none" placeholder={t('bookEditor.editorPlaceholder')}
placeholder="Start writing or use AI to generate this chapter..."
/> />
) : (
<div className="h-[500px] flex items-center justify-center text-gray-400">
<div className="text-center">
<Wand2 className="w-12 h-12 mx-auto mb-3 opacity-50" />
<p>Click "Generate Chapter" to create content with AI</p>
<p className="text-sm mt-1">Or start writing manually</p>
</div>
</div>
)}
</div> </div>
{/* Word Count */} {/* Word Count */}
@@ -205,10 +198,10 @@ export default function BookEditor() {
{currentChapter?.content {currentChapter?.content
? currentChapter.content.split(/\s+/).length ? currentChapter.content.split(/\s+/).length
: 0}{' '} : 0}{' '}
words {t('bookEditor.words')}
</span> </span>
<span> <span>
Last updated:{' '} {t('bookEditor.lastUpdated')}{' '}
{new Date(activeBook.updatedAt).toLocaleDateString()} {new Date(activeBook.updatedAt).toLocaleDateString()}
</span> </span>
</div> </div>
@@ -1,5 +1,7 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { BookOpen, Wand2, Edit3, Users, FileText } from 'lucide-react'; import { BookOpen, Wand2, Edit3, Users, FileText } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import GenreSelector from './GenreSelector'; import GenreSelector from './GenreSelector';
import BookOutlineGenerator from './BookOutlineGenerator'; import BookOutlineGenerator from './BookOutlineGenerator';
import BookEditor from './BookEditor'; import BookEditor from './BookEditor';
@@ -10,6 +12,31 @@ import { useQuery } from '@tanstack/react-query';
export default function BookGenerator() { export default function BookGenerator() {
const [activeTab, setActiveTab] = useState<'genre' | 'outline' | 'editor' | 'characters'>('genre'); const [activeTab, setActiveTab] = useState<'genre' | 'outline' | 'editor' | 'characters'>('genre');
const { activeBook, setActiveBook, setOutline } = useBookStore(); const { activeBook, setActiveBook, setOutline } = useBookStore();
const location = useLocation();
const { t } = useTranslation();
// Auto-initialize from onboarding page state
useEffect(() => {
const state = location.state as { autoBook?: { idea: string; genre: string; useAI: boolean } } | null;
if (state?.autoBook) {
const { idea, genre, useAI } = state.autoBook;
const newBook: Book = {
id: `book-${Date.now()}`,
title: idea.substring(0, 50) || 'Untitled',
genre,
idea,
outline: null,
characters: [],
currentChapter: 1,
createdAt: new Date(),
updatedAt: new Date(),
};
setActiveBook(newBook);
setActiveTab(useAI ? 'outline' : 'editor');
// Clear the state so refreshing doesn't re-trigger
window.history.replaceState({}, document.title);
}
}, []);
const { data: genresData } = useQuery({ const { data: genresData } = useQuery({
queryKey: ['genres'], queryKey: ['genres'],
@@ -46,9 +73,9 @@ export default function BookGenerator() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-2xl font-bold text-gray-900">Book Generator</h2> <h2 className="text-2xl font-bold text-gray-900">{t('bookGenerator.title')}</h2>
<p className="text-gray-500 mt-1"> <p className="text-gray-500 mt-1">
Generate book ideas and write with AI assistance {t('bookGenerator.subtitle')}
</p> </p>
</div> </div>
@@ -64,7 +91,7 @@ export default function BookGenerator() {
disabled={!activeBook && activeTab !== 'genre'} disabled={!activeBook && activeTab !== 'genre'}
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
<span className="hidden sm:inline">Genre</span> <span className="hidden sm:inline">{t('bookGenerator.tabGenre')}</span>
</button> </button>
<button <button
onClick={() => setActiveTab('outline')} onClick={() => setActiveTab('outline')}
@@ -76,7 +103,7 @@ export default function BookGenerator() {
}`} }`}
> >
<FileText className="w-4 h-4" /> <FileText className="w-4 h-4" />
<span className="hidden sm:inline">Outline</span> <span className="hidden sm:inline">{t('bookGenerator.tabOutline')}</span>
</button> </button>
<button <button
onClick={() => setActiveTab('editor')} onClick={() => setActiveTab('editor')}
@@ -88,7 +115,7 @@ export default function BookGenerator() {
}`} }`}
> >
<Edit3 className="w-4 h-4" /> <Edit3 className="w-4 h-4" />
<span className="hidden sm:inline">Write</span> <span className="hidden sm:inline">{t('bookGenerator.tabWrite')}</span>
</button> </button>
<button <button
onClick={() => setActiveTab('characters')} onClick={() => setActiveTab('characters')}
@@ -100,7 +127,7 @@ export default function BookGenerator() {
}`} }`}
> >
<Users className="w-4 h-4" /> <Users className="w-4 h-4" />
<span className="hidden sm:inline">Characters</span> <span className="hidden sm:inline">{t('bookGenerator.tabCharacters')}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -133,6 +160,7 @@ export default function BookGenerator() {
function CharacterGenerator() { function CharacterGenerator() {
const { activeBook, addCharacter } = useBookStore(); const { activeBook, addCharacter } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const { t } = useTranslation();
const handleGenerateCharacters = async () => { const handleGenerateCharacters = async () => {
if (!activeBook) return; if (!activeBook) return;
@@ -166,7 +194,7 @@ function CharacterGenerator() {
<div className="card"> <div className="card">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-gray-900"> <h3 className="text-lg font-semibold text-gray-900">
Character Development {t('characters.title')}
</h3> </h3>
<button <button
onClick={handleGenerateCharacters} onClick={handleGenerateCharacters}
@@ -174,7 +202,7 @@ function CharacterGenerator() {
className="btn-primary flex items-center gap-2" className="btn-primary flex items-center gap-2"
> >
<Wand2 className="w-4 h-4" /> <Wand2 className="w-4 h-4" />
{isGenerating ? 'Generating...' : 'Generate Characters'} {isGenerating ? t('characters.generating') : t('characters.generate')}
</button> </button>
</div> </div>
@@ -182,7 +210,7 @@ function CharacterGenerator() {
<div className="text-center py-8"> <div className="text-center py-8">
<Users className="w-12 h-12 text-gray-300 mx-auto mb-3" /> <Users className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500"> <p className="text-gray-500">
No characters yet. Click "Generate Characters" to create AI-suggested characters. {t('characters.empty')}
</p> </p>
</div> </div>
) : ( ) : (
@@ -206,11 +234,11 @@ function CharacterGenerator() {
</div> </div>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div> <div>
<span className="text-gray-500">Traits:</span> <span className="text-gray-500">{t('characters.traits')}:</span>
<p className="text-gray-700">{character.traits?.join(', ') || 'N/A'}</p> <p className="text-gray-700">{character.traits?.join(', ') || 'N/A'}</p>
</div> </div>
<div> <div>
<span className="text-gray-500">Motivation:</span> <span className="text-gray-500">{t('characters.motivation')}:</span>
<p className="text-gray-700">{character.motivation || 'N/A'}</p> <p className="text-gray-700">{character.motivation || 'N/A'}</p>
</div> </div>
</div> </div>
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Wand2, CheckCircle, Loader2 } from 'lucide-react'; import { Wand2, CheckCircle, Loader2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Book, useBookStore } from '../../stores/bookStore'; import { Book, useBookStore } from '../../stores/bookStore';
import { booksApi } from '../../services/api'; import { booksApi } from '../../services/api';
@@ -12,6 +13,7 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
const { activeBook } = useBookStore(); const { activeBook } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const { t } = useTranslation();
const outline = activeBook?.outline; const outline = activeBook?.outline;
@@ -22,9 +24,12 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
try { try {
const response = await booksApi.generateOutline(book.genre, book.idea, book.title); const response = await booksApi.generateOutline(book.genre, book.idea, book.title);
const generatedOutline = response.data.outline; const generatedOutline = response.data.outline;
console.log('Generated outline:', generatedOutline);
console.log('Outline chapters:', generatedOutline?.chapters);
onComplete(generatedOutline); onComplete(generatedOutline);
} catch (err: any) { } catch (err: any) {
setError(err.response?.data?.error || 'Failed to generate outline'); console.error('Outline generation error:', err);
setError(err.response?.data?.error || t('bookOutline.failedToGenerate'));
} finally { } finally {
setIsGenerating(false); setIsGenerating(false);
} }
@@ -36,8 +41,8 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
<div className="card bg-gradient-to-r from-primary-50 to-accent-50"> <div className="card bg-gradient-to-r from-primary-50 to-accent-50">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>
<h3 className="font-semibold text-gray-900">{book.title || 'Untitled Book'}</h3> <h3 className="font-semibold text-gray-900">{book.title || t('bookOutline.untitledBook')}</h3>
<p className="text-sm text-gray-600 mt-1 capitalize">Genre: {book.genre}</p> <p className="text-sm text-gray-600 mt-1 capitalize">{t('bookOutline.genre')}: {book.genre}</p>
<p className="text-sm text-gray-500 mt-2 line-clamp-2">{book.idea}</p> <p className="text-sm text-gray-500 mt-2 line-clamp-2">{book.idea}</p>
</div> </div>
{!outline && ( {!outline && (
@@ -49,12 +54,12 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{isGenerating ? ( {isGenerating ? (
<> <>
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />
Generating... {t('bookOutline.generatingOutline')}
</> </>
) : ( ) : (
<> <>
<Wand2 className="w-4 h-4" /> <Wand2 className="w-4 h-4" />
Generate Outline {t('bookOutline.generateOutline')}
</> </>
)} )}
</button> </button>
@@ -72,22 +77,37 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{/* Outline Display */} {/* Outline Display */}
{outline && ( {outline && (
<div className="card"> <div className="card">
{outline.error ? (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<h4 className="text-red-800 font-medium mb-2">Generation Error</h4>
<p className="text-red-700 text-sm">{outline.error}</p>
{outline.raw && (
<details className="mt-2">
<summary className="text-red-600 cursor-pointer">Raw AI Response</summary>
<pre className="text-xs text-gray-600 mt-2 whitespace-pre-wrap bg-gray-100 p-2 rounded">
{outline.raw}
</pre>
</details>
)}
</div>
) : (
<>
<div className="flex items-center gap-2 mb-6"> <div className="flex items-center gap-2 mb-6">
<CheckCircle className="w-5 h-5 text-green-500" /> <CheckCircle className="w-5 h-5 text-green-500" />
<h3 className="text-lg font-semibold text-gray-900"> <h3 className="text-lg font-semibold text-gray-900">
Generated Outline: {outline.title || book.title} {t('bookOutline.generatedOutline')}: {outline.title || book.title}
</h3> </h3>
</div> </div>
{outline.logline && ( {outline.logline && (
<div className="mb-6 pb-6 border-b border-gray-200"> <div className="mb-6 pb-6 border-b border-gray-200">
<h4 className="text-sm font-medium text-gray-500 mb-2">Logline</h4> <h4 className="text-sm font-medium text-gray-500 mb-2">{t('bookOutline.logline')}</h4>
<p className="text-gray-900 italic">{outline.logline}</p> <p className="text-gray-900 italic">{outline.logline}</p>
</div> </div>
)} )}
<div> <div>
<h4 className="text-sm font-medium text-gray-500 mb-4">Chapter Outline</h4> <h4 className="text-sm font-medium text-gray-500 mb-4">{t('bookOutline.chapterOutline')}</h4>
<div className="space-y-3"> <div className="space-y-3">
{outline.chapters?.map((chapter: any, index: number) => ( {outline.chapters?.map((chapter: any, index: number) => (
<div <div
@@ -100,10 +120,10 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
</span> </span>
<div className="flex-1"> <div className="flex-1">
<h5 className="font-medium text-gray-900"> <h5 className="font-medium text-gray-900">
{chapter.title || `Chapter ${chapter.number || index + 1}`} {chapter.title || `${t('bookOutline.chapter')} ${chapter.number || index + 1}`}
</h5> </h5>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
{chapter.summary || 'No summary available'} {chapter.summary || t('bookOutline.noSummary')}
</p> </p>
</div> </div>
</div> </div>
@@ -114,9 +134,11 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
<div className="mt-6 flex items-center justify-end gap-3"> <div className="mt-6 flex items-center justify-end gap-3">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Ready to start writing! Navigate to the Write tab to begin. {t('bookOutline.readyToWrite')}
</p> </p>
</div> </div>
</>
)}
</div> </div>
)} )}
@@ -124,10 +146,9 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{!outline && !isGenerating && ( {!outline && !isGenerating && (
<div className="card text-center py-12"> <div className="card text-center py-12">
<Wand2 className="w-12 h-12 text-gray-300 mx-auto mb-4" /> <Wand2 className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900">Ready to Generate</h3> <h3 className="text-lg font-medium text-gray-900">{t('bookOutline.readyToGenerate')}</h3>
<p className="text-gray-500 mt-2 max-w-md mx-auto"> <p className="text-gray-500 mt-2 max-w-md mx-auto">
Click "Generate Outline" to create a detailed chapter outline based on your genre and book idea. {t('bookOutline.readyToGenerateDesc')}
The AI will create a structure following genre-specific patterns.
</p> </p>
</div> </div>
)} )}
@@ -1,4 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BookOpen, Sparkles } from 'lucide-react'; import { BookOpen, Sparkles } from 'lucide-react';
interface Genre { interface Genre {
@@ -14,6 +15,7 @@ interface GenreSelectorProps {
} }
export default function GenreSelector({ genres, onSelect }: GenreSelectorProps) { export default function GenreSelector({ genres, onSelect }: GenreSelectorProps) {
const { t } = useTranslation();
const [selectedGenre, setSelectedGenre] = useState<string>(''); const [selectedGenre, setSelectedGenre] = useState<string>('');
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [idea, setIdea] = useState(''); const [idea, setIdea] = useState('');
@@ -32,7 +34,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="card"> <div className="card">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<BookOpen className="w-5 h-5" /> <BookOpen className="w-5 h-5" />
Select Your Genre {t('genreSelector.selectGenre')}
</h3> </h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
@@ -47,9 +49,11 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
}`} }`}
> >
<span className="text-2xl mb-2 block">{genre.icon}</span> <span className="text-2xl mb-2 block">{genre.icon}</span>
<h4 className="font-medium text-gray-900">{genre.name}</h4> <h4 className="font-medium text-gray-900">
{t(`genres.${genre.id}.name`, genre.name)}
</h4>
<p className="text-xs text-gray-500 mt-1 line-clamp-2"> <p className="text-xs text-gray-500 mt-1 line-clamp-2">
{genre.description} {t(`genres.${genre.id}.description`, genre.description)}
</p> </p>
</button> </button>
))} ))}
@@ -62,29 +66,29 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="card sticky top-6"> <div className="card sticky top-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<Sparkles className="w-5 h-5" /> <Sparkles className="w-5 h-5" />
Book Details {t('genreSelector.bookDetails')}
</h3> </h3>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="label">Book Title (optional)</label> <label className="label">{t('genreSelector.bookTitle')}</label>
<input <input
type="text" type="text"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
placeholder="My Amazing Book" placeholder={t('genreSelector.bookTitlePlaceholder')}
className="input" className="input"
/> />
</div> </div>
<div> <div>
<label className="label"> <label className="label">
Core Idea <span className="text-red-500">*</span> {t('genreSelector.coreIdea')} <span className="text-red-500">*</span>
</label> </label>
<textarea <textarea
value={idea} value={idea}
onChange={(e) => setIdea(e.target.value)} onChange={(e) => setIdea(e.target.value)}
placeholder="Describe your book idea... What's the story about? Who are the main characters? What conflict drives the narrative?" placeholder={t('genreSelector.coreIdeaPlaceholder')}
className="input min-h-[150px] resize-y" className="input min-h-[150px] resize-y"
required required
/> />
@@ -92,7 +96,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3"> <div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
<p className="text-xs text-amber-700"> <p className="text-xs text-amber-700">
<strong>Tip:</strong> The more details you provide, the better the AI can generate your outline and content. <strong>Tip:</strong> {t('genreSelector.tip')}
</p> </p>
</div> </div>
@@ -101,7 +105,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
disabled={!selectedGenre || !idea.trim()} disabled={!selectedGenre || !idea.trim()}
className="btn-primary w-full py-3" className="btn-primary w-full py-3"
> >
Generate Outline {t('genreSelector.generateOutline')}
</button> </button>
</form> </form>
</div> </div>
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Image, Wand2, Type } from 'lucide-react'; import { Image, Wand2, Type } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import CoverGallery from './CoverGallery'; import CoverGallery from './CoverGallery';
import CoverEditor from './CoverEditor'; import CoverEditor from './CoverEditor';
import { useCoverStore, Cover } from '../../stores/coverStore'; import { useCoverStore, Cover } from '../../stores/coverStore';
@@ -10,6 +11,8 @@ export default function CoverDesigner() {
const [activeTab, setActiveTab] = useState<'gallery' | 'editor' | 'ai'>('gallery'); const [activeTab, setActiveTab] = useState<'gallery' | 'editor' | 'ai'>('gallery');
const { activeCover, setActiveCover } = useCoverStore(); const { activeCover, setActiveCover } = useCoverStore();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t: tMain } = useTranslation();
const { t } = useTranslation();
const { data: coversData } = useQuery({ const { data: coversData } = useQuery({
queryKey: ['covers'], queryKey: ['covers'],
@@ -37,8 +40,8 @@ export default function CoverDesigner() {
const handleSelectCover = (coverUrl: string) => { const handleSelectCover = (coverUrl: string) => {
const newCover: Cover = { const newCover: Cover = {
id: `cover-${Date.now()}`, id: `cover-${Date.now()}`,
title: 'New Book', title: tMain('coverDesigner.newBookTitle'),
author: 'Author Name', author: tMain('coverDesigner.authorName'),
backgroundImage: coverUrl, backgroundImage: coverUrl,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
width: 1600, width: 1600,
@@ -55,9 +58,9 @@ export default function CoverDesigner() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-2xl font-bold text-gray-900">Cover Designer</h2> <h2 className="text-2xl font-bold text-gray-900">{tMain('coverDesigner.title')}</h2>
<p className="text-gray-500 mt-1"> <p className="text-gray-500 mt-1">
Design stunning book covers with AI or manual editing {tMain('coverDesigner.subtitle')}
</p> </p>
</div> </div>
@@ -72,7 +75,7 @@ export default function CoverDesigner() {
}`} }`}
> >
<Image className="w-4 h-4" /> <Image className="w-4 h-4" />
Gallery {tMain('coverDesigner.tabGallery')}
</button> </button>
<button <button
onClick={() => setActiveTab('editor')} onClick={() => setActiveTab('editor')}
@@ -83,7 +86,7 @@ export default function CoverDesigner() {
}`} }`}
> >
<Type className="w-4 h-4" /> <Type className="w-4 h-4" />
Editor {tMain('coverDesigner.tabEditor')}
</button> </button>
<button <button
onClick={() => setActiveTab('ai')} onClick={() => setActiveTab('ai')}
@@ -94,7 +97,7 @@ export default function CoverDesigner() {
}`} }`}
> >
<Wand2 className="w-4 h-4" /> <Wand2 className="w-4 h-4" />
AI Generate {tMain('coverDesigner.tabAIGenerate')}
</button> </button>
</div> </div>
</div> </div>
@@ -131,36 +134,37 @@ function AIGenerator({
}) { }) {
const [prompt, setPrompt] = useState(''); const [prompt, setPrompt] = useState('');
const [selectedStyle, setSelectedStyle] = useState('modern'); const [selectedStyle, setSelectedStyle] = useState('modern');
const { t } = useTranslation();
const styles = [ const styles = [
{ id: 'modern', name: 'Modern', icon: '✨' }, { id: 'modern', name: t('coverDesigner.aiGenerator.modern'), icon: '✨' },
{ id: 'minimal', name: 'Minimalist', icon: '🎯' }, { id: 'minimalist', name: t('coverDesigner.aiGenerator.minimalist'), icon: '🎯' },
{ id: 'vintage', name: 'Vintage', icon: '📜' }, { id: 'vintage', name: t('coverDesigner.aiGenerator.vintage'), icon: '📜' },
{ id: 'bold', name: 'Bold', icon: '🔥' }, { id: 'bold', name: t('coverDesigner.aiGenerator.bold'), icon: '🔥' },
{ id: 'elegant', name: 'Elegant', icon: '💎' }, { id: 'elegant', name: t('coverDesigner.aiGenerator.elegant'), icon: '💎' },
{ id: 'playful', name: 'Playful', icon: '🎨' }, { id: 'playful', name: t('coverDesigner.aiGenerator.playful'), icon: '🎨' },
]; ];
const genrePresets = [ const genrePresets = [
{ {
genre: 'Mystery', genre: t('coverDesigner.aiGenerator.mystery'),
prompt: 'A mysterious book cover with dark shadows, silhouette of a detective, foggy street scene, noir atmosphere', prompt: t('coverDesigner.aiGenerator.mysteryPrompt'),
}, },
{ {
genre: 'Romance', genre: t('coverDesigner.aiGenerator.romance'),
prompt: 'A romantic book cover with soft pastel colors, couple silhouette at sunset, dreamy and emotional', prompt: t('coverDesigner.aiGenerator.romancePrompt'),
}, },
{ {
genre: 'Fantasy', genre: t('coverDesigner.aiGenerator.fantasy'),
prompt: 'An epic fantasy book cover with magical elements, dragon, castle in the background, mystical glowing effects', prompt: t('coverDesigner.aiGenerator.fantasyPrompt'),
}, },
{ {
genre: 'Sci-Fi', genre: t('coverDesigner.aiGenerator.scifi'),
prompt: 'A futuristic sci-fi book cover with spaceships, neon lights, cyberpunk city, high-tech atmosphere', prompt: t('coverDesigner.aiGenerator.scifiPrompt'),
}, },
{ {
genre: 'Self-Help', genre: t('coverDesigner.aiGenerator.selfhelp'),
prompt: 'A clean self-help book cover with inspiring imagery, mountain peak or sunrise, professional and motivating', prompt: t('coverDesigner.aiGenerator.selfhelpPrompt'),
}, },
]; ];
@@ -179,12 +183,12 @@ function AIGenerator({
<div className="card space-y-6"> <div className="card space-y-6">
<div> <div>
<h3 className="text-lg font-semibold text-gray-900 mb-4"> <h3 className="text-lg font-semibold text-gray-900 mb-4">
AI Cover Generator {t('coverDesigner.aiGenerator.title')}
</h3> </h3>
{/* Style Selection */} {/* Style Selection */}
<div className="mb-6"> <div className="mb-6">
<label className="label">Select Style</label> <label className="label">{t('coverDesigner.aiGenerator.selectStyle')}</label>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2 mt-2"> <div className="grid grid-cols-3 sm:grid-cols-6 gap-2 mt-2">
{styles.map((style) => ( {styles.map((style) => (
<button <button
@@ -205,18 +209,18 @@ function AIGenerator({
{/* Prompt Input */} {/* Prompt Input */}
<div className="mb-6"> <div className="mb-6">
<label className="label">Describe Your Cover</label> <label className="label">{t('coverDesigner.aiGenerator.describeCover')}</label>
<textarea <textarea
value={prompt} value={prompt}
onChange={(e) => setPrompt(e.target.value)} onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe the book cover you want to generate... e.g., 'A mysterious forest with glowing eyes in the darkness'" placeholder={t('coverDesigner.aiGenerator.coverPlaceholder')}
className="input min-h-[120px] resize-y" className="input min-h-[120px] resize-y"
/> />
</div> </div>
{/* Genre Presets */} {/* Genre Presets */}
<div className="mb-6"> <div className="mb-6">
<label className="label">Quick Presets by Genre</label> <label className="label">{t('coverDesigner.aiGenerator.quickPresets')}</label>
<div className="grid gap-2 mt-2"> <div className="grid gap-2 mt-2">
{genrePresets.map((preset) => ( {genrePresets.map((preset) => (
<button <button
@@ -238,7 +242,7 @@ function AIGenerator({
className="btn-primary w-full py-3 flex items-center justify-center gap-2" className="btn-primary w-full py-3 flex items-center justify-center gap-2"
> >
<Wand2 className="w-5 h-5" /> <Wand2 className="w-5 h-5" />
{isGenerating ? 'Generating...' : 'Generate Cover'} {isGenerating ? t('coverDesigner.aiGenerator.generating') : t('coverDesigner.aiGenerator.generateCover')}
</button> </button>
</div> </div>
</div> </div>
@@ -3,6 +3,7 @@ import { Stage, Layer, Rect, Text, Transformer, Image as KonvaImage } from 'reac
import type Konva from 'konva'; import type Konva from 'konva';
import { useCoverStore } from '../../stores/coverStore'; import { useCoverStore } from '../../stores/coverStore';
import { Type, Download } from 'lucide-react'; import { Type, Download } from 'lucide-react';
import { useTranslation } from 'react-i18next';
const BackgroundImage = ({ url, width, height }: { url: string; width: number; height: number }) => { const BackgroundImage = ({ url, width, height }: { url: string; width: number; height: number }) => {
const [image, setImage] = useState<HTMLImageElement | null>(null); const [image, setImage] = useState<HTMLImageElement | null>(null);
@@ -21,13 +22,13 @@ const BackgroundImage = ({ url, width, height }: { url: string; width: number; h
}; };
const FONTS = [ const FONTS = [
{ name: 'Roboto', label: 'Modern (Roboto)' }, { name: 'Roboto', labelKey: 'coverDesigner.modernFont' },
{ name: 'Montserrat', label: 'Minimalist (Montserrat)' }, { name: 'Montserrat', labelKey: 'coverDesigner.minimalistFont' },
{ name: 'Playfair Display', label: 'Elegant (Playfair Display)' }, { name: 'Playfair Display', labelKey: 'coverDesigner.elegantFont' },
{ name: 'Bebas Neue', label: 'Bold (Bebas Neue)' }, { name: 'Bebas Neue', labelKey: 'coverDesigner.boldFont' },
{ name: 'Pacifico', label: 'Playful (Pacifico)' }, { name: 'Pacifico', labelKey: 'coverDesigner.playfulFont' },
{ name: 'Georgia', label: 'Classic (Georgia)' }, { name: 'Georgia', labelKey: 'coverDesigner.classicFont' },
{ name: 'Arial', label: 'Standard (Arial)' }, { name: 'Arial', labelKey: 'coverDesigner.standardFont' },
]; ];
export default function CoverEditor() { export default function CoverEditor() {
@@ -37,11 +38,12 @@ export default function CoverEditor() {
const [textTool, setTextTool] = useState<'title' | 'author'>('title'); const [textTool, setTextTool] = useState<'title' | 'author'>('title');
const [newText, setNewText] = useState(''); const [newText, setNewText] = useState('');
const [selectedFont, setSelectedFont] = useState('Roboto'); const [selectedFont, setSelectedFont] = useState('Roboto');
const { t } = useTranslation();
if (!activeCover) { if (!activeCover) {
return ( return (
<div className="card text-center py-12"> <div className="card text-center py-12">
<p className="text-gray-500">Select a cover from the gallery to edit</p> <p className="text-gray-500">{t('coverDesigner.selectCover')}</p>
</div> </div>
); );
} }
@@ -86,7 +88,7 @@ export default function CoverEditor() {
<div className="lg:col-span-3 card p-4 bg-gray-100"> <div className="lg:col-span-3 card p-4 bg-gray-100">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-gray-900"> <h3 className="font-semibold text-gray-900">
Editing: {activeCover.title} {t('coverDesigner.editing')}: {activeCover.title}
</h3> </h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
@@ -94,7 +96,7 @@ export default function CoverEditor() {
className="btn-primary flex items-center gap-2" className="btn-primary flex items-center gap-2"
> >
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
Export {t('coverDesigner.export')}
</button> </button>
</div> </div>
</div> </div>
@@ -185,12 +187,12 @@ export default function CoverEditor() {
<div className="card"> <div className="card">
<h4 className="font-medium text-gray-900 mb-4 flex items-center gap-2"> <h4 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Type className="w-4 h-4" /> <Type className="w-4 h-4" />
Add Text {t('coverDesigner.addText')}
</h4> </h4>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<label className="label">Text Type</label> <label className="label">{t('coverDesigner.textType')}</label>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => setTextTool('title')} onClick={() => setTextTool('title')}
@@ -200,7 +202,7 @@ export default function CoverEditor() {
: 'bg-gray-100 text-gray-600 hover:bg-gray-200' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`} }`}
> >
Title {t('coverDesigner.titleText')}
</button> </button>
<button <button
onClick={() => setTextTool('author')} onClick={() => setTextTool('author')}
@@ -210,13 +212,13 @@ export default function CoverEditor() {
: 'bg-gray-100 text-gray-600 hover:bg-gray-200' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`} }`}
> >
Author {t('coverDesigner.authorText')}
</button> </button>
</div> </div>
</div> </div>
<div> <div>
<label className="label">Font</label> <label className="label">{t('coverDesigner.font')}</label>
<select <select
value={selectedFont} value={selectedFont}
onChange={(e) => setSelectedFont(e.target.value)} onChange={(e) => setSelectedFont(e.target.value)}
@@ -224,19 +226,19 @@ export default function CoverEditor() {
> >
{FONTS.map((font) => ( {FONTS.map((font) => (
<option key={font.name} value={font.name}> <option key={font.name} value={font.name}>
{font.label} {t(font.labelKey)}
</option> </option>
))} ))}
</select> </select>
</div> </div>
<div> <div>
<label className="label">Text Content</label> <label className="label">{t('coverDesigner.textContent')}</label>
<input <input
type="text" type="text"
value={newText} value={newText}
onChange={(e) => setNewText(e.target.value)} onChange={(e) => setNewText(e.target.value)}
placeholder={textTool === 'title' ? 'Book Title' : 'Author Name'} placeholder={textTool === 'title' ? t('coverDesigner.bookTitlePlaceholder') : t('coverDesigner.authorNamePlaceholder')}
className="input" className="input"
onKeyPress={(e) => e.key === 'Enter' && handleAddText()} onKeyPress={(e) => e.key === 'Enter' && handleAddText()}
/> />
@@ -247,7 +249,7 @@ export default function CoverEditor() {
disabled={!newText.trim()} disabled={!newText.trim()}
className="btn-primary w-full" className="btn-primary w-full"
> >
Add Text Layer {t('coverDesigner.addTextLayer')}
</button> </button>
</div> </div>
</div> </div>
@@ -269,7 +271,7 @@ export default function CoverEditor() {
> >
{FONTS.map((font) => ( {FONTS.map((font) => (
<option key={font.name} value={font.name}> <option key={font.name} value={font.name}>
{font.label} {t(font.labelKey)}
</option> </option>
))} ))}
</select> </select>
@@ -1,4 +1,5 @@
import { Image, Eye, Trash2, Download } from 'lucide-react'; import { Image, Eye, Trash2, Download } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface CoverData { interface CoverData {
id: string; id: string;
@@ -14,13 +15,15 @@ interface CoverGalleryProps {
} }
export default function CoverGallery({ covers, onSelect, onDelete }: CoverGalleryProps) { export default function CoverGallery({ covers, onSelect, onDelete }: CoverGalleryProps) {
const { t } = useTranslation();
if (covers.length === 0) { if (covers.length === 0) {
return ( return (
<div className="card text-center py-12"> <div className="card text-center py-12">
<Image className="w-16 h-16 text-gray-300 mx-auto mb-4" /> <Image className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900">No covers yet</h3> <h3 className="text-lg font-medium text-gray-900">{t('coverDesigner.noCovers')}</h3>
<p className="text-gray-500 mt-2"> <p className="text-gray-500 mt-2">
Upload a cover image or generate one with AI to get started {t('coverDesigner.noCoversDesc')}
</p> </p>
</div> </div>
); );
@@ -43,7 +46,7 @@ export default function CoverGallery({ covers, onSelect, onDelete }: CoverGaller
<button <button
onClick={() => onSelect(cover.url)} onClick={() => onSelect(cover.url)}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors" className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title="Edit" title={t('coverDesigner.edit')}
> >
<Eye className="w-5 h-5 text-gray-700" /> <Eye className="w-5 h-5 text-gray-700" />
</button> </button>
@@ -55,14 +58,14 @@ export default function CoverGallery({ covers, onSelect, onDelete }: CoverGaller
link.click(); link.click();
}} }}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors" className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title="Download" title={t('coverDesigner.download')}
> >
<Download className="w-5 h-5 text-gray-700" /> <Download className="w-5 h-5 text-gray-700" />
</button> </button>
<button <button
onClick={() => onDelete(cover.id)} onClick={() => onDelete(cover.id)}
className="p-2 bg-red-500 rounded-lg hover:bg-red-600 transition-colors" className="p-2 bg-red-500 rounded-lg hover:bg-red-600 transition-colors"
title="Delete" title={t('coverDesigner.delete')}
> >
<Trash2 className="w-5 h-5 text-white" /> <Trash2 className="w-5 h-5 text-white" />
</button> </button>
+68
View File
@@ -35,3 +35,71 @@ body {
@apply block text-sm font-medium text-gray-700 mb-1; @apply block text-sm font-medium text-gray-700 mb-1;
} }
} }
/* TipTap WYSIWYG Editor Styles */
.tiptap {
min-height: 500px;
}
.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
/* Book chapter formatting */
.tiptap p {
text-indent: 2rem;
margin-bottom: 1.5rem;
line-height: 1.8;
}
.tiptap p:first-child {
text-indent: 0;
}
.tiptap h1, .tiptap h2, .tiptap h3 {
text-align: center;
text-indent: 0;
margin-top: 2rem;
margin-bottom: 1rem;
}
.tiptap blockquote {
text-align: left;
text-indent: 0;
margin-left: 2rem;
margin-right: 2rem;
}
.tiptap ul, .tiptap ol {
text-align: left;
text-indent: 0;
margin-left: 2rem;
}
.tiptap h1 { font-size: 2em; font-weight: 700; margin: 0.67em 0; }
.tiptap h2 { font-size: 1.5em; font-weight: 600; margin: 0.75em 0; }
.tiptap h3 { font-size: 1.25em; font-weight: 600; margin: 0.83em 0; }
.tiptap ul { list-style: disc; padding-left: 1.5em; margin: 0.5em 0; }
.tiptap ol { list-style: decimal; padding-left: 1.5em; margin: 0.5em 0; }
.tiptap li { margin: 0.25em 0; }
.tiptap blockquote {
border-left: 3px solid #e5e7eb;
padding-left: 1em;
margin: 1em 0;
color: #6b7280;
font-style: italic;
}
.tiptap hr {
border: none;
border-top: 2px solid #e5e7eb;
margin: 1.5em 0;
}
.tiptap p { margin: 0.5em 0; }
+143
View File
@@ -24,5 +24,148 @@
"languageSelect": "Select your preferred language", "languageSelect": "Select your preferred language",
"english": "English", "english": "English",
"spanish": "Español" "spanish": "Español"
},
"onboarding": {
"title": "Welcome to CreaBook",
"subtitle": "Let's personalize your creative journey.",
"step1": "1. Select your language",
"step2": "2. What kind of book do you want to create?",
"ideaPlaceholder": "E.g., A sci-fi thriller about a detective on Mars...",
"genreLabel": "Genre (optional)",
"step3": "3. How do you want to start?",
"btnFree": "Move Freely (Blank Canvas)",
"btnAI": "Use AI to Generate Outline"
},
"genres": {
"fiction": { "name": "Fiction", "description": "General literary fiction with focus on character development and narrative" },
"mystery": { "name": "Mystery", "description": "Puzzle-driven narratives with clues, suspects, and a satisfying reveal" },
"romance": { "name": "Romance", "description": "Love-centered stories with emotional intimacy and satisfying relationship resolution" },
"scifi": { "name": "Science Fiction", "description": "Speculative fiction exploring technology, space, time, and their impact on humanity" },
"fantasy": { "name": "Fantasy", "description": "Magical worlds with supernatural elements, quests, and epic stakes" },
"horror": { "name": "Horror", "description": "Fear-driven narratives designed to unsettle, frighten, and provoke dread" },
"thriller": { "name": "Thriller", "description": "High-stakes, fast-paced narratives with constant tension and danger" },
"children": { "name": "Children's Book", "description": "Age-appropriate stories with clear morals, simple language, and engaging characters" },
"nonfiction": { "name": "Non-Fiction", "description": "Factual, informative content organized around a central topic or argument" },
"selfhelp": { "name": "Self-Help", "description": "Practical guidance for personal improvement and growth" },
"business": { "name": "Business", "description": "Professional insights, strategies, and case studies for business success" },
"memoir": { "name": "Memoir", "description": "Personal life stories focused on transformation and universal themes" }
},
"genreSelector": {
"selectGenre": "Select Your Genre",
"bookDetails": "Book Details",
"bookTitle": "Book Title (optional)",
"bookTitlePlaceholder": "My Amazing Book",
"coreIdea": "Core Idea",
"coreIdeaPlaceholder": "Describe your book idea... What's the story about? Who are the main characters? What conflict drives the narrative?",
"tip": "The more details you provide, the better the AI can generate your outline and content.",
"generateOutline": "Generate Outline"
},
"bookGenerator": {
"title": "Book Generator",
"subtitle": "Generate book ideas and write with AI assistance",
"tabGenre": "Genre",
"tabOutline": "Outline",
"tabWrite": "Write",
"tabCharacters": "Characters"
},
"bookEditor": {
"chapters": "Chapters",
"written": "Written",
"chapterSummary": "Chapter Summary",
"chapterOf": "Chapter {{current}} of {{total}}",
"aiAssist": "AI Assist",
"generating": "Generating...",
"generateChapter": "Generate Chapter",
"aiWritingAssistant": "AI Writing Assistant",
"expandSection": "Expand this section",
"improveProse": "Improve prose",
"addDescription": "Add description",
"rewriteParagraph": "Rewrite paragraph",
"words": "words",
"lastUpdated": "Last updated",
"noOutline": "Generate an outline first to start writing your book.",
"editorPlaceholder": "Start writing or use AI to generate this chapter...",
"written": "Written"
},
"bookOutline": {
"untitledBook": "Untitled Book",
"genre": "Genre",
"generatingOutline": "Generating...",
"generateOutline": "Generate Outline",
"generatedOutline": "Generated Outline",
"logline": "Logline",
"chapterOutline": "Chapter Outline",
"chapter": "Chapter",
"noSummary": "No summary available",
"readyToWrite": "Ready to start writing! Navigate to the Write tab to begin.",
"readyToGenerate": "Ready to Generate",
"readyToGenerateDesc": "Click \"Generate Outline\" to create a detailed chapter outline based on your genre and book idea. The AI will create a structure following genre-specific patterns.",
"failedToGenerate": "Failed to generate outline"
},
"characters": {
"title": "Character Development",
"generating": "Generating...",
"generate": "Generate Characters",
"empty": "No characters yet. Click \"Generate Characters\" to create AI-suggested characters.",
"traits": "Traits",
"motivation": "Motivation"
},
"coverDesigner": {
"title": "Cover Designer",
"subtitle": "Design stunning book covers with AI or manual editing",
"tabGallery": "Gallery",
"tabEditor": "Editor",
"tabAIGenerate": "AI Generate",
"newBookTitle": "New Book",
"authorName": "Author Name",
"noCovers": "No covers yet",
"noCoversDesc": "Upload a cover image or generate one with AI to get started",
"edit": "Edit",
"delete": "Delete",
"download": "Download",
"selectCover": "Select a cover from the gallery to edit",
"editing": "Editing",
"export": "Export",
"addText": "Add Text",
"textType": "Text Type",
"titleText": "Title",
"authorText": "Author",
"font": "Font",
"modernFont": "Modern (Roboto)",
"minimalistFont": "Minimalist (Montserrat)",
"elegantFont": "Elegant (Playfair Display)",
"boldFont": "Bold (Bebas Neue)",
"playfulFont": "Playful (Pacifico)",
"classicFont": "Classic (Georgia)",
"standardFont": "Standard (Arial)",
"textContent": "Text Content",
"bookTitlePlaceholder": "Book Title",
"authorNamePlaceholder": "Author Name",
"addTextLayer": "Add Text Layer",
"aiGenerator": {
"title": "AI Cover Generator",
"selectStyle": "Select Style",
"modern": "Modern",
"minimalist": "Minimalist",
"vintage": "Vintage",
"bold": "Bold",
"elegant": "Elegant",
"playful": "Playful",
"describeCover": "Describe Your Cover",
"coverPlaceholder": "Describe the book cover you want to generate... e.g., 'A mysterious forest with glowing eyes in the darkness'",
"quickPresets": "Quick Presets by Genre",
"mystery": "Mystery",
"mysteryPrompt": "A mysterious book cover with dark shadows, silhouette of a detective, foggy street scene, noir atmosphere",
"romance": "Romance",
"romancePrompt": "A romantic book cover with soft pastel colors, couple silhouette at sunset, dreamy and emotional",
"fantasy": "Fantasy",
"fantasyPrompt": "An epic fantasy book cover with magical elements, dragon, castle in the background, mystical glowing effects",
"scifi": "Sci-Fi",
"scifiPrompt": "A futuristic sci-fi book cover with spaceships, neon lights, cyberpunk city, high-tech atmosphere",
"selfhelp": "Self-Help",
"selfhelpPrompt": "A clean self-help book cover with inspiring imagery, mountain peak or sunrise, professional and motivating",
"generating": "Generating...",
"generateCover": "Generate Cover"
}
} }
} }
+143
View File
@@ -24,5 +24,148 @@
"languageSelect": "Selecciona tu idioma preferido", "languageSelect": "Selecciona tu idioma preferido",
"english": "Inglés", "english": "Inglés",
"spanish": "Español" "spanish": "Español"
},
"onboarding": {
"title": "Bienvenido a CreaBook",
"subtitle": "Vamos a personalizar tu viaje creativo.",
"step1": "1. Selecciona tu idioma",
"step2": "2. ¿Qué tipo de libro quieres crear?",
"ideaPlaceholder": "Ej. Un thriller de ciencia ficción sobre un detective en Marte...",
"genreLabel": "Género (opcional)",
"step3": "3. ¿Cómo quieres empezar?",
"btnFree": "Moverse libremente (Lienzo en blanco)",
"btnAI": "Usar IA para generar tu esquema"
},
"genres": {
"fiction": { "name": "Ficción", "description": "Ficción literaria general centrada en el desarrollo de personajes y la narrativa" },
"mystery": { "name": "Misterio", "description": "Narrativas basadas en enigmas con pistas, sospechosos y una revelación satisfactoria" },
"romance": { "name": "Romance", "description": "Historias centradas en el amor con intimidad emocional y una resolución satisfactoria" },
"scifi": { "name": "Ciencia Ficción", "description": "Ficción especulativa que explora la tecnología, el espacio, el tiempo y su impacto en la humanidad" },
"fantasy": { "name": "Fantasía", "description": "Mundos mágicos con elementos sobrenaturales, misiones y grandes desafíos" },
"horror": { "name": "Terror", "description": "Narrativas de miedo diseñadas para inquietar, asustar y provocar pavor" },
"thriller": { "name": "Thriller", "description": "Narrativas de alto riesgo y ritmo rápido con tensión y peligro constantes" },
"children": { "name": "Libro Infantil", "description": "Historias apropiadas para niños con moralejas claras, lenguaje sencillo y personajes atractivos" },
"nonfiction": { "name": "No Ficción", "description": "Contenido informativo y factual organizado en torno a un tema o argumento central" },
"selfhelp": { "name": "Autoayuda", "description": "Guía práctica para la mejora y el crecimiento personal" },
"business": { "name": "Negocios", "description": "Perspectivas profesionales, estrategias y casos de estudio para el éxito empresarial" },
"memoir": { "name": "Memorias", "description": "Historias de vida personales centradas en la transformación y temas universales" }
},
"genreSelector": {
"selectGenre": "Selecciona tu Género",
"bookDetails": "Detalles del Libro",
"bookTitle": "Título del Libro (opcional)",
"bookTitlePlaceholder": "Mi Libro Increíble",
"coreIdea": "Idea Principal",
"coreIdeaPlaceholder": "Describe tu idea para el libro... ¿De qué trata la historia? ¿Quiénes son los personajes principales? ¿Qué conflicto impulsa la narrativa?",
"tip": "Cuantos más detalles proporciones, mejor podrá la IA generar tu esquema y contenido.",
"generateOutline": "Generar Esquema"
},
"bookGenerator": {
"title": "Generador de Libros",
"subtitle": "Genera ideas de libros y escribe con asistencia de IA",
"tabGenre": "Género",
"tabOutline": "Esquema",
"tabWrite": "Escribir",
"tabCharacters": "Personajes"
},
"bookEditor": {
"chapters": "Capítulos",
"written": "Escrito",
"chapterSummary": "Resumen del Capítulo",
"chapterOf": "Capítulo {{current}} de {{total}}",
"aiAssist": "Asistente IA",
"generating": "Generando...",
"generateChapter": "Generar Capítulo",
"aiWritingAssistant": "Asistente de Escritura IA",
"expandSection": "Expandir esta sección",
"improveProse": "Mejorar prosa",
"addDescription": "Añadir descripción",
"rewriteParagraph": "Reescribir párrafo",
"words": "palabras",
"lastUpdated": "Última actualización",
"noOutline": "Genera un esquema primero para empezar a escribir tu libro.",
"editorPlaceholder": "Empieza a escribir o usa la IA para generar este capítulo...",
"written": "Escrito"
},
"bookOutline": {
"untitledBook": "Libro sin título",
"genre": "Género",
"generatingOutline": "Generando...",
"generateOutline": "Generar Esquema",
"generatedOutline": "Esquema Generado",
"logline": "Sinopsis",
"chapterOutline": "Esquema de Capítulos",
"chapter": "Capítulo",
"noSummary": "Sin resumen disponible",
"readyToWrite": "¡Listo para escribir! Navega a la pestaña Escribir para comenzar.",
"readyToGenerate": "Listo para Generar",
"readyToGenerateDesc": "Haz clic en \"Generar Esquema\" para crear un esquema detallado de capítulos basado en tu género e idea. La IA creará una estructura siguiendo patrones específicos del género.",
"failedToGenerate": "Error al generar el esquema"
},
"characters": {
"title": "Desarrollo de Personajes",
"generating": "Generando...",
"generate": "Generar Personajes",
"empty": "Aún no hay personajes. Haz clic en \"Generar Personajes\" para crear personajes sugeridos por la IA.",
"traits": "Rasgos",
"motivation": "Motivación"
},
"coverDesigner": {
"title": "Diseñador de Portadas",
"subtitle": "Diseña portadas de libros impresionantes con IA o edición manual",
"tabGallery": "Galería",
"tabEditor": "Editor",
"tabAIGenerate": "Generar con IA",
"newBookTitle": "Nuevo Libro",
"authorName": "Nombre del Autor",
"noCovers": "Aún no hay portadas",
"noCoversDesc": "Sube una imagen de portada o genera una con IA para comenzar",
"edit": "Editar",
"delete": "Eliminar",
"download": "Descargar",
"selectCover": "Selecciona una portada de la galería para editar",
"editing": "Editando",
"export": "Exportar",
"addText": "Añadir Texto",
"textType": "Tipo de Texto",
"titleText": "Título",
"authorText": "Autor",
"font": "Fuente",
"modernFont": "Moderno (Roboto)",
"minimalistFont": "Minimalista (Montserrat)",
"elegantFont": "Elegante (Playfair Display)",
"boldFont": "Audaz (Bebas Neue)",
"playfulFont": "Juguetón (Pacifico)",
"classicFont": "Clásico (Georgia)",
"standardFont": "Estándar (Arial)",
"textContent": "Contenido del Texto",
"bookTitlePlaceholder": "Título del Libro",
"authorNamePlaceholder": "Nombre del Autor",
"addTextLayer": "Añadir Capa de Texto",
"aiGenerator": {
"title": "Generador de Portadas con IA",
"selectStyle": "Seleccionar Estilo",
"modern": "Moderno",
"minimalist": "Minimalista",
"vintage": "Antiguo",
"bold": "Audaz",
"elegant": "Elegante",
"playful": "Juguetón",
"describeCover": "Describe tu Portada",
"coverPlaceholder": "Describe la portada del libro que quieres generar... ej. 'Un bosque misterioso con ojos brillantes en la oscuridad'",
"quickPresets": "Preajustes Rápidos por Género",
"mystery": "Misterio",
"mysteryPrompt": "Una portada de libro misteriosa con sombras oscuras, silueta de un detective, escena de calle brumosa, atmósfera noir",
"romance": "Romance",
"romancePrompt": "Una portada de libro romántica con colores pastel suaves, silueta de pareja al atardecer, soñadora y emocional",
"fantasy": "Fantasía",
"fantasyPrompt": "Una portada de libro de fantasía épica con elementos mágicos, dragón, castillo al fondo, efectos luminosos místicos",
"scifi": "Ciencia Ficción",
"scifiPrompt": "Una portada de libro de ciencia ficción futurista con naves espaciales, luces de neón, ciudad cyberpunk, atmósfera high-tech",
"selfhelp": "Autoayuda",
"selfhelpPrompt": "Una portada de libro de autoayuda limpia con imágenes inspiradoras, cima de montaña o amanecer, profesional y motivadora",
"generating": "Generando...",
"generateCover": "Generar Portada"
}
} }
} }
+3
View File
@@ -13,6 +13,9 @@ export interface BookOutline {
genre: string; genre: string;
logline: string; logline: string;
chapters: Chapter[]; chapters: Chapter[];
// Error handling properties
error?: string;
raw?: string;
} }
export interface Character { export interface Character {
+811 -1
View File
@@ -13,6 +13,12 @@
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0", "@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5", "axios": "^1.6.5",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"i18next": "^26.0.3", "i18next": "^26.0.3",
@@ -1544,6 +1550,12 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.23.2", "version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
@@ -1609,6 +1621,466 @@
"react": "^18 || ^19" "react": "^18 || ^19"
} }
}, },
"node_modules/@tiptap/core": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.2.tgz",
"integrity": "sha512-atq35NkpeEphH6vNYJ0pTLLBA73FAbvTV9Ovd3AaTC5s99/KF5Q86zVJXvml8xPRcMGM6dLp+eSSd06oTscMSA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.2.tgz",
"integrity": "sha512-iTdlmGFcgxi4LKaOW2Rc9/yD83qTXgRm5BN3vCHWy5+TbEnReYxYqU5qKsbtTbKy30sO8TJTdAXTZ29uomShQQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.2.tgz",
"integrity": "sha512-bqsPJyKcT/RWse4e16U2EKhraR8a2+98TUuk1amG3yCyFJZStoO/j+pN0IqZdZZjr3WtxFyvwWp7Kc59UN+jUA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.2.tgz",
"integrity": "sha512-5hbyDOSkJwA2uh0v9Mm0Dd9bb9inx6tHBEDSH2tCB9Rm23poz3yOreB7SNX8xDMe5L0/PQesfWC14RitcmhKPg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.2.tgz",
"integrity": "sha512-llrTJnA72RGcWLLO+ro0QN4sjHynhaCerhpV+GZE/ATd8BqV/ekQFdBLJrvC/09My2XQfCwLsyCh92NPXUdELA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.2.tgz",
"integrity": "sha512-iYFY+yzfYA9MKt7nupyW/PzqL9XC2D0mC8l1z2Y10i0/fGL8NbqIYjhNUAyXGqH3QWcI+DirI66842y2OadPOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.2.tgz",
"integrity": "sha512-PEwFlDyvtKF19WCrOFg77qJV9WqhvjCY4ZoXlHP9Hx0KTcOA8W39mtw8d4NWU5pLRK94yHKF1DVVL8UUkEOnww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.2.tgz",
"integrity": "sha512-yPw9pQeVC4QDh86TuyKCZxxM4g0NAw7mEtGnAo6EpxaBQr1wyBr9yFpys+QTsQpRTmyTf1VHp4iTTLuWHMljIw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.2.tgz",
"integrity": "sha512-sDv3fv4LtX0X4nqwh9Gn3C/aZXT+C2JlK7tJovPOpaYP/a6hr03Sn35X5moAfgMCSiWFygEvlTriqwmCsJuxog==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.2.tgz",
"integrity": "sha512-r0ZTeh9rNtj9Api+G0YyaB+tAKPDn7aYWg+qSrmAC5EyUPee6Zjn3zlw0q4renCeQflvNRK20xHM8zokC41jOA==",
"license": "MIT",
"optional": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.2.tgz",
"integrity": "sha512-rR2OLrl/k2kj7xehaZHq0Y7T+1wy2DOTabir9LsTrktTFEcklrh9qY1KC6rEBkwMKaWrmignR1l39kS6RlKFNw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.2.tgz",
"integrity": "sha512-ChsoqF4XRp6EWatTRlXL4LMFh/ggwRVCyt09brSfjJV5knFaXlECSa5/+rKLMLMULaj6dVlJqoAD15exgu2HHA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.2.tgz",
"integrity": "sha512-QPHLef+ikAyf7RVc4EdGeKxH4OEGb3ueCEwJ41RcYPtZ1BX9ueei7FC936guTdL1U7w3vQ65qfy86HznzkYgvw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.2.tgz",
"integrity": "sha512-Oz8KN5KJAWV1mFNE9UIWXdMD6xa5zPf/0yLsT8V4sgaRm+VsdFKllN58BY9qCZf/kIZbaOez5KkaoeAcm0MAZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.2.tgz",
"integrity": "sha512-fmtQu2HDnV3sOZPdz0+1lOLI7UtrIhusohJj2UwOLQxG8qqhLwbvWx2OQTlfblgY0z+CjLRr6ANbNDxOTIblfg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-link": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.2.tgz",
"integrity": "sha512-TXfSoKmng5pecvQUZqdsx6ICeob5V5hhYOj2vCEtjfcjWsyCndqFIl1w+Nt/yI5ehrFNOVPyj3ZvcELuuAW6pw==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.3.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.2.tgz",
"integrity": "sha512-Vq9xScgkA2A3Zj9dQ4WUBKK7u7UCzeSFRz9FcKTQVZHRPbZoqFGnlRUVngqsE7JXrCOthXQ1dXxgk40nAsBFRw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.2.tgz",
"integrity": "sha512-Mk+iiLIFh8Pfuarr6mWfTO7QJbd2ZQd0nGNhNWXlGAO7DJCb4BP9nj4bEIJ17SbcykGRjsi4WMqY50z4MHXqKQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-keymap": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.2.tgz",
"integrity": "sha512-TozU9V2vldMUPpTXnfLCO33EO06jLxn7uEJTMBnN4iX/dLV3cBVCbE4kHyDKS0sLd7joUeekS06vYP9uQb1hFw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.2.tgz",
"integrity": "sha512-K7qxoBKmsVkAd3kW64ZRCUPFrDcNGpXRDUBx9YgAO/bTfsfxtH2oil+igsUWGXPczpP4yoHPKjTfhpBpLjGl6Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.2.tgz",
"integrity": "sha512-EHZZzxVhvzEPDPWtRBF1YKhB+WCUjd1C2NhjHfL3Dl71PBqM3ZWA6qN7NDGPyNyGGWauui/NR/4X+5AfPqlHyA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.22.2.tgz",
"integrity": "sha512-xYw733CmSeG7MyYBDdV5NFiwlBdXXzw4Mvjb2t4QRXagkDbHeNY/LtKTcrtcMNfO4Jx0mwivGQZUIEC8oAfvxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.2.tgz",
"integrity": "sha512-YFC3elKU1L8PiGbcB6tqd/7vWPF5IbydJz0POJpHzSjstX+VfT8VsvS7ubxVuSIWQ11kGkH3mzX6LX8JHsHZxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.2.tgz",
"integrity": "sha512-J1w7JwijfSD7ah0WfiwZ/DVWCIGT9x369RM4RJc57i44mIBElj7tl1dh+N5KPGOXKUup4gr7sSJAE38lgeaDMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.2.tgz",
"integrity": "sha512-pgqyXzVHo4WmDhK26rDwhK2lxQwnjl/9DP816C2k3To/fZRK1eW7q0pSAYteHWmKkaYAxwj/0UvCU0nXKlPujw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.2.tgz",
"integrity": "sha512-BaV6WOowxdkGTLWiU7DdZ3Twh633O4RGqwUM5dDas5LvaqL8AMWGTO8Wg9yAaaKXzd9MtKI1ZCqS/+MtzusgkQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extensions": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.2.tgz",
"integrity": "sha512-s7MZmm2Xdq+8feIXgY3v7gVpQ5ClqBZi20KheouS7KSbBlrY4fu2irYR1EGc6r1UUVaHMxEa+cx5knhx+mIPUw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/pm": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.2.tgz",
"integrity": "sha512-G2ENwIazoSKkAnN5MN5yN91TIZNFm6TxB74kPf3Empr2k9W51Hkcier70jHGpArhgcEaL4BVreuU1PRDRwCeGw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.2.tgz",
"integrity": "sha512-tyGKG69e/MkpoD/JTpVPz0XydEHxh1MSAYnLb3gRvyvBDv2r/veLea+cApkmjQaCfkKC/CWwTFXBYlOB0caSBA==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"fast-equals": "^5.3.3",
"use-sync-external-store": "^1.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.22.2",
"@tiptap/extension-floating-menu": "^3.22.2"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.2.tgz",
"integrity": "sha512-+CCKX8tOQ/ZPb2k/z6em4AQCFYAcdd8+0TOzPWiuLxRyCHRPBBVhnPsXOKgKwE4OO3E8BsezquuYRYRwsyzCqg==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/extension-blockquote": "^3.22.2",
"@tiptap/extension-bold": "^3.22.2",
"@tiptap/extension-bullet-list": "^3.22.2",
"@tiptap/extension-code": "^3.22.2",
"@tiptap/extension-code-block": "^3.22.2",
"@tiptap/extension-document": "^3.22.2",
"@tiptap/extension-dropcursor": "^3.22.2",
"@tiptap/extension-gapcursor": "^3.22.2",
"@tiptap/extension-hard-break": "^3.22.2",
"@tiptap/extension-heading": "^3.22.2",
"@tiptap/extension-horizontal-rule": "^3.22.2",
"@tiptap/extension-italic": "^3.22.2",
"@tiptap/extension-link": "^3.22.2",
"@tiptap/extension-list": "^3.22.2",
"@tiptap/extension-list-item": "^3.22.2",
"@tiptap/extension-list-keymap": "^3.22.2",
"@tiptap/extension-ordered-list": "^3.22.2",
"@tiptap/extension-paragraph": "^3.22.2",
"@tiptap/extension-strike": "^3.22.2",
"@tiptap/extension-text": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/extensions": "^3.22.2",
"@tiptap/pm": "^3.22.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1741,6 +2213,28 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -1807,7 +2301,6 @@
"version": "18.3.7", "version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
@@ -1855,6 +2348,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.18.1", "version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -1974,6 +2473,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/aria-hidden": { "node_modules/aria-hidden": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -2662,6 +3167,12 @@
"resolved": "server", "resolved": "server",
"link": true "link": true
}, },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-fetch": { "node_modules/cross-fetch": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
@@ -2938,6 +3449,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": { "node_modules/env-paths": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
@@ -3053,6 +3576,18 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -3195,6 +3730,15 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-equals": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
"integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4037,6 +4581,21 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
"license": "MIT"
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -4138,6 +4697,23 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4147,6 +4723,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -4505,6 +5087,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -4836,6 +5424,201 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/prosemirror-changeset": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.4",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.41.8",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -4858,6 +5641,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/pure-rand": { "node_modules/pure-rand": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -5331,6 +6123,12 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6034,6 +6832,12 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uid-safe": { "node_modules/uid-safe": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -6307,6 +7111,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+811 -1
View File
@@ -24,6 +24,12 @@
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0", "@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5", "axios": "^1.6.5",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"i18next": "^26.0.3", "i18next": "^26.0.3",
@@ -2434,6 +2440,12 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.23.2", "version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
@@ -2871,6 +2883,466 @@
"react": "^18 || ^19" "react": "^18 || ^19"
} }
}, },
"node_modules/@tiptap/core": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.2.tgz",
"integrity": "sha512-atq35NkpeEphH6vNYJ0pTLLBA73FAbvTV9Ovd3AaTC5s99/KF5Q86zVJXvml8xPRcMGM6dLp+eSSd06oTscMSA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.2.tgz",
"integrity": "sha512-iTdlmGFcgxi4LKaOW2Rc9/yD83qTXgRm5BN3vCHWy5+TbEnReYxYqU5qKsbtTbKy30sO8TJTdAXTZ29uomShQQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.2.tgz",
"integrity": "sha512-bqsPJyKcT/RWse4e16U2EKhraR8a2+98TUuk1amG3yCyFJZStoO/j+pN0IqZdZZjr3WtxFyvwWp7Kc59UN+jUA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.2.tgz",
"integrity": "sha512-5hbyDOSkJwA2uh0v9Mm0Dd9bb9inx6tHBEDSH2tCB9Rm23poz3yOreB7SNX8xDMe5L0/PQesfWC14RitcmhKPg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.2.tgz",
"integrity": "sha512-llrTJnA72RGcWLLO+ro0QN4sjHynhaCerhpV+GZE/ATd8BqV/ekQFdBLJrvC/09My2XQfCwLsyCh92NPXUdELA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.2.tgz",
"integrity": "sha512-iYFY+yzfYA9MKt7nupyW/PzqL9XC2D0mC8l1z2Y10i0/fGL8NbqIYjhNUAyXGqH3QWcI+DirI66842y2OadPOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.2.tgz",
"integrity": "sha512-PEwFlDyvtKF19WCrOFg77qJV9WqhvjCY4ZoXlHP9Hx0KTcOA8W39mtw8d4NWU5pLRK94yHKF1DVVL8UUkEOnww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.2.tgz",
"integrity": "sha512-yPw9pQeVC4QDh86TuyKCZxxM4g0NAw7mEtGnAo6EpxaBQr1wyBr9yFpys+QTsQpRTmyTf1VHp4iTTLuWHMljIw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.2.tgz",
"integrity": "sha512-sDv3fv4LtX0X4nqwh9Gn3C/aZXT+C2JlK7tJovPOpaYP/a6hr03Sn35X5moAfgMCSiWFygEvlTriqwmCsJuxog==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.2.tgz",
"integrity": "sha512-r0ZTeh9rNtj9Api+G0YyaB+tAKPDn7aYWg+qSrmAC5EyUPee6Zjn3zlw0q4renCeQflvNRK20xHM8zokC41jOA==",
"license": "MIT",
"optional": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.2.tgz",
"integrity": "sha512-rR2OLrl/k2kj7xehaZHq0Y7T+1wy2DOTabir9LsTrktTFEcklrh9qY1KC6rEBkwMKaWrmignR1l39kS6RlKFNw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.2.tgz",
"integrity": "sha512-ChsoqF4XRp6EWatTRlXL4LMFh/ggwRVCyt09brSfjJV5knFaXlECSa5/+rKLMLMULaj6dVlJqoAD15exgu2HHA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.2.tgz",
"integrity": "sha512-QPHLef+ikAyf7RVc4EdGeKxH4OEGb3ueCEwJ41RcYPtZ1BX9ueei7FC936guTdL1U7w3vQ65qfy86HznzkYgvw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.2.tgz",
"integrity": "sha512-Oz8KN5KJAWV1mFNE9UIWXdMD6xa5zPf/0yLsT8V4sgaRm+VsdFKllN58BY9qCZf/kIZbaOez5KkaoeAcm0MAZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.2.tgz",
"integrity": "sha512-fmtQu2HDnV3sOZPdz0+1lOLI7UtrIhusohJj2UwOLQxG8qqhLwbvWx2OQTlfblgY0z+CjLRr6ANbNDxOTIblfg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-link": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.2.tgz",
"integrity": "sha512-TXfSoKmng5pecvQUZqdsx6ICeob5V5hhYOj2vCEtjfcjWsyCndqFIl1w+Nt/yI5ehrFNOVPyj3ZvcELuuAW6pw==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.3.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.2.tgz",
"integrity": "sha512-Vq9xScgkA2A3Zj9dQ4WUBKK7u7UCzeSFRz9FcKTQVZHRPbZoqFGnlRUVngqsE7JXrCOthXQ1dXxgk40nAsBFRw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.2.tgz",
"integrity": "sha512-Mk+iiLIFh8Pfuarr6mWfTO7QJbd2ZQd0nGNhNWXlGAO7DJCb4BP9nj4bEIJ17SbcykGRjsi4WMqY50z4MHXqKQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-keymap": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.2.tgz",
"integrity": "sha512-TozU9V2vldMUPpTXnfLCO33EO06jLxn7uEJTMBnN4iX/dLV3cBVCbE4kHyDKS0sLd7joUeekS06vYP9uQb1hFw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.2.tgz",
"integrity": "sha512-K7qxoBKmsVkAd3kW64ZRCUPFrDcNGpXRDUBx9YgAO/bTfsfxtH2oil+igsUWGXPczpP4yoHPKjTfhpBpLjGl6Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.2.tgz",
"integrity": "sha512-EHZZzxVhvzEPDPWtRBF1YKhB+WCUjd1C2NhjHfL3Dl71PBqM3ZWA6qN7NDGPyNyGGWauui/NR/4X+5AfPqlHyA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.22.2.tgz",
"integrity": "sha512-xYw733CmSeG7MyYBDdV5NFiwlBdXXzw4Mvjb2t4QRXagkDbHeNY/LtKTcrtcMNfO4Jx0mwivGQZUIEC8oAfvxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.2.tgz",
"integrity": "sha512-YFC3elKU1L8PiGbcB6tqd/7vWPF5IbydJz0POJpHzSjstX+VfT8VsvS7ubxVuSIWQ11kGkH3mzX6LX8JHsHZxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.2.tgz",
"integrity": "sha512-J1w7JwijfSD7ah0WfiwZ/DVWCIGT9x369RM4RJc57i44mIBElj7tl1dh+N5KPGOXKUup4gr7sSJAE38lgeaDMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.2.tgz",
"integrity": "sha512-pgqyXzVHo4WmDhK26rDwhK2lxQwnjl/9DP816C2k3To/fZRK1eW7q0pSAYteHWmKkaYAxwj/0UvCU0nXKlPujw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.2.tgz",
"integrity": "sha512-BaV6WOowxdkGTLWiU7DdZ3Twh633O4RGqwUM5dDas5LvaqL8AMWGTO8Wg9yAaaKXzd9MtKI1ZCqS/+MtzusgkQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extensions": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.2.tgz",
"integrity": "sha512-s7MZmm2Xdq+8feIXgY3v7gVpQ5ClqBZi20KheouS7KSbBlrY4fu2irYR1EGc6r1UUVaHMxEa+cx5knhx+mIPUw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/pm": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.2.tgz",
"integrity": "sha512-G2ENwIazoSKkAnN5MN5yN91TIZNFm6TxB74kPf3Empr2k9W51Hkcier70jHGpArhgcEaL4BVreuU1PRDRwCeGw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.2.tgz",
"integrity": "sha512-tyGKG69e/MkpoD/JTpVPz0XydEHxh1MSAYnLb3gRvyvBDv2r/veLea+cApkmjQaCfkKC/CWwTFXBYlOB0caSBA==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"fast-equals": "^5.3.3",
"use-sync-external-store": "^1.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.22.2",
"@tiptap/extension-floating-menu": "^3.22.2"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.2.tgz",
"integrity": "sha512-+CCKX8tOQ/ZPb2k/z6em4AQCFYAcdd8+0TOzPWiuLxRyCHRPBBVhnPsXOKgKwE4OO3E8BsezquuYRYRwsyzCqg==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/extension-blockquote": "^3.22.2",
"@tiptap/extension-bold": "^3.22.2",
"@tiptap/extension-bullet-list": "^3.22.2",
"@tiptap/extension-code": "^3.22.2",
"@tiptap/extension-code-block": "^3.22.2",
"@tiptap/extension-document": "^3.22.2",
"@tiptap/extension-dropcursor": "^3.22.2",
"@tiptap/extension-gapcursor": "^3.22.2",
"@tiptap/extension-hard-break": "^3.22.2",
"@tiptap/extension-heading": "^3.22.2",
"@tiptap/extension-horizontal-rule": "^3.22.2",
"@tiptap/extension-italic": "^3.22.2",
"@tiptap/extension-link": "^3.22.2",
"@tiptap/extension-list": "^3.22.2",
"@tiptap/extension-list-item": "^3.22.2",
"@tiptap/extension-list-keymap": "^3.22.2",
"@tiptap/extension-ordered-list": "^3.22.2",
"@tiptap/extension-paragraph": "^3.22.2",
"@tiptap/extension-strike": "^3.22.2",
"@tiptap/extension-text": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/extensions": "^3.22.2",
"@tiptap/pm": "^3.22.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3003,6 +3475,28 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -3069,7 +3563,6 @@
"version": "18.3.7", "version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
@@ -3117,6 +3610,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.18.1", "version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -3236,6 +3735,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/aria-hidden": { "node_modules/aria-hidden": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -3924,6 +4429,12 @@
"resolved": "server", "resolved": "server",
"link": true "link": true
}, },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-fetch": { "node_modules/cross-fetch": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
@@ -4200,6 +4711,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": { "node_modules/env-paths": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
@@ -4315,6 +4838,18 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -4457,6 +4992,15 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-equals": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
"integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -5314,6 +5858,21 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
"license": "MIT"
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -5415,6 +5974,23 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5424,6 +6000,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5784,6 +6366,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -6115,6 +6703,201 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/prosemirror-changeset": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.4",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.41.8",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6137,6 +6920,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/pure-rand": { "node_modules/pure-rand": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -6610,6 +7402,12 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7313,6 +8111,12 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uid-safe": { "node_modules/uid-safe": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -7960,6 +8764,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+38 -17
View File
@@ -46,16 +46,22 @@ Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')} Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English. CRITICAL INSTRUCTIONS:
- You MUST respond with VALID JSON only
- Do NOT include any text before or after the JSON
- Do NOT wrap the JSON in markdown code blocks
- All text content (titles, summaries, etc.) MUST be in ${targetLang}
- JSON keys MUST remain in English
- Ensure the JSON is properly formatted and parseable
Return the response in JSON format: Required JSON format:
{ {
"title": "Book Title", "title": "Book Title in ${targetLang}",
"genre": "${genre}", "genre": "${genre}",
"logline": "One sentence summary", "logline": "One sentence summary in ${targetLang}",
"chapters": [ "chapters": [
{"number": 1, "title": "Chapter Title", "summary": "Brief description"}, {"number": 1, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"},
... {"number": 2, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"}
] ]
}`; }`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', { const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
@@ -74,22 +80,29 @@ Return the response in JSON format:
const content = response.data.choices[0].message.content; const content = response.data.choices[0].message.content;
console.log('=== Outline AI Response ==='); console.log('=== Outline AI Response ===');
console.log('Raw content:', content); console.log('Raw content:', content);
// Try to parse JSON from response console.log('Target language:', targetLang);
// Try to parse JSON from response - improved parsing for multilingual content
let outline; let outline;
try {
// First try to find JSON object
const jsonMatch = content.match(/\{[\s\S]*\}/); const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) { if (jsonMatch) {
try { // Clean the JSON string to handle potential encoding issues
outline = JSON.parse(jsonMatch[0]); let jsonString = jsonMatch[0];
// Remove any markdown formatting that might interfere
jsonString = jsonString.replace(/```json\s*/g, '').replace(/```\s*$/g, '');
outline = JSON.parse(jsonString);
console.log('Parsed outline:', outline); console.log('Parsed outline:', outline);
} }
catch (parseError) {
console.error('JSON parse error:', parseError);
outline = { raw: content, error: 'Failed to parse JSON' };
}
}
else { else {
console.error('No JSON object found in response'); console.error('No JSON object found in response');
outline = { raw: content, error: 'No JSON found' }; outline = { raw: content, error: 'No JSON found in AI response' };
}
}
catch (parseError) {
console.error('JSON parse error:', parseError);
console.error('Failed content:', content);
outline = { raw: content, error: 'Failed to parse JSON from AI response' };
} }
res.json({ outline }); res.json({ outline });
} }
@@ -117,8 +130,11 @@ Summary: ${chapterSummary}
Tone: ${template.defaults.tone} Tone: ${template.defaults.tone}
POV: ${template.defaults.pov} POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}. CRITICAL: Write the ENTIRE chapter content in ${targetLang} only. Do NOT include any English text.
`; Write a complete, engaging chapter that matches the summary above.
The chapter should be between 1500-3000 words.
IMPORTANT: Respond with the chapter content directly, without any introduction, conclusion, or formatting markers. Just the narrative text in ${targetLang}.`;
if (previousContent) { if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`; prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
} }
@@ -136,6 +152,11 @@ IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
} }
}); });
const content = response.data.choices[0].message.content; const content = response.data.choices[0].message.content;
console.log('=== Chapter AI Response ===');
console.log('Target language:', targetLang);
console.log('Chapter title:', chapterTitle);
console.log('Content length:', content.length);
console.log('Content preview:', content.substring(0, 200) + '...');
res.json({ res.json({
content, content,
chapterTitle chapterTitle
+1 -1
View File
File diff suppressed because one or more lines are too long
+39 -18
View File
@@ -49,16 +49,22 @@ Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')} Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English. CRITICAL INSTRUCTIONS:
- You MUST respond with VALID JSON only
- Do NOT include any text before or after the JSON
- Do NOT wrap the JSON in markdown code blocks
- All text content (titles, summaries, etc.) MUST be in ${targetLang}
- JSON keys MUST remain in English
- Ensure the JSON is properly formatted and parseable
Return the response in JSON format: Required JSON format:
{ {
"title": "Book Title", "title": "Book Title in ${targetLang}",
"genre": "${genre}", "genre": "${genre}",
"logline": "One sentence summary", "logline": "One sentence summary in ${targetLang}",
"chapters": [ "chapters": [
{"number": 1, "title": "Chapter Title", "summary": "Brief description"}, {"number": 1, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"},
... {"number": 2, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"}
] ]
}`; }`;
@@ -80,22 +86,28 @@ Return the response in JSON format:
console.log('=== Outline AI Response ==='); console.log('=== Outline AI Response ===');
console.log('Raw content:', content); console.log('Raw content:', content);
console.log('Target language:', targetLang);
// Try to parse JSON from response // Try to parse JSON from response - improved parsing for multilingual content
let outline; let outline;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try { try {
outline = JSON.parse(jsonMatch[0]); // First try to find JSON object
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
// Clean the JSON string to handle potential encoding issues
let jsonString = jsonMatch[0];
// Remove any markdown formatting that might interfere
jsonString = jsonString.replace(/```json\s*/g, '').replace(/```\s*$/g, '');
outline = JSON.parse(jsonString);
console.log('Parsed outline:', outline); console.log('Parsed outline:', outline);
} catch (parseError) {
console.error('JSON parse error:', parseError);
outline = { raw: content, error: 'Failed to parse JSON' };
}
} else { } else {
console.error('No JSON object found in response'); console.error('No JSON object found in response');
outline = { raw: content, error: 'No JSON found' }; outline = { raw: content, error: 'No JSON found in AI response' };
}
} catch (parseError) {
console.error('JSON parse error:', parseError);
console.error('Failed content:', content);
outline = { raw: content, error: 'Failed to parse JSON from AI response' };
} }
res.json({ outline }); res.json({ outline });
@@ -127,8 +139,11 @@ Summary: ${chapterSummary}
Tone: ${template.defaults.tone} Tone: ${template.defaults.tone}
POV: ${template.defaults.pov} POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}. CRITICAL: Write the ENTIRE chapter content in ${targetLang} only. Do NOT include any English text.
`; Write a complete, engaging chapter that matches the summary above.
The chapter should be between 1500-3000 words.
IMPORTANT: Respond with the chapter content directly, without any introduction, conclusion, or formatting markers. Just the narrative text in ${targetLang}.`;
if (previousContent) { if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`; prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
@@ -150,6 +165,12 @@ IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
const content = response.data.choices[0].message.content; const content = response.data.choices[0].message.content;
console.log('=== Chapter AI Response ===');
console.log('Target language:', targetLang);
console.log('Chapter title:', chapterTitle);
console.log('Content length:', content.length);
console.log('Content preview:', content.substring(0, 200) + '...');
res.json({ res.json({
content, content,
chapterTitle chapterTitle