tetrons 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.next/types/app/api/export/route.d.ts +12 -0
- package/dist/.next/types/app/api/export/route.js +52 -0
- package/dist/.next/types/app/api/save/route.d.ts +12 -0
- package/dist/.next/types/app/api/save/route.js +52 -0
- package/dist/.next/types/app/layout.d.ts +12 -0
- package/dist/.next/types/app/layout.js +22 -0
- package/dist/.next/types/app/page.d.ts +12 -0
- package/dist/.next/types/app/page.js +22 -0
- package/dist/next.config.d.ts +3 -0
- package/dist/next.config.js +4 -0
- package/dist/src/app/api/export/route.d.ts +1 -0
- package/dist/src/app/api/export/route.js +4 -0
- package/dist/src/app/api/save/route.d.ts +6 -0
- package/dist/src/app/api/save/route.js +15 -0
- package/dist/src/app/layout.d.ts +6 -0
- package/dist/src/app/layout.jsx +34 -0
- package/dist/src/app/page.d.ts +1 -0
- package/dist/src/app/page.jsx +10 -0
- package/dist/src/components/UI/Button.d.ts +0 -0
- package/dist/src/components/UI/Button.jsx +1 -0
- package/dist/src/components/UI/Dropdown.d.ts +0 -0
- package/dist/src/components/UI/Dropdown.jsx +1 -0
- package/dist/src/components/tetrons/EditorContent.d.ts +1 -0
- package/dist/src/components/tetrons/EditorContent.jsx +158 -0
- package/dist/src/components/tetrons/ResizableImage.d.ts +1 -0
- package/dist/src/components/tetrons/ResizableImage.js +40 -0
- package/dist/src/components/tetrons/ResizableImageComponent.d.ts +11 -0
- package/dist/src/components/tetrons/ResizableImageComponent.jsx +37 -0
- package/dist/src/components/tetrons/ResizableVideo.d.ts +12 -0
- package/dist/src/components/tetrons/ResizableVideo.js +61 -0
- package/dist/src/components/tetrons/ResizableVideoComponent.d.ts +4 -0
- package/dist/src/components/tetrons/ResizableVideoComponent.jsx +32 -0
- package/dist/src/components/tetrons/helpers.d.ts +0 -0
- package/dist/src/components/tetrons/helpers.js +1 -0
- package/dist/src/components/tetrons/toolbar/ActionGroup.d.ts +6 -0
- package/dist/src/components/tetrons/toolbar/ActionGroup.jsx +165 -0
- package/dist/src/components/tetrons/toolbar/ClipboardGroup.d.ts +4 -0
- package/dist/src/components/tetrons/toolbar/ClipboardGroup.jsx +35 -0
- package/dist/src/components/tetrons/toolbar/FileGroup.d.ts +6 -0
- package/dist/src/components/tetrons/toolbar/FileGroup.jsx +40 -0
- package/dist/src/components/tetrons/toolbar/FontStyleGroup.d.ts +6 -0
- package/dist/src/components/tetrons/toolbar/FontStyleGroup.jsx +104 -0
- package/dist/src/components/tetrons/toolbar/InsertGroup.d.ts +5 -0
- package/dist/src/components/tetrons/toolbar/InsertGroup.jsx +162 -0
- package/dist/src/components/tetrons/toolbar/ListAlignGroup.d.ts +4 -0
- package/dist/src/components/tetrons/toolbar/ListAlignGroup.jsx +15 -0
- package/dist/src/components/tetrons/toolbar/MiscGroup.d.ts +6 -0
- package/dist/src/components/tetrons/toolbar/MiscGroup.jsx +30 -0
- package/dist/src/components/tetrons/toolbar/TableContextMenu.d.ts +6 -0
- package/dist/src/components/tetrons/toolbar/TableContextMenu.jsx +52 -0
- package/dist/src/components/tetrons/toolbar/TetronsToolbar.d.ts +4 -0
- package/dist/src/components/tetrons/toolbar/TetronsToolbar.jsx +46 -0
- package/dist/src/components/tetrons/toolbar/ToolbarButton.d.ts +12 -0
- package/dist/src/components/tetrons/toolbar/ToolbarButton.jsx +8 -0
- package/dist/src/components/tetrons/toolbar/extensions/Comment.d.ts +17 -0
- package/dist/src/components/tetrons/toolbar/extensions/Comment.js +45 -0
- package/dist/src/components/tetrons/toolbar/extensions/Embed.d.ts +2 -0
- package/dist/src/components/tetrons/toolbar/extensions/Embed.js +90 -0
- package/dist/src/components/tetrons/toolbar/extensions/FontFamily.d.ts +9 -0
- package/dist/src/components/tetrons/toolbar/extensions/FontFamily.js +28 -0
- package/dist/src/components/tetrons/toolbar/extensions/FontSize.d.ts +9 -0
- package/dist/src/components/tetrons/toolbar/extensions/FontSize.js +28 -0
- package/dist/src/components/tetrons/toolbar/extensions/ResizableTable.d.ts +1 -0
- package/dist/src/components/tetrons/toolbar/extensions/ResizableTable.js +11 -0
- package/dist/src/components/tetrons/toolbar/marks/Subscript.d.ts +2 -0
- package/dist/src/components/tetrons/toolbar/marks/Subscript.js +35 -0
- package/dist/src/components/tetrons/toolbar/marks/Superscript.d.ts +2 -0
- package/dist/src/components/tetrons/toolbar/marks/Superscript.js +35 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/lib/export.d.ts +0 -0
- package/dist/src/lib/export.js +1 -0
- package/dist/src/lib/tiptap-extensions.d.ts +0 -0
- package/dist/src/lib/tiptap-extensions.js +1 -0
- package/dist/src/utils/loadEmojiPicker.d.ts +1 -0
- package/dist/src/utils/loadEmojiPicker.js +12 -0
- package/package.json +1 -1
- package/src/app/api/export/route.ts +4 -0
- package/src/types/global.d.ts +9 -2
- package/tsconfig.json +57 -41
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type SegmentParams<T extends Object = any> = T extends Record<string, any> ? {
|
|
2
|
+
[K in keyof T]: T[K] extends string ? string | string[] | undefined : never;
|
|
3
|
+
} : T;
|
|
4
|
+
export interface PageProps {
|
|
5
|
+
params?: Promise<SegmentParams>;
|
|
6
|
+
searchParams?: Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
export interface LayoutProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
params?: Promise<SegmentParams>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// File: C:\Users\rexab\Desktop\Finapsys-Site\Nextjs-projects\tetrons\Tetrons\src\app\api\export\route.ts
|
|
2
|
+
import * as entry from '../../../../../src/app/api/export/route.js';
|
|
3
|
+
// Check that the entry is a valid entry
|
|
4
|
+
checkFields();
|
|
5
|
+
// Check the prop type of the entry function
|
|
6
|
+
if ('GET' in entry) {
|
|
7
|
+
checkFields();
|
|
8
|
+
checkFields();
|
|
9
|
+
checkFields();
|
|
10
|
+
}
|
|
11
|
+
// Check the prop type of the entry function
|
|
12
|
+
if ('HEAD' in entry) {
|
|
13
|
+
checkFields();
|
|
14
|
+
checkFields();
|
|
15
|
+
checkFields();
|
|
16
|
+
}
|
|
17
|
+
// Check the prop type of the entry function
|
|
18
|
+
if ('OPTIONS' in entry) {
|
|
19
|
+
checkFields();
|
|
20
|
+
checkFields();
|
|
21
|
+
checkFields();
|
|
22
|
+
}
|
|
23
|
+
// Check the prop type of the entry function
|
|
24
|
+
if ('POST' in entry) {
|
|
25
|
+
checkFields();
|
|
26
|
+
checkFields();
|
|
27
|
+
checkFields();
|
|
28
|
+
}
|
|
29
|
+
// Check the prop type of the entry function
|
|
30
|
+
if ('PUT' in entry) {
|
|
31
|
+
checkFields();
|
|
32
|
+
checkFields();
|
|
33
|
+
checkFields();
|
|
34
|
+
}
|
|
35
|
+
// Check the prop type of the entry function
|
|
36
|
+
if ('DELETE' in entry) {
|
|
37
|
+
checkFields();
|
|
38
|
+
checkFields();
|
|
39
|
+
checkFields();
|
|
40
|
+
}
|
|
41
|
+
// Check the prop type of the entry function
|
|
42
|
+
if ('PATCH' in entry) {
|
|
43
|
+
checkFields();
|
|
44
|
+
checkFields();
|
|
45
|
+
checkFields();
|
|
46
|
+
}
|
|
47
|
+
// Check the arguments and return type of the generateStaticParams function
|
|
48
|
+
if ('generateStaticParams' in entry) {
|
|
49
|
+
checkFields();
|
|
50
|
+
checkFields();
|
|
51
|
+
}
|
|
52
|
+
function checkFields() { }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type SegmentParams<T extends Object = any> = T extends Record<string, any> ? {
|
|
2
|
+
[K in keyof T]: T[K] extends string ? string | string[] | undefined : never;
|
|
3
|
+
} : T;
|
|
4
|
+
export interface PageProps {
|
|
5
|
+
params?: Promise<SegmentParams>;
|
|
6
|
+
searchParams?: Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
export interface LayoutProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
params?: Promise<SegmentParams>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// File: C:\Users\rexab\Desktop\Finapsys-Site\Nextjs-projects\tetrons\Tetrons\src\app\api\save\route.ts
|
|
2
|
+
import * as entry from '../../../../../src/app/api/save/route.js';
|
|
3
|
+
// Check that the entry is a valid entry
|
|
4
|
+
checkFields();
|
|
5
|
+
// Check the prop type of the entry function
|
|
6
|
+
if ('GET' in entry) {
|
|
7
|
+
checkFields();
|
|
8
|
+
checkFields();
|
|
9
|
+
checkFields();
|
|
10
|
+
}
|
|
11
|
+
// Check the prop type of the entry function
|
|
12
|
+
if ('HEAD' in entry) {
|
|
13
|
+
checkFields();
|
|
14
|
+
checkFields();
|
|
15
|
+
checkFields();
|
|
16
|
+
}
|
|
17
|
+
// Check the prop type of the entry function
|
|
18
|
+
if ('OPTIONS' in entry) {
|
|
19
|
+
checkFields();
|
|
20
|
+
checkFields();
|
|
21
|
+
checkFields();
|
|
22
|
+
}
|
|
23
|
+
// Check the prop type of the entry function
|
|
24
|
+
if ('POST' in entry) {
|
|
25
|
+
checkFields();
|
|
26
|
+
checkFields();
|
|
27
|
+
checkFields();
|
|
28
|
+
}
|
|
29
|
+
// Check the prop type of the entry function
|
|
30
|
+
if ('PUT' in entry) {
|
|
31
|
+
checkFields();
|
|
32
|
+
checkFields();
|
|
33
|
+
checkFields();
|
|
34
|
+
}
|
|
35
|
+
// Check the prop type of the entry function
|
|
36
|
+
if ('DELETE' in entry) {
|
|
37
|
+
checkFields();
|
|
38
|
+
checkFields();
|
|
39
|
+
checkFields();
|
|
40
|
+
}
|
|
41
|
+
// Check the prop type of the entry function
|
|
42
|
+
if ('PATCH' in entry) {
|
|
43
|
+
checkFields();
|
|
44
|
+
checkFields();
|
|
45
|
+
checkFields();
|
|
46
|
+
}
|
|
47
|
+
// Check the arguments and return type of the generateStaticParams function
|
|
48
|
+
if ('generateStaticParams' in entry) {
|
|
49
|
+
checkFields();
|
|
50
|
+
checkFields();
|
|
51
|
+
}
|
|
52
|
+
function checkFields() { }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type SegmentParams<T extends Object = any> = T extends Record<string, any> ? {
|
|
2
|
+
[K in keyof T]: T[K] extends string ? string | string[] | undefined : never;
|
|
3
|
+
} : T;
|
|
4
|
+
export interface PageProps {
|
|
5
|
+
params?: Promise<SegmentParams>;
|
|
6
|
+
searchParams?: Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
export interface LayoutProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
params?: Promise<SegmentParams>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// File: C:\Users\rexab\Desktop\Finapsys-Site\Nextjs-projects\tetrons\Tetrons\src\app\layout.tsx
|
|
2
|
+
import * as entry from '../../../src/app/layout.js';
|
|
3
|
+
// Check that the entry is a valid entry
|
|
4
|
+
checkFields();
|
|
5
|
+
// Check the prop type of the entry function
|
|
6
|
+
checkFields();
|
|
7
|
+
// Check the arguments and return type of the generateMetadata function
|
|
8
|
+
if ('generateMetadata' in entry) {
|
|
9
|
+
checkFields();
|
|
10
|
+
checkFields();
|
|
11
|
+
}
|
|
12
|
+
// Check the arguments and return type of the generateViewport function
|
|
13
|
+
if ('generateViewport' in entry) {
|
|
14
|
+
checkFields();
|
|
15
|
+
checkFields();
|
|
16
|
+
}
|
|
17
|
+
// Check the arguments and return type of the generateStaticParams function
|
|
18
|
+
if ('generateStaticParams' in entry) {
|
|
19
|
+
checkFields();
|
|
20
|
+
checkFields();
|
|
21
|
+
}
|
|
22
|
+
function checkFields() { }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type SegmentParams<T extends Object = any> = T extends Record<string, any> ? {
|
|
2
|
+
[K in keyof T]: T[K] extends string ? string | string[] | undefined : never;
|
|
3
|
+
} : T;
|
|
4
|
+
export interface PageProps {
|
|
5
|
+
params?: Promise<SegmentParams>;
|
|
6
|
+
searchParams?: Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
export interface LayoutProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
params?: Promise<SegmentParams>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// File: C:\Users\rexab\Desktop\Finapsys-Site\Nextjs-projects\tetrons\Tetrons\src\app\page.tsx
|
|
2
|
+
import * as entry from '../../../src/app/page.js';
|
|
3
|
+
// Check that the entry is a valid entry
|
|
4
|
+
checkFields();
|
|
5
|
+
// Check the prop type of the entry function
|
|
6
|
+
checkFields();
|
|
7
|
+
// Check the arguments and return type of the generateMetadata function
|
|
8
|
+
if ('generateMetadata' in entry) {
|
|
9
|
+
checkFields();
|
|
10
|
+
checkFields();
|
|
11
|
+
}
|
|
12
|
+
// Check the arguments and return type of the generateViewport function
|
|
13
|
+
if ('generateViewport' in entry) {
|
|
14
|
+
checkFields();
|
|
15
|
+
checkFields();
|
|
16
|
+
}
|
|
17
|
+
// Check the arguments and return type of the generateStaticParams function
|
|
18
|
+
if ('generateStaticParams' in entry) {
|
|
19
|
+
checkFields();
|
|
20
|
+
checkFields();
|
|
21
|
+
}
|
|
22
|
+
function checkFields() { }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function GET(req: Request): Response;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
export async function POST(request) {
|
|
5
|
+
try {
|
|
6
|
+
const json = await request.json();
|
|
7
|
+
const publicDir = path.join(process.cwd(), "public");
|
|
8
|
+
const filePath = path.join(publicDir, "editor-content.json");
|
|
9
|
+
await fs.writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
10
|
+
return NextResponse.json({ message: "File saved successfully" });
|
|
11
|
+
}
|
|
12
|
+
catch (_a) {
|
|
13
|
+
return NextResponse.json({ error: "Failed to save file" }, { status: 500 });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
const geistSans = Geist({
|
|
4
|
+
variable: "--font-geist-sans",
|
|
5
|
+
subsets: ["latin"],
|
|
6
|
+
});
|
|
7
|
+
const geistMono = Geist_Mono({
|
|
8
|
+
variable: "--font-geist-mono",
|
|
9
|
+
subsets: ["latin"],
|
|
10
|
+
});
|
|
11
|
+
export const metadata = {
|
|
12
|
+
title: "Tetrons",
|
|
13
|
+
description: "A modern Word-style rich text editor built with Next.js",
|
|
14
|
+
icons: {
|
|
15
|
+
icon: [
|
|
16
|
+
{ url: "/favicon.ico", type: "image/x-icon" },
|
|
17
|
+
{ url: "/favicon-32x32.png", type: "image/png", sizes: "32x32" },
|
|
18
|
+
{ url: "/favicon-16x16.png", type: "image/png", sizes: "16x16" },
|
|
19
|
+
],
|
|
20
|
+
apple: "/apple-touch-icon.png",
|
|
21
|
+
},
|
|
22
|
+
manifest: "/site.webmanifest",
|
|
23
|
+
};
|
|
24
|
+
export default function RootLayout({ children, }) {
|
|
25
|
+
console.log("Rendering RootLayout", {
|
|
26
|
+
geistSans: geistSans.variable,
|
|
27
|
+
geistMono: geistMono.variable,
|
|
28
|
+
});
|
|
29
|
+
return (<html lang="en">
|
|
30
|
+
<body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased bg-gray-50 text-gray-900`}>
|
|
31
|
+
<main className="max-w-screen-xl mx-auto p-4">{children}</main>
|
|
32
|
+
</body>
|
|
33
|
+
</html>);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function Home(): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import EditorContent from "../../components/tetrons/EditorContent";
|
|
2
|
+
export default function Home() {
|
|
3
|
+
return (
|
|
4
|
+
<main className="flex flex-col h-screen overflow-hidden">
|
|
5
|
+
<div className="flex-1 overflow-auto flex flex-col">
|
|
6
|
+
<EditorContent />
|
|
7
|
+
</div>
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function EditorContent(): import("react").JSX.Element;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Comment } from "./toolbar/extensions/Comment";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useEditor, EditorContent as TiptapEditorContent, } from "@tiptap/react";
|
|
5
|
+
import Document from "@tiptap/extension-document";
|
|
6
|
+
import Paragraph from "@tiptap/extension-paragraph";
|
|
7
|
+
import Text from "@tiptap/extension-text";
|
|
8
|
+
import History from "@tiptap/extension-history";
|
|
9
|
+
import Bold from "@tiptap/extension-bold";
|
|
10
|
+
import Italic from "@tiptap/extension-italic";
|
|
11
|
+
import Underline from "@tiptap/extension-underline";
|
|
12
|
+
import Strike from "@tiptap/extension-strike";
|
|
13
|
+
import Code from "@tiptap/extension-code";
|
|
14
|
+
import Blockquote from "@tiptap/extension-blockquote";
|
|
15
|
+
import HardBreak from "@tiptap/extension-hard-break";
|
|
16
|
+
import Heading from "@tiptap/extension-heading";
|
|
17
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
18
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
19
|
+
import Color from "@tiptap/extension-color";
|
|
20
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
21
|
+
import Image from "@tiptap/extension-image";
|
|
22
|
+
import Link from "@tiptap/extension-link";
|
|
23
|
+
import TextStyle from "@tiptap/extension-text-style";
|
|
24
|
+
import ListItem from "@tiptap/extension-list-item";
|
|
25
|
+
import BulletList from "@tiptap/extension-bullet-list";
|
|
26
|
+
import OrderedList from "@tiptap/extension-ordered-list";
|
|
27
|
+
import { Subscript } from "./toolbar/marks/Subscript";
|
|
28
|
+
import { Superscript } from "./toolbar/marks/Superscript";
|
|
29
|
+
import { ResizableTable } from "./toolbar/extensions/ResizableTable";
|
|
30
|
+
import { Embed } from "./toolbar/extensions/Embed";
|
|
31
|
+
import TableRow from "@tiptap/extension-table-row";
|
|
32
|
+
import TableCell from "@tiptap/extension-table-cell";
|
|
33
|
+
import TableHeader from "@tiptap/extension-table-header";
|
|
34
|
+
import { FontFamily } from "./toolbar/extensions/FontFamily";
|
|
35
|
+
import { FontSize } from "./toolbar/extensions/FontSize";
|
|
36
|
+
import TetronsToolbar from "./toolbar/TetronsToolbar";
|
|
37
|
+
import { ResizableImage } from "./ResizableImage";
|
|
38
|
+
import { ResizableVideo } from "./ResizableVideo";
|
|
39
|
+
import TableContextMenu from "./toolbar/TableContextMenu";
|
|
40
|
+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
41
|
+
import { createLowlight } from "lowlight";
|
|
42
|
+
import js from "highlight.js/lib/languages/javascript";
|
|
43
|
+
import ts from "highlight.js/lib/languages/typescript";
|
|
44
|
+
const lowlight = createLowlight();
|
|
45
|
+
lowlight.register("js", js);
|
|
46
|
+
lowlight.register("ts", ts);
|
|
47
|
+
export default function EditorContent() {
|
|
48
|
+
const [versions, setVersions] = useState([]);
|
|
49
|
+
const [currentVersionIndex, setCurrentVersionIndex] = useState(null);
|
|
50
|
+
const editor = useEditor({
|
|
51
|
+
extensions: [
|
|
52
|
+
Document,
|
|
53
|
+
Paragraph,
|
|
54
|
+
Text,
|
|
55
|
+
History,
|
|
56
|
+
Bold,
|
|
57
|
+
Italic,
|
|
58
|
+
Underline,
|
|
59
|
+
Strike,
|
|
60
|
+
Code,
|
|
61
|
+
Blockquote,
|
|
62
|
+
HardBreak,
|
|
63
|
+
Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
|
|
64
|
+
HorizontalRule,
|
|
65
|
+
TextStyle,
|
|
66
|
+
Color,
|
|
67
|
+
Highlight.configure({ multicolor: true }),
|
|
68
|
+
FontFamily,
|
|
69
|
+
FontSize,
|
|
70
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
71
|
+
ListItem,
|
|
72
|
+
BulletList,
|
|
73
|
+
OrderedList,
|
|
74
|
+
Subscript,
|
|
75
|
+
Superscript,
|
|
76
|
+
Image,
|
|
77
|
+
Link.configure({
|
|
78
|
+
openOnClick: false,
|
|
79
|
+
autolink: true,
|
|
80
|
+
linkOnPaste: true,
|
|
81
|
+
}),
|
|
82
|
+
ResizableTable.configure({
|
|
83
|
+
resizable: true,
|
|
84
|
+
}),
|
|
85
|
+
TableRow,
|
|
86
|
+
TableCell,
|
|
87
|
+
TableHeader,
|
|
88
|
+
Embed,
|
|
89
|
+
ResizableImage,
|
|
90
|
+
ResizableVideo,
|
|
91
|
+
Comment,
|
|
92
|
+
CodeBlockLowlight.configure({
|
|
93
|
+
lowlight,
|
|
94
|
+
HTMLAttributes: {
|
|
95
|
+
class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
content: "",
|
|
100
|
+
editorProps: {
|
|
101
|
+
attributes: {
|
|
102
|
+
class: "min-h-full focus:outline-none p-0",
|
|
103
|
+
"data-placeholder": "Start typing here...",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const wrapperRef = useRef(null);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
return () => {
|
|
110
|
+
editor === null || editor === void 0 ? void 0 : editor.destroy();
|
|
111
|
+
};
|
|
112
|
+
}, [editor]);
|
|
113
|
+
const handleEditorClick = () => {
|
|
114
|
+
if (editor && !editor.isFocused) {
|
|
115
|
+
editor.commands.focus();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const saveVersion = () => {
|
|
119
|
+
if (!editor)
|
|
120
|
+
return;
|
|
121
|
+
const content = editor.getJSON();
|
|
122
|
+
setVersions((prev) => [...prev, JSON.stringify(content)]);
|
|
123
|
+
setCurrentVersionIndex(versions.length);
|
|
124
|
+
};
|
|
125
|
+
const restoreVersion = (index) => {
|
|
126
|
+
if (!editor)
|
|
127
|
+
return;
|
|
128
|
+
const versionContent = versions[index];
|
|
129
|
+
if (versionContent) {
|
|
130
|
+
editor.commands.setContent(JSON.parse(versionContent));
|
|
131
|
+
setCurrentVersionIndex(index);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return (<div className="flex flex-col h-full">
|
|
135
|
+
<div className="p-2 border-b border-gray-300 bg-gray-50 flex flex-wrap items-center gap-3">
|
|
136
|
+
<button onClick={saveVersion} disabled={!editor} className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">
|
|
137
|
+
Save Version
|
|
138
|
+
</button>
|
|
139
|
+
|
|
140
|
+
<div className="flex items-center gap-2 overflow-x-auto max-w-full">
|
|
141
|
+
{versions.length === 0 && (<span className="text-gray-500 text-sm">No saved versions</span>)}
|
|
142
|
+
{versions.map((_, idx) => (<button key={idx} onClick={() => restoreVersion(idx)} className={`px-2 py-1 rounded border ${idx === currentVersionIndex
|
|
143
|
+
? "border-blue-600 font-semibold text-blue-600"
|
|
144
|
+
: "border-gray-300 text-gray-700 hover:border-gray-600"}`} title={`Restore Version ${idx + 1}`}>
|
|
145
|
+
{`V${idx + 1}`}
|
|
146
|
+
</button>))}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{editor && <TetronsToolbar editor={editor}/>}
|
|
151
|
+
<div ref={wrapperRef} className="flex-grow p-4 md:p-6 bg-white border border-gray-300 rounded shadow-sm overflow-auto min-h-0 prose relative" onClick={handleEditorClick}>
|
|
152
|
+
{editor ? (<>
|
|
153
|
+
<TiptapEditorContent editor={editor}/>
|
|
154
|
+
{editor && <TableContextMenu editor={editor}/>}
|
|
155
|
+
</>) : (<div className="text-gray-500">Loading editor...</div>)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>);
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ResizableImage: import("@tiptap/react").Node<import("@tiptap/extension-image").ImageOptions, any>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import Image from "@tiptap/extension-image";
|
|
13
|
+
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
14
|
+
import ResizableImageComponent from "./ResizableImageComponent";
|
|
15
|
+
export const ResizableImage = Image.extend({
|
|
16
|
+
name: "resizableImage",
|
|
17
|
+
addAttributes() {
|
|
18
|
+
var _a;
|
|
19
|
+
return Object.assign(Object.assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { width: {
|
|
20
|
+
default: null,
|
|
21
|
+
}, height: {
|
|
22
|
+
default: null,
|
|
23
|
+
} });
|
|
24
|
+
},
|
|
25
|
+
renderHTML({ HTMLAttributes }) {
|
|
26
|
+
const { width, height } = HTMLAttributes, rest = __rest(HTMLAttributes, ["width", "height"]);
|
|
27
|
+
const style = [];
|
|
28
|
+
if (width)
|
|
29
|
+
style.push(`width: ${width}px`);
|
|
30
|
+
if (height)
|
|
31
|
+
style.push(`height: ${height}px`);
|
|
32
|
+
return [
|
|
33
|
+
"img",
|
|
34
|
+
Object.assign(Object.assign({}, rest), { style: style.join("; ") }),
|
|
35
|
+
];
|
|
36
|
+
},
|
|
37
|
+
addNodeView() {
|
|
38
|
+
return ReactNodeViewRenderer(ResizableImageComponent);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { NodeViewRendererProps } from "@tiptap/react";
|
|
3
|
+
interface ResizableImageProps extends NodeViewRendererProps {
|
|
4
|
+
updateAttributes: (attrs: {
|
|
5
|
+
width?: number | null;
|
|
6
|
+
height?: number | null;
|
|
7
|
+
}) => void;
|
|
8
|
+
selected?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const ResizableImageComponent: React.FC<ResizableImageProps>;
|
|
11
|
+
export default ResizableImageComponent;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from "react";
|
|
2
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
3
|
+
const ResizableImageComponent = ({ node, updateAttributes, selected, }) => {
|
|
4
|
+
const { src, alt, title, width, height } = node.attrs;
|
|
5
|
+
const wrapperRef = useRef(null);
|
|
6
|
+
const imgRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const img = imgRef.current;
|
|
9
|
+
if (!img)
|
|
10
|
+
return;
|
|
11
|
+
const observer = new ResizeObserver(() => {
|
|
12
|
+
const w = Math.round(img.offsetWidth);
|
|
13
|
+
const h = Math.round(img.offsetHeight);
|
|
14
|
+
updateAttributes({ width: w, height: h });
|
|
15
|
+
});
|
|
16
|
+
observer.observe(img);
|
|
17
|
+
return () => observer.disconnect();
|
|
18
|
+
}, [updateAttributes]);
|
|
19
|
+
return (<NodeViewWrapper ref={wrapperRef} contentEditable={false} className={`resizable-image-wrapper ${selected ? "ProseMirror-selectednode" : ""}`} style={{
|
|
20
|
+
resize: "both",
|
|
21
|
+
overflow: "auto",
|
|
22
|
+
border: "1px solid #ccc",
|
|
23
|
+
padding: 2,
|
|
24
|
+
display: "inline-block",
|
|
25
|
+
maxWidth: "100%",
|
|
26
|
+
}}>
|
|
27
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
28
|
+
<img ref={imgRef} src={src} alt={alt !== null && alt !== void 0 ? alt : ""} title={title !== null && title !== void 0 ? title : ""} loading="lazy" style={{
|
|
29
|
+
width: width ? `${width}px` : "auto",
|
|
30
|
+
height: height ? `${height}px` : "auto",
|
|
31
|
+
display: "block",
|
|
32
|
+
userSelect: "none",
|
|
33
|
+
pointerEvents: "auto",
|
|
34
|
+
}} draggable={false}/>
|
|
35
|
+
</NodeViewWrapper>);
|
|
36
|
+
};
|
|
37
|
+
export default ResizableImageComponent;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from "@tiptap/core";
|
|
2
|
+
declare module "@tiptap/core" {
|
|
3
|
+
interface Commands<ReturnType> {
|
|
4
|
+
video: {
|
|
5
|
+
setVideo: (options: {
|
|
6
|
+
src: string;
|
|
7
|
+
controls?: boolean;
|
|
8
|
+
}) => ReturnType;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export declare const ResizableVideo: Node<any, any>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { Node } from "@tiptap/core";
|
|
13
|
+
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
14
|
+
import ResizableVideoComponent from "./ResizableVideoComponent";
|
|
15
|
+
export const ResizableVideo = Node.create({
|
|
16
|
+
name: "video",
|
|
17
|
+
group: "block",
|
|
18
|
+
draggable: true,
|
|
19
|
+
atom: true,
|
|
20
|
+
addAttributes() {
|
|
21
|
+
return {
|
|
22
|
+
src: { default: null },
|
|
23
|
+
controls: {
|
|
24
|
+
default: true,
|
|
25
|
+
parseHTML: (element) => element.hasAttribute("controls"),
|
|
26
|
+
renderHTML: (attributes) => attributes.controls ? { controls: "controls" } : {},
|
|
27
|
+
},
|
|
28
|
+
width: {
|
|
29
|
+
default: null,
|
|
30
|
+
},
|
|
31
|
+
height: {
|
|
32
|
+
default: null,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
parseHTML() {
|
|
37
|
+
return [{ tag: "video[src]" }];
|
|
38
|
+
},
|
|
39
|
+
renderHTML({ HTMLAttributes }) {
|
|
40
|
+
const { width, height } = HTMLAttributes, rest = __rest(HTMLAttributes, ["width", "height"]);
|
|
41
|
+
const style = [];
|
|
42
|
+
if (width)
|
|
43
|
+
style.push(`width: ${width}px`);
|
|
44
|
+
if (height)
|
|
45
|
+
style.push(`height: ${height}px`);
|
|
46
|
+
return ["video", Object.assign(Object.assign({}, rest), { style: style.join("; ") })];
|
|
47
|
+
},
|
|
48
|
+
addCommands() {
|
|
49
|
+
return {
|
|
50
|
+
setVideo: (attributes) => ({ commands }) => {
|
|
51
|
+
return commands.insertContent({
|
|
52
|
+
type: this.name,
|
|
53
|
+
attrs: attributes,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
addNodeView() {
|
|
59
|
+
return ReactNodeViewRenderer(ResizableVideoComponent);
|
|
60
|
+
},
|
|
61
|
+
});
|