generated from template/vite-react-template
init markdown
This commit is contained in:
parent
3600defda1
commit
f724074b20
49
package.json
49
package.json
@ -1,50 +1,77 @@
|
||||
{
|
||||
"name": "vite-react",
|
||||
"name": "@kevisual/markdown-editor",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"basename": "/",
|
||||
"basename": "/root/markdown-editor",
|
||||
"app": {
|
||||
"key": "markdown-editor"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:css": "tailwindcss -i ./src/index.css -o ./dist/render.css --minify",
|
||||
"postbuild2": "pnpm build:css",
|
||||
"postbuild": "pnpm build:css",
|
||||
"preview": "vite preview",
|
||||
"pub": "envision deploy ./dist -k vite-react -v 0.0.1",
|
||||
"pub": "envision deploy ./dist -k markdown-editor -v 0.0.1",
|
||||
"dev:lib": "turbo dev"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kevisual/router": "0.0.9",
|
||||
"@kevisual/router": "0.0.10",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.487.0",
|
||||
"@tiptap/core": "^2.11.7",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.11.7",
|
||||
"@tiptap/extension-document": "^2.11.7",
|
||||
"@tiptap/extension-highlight": "^2.11.7",
|
||||
"@tiptap/extension-paragraph": "^2.11.7",
|
||||
"@tiptap/extension-placeholder": "^2.11.7",
|
||||
"@tiptap/extension-text": "^2.11.7",
|
||||
"@tiptap/extension-typography": "^2.11.7",
|
||||
"@tiptap/pm": "^2.11.7",
|
||||
"@tiptap/starter-kit": "^2.11.7",
|
||||
"@tiptap/suggestion": "^2.11.7",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"idb": "^8.0.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"immer": "^10.1.1",
|
||||
"lowlight": "^3.3.0",
|
||||
"marked": "^15.0.7",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"nanoid": "^5.1.5",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/query": "0.0.15",
|
||||
"@kevisual/types": "^0.0.6",
|
||||
"@tailwindcss/vite": "^4.1.1",
|
||||
"@types/node": "^22.13.17",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.1",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.4"
|
||||
"tailwindcss": "^4.1.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.2.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.7.1"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
packages:
|
||||
- 'submodules/*'
|
||||
- 'packages/*'
|
@ -1,11 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const root = process.cwd();
|
||||
|
||||
export const clearWorkspace = () => {
|
||||
const files = ['submodules', 'packages', 'pnpm-workspace.yaml', 'turbo.json'];
|
||||
for (const file of files) {
|
||||
fs.rmSync(path.join(root, file), { recursive: true, force: true });
|
||||
}
|
||||
};
|
@ -1 +1,30 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
@import 'github-markdown-css/github-markdown.css';
|
||||
@import 'highlight.js/styles/github.css';
|
||||
@import './tiptap/tiptap.css';
|
||||
|
||||
.markdown-body,
|
||||
.tiptap {
|
||||
ul,
|
||||
li {
|
||||
list-style: unset;
|
||||
}
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror.ProseMirror-focused {
|
||||
outline: none;
|
||||
}
|
||||
.ProseMirror p.is-empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: #aaa; /* Adjust the color as needed */
|
||||
font-style: italic; /* Optional: make the placeholder italic */
|
||||
pointer-events: none; /* Ensure the placeholder is not interactive */
|
||||
height: 0; /* Ensure it doesn't affect layout */
|
||||
display: block; /* Ensure it displays as a block element */
|
||||
}
|
||||
.tiptap.ProseMirror {
|
||||
border: none;
|
||||
}
|
||||
|
112
src/tiptap/components/CommandsList.tsx
Normal file
112
src/tiptap/components/CommandsList.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { CommandItem } from '../extensions/suggestions/commands';
|
||||
|
||||
interface CommandsListProps {
|
||||
items: CommandItem[];
|
||||
command: (props: { content: string }) => void;
|
||||
}
|
||||
|
||||
export const CommandsList = forwardRef((props: CommandsListProps, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index: number) => {
|
||||
const item = props.items[index];
|
||||
|
||||
if (item) {
|
||||
props.command({ content: item.content });
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
|
||||
};
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
||||
};
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex);
|
||||
};
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }: { event: KeyboardEvent }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
// Scroll to selected item when it changes
|
||||
useEffect(() => {
|
||||
const element = document.getElementById(`command-item-${selectedIndex}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
}
|
||||
}, [selectedIndex]);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-md shadow-lg border border-gray-200 overflow-hidden" style={{ width: '350px', maxHeight: '80vh' }}>
|
||||
<div className="p-2 bg-gray-50 border-b border-gray-200 sticky top-0 z-10">
|
||||
<div className="text-sm font-medium text-gray-700">Commands ({props.items.length})</div>
|
||||
<div className="text-xs text-gray-500">Type to filter commands</div>
|
||||
</div>
|
||||
|
||||
<div className="max-h-72 overflow-y-auto">
|
||||
{props.items.length ? (
|
||||
props.items.map((item, index) => (
|
||||
<button
|
||||
id={`command-item-${index}`}
|
||||
key={index}
|
||||
className={`block w-full text-left px-4 py-2 text-sm transition-colors ${
|
||||
index === selectedIndex ? 'bg-blue-100 border-l-4 border-blue-500' : 'border-l-4 border-transparent'
|
||||
} hover:bg-gray-50`}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
<div className="font-medium flex items-center">
|
||||
!{item.title}
|
||||
{index === selectedIndex && (
|
||||
<span className="ml-2 text-xs bg-blue-500 text-white px-2 py-0.5 rounded">
|
||||
Press Enter to select
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs">{item.description}</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="px-4 py-2 text-sm text-gray-500">No results</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 px-3 py-2 text-xs text-gray-500 border-t flex justify-between items-center sticky bottom-0 z-10">
|
||||
<span className="inline-flex items-center">
|
||||
<kbd className="px-2 py-1 bg-white rounded border border-gray-300 shadow-sm mr-1">↑</kbd>
|
||||
<kbd className="px-2 py-1 bg-white rounded border border-gray-300 shadow-sm">↓</kbd>
|
||||
<span className="ml-1">to navigate</span>
|
||||
</span>
|
||||
<span className="inline-flex items-center">
|
||||
<kbd className="px-2 py-1 bg-white rounded border border-gray-300 shadow-sm">Enter</kbd>
|
||||
<span className="ml-1">to select</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CommandsList.displayName = 'CommandsList';
|
42
src/tiptap/components/ReactRenderer.tsx
Normal file
42
src/tiptap/components/ReactRenderer.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
export class ReactRenderer {
|
||||
component: any;
|
||||
element: HTMLElement;
|
||||
ref: React.RefObject<any>;
|
||||
props: any;
|
||||
editor: any;
|
||||
root: any;
|
||||
|
||||
constructor(component: any, { props, editor }: any) {
|
||||
this.component = component;
|
||||
this.element = document.createElement('div');
|
||||
this.ref = React.createRef();
|
||||
this.props = {
|
||||
...props,
|
||||
ref: this.ref,
|
||||
};
|
||||
this.editor = editor;
|
||||
this.root = createRoot(this.element);
|
||||
this.render();
|
||||
}
|
||||
|
||||
updateProps(props: any) {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...props,
|
||||
};
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.root.render(React.createElement(this.component, this.props));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactRenderer;
|
136
src/tiptap/editor.ts
Normal file
136
src/tiptap/editor.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { Editor } from '@tiptap/core';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Highlight from '@tiptap/extension-highlight';
|
||||
import Typography from '@tiptap/extension-typography';
|
||||
import { Markdown } from 'tiptap-markdown';
|
||||
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import { Commands, getSuggestionItems, createSuggestionConfig, CommandItem } from './extensions/suggestions';
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
||||
import { all, createLowlight } from 'lowlight';
|
||||
// import 'highlight.js/styles/github.css';
|
||||
// 根据需要引入的语言支持
|
||||
import js from 'highlight.js/lib/languages/javascript';
|
||||
import ts from 'highlight.js/lib/languages/typescript';
|
||||
import html from 'highlight.js/lib/languages/xml';
|
||||
import css from 'highlight.js/lib/languages/css';
|
||||
import markdown from 'highlight.js/lib/languages/markdown';
|
||||
// import './editor.css';
|
||||
|
||||
const lowlight = createLowlight(all);
|
||||
|
||||
// you can also register individual languages
|
||||
lowlight.register('html', html);
|
||||
lowlight.register('css', css);
|
||||
lowlight.register('js', js);
|
||||
lowlight.register('ts', ts);
|
||||
lowlight.register('markdown', markdown);
|
||||
|
||||
export class TextEditor {
|
||||
private editor?: Editor;
|
||||
private opts?: { markdown?: string; html?: string; items?: CommandItem[]; onUpdateHtml?: (html: string) => void };
|
||||
private element?: HTMLElement;
|
||||
private isInitialSetup: boolean = true;
|
||||
|
||||
constructor() {}
|
||||
createEditor(el: HTMLElement, opts?: { markdown?: string; html?: string; items?: CommandItem[]; onUpdateHtml?: (html: string) => void }) {
|
||||
if (this.editor) {
|
||||
this.destroy();
|
||||
}
|
||||
this.opts = opts;
|
||||
this.element = el;
|
||||
const html = opts?.html || '';
|
||||
const items = opts?.items || getSuggestionItems();
|
||||
const suggestionConfig = createSuggestionConfig(items);
|
||||
this.isInitialSetup = true;
|
||||
this.editor = new Editor({
|
||||
element: el, // 指定编辑器容器
|
||||
extensions: [
|
||||
StarterKit, // 使用 StarterKit 包含基础功能
|
||||
Highlight,
|
||||
Placeholder.configure({
|
||||
placeholder: 'Type @ to see commands (e.g., @today, @list @test )...',
|
||||
}),
|
||||
Typography,
|
||||
Markdown,
|
||||
CodeBlockLowlight.extend({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Tab: () => {
|
||||
const { state, dispatch } = this.editor.view;
|
||||
const { tr, selection } = state;
|
||||
const { from, to } = selection;
|
||||
|
||||
// 插入4个空格的缩进
|
||||
dispatch(tr.insertText(' ', from, to));
|
||||
return true;
|
||||
},
|
||||
'Shift-Tab': () => {
|
||||
const { state, dispatch } = this.editor.view;
|
||||
const { tr, selection } = state;
|
||||
const { from, to } = selection;
|
||||
|
||||
// 获取当前选中的文本
|
||||
const selectedText = state.doc.textBetween(from, to, '\n');
|
||||
|
||||
// 取消缩进:移除前面的4个空格
|
||||
const unindentedText = selectedText.replace(/^ {1,4}/gm, '');
|
||||
dispatch(tr.insertText(unindentedText, from, to));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
}).configure({
|
||||
lowlight,
|
||||
}),
|
||||
Commands.configure({
|
||||
suggestion: suggestionConfig,
|
||||
}),
|
||||
],
|
||||
content: html, // 初始化内容,
|
||||
onUpdate: () => {
|
||||
if (this.isInitialSetup) {
|
||||
this.isInitialSetup = false;
|
||||
return;
|
||||
}
|
||||
if (this.opts?.onUpdateHtml) {
|
||||
this.opts.onUpdateHtml(this.editor?.getHTML() || '');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
updateSugestionConfig(items: CommandItem[]) {
|
||||
if (!this.element) return;
|
||||
const element = this.element;
|
||||
if (this.editor) {
|
||||
const content = this.editor.getHTML(); // Save current content
|
||||
const opts = { ...this.opts, html: content, items };
|
||||
this.createEditor(element, opts); // Recreate the editor with the new config
|
||||
}
|
||||
}
|
||||
setContent(html: string, emitUpdate?: boolean) {
|
||||
this.editor?.commands.setContent(html, emitUpdate);
|
||||
}
|
||||
/**
|
||||
* before set options ,you should has element and editor
|
||||
* @param opts
|
||||
*/
|
||||
setOptions(opts: { markdown?: string; html?: string; items?: CommandItem[]; onUpdateHtml?: (html: string) => void }) {
|
||||
this.opts = { ...this.opts, ...opts };
|
||||
this.createEditor(this.element!, this.opts!);
|
||||
}
|
||||
getHtml() {
|
||||
return this.editor?.getHTML();
|
||||
}
|
||||
getContent() {
|
||||
return this.editor?.getText();
|
||||
}
|
||||
foucus() {
|
||||
this.editor?.view?.focus?.();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.editor?.destroy();
|
||||
this.editor = undefined;
|
||||
}
|
||||
}
|
40
src/tiptap/extensions/suggestions/commands.ts
Normal file
40
src/tiptap/extensions/suggestions/commands.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Extension } from '@tiptap/core';
|
||||
import Suggestion from '@tiptap/suggestion';
|
||||
import { PluginKey } from '@tiptap/pm/state';
|
||||
|
||||
export const CommandsPluginKey = new PluginKey('commands');
|
||||
|
||||
export interface CommandItem {
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const Commands = Extension.create({
|
||||
name: 'commands',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
suggestion: {
|
||||
char: '@',
|
||||
command: ({ editor, range, props }: any) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertContent(props.content)
|
||||
.run();
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
Suggestion({
|
||||
editor: this.editor,
|
||||
...this.options.suggestion,
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
3
src/tiptap/extensions/suggestions/index.ts
Normal file
3
src/tiptap/extensions/suggestions/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './commands';
|
||||
export * from './suggestionConfig';
|
||||
export * from './suggestionItems';
|
121
src/tiptap/extensions/suggestions/suggestionConfig.ts
Normal file
121
src/tiptap/extensions/suggestions/suggestionConfig.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { CommandItem } from './commands';
|
||||
import { CommandsList } from '../../components/CommandsList';
|
||||
import ReactRenderer from '../../components/ReactRenderer';
|
||||
|
||||
export const createSuggestionConfig = (items: CommandItem[]) => {
|
||||
return {
|
||||
items: ({ query }: { query: string }) => {
|
||||
return items.filter(item => item.title.toLowerCase().startsWith(query.toLowerCase()));
|
||||
},
|
||||
render: () => {
|
||||
let component: ReactRenderer | null = null;
|
||||
let popup: HTMLElement | null = null;
|
||||
|
||||
const calculatePosition = (view: any, from: number) => {
|
||||
const coords = view.coordsAtPos(from);
|
||||
const editorRect = view.dom.getBoundingClientRect();
|
||||
const popupRect = popup?.getBoundingClientRect();
|
||||
|
||||
if (!popup || !popupRect) return { left: coords.left, top: coords.bottom + 10 };
|
||||
|
||||
// Default position below the cursor
|
||||
let left = coords.left;
|
||||
let top = coords.bottom + 10;
|
||||
|
||||
// Check if we're near the bottom of the viewport
|
||||
const viewportHeight = window.innerHeight;
|
||||
const bottomSpace = viewportHeight - coords.bottom;
|
||||
const popupHeight = popupRect.height;
|
||||
|
||||
// If there's not enough space below, position above
|
||||
if (bottomSpace < popupHeight + 10 && coords.top > popupHeight + 10) {
|
||||
top = coords.top - popupHeight - 10;
|
||||
}
|
||||
|
||||
// Check if we're near the right edge of the viewport
|
||||
const viewportWidth = window.innerWidth;
|
||||
const rightSpace = viewportWidth - coords.left;
|
||||
const popupWidth = popupRect.width;
|
||||
|
||||
// If there's not enough space to the right, align right edge
|
||||
if (rightSpace < popupWidth) {
|
||||
left = Math.max(10, viewportWidth - popupWidth - 10);
|
||||
}
|
||||
|
||||
// Ensure popup stays within editor bounds horizontally if possible
|
||||
if (left < editorRect.left) {
|
||||
left = editorRect.left;
|
||||
}
|
||||
|
||||
return { left, top };
|
||||
};
|
||||
|
||||
return {
|
||||
onStart: (props: any) => {
|
||||
component = new ReactRenderer(CommandsList, {
|
||||
props,
|
||||
editor: props.editor,
|
||||
});
|
||||
|
||||
popup = document.createElement('div');
|
||||
popup.className = 'commands-popup';
|
||||
popup.style.position = 'fixed'; // Use fixed instead of absolute for better viewport positioning
|
||||
popup.style.zIndex = '9999';
|
||||
document.body.appendChild(popup);
|
||||
|
||||
popup.appendChild(component.element);
|
||||
|
||||
// Initial position
|
||||
const { view } = props.editor;
|
||||
const { from } = props.range;
|
||||
|
||||
// Set initial position to get popup dimensions
|
||||
popup.style.left = '0px';
|
||||
popup.style.top = '0px';
|
||||
|
||||
// Calculate proper position after the popup is rendered
|
||||
setTimeout(() => {
|
||||
if (!popup) return;
|
||||
const { left, top } = calculatePosition(view, from);
|
||||
popup.style.left = `${left}px`;
|
||||
popup.style.top = `${top}px`;
|
||||
}, 0);
|
||||
},
|
||||
onUpdate: (props: any) => {
|
||||
if (!component) return;
|
||||
|
||||
component.updateProps(props);
|
||||
|
||||
if (!popup) return;
|
||||
|
||||
// Update position
|
||||
const { view } = props.editor;
|
||||
const { from } = props.range;
|
||||
const { left, top } = calculatePosition(view, from);
|
||||
|
||||
popup.style.left = `${left}px`;
|
||||
popup.style.top = `${top}px`;
|
||||
},
|
||||
onKeyDown: (props: any) => {
|
||||
if (props.event.key === 'Escape') {
|
||||
if (popup) popup.remove();
|
||||
if (component) component.destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (component && component.ref && component.ref.current) {
|
||||
return component.ref.current.onKeyDown(props);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
onExit: () => {
|
||||
if (popup) popup.remove();
|
||||
if (component) component.destroy();
|
||||
component = null;
|
||||
popup = null;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
203
src/tiptap/extensions/suggestions/suggestionItems.ts
Normal file
203
src/tiptap/extensions/suggestions/suggestionItems.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { CommandItem } from './commands';
|
||||
|
||||
export const getSuggestionItems = (): CommandItem[] => {
|
||||
// Basic commands
|
||||
const basicCommands = [
|
||||
{
|
||||
title: 'today',
|
||||
description: 'Insert today\'s date',
|
||||
content: new Date().toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
title: 'now',
|
||||
description: 'Insert current time',
|
||||
content: new Date().toLocaleTimeString(),
|
||||
},
|
||||
{
|
||||
title: 'datetime',
|
||||
description: 'Insert current date and time',
|
||||
content: new Date().toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: 'list',
|
||||
description: 'Insert a bullet list',
|
||||
content: '<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'numbered',
|
||||
description: 'Insert a numbered list',
|
||||
content: '<ol><li>First item</li><li>Second item</li><li>Third item</li></ol>',
|
||||
},
|
||||
{
|
||||
title: 'good',
|
||||
description: 'Insert a positive message',
|
||||
content: 'Great job! Keep up the good work! 👍',
|
||||
},
|
||||
{
|
||||
title: 'meeting',
|
||||
description: 'Insert meeting template',
|
||||
content: '<h3>Meeting Notes</h3><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Attendees:</strong></p><ul><li>Person 1</li><li>Person 2</li></ul><p><strong>Agenda:</strong></p><ol><li>Topic 1</li><li>Topic 2</li></ol><p><strong>Action Items:</strong></p><ul><li>[ ] Task 1</li><li>[ ] Task 2</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'signature',
|
||||
description: 'Insert your signature',
|
||||
content: '<p>Best regards,<br>Your Name<br>your.email@example.com</p>',
|
||||
},
|
||||
];
|
||||
|
||||
// Text formatting commands
|
||||
const formattingCommands = [
|
||||
{
|
||||
title: 'h1',
|
||||
description: 'Insert heading 1',
|
||||
content: '<h1>Heading 1</h1>',
|
||||
},
|
||||
{
|
||||
title: 'h2',
|
||||
description: 'Insert heading 2',
|
||||
content: '<h2>Heading 2</h2>',
|
||||
},
|
||||
{
|
||||
title: 'h3',
|
||||
description: 'Insert heading 3',
|
||||
content: '<h3>Heading 3</h3>',
|
||||
},
|
||||
{
|
||||
title: 'quote',
|
||||
description: 'Insert blockquote',
|
||||
content: '<blockquote>This is a quote</blockquote>',
|
||||
},
|
||||
{
|
||||
title: 'code',
|
||||
description: 'Insert code block',
|
||||
content: '<pre><code>// Your code here\nconsole.log("Hello world");</code></pre>',
|
||||
},
|
||||
{
|
||||
title: 'bold',
|
||||
description: 'Insert bold text',
|
||||
content: '<strong>Bold text</strong>',
|
||||
},
|
||||
{
|
||||
title: 'italic',
|
||||
description: 'Insert italic text',
|
||||
content: '<em>Italic text</em>',
|
||||
},
|
||||
{
|
||||
title: 'underline',
|
||||
description: 'Insert underlined text',
|
||||
content: '<u>Underlined text</u>',
|
||||
},
|
||||
{
|
||||
title: 'strike',
|
||||
description: 'Insert strikethrough text',
|
||||
content: '<s>Strikethrough text</s>',
|
||||
},
|
||||
{
|
||||
title: 'highlight',
|
||||
description: 'Insert highlighted text',
|
||||
content: '<mark>Highlighted text</mark>',
|
||||
},
|
||||
];
|
||||
|
||||
// Template commands
|
||||
const templateCommands = [
|
||||
{
|
||||
title: 'email',
|
||||
description: 'Insert email template',
|
||||
content: '<p>Subject: [Your Subject]</p><p>Dear [Name],</p><p>I hope this email finds you well.</p><p>[Your message here]</p><p>Thank you for your time and consideration.</p><p>Best regards,<br>Your Name</p>',
|
||||
},
|
||||
{
|
||||
title: 'letter',
|
||||
description: 'Insert formal letter template',
|
||||
content: '<p>[Your Name]<br>[Your Address]<br>[City, State ZIP]<br>[Your Email]<br>[Your Phone]</p><p>[Date]</p><p>[Recipient Name]<br>[Recipient Title]<br>[Company Name]<br>[Street Address]<br>[City, State ZIP]</p><p>Dear [Recipient Name],</p><p>[Letter content]</p><p>Sincerely,</p><p>[Your Name]</p>',
|
||||
},
|
||||
{
|
||||
title: 'report',
|
||||
description: 'Insert report template',
|
||||
content: '<h1>Report Title</h1><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Author:</strong> Your Name</p><h2>Executive Summary</h2><p>[Brief summary of the report]</p><h2>Introduction</h2><p>[Introduction text]</p><h2>Findings</h2><p>[Detailed findings]</p><h2>Conclusion</h2><p>[Conclusion text]</p><h2>Recommendations</h2><p>[Recommendations]</p>',
|
||||
},
|
||||
{
|
||||
title: 'proposal',
|
||||
description: 'Insert proposal template',
|
||||
content: '<h1>Project Proposal</h1><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Prepared by:</strong> Your Name</p><h2>Project Overview</h2><p>[Brief description of the project]</p><h2>Objectives</h2><ul><li>[Objective 1]</li><li>[Objective 2]</li></ul><h2>Scope of Work</h2><p>[Detailed scope]</p><h2>Timeline</h2><p>[Project timeline]</p><h2>Budget</h2><p>[Budget details]</p>',
|
||||
},
|
||||
{
|
||||
title: 'invoice',
|
||||
description: 'Insert invoice template',
|
||||
content: '<h1>INVOICE</h1><p><strong>Invoice #:</strong> [Number]</p><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Due Date:</strong> [Due Date]</p><div><strong>From:</strong><br>[Your Name/Company]<br>[Your Address]<br>[Your Contact Info]</div><div><strong>To:</strong><br>[Client Name/Company]<br>[Client Address]</div><table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="text-align:left; padding: 8px;">Description</th><th style="text-align:right; padding: 8px;">Amount</th></tr><tr style="border-bottom: 1px solid #ddd;"><td style="padding: 8px;">[Item/Service Description]</td><td style="text-align:right; padding: 8px;">[Amount]</td></tr><tr><td style="text-align:right; padding: 8px;"><strong>Total</strong></td><td style="text-align:right; padding: 8px;"><strong>[Total Amount]</strong></td></tr></table><p><strong>Payment Terms:</strong> [Terms]</p><p><strong>Payment Method:</strong> [Method]</p>',
|
||||
},
|
||||
];
|
||||
|
||||
// Task management commands
|
||||
const taskCommands = [
|
||||
{
|
||||
title: 'todo',
|
||||
description: 'Insert todo list',
|
||||
content: '<h3>To-Do List</h3><ul><li>[ ] Task 1</li><li>[ ] Task 2</li><li>[ ] Task 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'checklist',
|
||||
description: 'Insert checklist',
|
||||
content: '<h3>Checklist</h3><ul><li>[ ] Item 1</li><li>[ ] Item 2</li><li>[ ] Item 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'progress',
|
||||
description: 'Insert progress tracker',
|
||||
content: '<h3>Project Progress</h3><ul><li>[x] Planning - Complete</li><li>[x] Research - Complete</li><li>[ ] Implementation - In Progress</li><li>[ ] Testing</li><li>[ ] Deployment</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'timeline',
|
||||
description: 'Insert project timeline',
|
||||
content: '<h3>Project Timeline</h3><ul><li><strong>Week 1:</strong> Planning and Research</li><li><strong>Week 2-3:</strong> Design and Development</li><li><strong>Week 4:</strong> Testing</li><li><strong>Week 5:</strong> Deployment</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'goals',
|
||||
description: 'Insert goals list',
|
||||
content: '<h3>Goals</h3><ol><li>Short-term goal 1</li><li>Short-term goal 2</li><li>Long-term goal 1</li><li>Long-term goal 2</li></ol>',
|
||||
},
|
||||
];
|
||||
|
||||
// Table commands
|
||||
const tableCommands = [
|
||||
{
|
||||
title: 'table2x2',
|
||||
description: 'Insert 2x2 table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Header 1</th><th style="border: 1px solid #ddd; padding: 8px;">Header 2</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 2</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 2</td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'table3x3',
|
||||
description: 'Insert 3x3 table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Header 1</th><th style="border: 1px solid #ddd; padding: 8px;">Header 2</th><th style="border: 1px solid #ddd; padding: 8px;">Header 3</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 3</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 3</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 3</td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'schedule',
|
||||
description: 'Insert schedule table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Time</th><th style="border: 1px solid #ddd; padding: 8px;">Monday</th><th style="border: 1px solid #ddd; padding: 8px;">Tuesday</th><th style="border: 1px solid #ddd; padding: 8px;">Wednesday</th><th style="border: 1px solid #ddd; padding: 8px;">Thursday</th><th style="border: 1px solid #ddd; padding: 8px;">Friday</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">9:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">10:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">11:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'comparison',
|
||||
description: 'Insert comparison table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Feature</th><th style="border: 1px solid #ddd; padding: 8px;">Option A</th><th style="border: 1px solid #ddd; padding: 8px;">Option B</th><th style="border: 1px solid #ddd; padding: 8px;">Option C</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 1</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 2</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✗</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 3</td><td style="border: 1px solid #ddd; padding: 8px;">✗</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Price</td><td style="border: 1px solid #ddd; padding: 8px;">$</td><td style="border: 1px solid #ddd; padding: 8px;">$$</td><td style="border: 1px solid #ddd; padding: 8px;">$$$</td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
// Additional commands to reach 100 total
|
||||
const additionalCommands = Array.from({ length: 100 - (basicCommands.length + formattingCommands.length + templateCommands.length + taskCommands.length + tableCommands.length) }, (_, i) => {
|
||||
const index = i + 1;
|
||||
return {
|
||||
title: `command${index}`,
|
||||
description: `Example command ${index}`,
|
||||
content: `<p>This is example command ${index}</p>`,
|
||||
};
|
||||
});
|
||||
|
||||
// Combine all command categories
|
||||
return [
|
||||
...basicCommands,
|
||||
...formattingCommands,
|
||||
...templateCommands,
|
||||
...taskCommands,
|
||||
...tableCommands,
|
||||
...additionalCommands,
|
||||
];
|
||||
};
|
203
src/tiptap/extensions/suggestions/suggestions.ts
Normal file
203
src/tiptap/extensions/suggestions/suggestions.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { CommandItem } from './commands';
|
||||
|
||||
export const getSuggestionItems = (): CommandItem[] => {
|
||||
// Basic commands
|
||||
const basicCommands = [
|
||||
{
|
||||
title: 'today',
|
||||
description: 'Insert today\'s date',
|
||||
content: new Date().toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
title: 'now',
|
||||
description: 'Insert current time',
|
||||
content: new Date().toLocaleTimeString(),
|
||||
},
|
||||
{
|
||||
title: 'datetime',
|
||||
description: 'Insert current date and time',
|
||||
content: new Date().toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: 'list',
|
||||
description: 'Insert a bullet list',
|
||||
content: '<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'numbered',
|
||||
description: 'Insert a numbered list',
|
||||
content: '<ol><li>First item</li><li>Second item</li><li>Third item</li></ol>',
|
||||
},
|
||||
{
|
||||
title: 'good',
|
||||
description: 'Insert a positive message',
|
||||
content: 'Great job! Keep up the good work! 👍',
|
||||
},
|
||||
{
|
||||
title: 'meeting',
|
||||
description: 'Insert meeting template',
|
||||
content: '<h3>Meeting Notes</h3><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Attendees:</strong></p><ul><li>Person 1</li><li>Person 2</li></ul><p><strong>Agenda:</strong></p><ol><li>Topic 1</li><li>Topic 2</li></ol><p><strong>Action Items:</strong></p><ul><li>[ ] Task 1</li><li>[ ] Task 2</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'signature',
|
||||
description: 'Insert your signature',
|
||||
content: '<p>Best regards,<br>Your Name<br>your.email@example.com</p>',
|
||||
},
|
||||
];
|
||||
|
||||
// Text formatting commands
|
||||
const formattingCommands = [
|
||||
{
|
||||
title: 'h1',
|
||||
description: 'Insert heading 1',
|
||||
content: '<h1>Heading 1</h1>',
|
||||
},
|
||||
{
|
||||
title: 'h2',
|
||||
description: 'Insert heading 2',
|
||||
content: '<h2>Heading 2</h2>',
|
||||
},
|
||||
{
|
||||
title: 'h3',
|
||||
description: 'Insert heading 3',
|
||||
content: '<h3>Heading 3</h3>',
|
||||
},
|
||||
{
|
||||
title: 'quote',
|
||||
description: 'Insert blockquote',
|
||||
content: '<blockquote>This is a quote</blockquote>',
|
||||
},
|
||||
{
|
||||
title: 'code',
|
||||
description: 'Insert code block',
|
||||
content: '<pre><code>// Your code here\nconsole.log("Hello world");</code></pre>',
|
||||
},
|
||||
{
|
||||
title: 'bold',
|
||||
description: 'Insert bold text',
|
||||
content: '<strong>Bold text</strong>',
|
||||
},
|
||||
{
|
||||
title: 'italic',
|
||||
description: 'Insert italic text',
|
||||
content: '<em>Italic text</em>',
|
||||
},
|
||||
{
|
||||
title: 'underline',
|
||||
description: 'Insert underlined text',
|
||||
content: '<u>Underlined text</u>',
|
||||
},
|
||||
{
|
||||
title: 'strike',
|
||||
description: 'Insert strikethrough text',
|
||||
content: '<s>Strikethrough text</s>',
|
||||
},
|
||||
{
|
||||
title: 'highlight',
|
||||
description: 'Insert highlighted text',
|
||||
content: '<mark>Highlighted text</mark>',
|
||||
},
|
||||
];
|
||||
|
||||
// Template commands
|
||||
const templateCommands = [
|
||||
{
|
||||
title: 'email',
|
||||
description: 'Insert email template',
|
||||
content: '<p>Subject: [Your Subject]</p><p>Dear [Name],</p><p>I hope this email finds you well.</p><p>[Your message here]</p><p>Thank you for your time and consideration.</p><p>Best regards,<br>Your Name</p>',
|
||||
},
|
||||
{
|
||||
title: 'letter',
|
||||
description: 'Insert formal letter template',
|
||||
content: '<p>[Your Name]<br>[Your Address]<br>[City, State ZIP]<br>[Your Email]<br>[Your Phone]</p><p>[Date]</p><p>[Recipient Name]<br>[Recipient Title]<br>[Company Name]<br>[Street Address]<br>[City, State ZIP]</p><p>Dear [Recipient Name],</p><p>[Letter content]</p><p>Sincerely,</p><p>[Your Name]</p>',
|
||||
},
|
||||
{
|
||||
title: 'report',
|
||||
description: 'Insert report template',
|
||||
content: '<h1>Report Title</h1><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Author:</strong> Your Name</p><h2>Executive Summary</h2><p>[Brief summary of the report]</p><h2>Introduction</h2><p>[Introduction text]</p><h2>Findings</h2><p>[Detailed findings]</p><h2>Conclusion</h2><p>[Conclusion text]</p><h2>Recommendations</h2><p>[Recommendations]</p>',
|
||||
},
|
||||
{
|
||||
title: 'proposal',
|
||||
description: 'Insert proposal template',
|
||||
content: '<h1>Project Proposal</h1><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Prepared by:</strong> Your Name</p><h2>Project Overview</h2><p>[Brief description of the project]</p><h2>Objectives</h2><ul><li>[Objective 1]</li><li>[Objective 2]</li></ul><h2>Scope of Work</h2><p>[Detailed scope]</p><h2>Timeline</h2><p>[Project timeline]</p><h2>Budget</h2><p>[Budget details]</p>',
|
||||
},
|
||||
{
|
||||
title: 'invoice',
|
||||
description: 'Insert invoice template',
|
||||
content: '<h1>INVOICE</h1><p><strong>Invoice #:</strong> [Number]</p><p><strong>Date:</strong> ' + new Date().toLocaleDateString() + '</p><p><strong>Due Date:</strong> [Due Date]</p><div><strong>From:</strong><br>[Your Name/Company]<br>[Your Address]<br>[Your Contact Info]</div><div><strong>To:</strong><br>[Client Name/Company]<br>[Client Address]</div><table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="text-align:left; padding: 8px;">Description</th><th style="text-align:right; padding: 8px;">Amount</th></tr><tr style="border-bottom: 1px solid #ddd;"><td style="padding: 8px;">[Item/Service Description]</td><td style="text-align:right; padding: 8px;">[Amount]</td></tr><tr><td style="text-align:right; padding: 8px;"><strong>Total</strong></td><td style="text-align:right; padding: 8px;"><strong>[Total Amount]</strong></td></tr></table><p><strong>Payment Terms:</strong> [Terms]</p><p><strong>Payment Method:</strong> [Method]</p>',
|
||||
},
|
||||
];
|
||||
|
||||
// Task management commands
|
||||
const taskCommands = [
|
||||
{
|
||||
title: 'todo',
|
||||
description: 'Insert todo list',
|
||||
content: '<h3>To-Do List</h3><ul><li>[ ] Task 1</li><li>[ ] Task 2</li><li>[ ] Task 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'checklist',
|
||||
description: 'Insert checklist',
|
||||
content: '<h3>Checklist</h3><ul><li>[ ] Item 1</li><li>[ ] Item 2</li><li>[ ] Item 3</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'progress',
|
||||
description: 'Insert progress tracker',
|
||||
content: '<h3>Project Progress</h3><ul><li>[x] Planning - Complete</li><li>[x] Research - Complete</li><li>[ ] Implementation - In Progress</li><li>[ ] Testing</li><li>[ ] Deployment</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'timeline',
|
||||
description: 'Insert project timeline',
|
||||
content: '<h3>Project Timeline</h3><ul><li><strong>Week 1:</strong> Planning and Research</li><li><strong>Week 2-3:</strong> Design and Development</li><li><strong>Week 4:</strong> Testing</li><li><strong>Week 5:</strong> Deployment</li></ul>',
|
||||
},
|
||||
{
|
||||
title: 'goals',
|
||||
description: 'Insert goals list',
|
||||
content: '<h3>Goals</h3><ol><li>Short-term goal 1</li><li>Short-term goal 2</li><li>Long-term goal 1</li><li>Long-term goal 2</li></ol>',
|
||||
},
|
||||
];
|
||||
|
||||
// Table commands
|
||||
const tableCommands = [
|
||||
{
|
||||
title: 'table2x2',
|
||||
description: 'Insert 2x2 table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Header 1</th><th style="border: 1px solid #ddd; padding: 8px;">Header 2</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 2</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 2</td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'table3x3',
|
||||
description: 'Insert 3x3 table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Header 1</th><th style="border: 1px solid #ddd; padding: 8px;">Header 2</th><th style="border: 1px solid #ddd; padding: 8px;">Header 3</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 1, Cell 3</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 2, Cell 3</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 1</td><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 2</td><td style="border: 1px solid #ddd; padding: 8px;">Row 3, Cell 3</td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'schedule',
|
||||
description: 'Insert schedule table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Time</th><th style="border: 1px solid #ddd; padding: 8px;">Monday</th><th style="border: 1px solid #ddd; padding: 8px;">Tuesday</th><th style="border: 1px solid #ddd; padding: 8px;">Wednesday</th><th style="border: 1px solid #ddd; padding: 8px;">Thursday</th><th style="border: 1px solid #ddd; padding: 8px;">Friday</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">9:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">10:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">11:00 AM</td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td><td style="border: 1px solid #ddd; padding: 8px;"></td></tr></table>',
|
||||
},
|
||||
{
|
||||
title: 'comparison',
|
||||
description: 'Insert comparison table',
|
||||
content: '<table style="width:100%; border-collapse: collapse;"><tr style="border-bottom: 1px solid #ddd;"><th style="border: 1px solid #ddd; padding: 8px;">Feature</th><th style="border: 1px solid #ddd; padding: 8px;">Option A</th><th style="border: 1px solid #ddd; padding: 8px;">Option B</th><th style="border: 1px solid #ddd; padding: 8px;">Option C</th></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 1</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 2</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✗</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Feature 3</td><td style="border: 1px solid #ddd; padding: 8px;">✗</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td><td style="border: 1px solid #ddd; padding: 8px;">✓</td></tr><tr><td style="border: 1px solid #ddd; padding: 8px;">Price</td><td style="border: 1px solid #ddd; padding: 8px;">$</td><td style="border: 1px solid #ddd; padding: 8px;">$$</td><td style="border: 1px solid #ddd; padding: 8px;">$$$</td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
// Additional commands to reach 100 total
|
||||
const additionalCommands = Array.from({ length: 100 - (basicCommands.length + formattingCommands.length + templateCommands.length + taskCommands.length + tableCommands.length) }, (_, i) => {
|
||||
const index = i + 1;
|
||||
return {
|
||||
title: `command${index}`,
|
||||
description: `Example command ${index}`,
|
||||
content: `<p>This is example command ${index}</p>`,
|
||||
};
|
||||
});
|
||||
|
||||
// Combine all command categories
|
||||
return [
|
||||
...basicCommands,
|
||||
...formattingCommands,
|
||||
...templateCommands,
|
||||
...taskCommands,
|
||||
...tableCommands,
|
||||
...additionalCommands,
|
||||
];
|
||||
};
|
123
src/tiptap/tiptap.css
Normal file
123
src/tiptap/tiptap.css
Normal file
@ -0,0 +1,123 @@
|
||||
:root {
|
||||
--purple-light: #e0e0ff; /* 默认浅紫色背景 */
|
||||
--black: #000000; /* 默认黑色 */
|
||||
--white: #ffffff; /* 默认白色 */
|
||||
--gray-3: #d3d3d3; /* 默认灰色3 */
|
||||
--gray-2: #e5e5e5; /* 默认灰色2 */
|
||||
}
|
||||
.tiptap-preview {
|
||||
.tiptap {
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
border: unset;
|
||||
}
|
||||
}
|
||||
.tiptap {
|
||||
/* margin: 0.5rem 1rem; */
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
/* Basic editor styles */
|
||||
.tiptap:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
.tiptap ul,
|
||||
.tiptap ol {
|
||||
padding: 0 1rem;
|
||||
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||
}
|
||||
|
||||
.tiptap ul li p,
|
||||
.tiptap ol li p {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
/* Heading styles */
|
||||
.tiptap h1,
|
||||
.tiptap h2,
|
||||
.tiptap h3,
|
||||
.tiptap h4,
|
||||
.tiptap h5,
|
||||
.tiptap h6 {
|
||||
line-height: 1.1;
|
||||
margin-top: 2.5rem;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.tiptap h1,
|
||||
.tiptap h2 {
|
||||
/* margin-top: 3.5rem; */
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tiptap h1 {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tiptap h2 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tiptap h3 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tiptap h4,
|
||||
.tiptap h5,
|
||||
.tiptap h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Code and preformatted text styles */
|
||||
.tiptap code {
|
||||
background-color: var(--purple-light);
|
||||
border-radius: 0.4rem;
|
||||
color: var(--black);
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25em 0.3em;
|
||||
}
|
||||
|
||||
.tiptap pre {
|
||||
border: 1px solid #ccc;
|
||||
/* background: var(--black); */
|
||||
border-radius: 0.5rem;
|
||||
/* color: var(--white); */
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.tiptap pre code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tiptap mark {
|
||||
background-color: #faf594;
|
||||
border-radius: 0.4rem;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
.tiptap blockquote {
|
||||
border-left: 3px solid var(--gray-3);
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.tiptap hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--gray-2);
|
||||
margin: 2rem 0;
|
||||
}
|
22
turbo.json
22
turbo.json
@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
]
|
||||
},
|
||||
"dev:lib": {
|
||||
"persistent": true,
|
||||
"cache": true
|
||||
},
|
||||
"build:lib": {
|
||||
"dependsOn": [
|
||||
"^build:lib"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user