md-editor-lite 0.1.0 → 0.1.2
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/README.md +32 -6
- package/package.json +9 -3
- package/playground/App.tsx +0 -19
- package/playground/index.html +0 -12
- package/playground/main.tsx +0 -9
- package/playground/package-lock.json +0 -970
- package/playground/package.json +0 -14
- package/playground/tsconfig.app.json +0 -31
- package/playground/tsconfig.json +0 -7
- package/playground/tsconfig.node.json +0 -24
- package/playground/vite.config.ts +0 -6
- package/src/components/EditorHeader.tsx +0 -21
- package/src/components/EditorTab.tsx +0 -27
- package/src/components/EditorTextarea.tsx +0 -39
- package/src/components/IconDropdown.tsx +0 -90
- package/src/components/MarkdownEditor.tsx +0 -42
- package/src/components/Preview.tsx +0 -16
- package/src/components/Toolbar.tsx +0 -25
- package/src/components/ToolbarDropdown.tsx +0 -35
- package/src/components/index.ts +0 -7
- package/src/constants/index.ts +0 -1
- package/src/constants/toolbar.ts +0 -36
- package/src/css/dropdown.css +0 -101
- package/src/css/editor.css +0 -67
- package/src/css/preview.css +0 -11
- package/src/css/toolbar.css +0 -25
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useMarkdownEditor.ts +0 -39
- package/src/index.ts +0 -4
- package/src/utils/cx.ts +0 -3
- package/src/utils/index.ts +0 -2
- package/src/utils/markdown.ts +0 -89
- package/tsconfig.build.json +0 -18
package/playground/package.json
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
-
"target": "ES2020",
|
|
5
|
-
"useDefineForClassFields": true,
|
|
6
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
-
"module": "ESNext",
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
|
|
10
|
-
/* Bundler mode */
|
|
11
|
-
"moduleResolution": "bundler",
|
|
12
|
-
"allowImportingTsExtensions": true,
|
|
13
|
-
"verbatimModuleSyntax": true,
|
|
14
|
-
"moduleDetection": "force",
|
|
15
|
-
"noEmit": true,
|
|
16
|
-
"jsx": "react-jsx",
|
|
17
|
-
|
|
18
|
-
/* Linting */
|
|
19
|
-
"strict": true,
|
|
20
|
-
"noUnusedLocals": true,
|
|
21
|
-
"noUnusedParameters": true,
|
|
22
|
-
"noFallthroughCasesInSwitch": true,
|
|
23
|
-
"noUncheckedSideEffectImports": true,
|
|
24
|
-
|
|
25
|
-
"baseUrl": ".",
|
|
26
|
-
"paths": {
|
|
27
|
-
"@/*": ["src/*"]
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"include": ["src"]
|
|
31
|
-
}
|
package/playground/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"lib": ["ES2023"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
|
|
9
|
-
/* Bundler mode */
|
|
10
|
-
"moduleResolution": "bundler",
|
|
11
|
-
"allowImportingTsExtensions": true,
|
|
12
|
-
"verbatimModuleSyntax": true,
|
|
13
|
-
"moduleDetection": "force",
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
|
|
16
|
-
/* Linting */
|
|
17
|
-
"strict": true,
|
|
18
|
-
"noUnusedLocals": true,
|
|
19
|
-
"noUnusedParameters": true,
|
|
20
|
-
"noFallthroughCasesInSwitch": true,
|
|
21
|
-
"noUncheckedSideEffectImports": true
|
|
22
|
-
},
|
|
23
|
-
"include": ["vite.config.ts"]
|
|
24
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { EditorTab, Toolbar } from "../components";
|
|
2
|
-
import "../css/editor.css";
|
|
3
|
-
|
|
4
|
-
interface EditorHeaderProps {
|
|
5
|
-
mode: "write" | "preview";
|
|
6
|
-
setMode: (mode: "write" | "preview") => void;
|
|
7
|
-
insertMarkdown: (before: string, after?: string) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function EditorHeader({
|
|
11
|
-
mode,
|
|
12
|
-
setMode,
|
|
13
|
-
insertMarkdown,
|
|
14
|
-
}: EditorHeaderProps) {
|
|
15
|
-
return (
|
|
16
|
-
<header className="editor-header">
|
|
17
|
-
<EditorTab mode={mode} setMode={setMode} />
|
|
18
|
-
{mode === "write" && <Toolbar insertMarkdown={insertMarkdown} />}
|
|
19
|
-
</header>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import "../css/editor.css";
|
|
2
|
-
|
|
3
|
-
interface EditorTabProps {
|
|
4
|
-
mode: string;
|
|
5
|
-
setMode: (mode: "write" | "preview") => void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function EditorTab({ mode, setMode }: EditorTabProps) {
|
|
9
|
-
return (
|
|
10
|
-
<div className="editor-tab-container">
|
|
11
|
-
<div
|
|
12
|
-
className={`editor-tab ${mode === "write" ? "editor-tab--active" : ""}`}
|
|
13
|
-
onClick={() => setMode("write")}
|
|
14
|
-
>
|
|
15
|
-
Write
|
|
16
|
-
</div>
|
|
17
|
-
<div
|
|
18
|
-
className={`editor-tab ${
|
|
19
|
-
mode === "preview" ? "editor-tab--active" : ""
|
|
20
|
-
}`}
|
|
21
|
-
onClick={() => setMode("preview")}
|
|
22
|
-
>
|
|
23
|
-
Preview
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import "../css/editor.css";
|
|
3
|
-
|
|
4
|
-
interface EditorTextareaProps {
|
|
5
|
-
value: string;
|
|
6
|
-
placeholder?: string;
|
|
7
|
-
setValue: (value: string) => void;
|
|
8
|
-
onImageUpload?: (file: File) => Promise<string>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const EditorTextarea = forwardRef<
|
|
12
|
-
HTMLTextAreaElement,
|
|
13
|
-
EditorTextareaProps
|
|
14
|
-
>(({ value, placeholder, setValue, onImageUpload }, ref) => {
|
|
15
|
-
const handleDrop = async (e: React.DragEvent) => {
|
|
16
|
-
e.preventDefault();
|
|
17
|
-
if (!onImageUpload) return;
|
|
18
|
-
|
|
19
|
-
const files = Array.from(e.dataTransfer.files).filter((file) =>
|
|
20
|
-
file.type.startsWith("image/")
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
for (const file of files) {
|
|
24
|
-
const url = await onImageUpload(file);
|
|
25
|
-
setValue(value + `\n\n`);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
return (
|
|
29
|
-
<textarea
|
|
30
|
-
ref={ref}
|
|
31
|
-
value={value}
|
|
32
|
-
onChange={(e) => setValue(e.target.value)}
|
|
33
|
-
onDragOver={(e) => e.preventDefault()}
|
|
34
|
-
onDrop={handleDrop}
|
|
35
|
-
placeholder={placeholder}
|
|
36
|
-
className="editor-textarea"
|
|
37
|
-
></textarea>
|
|
38
|
-
);
|
|
39
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { cx } from "../utils/cx";
|
|
3
|
-
import "../css/dropdown.css";
|
|
4
|
-
|
|
5
|
-
export type DropdownOption = {
|
|
6
|
-
value: string;
|
|
7
|
-
label?: string;
|
|
8
|
-
Icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
interface IconDropdownProps {
|
|
12
|
-
className?: string;
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
options: DropdownOption[];
|
|
15
|
-
selected?: boolean;
|
|
16
|
-
triggerIcon: React.ReactNode;
|
|
17
|
-
onChange: (value: string) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function IconDropdown({
|
|
21
|
-
className,
|
|
22
|
-
disabled,
|
|
23
|
-
options,
|
|
24
|
-
selected,
|
|
25
|
-
triggerIcon,
|
|
26
|
-
onChange,
|
|
27
|
-
}: IconDropdownProps) {
|
|
28
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
29
|
-
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
|
30
|
-
|
|
31
|
-
const toggle = () => {
|
|
32
|
-
if (!disabled) setIsOpen((prev) => !prev);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
React.useEffect(() => {
|
|
36
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
37
|
-
if (
|
|
38
|
-
dropdownRef.current &&
|
|
39
|
-
!dropdownRef.current.contains(e.target as Node)
|
|
40
|
-
) {
|
|
41
|
-
setIsOpen(false);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
45
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<div ref={dropdownRef} className={cx("md-dropdown", className)}>
|
|
50
|
-
<button
|
|
51
|
-
type="button"
|
|
52
|
-
onClick={toggle}
|
|
53
|
-
disabled={disabled}
|
|
54
|
-
className={cx(
|
|
55
|
-
"md-dropdown__trigger",
|
|
56
|
-
disabled && "md-dropdown__trigger--disabled"
|
|
57
|
-
)}
|
|
58
|
-
>
|
|
59
|
-
{triggerIcon}
|
|
60
|
-
</button>
|
|
61
|
-
|
|
62
|
-
{isOpen && (
|
|
63
|
-
<ul className="md-dropdown__menu">
|
|
64
|
-
{options.map((option) => (
|
|
65
|
-
<li
|
|
66
|
-
key={option.value}
|
|
67
|
-
className={cx(
|
|
68
|
-
"md-dropdown__item",
|
|
69
|
-
selected && "md-dropdown__item--selected"
|
|
70
|
-
)}
|
|
71
|
-
onClick={() => {
|
|
72
|
-
onChange(option.value);
|
|
73
|
-
setIsOpen(false);
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
{option.Icon && (
|
|
77
|
-
<span className="md-dropdown__icon">
|
|
78
|
-
<option.Icon />
|
|
79
|
-
</span>
|
|
80
|
-
)}
|
|
81
|
-
{option.label && (
|
|
82
|
-
<span className="md-dropdown__label">{option.label}</span>
|
|
83
|
-
)}
|
|
84
|
-
</li>
|
|
85
|
-
))}
|
|
86
|
-
</ul>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { EditorHeader, EditorTextarea, Preview } from "../components";
|
|
3
|
-
import { useMarkdownEditor } from "../hooks/useMarkdownEditor";
|
|
4
|
-
import "../css/editor.css";
|
|
5
|
-
|
|
6
|
-
interface MarkdownEditorProps {
|
|
7
|
-
value: string;
|
|
8
|
-
onChange: (v: string) => void;
|
|
9
|
-
onImageUpload?: (file: File) => Promise<string>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function MarkdownEditor({
|
|
13
|
-
value,
|
|
14
|
-
onChange: setValue,
|
|
15
|
-
onImageUpload,
|
|
16
|
-
}: MarkdownEditorProps) {
|
|
17
|
-
const { textareaRef, insertMarkdown } = useMarkdownEditor({
|
|
18
|
-
value,
|
|
19
|
-
setValue,
|
|
20
|
-
});
|
|
21
|
-
const [mode, setMode] = useState<"write" | "preview">("write");
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className="editor">
|
|
25
|
-
<EditorHeader
|
|
26
|
-
mode={mode}
|
|
27
|
-
setMode={setMode}
|
|
28
|
-
insertMarkdown={insertMarkdown}
|
|
29
|
-
/>
|
|
30
|
-
{mode === "write" ? (
|
|
31
|
-
<EditorTextarea
|
|
32
|
-
ref={textareaRef}
|
|
33
|
-
value={value}
|
|
34
|
-
setValue={setValue}
|
|
35
|
-
onImageUpload={onImageUpload}
|
|
36
|
-
/>
|
|
37
|
-
) : (
|
|
38
|
-
<Preview value={value} />
|
|
39
|
-
)}
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { markdownToHtml } from "../utils/markdown";
|
|
2
|
-
import "../css/preview.css";
|
|
3
|
-
|
|
4
|
-
interface PreviewProps {
|
|
5
|
-
value: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function Preview({ value }: PreviewProps) {
|
|
9
|
-
return (
|
|
10
|
-
<div
|
|
11
|
-
className="preview"
|
|
12
|
-
dangerouslySetInnerHTML={{ __html: markdownToHtml(value) }}
|
|
13
|
-
style={{ listStyle: "decimal" }}
|
|
14
|
-
/>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { TOOLBAR_BUTTONS } from "../constants/toolbar";
|
|
2
|
-
import { ToolbarDropdownHeading, ToolbarDropdownList } from "../components";
|
|
3
|
-
import "../css/toolbar.css";
|
|
4
|
-
|
|
5
|
-
interface ToolbarProps {
|
|
6
|
-
insertMarkdown: (before: string, after?: string) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Toolbar({ insertMarkdown }: ToolbarProps) {
|
|
10
|
-
return (
|
|
11
|
-
<menu className="toolbar-menu">
|
|
12
|
-
{TOOLBAR_BUTTONS.map(({ key, before, after, Icon }) => (
|
|
13
|
-
<button
|
|
14
|
-
key={key}
|
|
15
|
-
className="toolbar-button"
|
|
16
|
-
onClick={() => insertMarkdown(before, after)}
|
|
17
|
-
>
|
|
18
|
-
<Icon size={18} />
|
|
19
|
-
</button>
|
|
20
|
-
))}
|
|
21
|
-
<ToolbarDropdownHeading insertMarkdown={insertMarkdown} />
|
|
22
|
-
<ToolbarDropdownList insertMarkdown={insertMarkdown} />
|
|
23
|
-
</menu>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { IconDropdown } from "../components/IconDropdown";
|
|
2
|
-
import { headingOptions, listOptions } from "../constants/toolbar";
|
|
3
|
-
import { Heading1Icon, ListIcon } from "lucide-react";
|
|
4
|
-
|
|
5
|
-
interface ToolbarDropdownProps {
|
|
6
|
-
insertMarkdown: (before: string, after?: string) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function ToolbarDropdownHeading({
|
|
10
|
-
insertMarkdown,
|
|
11
|
-
}: ToolbarDropdownProps) {
|
|
12
|
-
return (
|
|
13
|
-
<IconDropdown
|
|
14
|
-
options={headingOptions}
|
|
15
|
-
triggerIcon={<Heading1Icon />}
|
|
16
|
-
onChange={(value) => {
|
|
17
|
-
const option = headingOptions.find((o) => o.value === value);
|
|
18
|
-
if (option) insertMarkdown(option.before);
|
|
19
|
-
}}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function ToolbarDropdownList({ insertMarkdown }: ToolbarDropdownProps) {
|
|
25
|
-
return (
|
|
26
|
-
<IconDropdown
|
|
27
|
-
options={listOptions}
|
|
28
|
-
triggerIcon={<ListIcon />}
|
|
29
|
-
onChange={(value) => {
|
|
30
|
-
const option = listOptions.find((o) => o.value === value);
|
|
31
|
-
if (option) insertMarkdown(option.before);
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
}
|
package/src/components/index.ts
DELETED
package/src/constants/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./toolbar";
|
package/src/constants/toolbar.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { DropdownOption } from "../components/IconDropdown";
|
|
2
|
-
import {
|
|
3
|
-
BoldIcon,
|
|
4
|
-
ItalicIcon,
|
|
5
|
-
CodeIcon,
|
|
6
|
-
LinkIcon,
|
|
7
|
-
Heading1Icon,
|
|
8
|
-
Heading2Icon,
|
|
9
|
-
Heading3Icon,
|
|
10
|
-
ListOrderedIcon,
|
|
11
|
-
ListIcon,
|
|
12
|
-
QuoteIcon,
|
|
13
|
-
} from "lucide-react";
|
|
14
|
-
|
|
15
|
-
type ToolbarDropdownOption = DropdownOption & {
|
|
16
|
-
before: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const TOOLBAR_BUTTONS = [
|
|
20
|
-
{ key: "bold", before: "**", after: "**", Icon: BoldIcon },
|
|
21
|
-
{ key: "italic", before: "*", after: "*", Icon: ItalicIcon },
|
|
22
|
-
{ key: "code", before: "`", after: "`", Icon: CodeIcon },
|
|
23
|
-
{ key: "blockquote", before: "> ", after: "", Icon: QuoteIcon },
|
|
24
|
-
{ key: "link", before: "[", after: "]()", Icon: LinkIcon },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
export const headingOptions: ToolbarDropdownOption[] = [
|
|
28
|
-
{ value: "h1", Icon: Heading1Icon, before: "# " },
|
|
29
|
-
{ value: "h2", Icon: Heading2Icon, before: "## " },
|
|
30
|
-
{ value: "h3", Icon: Heading3Icon, before: "### " },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
export const listOptions: ToolbarDropdownOption[] = [
|
|
34
|
-
{ value: "unordered list", Icon: ListIcon, before: "- " },
|
|
35
|
-
{ value: "ordered list", Icon: ListOrderedIcon, before: "1. " },
|
|
36
|
-
];
|
package/src/css/dropdown.css
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
.md-dropdown {
|
|
2
|
-
position: relative;
|
|
3
|
-
display: inline-flex;
|
|
4
|
-
align-items: center;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.md-dropdown__trigger {
|
|
8
|
-
display: flex;
|
|
9
|
-
align-items: center;
|
|
10
|
-
justify-content: center;
|
|
11
|
-
|
|
12
|
-
width: 28px;
|
|
13
|
-
height: 28px;
|
|
14
|
-
|
|
15
|
-
background: transparent;
|
|
16
|
-
border: none;
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
transition: background-color 0.15s ease;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.md-dropdown__trigger:hover {
|
|
22
|
-
background-color: #f3f4f6;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.md-dropdown__trigger--disabled {
|
|
26
|
-
cursor: not-allowed;
|
|
27
|
-
opacity: 0.5;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.md-dropdown__menu {
|
|
31
|
-
position: absolute;
|
|
32
|
-
top: 0;
|
|
33
|
-
left: -35%;
|
|
34
|
-
z-index: 50;
|
|
35
|
-
margin-top: 4px;
|
|
36
|
-
|
|
37
|
-
min-width: 40px;
|
|
38
|
-
max-height: 240px;
|
|
39
|
-
overflow-y: auto;
|
|
40
|
-
|
|
41
|
-
background: #ffffff;
|
|
42
|
-
border: 1px solid #e5e7eb;
|
|
43
|
-
border-radius: 8px;
|
|
44
|
-
padding: 4px 0;
|
|
45
|
-
|
|
46
|
-
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.md-dropdown__item {
|
|
50
|
-
display: flex;
|
|
51
|
-
align-items: center;
|
|
52
|
-
gap: 8px;
|
|
53
|
-
|
|
54
|
-
padding: 10px 12px;
|
|
55
|
-
font-size: 14px;
|
|
56
|
-
|
|
57
|
-
cursor: pointer;
|
|
58
|
-
user-select: none;
|
|
59
|
-
transition: background-color 0.15s ease;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.md-dropdown__item:hover {
|
|
63
|
-
background-color: #f3f4f6;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.md-dropdown__item--selected {
|
|
67
|
-
background-color: #fefce8;
|
|
68
|
-
color: #a16207;
|
|
69
|
-
font-weight: 500;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.md-dropdown__icon {
|
|
73
|
-
display: flex;
|
|
74
|
-
color: #9ca3af;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.md-dropdown__label {
|
|
78
|
-
white-space: nowrap;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.dropdown-item {
|
|
82
|
-
color: #374151;
|
|
83
|
-
background-color: #f3f4f6;
|
|
84
|
-
position: relative;
|
|
85
|
-
display: flex;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
align-items: center;
|
|
88
|
-
gap: 0.5rem;
|
|
89
|
-
padding-left: 0.75rem;
|
|
90
|
-
padding-right: 0.75rem;
|
|
91
|
-
padding-top: 0.625rem;
|
|
92
|
-
padding-bottom: 0.625rem;
|
|
93
|
-
font-size: 0.875rem;
|
|
94
|
-
transition: all ease-in-out;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.dropdown-item--selected {
|
|
98
|
-
background-color: #fefce8;
|
|
99
|
-
color: #a16207;
|
|
100
|
-
font-weight: 500;
|
|
101
|
-
}
|
package/src/css/editor.css
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
.editor {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
width: 100%;
|
|
5
|
-
border: 1px solid #e5e7eb;
|
|
6
|
-
border-radius: 8px;
|
|
7
|
-
box-sizing: border-box;
|
|
8
|
-
overflow: hidden;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.editor-header {
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
justify-content: space-evenly;
|
|
15
|
-
|
|
16
|
-
height: 88px;
|
|
17
|
-
padding: 0 16px;
|
|
18
|
-
|
|
19
|
-
background-color: #f9fafb;
|
|
20
|
-
border-bottom: 1px solid #e5e7eb;
|
|
21
|
-
border-top-left-radius: 8px;
|
|
22
|
-
border-top-right-radius: 8px;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@media (min-width: 768px) {
|
|
26
|
-
.editor-header {
|
|
27
|
-
flex-direction: row;
|
|
28
|
-
align-items: center;
|
|
29
|
-
justify-content: space-between;
|
|
30
|
-
height: 48px;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.editor-tab-container {
|
|
35
|
-
display: flex;
|
|
36
|
-
gap: 1rem;
|
|
37
|
-
font-weight: 500;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.editor-tab {
|
|
41
|
-
cursor: pointer;
|
|
42
|
-
padding: 0.25rem 0.5rem;
|
|
43
|
-
font-size: small;
|
|
44
|
-
font-weight: 500;
|
|
45
|
-
transition: color 0.2s;
|
|
46
|
-
color: #4b5563;
|
|
47
|
-
|
|
48
|
-
&:hover {
|
|
49
|
-
color: #1f2937;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.editor-tab--active {
|
|
54
|
-
color: #111827;
|
|
55
|
-
border-bottom: 2px solid #111827;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.editor-textarea {
|
|
59
|
-
width: 100%;
|
|
60
|
-
min-height: 200px;
|
|
61
|
-
margin: 0;
|
|
62
|
-
padding: 16px;
|
|
63
|
-
border: none;
|
|
64
|
-
resize: none;
|
|
65
|
-
outline: none;
|
|
66
|
-
box-sizing: border-box;
|
|
67
|
-
}
|
package/src/css/preview.css
DELETED
package/src/css/toolbar.css
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
.toolbar-menu {
|
|
2
|
-
display: flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
margin: 0;
|
|
5
|
-
padding: 0;
|
|
6
|
-
gap: 16px;
|
|
7
|
-
box-sizing: border-box;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.toolbar-button {
|
|
11
|
-
background: none;
|
|
12
|
-
width: 28px;
|
|
13
|
-
height: 28px;
|
|
14
|
-
border: none;
|
|
15
|
-
cursor: pointer;
|
|
16
|
-
transition: background-color 0.2s;
|
|
17
|
-
|
|
18
|
-
&:hover {
|
|
19
|
-
background-color: #f3f4f6;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
&:active {
|
|
23
|
-
background-color: #e5e7eb;
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useMarkdownEditor";
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
interface UseMarkdownEditorProps {
|
|
4
|
-
value: string
|
|
5
|
-
setValue: (v: string) => void
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function useMarkdownEditor({ value, setValue }: UseMarkdownEditorProps) {
|
|
9
|
-
const textareaRef = useRef<HTMLTextAreaElement | null>(null)
|
|
10
|
-
|
|
11
|
-
const insertMarkdown = useCallback(
|
|
12
|
-
(before: string, after: string = '') => {
|
|
13
|
-
const textarea = textareaRef.current
|
|
14
|
-
if (!textarea) return
|
|
15
|
-
|
|
16
|
-
const start = textarea.selectionStart
|
|
17
|
-
const end = textarea.selectionEnd
|
|
18
|
-
|
|
19
|
-
const selected = value.slice(start, end)
|
|
20
|
-
const replaced = before + (selected || '') + after
|
|
21
|
-
|
|
22
|
-
setValue(value.slice(0, start) + replaced + value.slice(end))
|
|
23
|
-
|
|
24
|
-
requestAnimationFrame(() => {
|
|
25
|
-
if (!textareaRef.current) return
|
|
26
|
-
const next = start + replaced.length
|
|
27
|
-
textareaRef.current.focus()
|
|
28
|
-
textareaRef.current.selectionStart = next
|
|
29
|
-
textareaRef.current.selectionEnd = next
|
|
30
|
-
})
|
|
31
|
-
},
|
|
32
|
-
[value, setValue]
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
textareaRef,
|
|
37
|
-
insertMarkdown,
|
|
38
|
-
}
|
|
39
|
-
}
|