mirror of
https://github.com/muerwre/markdown-home-tab.git
synced 2025-04-25 00:46:41 +07:00
add hotkeys
This commit is contained in:
parent
6d00bffbec
commit
db911e51e4
14 changed files with 91 additions and 73 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -22,4 +22,6 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
web-ext-artifacts
|
web-ext-artifacts
|
||||||
|
output
|
||||||
|
*.tgz
|
|
@ -22,4 +22,4 @@ yarn dev
|
||||||
|
|
||||||
## TO-DO
|
## TO-DO
|
||||||
|
|
||||||
- Use Remirror as editor
|
- Use HyperMD editor
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "markdown-home-tab",
|
"name": "markdown-home-tab",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"package": "yarn build && web-ext build -s ./dist",
|
"package": "yarn build && web-ext build -s ./dist -a ./output",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Markdown Home Tab",
|
"name": "Markdown Home Tab",
|
||||||
"short_name": "Markdown New Tab",
|
"short_name": "Markdown New Tab",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"description": "Markdown right in your home tab! Paste links, pictures, lists and more. You can also customize colors to match your needs.",
|
"description": "Markdown right in your home tab! Paste links, pictures, lists and more. You can also customize colors to match your needs.",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
|
|
|
@ -13,7 +13,7 @@ const EmptyViewer: FC<EmptyViewerProps> = ({ startEditing }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.empty} style={style}>
|
<div className={styles.empty} style={style} onDoubleClick={startEditing}>
|
||||||
<div className={styles.title}>{t(`Nothing's here yet`)}</div>
|
<div className={styles.title}>{t(`Nothing's here yet`)}</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.25s;
|
transition: opacity 0.25s;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FC } from "react";
|
import { FC, useCallback, MouseEvent } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
|
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
@ -7,20 +7,26 @@ import { useTranslation } from "react-i18next";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import rehypeRaw from "rehype-raw";
|
import rehypeRaw from "rehype-raw";
|
||||||
|
|
||||||
interface ReactMarkdownViewerProps {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
startEditing: () => void;
|
startEditing: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactMarkdownViewer: FC<ReactMarkdownViewerProps> = ({
|
const MarkdownViewer: FC<Props> = ({ value, startEditing }) => {
|
||||||
value,
|
|
||||||
startEditing,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const style = useContainerPaddings();
|
const style = useContainerPaddings();
|
||||||
|
|
||||||
|
const onDoubleClick = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
startEditing();
|
||||||
|
},
|
||||||
|
[startEditing]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className={styles.editor}>
|
<div style={style} className={styles.editor} onDoubleClick={onDoubleClick}>
|
||||||
<div className={styles.edit}>
|
<div className={styles.edit}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -41,4 +47,4 @@ const ReactMarkdownViewer: FC<ReactMarkdownViewerProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ReactMarkdownViewer };
|
export { MarkdownViewer };
|
|
@ -1,37 +1,16 @@
|
||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
EditorComponent,
|
||||||
FloatingToolbar,
|
FloatingToolbar,
|
||||||
|
FormattingButtonGroup,
|
||||||
|
HeadingLevelButtonGroup,
|
||||||
Remirror,
|
Remirror,
|
||||||
useRemirror,
|
useRemirror,
|
||||||
EditorComponent,
|
|
||||||
HeadingLevelButtonGroup,
|
|
||||||
FormattingButtonGroup,
|
|
||||||
} from "@remirror/react";
|
} from "@remirror/react";
|
||||||
import jsx from "refractor/lang/jsx.js";
|
import { Extension, RemirrorEventListener } from "remirror";
|
||||||
import typescript from "refractor/lang/typescript.js";
|
|
||||||
import { Extension, ExtensionPriority, RemirrorEventListener } from "remirror";
|
|
||||||
import {
|
|
||||||
BlockquoteExtension,
|
|
||||||
UnderlineExtension,
|
|
||||||
BoldExtension,
|
|
||||||
BulletListExtension,
|
|
||||||
CodeBlockExtension,
|
|
||||||
CodeExtension,
|
|
||||||
HardBreakExtension,
|
|
||||||
HeadingExtension,
|
|
||||||
ItalicExtension,
|
|
||||||
LinkExtension,
|
|
||||||
ListItemExtension,
|
|
||||||
MarkdownExtension,
|
|
||||||
OrderedListExtension,
|
|
||||||
StrikeExtension,
|
|
||||||
TableExtension,
|
|
||||||
TrailingNodeExtension,
|
|
||||||
GapCursorExtension,
|
|
||||||
} from "remirror/extensions";
|
|
||||||
import styles from "./styles.module.scss";
|
|
||||||
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
|
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface RemirrorEditorProps {
|
interface RemirrorEditorProps {
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
|
@ -50,7 +29,7 @@ const RemirrorEditor: FC<RemirrorEditorProps> = ({
|
||||||
exitMarksOnArrowPress: false,
|
exitMarksOnArrowPress: false,
|
||||||
},
|
},
|
||||||
content: value,
|
content: value,
|
||||||
stringHandler: "markdown",
|
// stringHandler: "markdown",
|
||||||
});
|
});
|
||||||
|
|
||||||
const onStateChange = useCallback<RemirrorEventListener<Extension>>(
|
const onStateChange = useCallback<RemirrorEventListener<Extension>>(
|
||||||
|
@ -90,26 +69,26 @@ const RemirrorEditor: FC<RemirrorEditorProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const extensions = (): Extension[] => [
|
const extensions = (): Extension[] => [
|
||||||
new LinkExtension({ autoLink: true }),
|
// new LinkExtension({ autoLink: true }),
|
||||||
new BoldExtension(),
|
// new BoldExtension(),
|
||||||
new UnderlineExtension(),
|
// new UnderlineExtension(),
|
||||||
new StrikeExtension(),
|
// new StrikeExtension(),
|
||||||
new ItalicExtension(),
|
// new ItalicExtension(),
|
||||||
new HeadingExtension(),
|
// new HeadingExtension(),
|
||||||
new BlockquoteExtension(),
|
// new BlockquoteExtension(),
|
||||||
new BulletListExtension({ enableSpine: false }),
|
// new BulletListExtension({ enableSpine: false }),
|
||||||
new OrderedListExtension(),
|
// new OrderedListExtension(),
|
||||||
new ListItemExtension({
|
// new ListItemExtension({
|
||||||
priority: ExtensionPriority.High,
|
// priority: ExtensionPriority.High,
|
||||||
// enableCollapsible: true,
|
// // enableCollapsible: true,
|
||||||
}),
|
// }),
|
||||||
new CodeExtension(),
|
// new CodeExtension(),
|
||||||
new CodeBlockExtension({ supportedLanguages: [jsx, typescript] }),
|
// new CodeBlockExtension({ supportedLanguages: [jsx, typescript] }),
|
||||||
new TrailingNodeExtension(),
|
// new TrailingNodeExtension(),
|
||||||
new TableExtension(),
|
// new TableExtension(),
|
||||||
new MarkdownExtension({ copyAsMarkdown: true }),
|
// new MarkdownExtension({ copyAsMarkdown: true }),
|
||||||
new GapCursorExtension(),
|
// new GapCursorExtension(),
|
||||||
new HardBreakExtension(),
|
// new HardBreakExtension(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export { RemirrorEditor };
|
export { RemirrorEditor };
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { ChangeEvent, FC, useCallback, useMemo } from "react";
|
import { ChangeEvent, FC, useCallback, useMemo, KeyboardEvent } from "react";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { useTheme } from "~/modules/theme/context/ThemeContext";
|
import { useTheme } from "~/modules/theme/context/ThemeContext";
|
||||||
|
|
||||||
interface ReactMarkdownEditorProps {
|
interface ReactMarkdownEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (val: string) => void;
|
onChange: (val: string) => void;
|
||||||
|
save: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactMarkdownEditor: FC<ReactMarkdownEditorProps> = ({
|
const SimpleTextareaEditor: FC<ReactMarkdownEditorProps> = ({
|
||||||
|
save,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -27,8 +29,22 @@ const ReactMarkdownEditor: FC<ReactMarkdownEditorProps> = ({
|
||||||
[paddingHorizontal, paddingVertical]
|
[paddingHorizontal, paddingVertical]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Enter" && event.ctrlKey) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[save]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
onChange={changeHandler}
|
onChange={changeHandler}
|
||||||
className={styles.textarea}
|
className={styles.textarea}
|
||||||
style={style}
|
style={style}
|
||||||
|
@ -39,4 +55,4 @@ const ReactMarkdownEditor: FC<ReactMarkdownEditorProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ReactMarkdownEditor };
|
export { SimpleTextareaEditor };
|
|
@ -1,6 +1,6 @@
|
||||||
import { FC, Suspense, lazy } from "react";
|
import { FC, Suspense, lazy } from "react";
|
||||||
import { ReactMarkdownEditor } from "../../components/ReactMarkdownEditor";
|
import { SimpleTextareaEditor } from "../../components/SimpleTextareaEditor";
|
||||||
import { ReactMarkdownViewer } from "../../components/ReactMarkdownViewer";
|
import { MarkdownViewer } from "../../components/MarkdownViewer";
|
||||||
import { usePersistedValue } from "./hooks/usePersistedValue";
|
import { usePersistedValue } from "./hooks/usePersistedValue";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { useSettings } from "~/modules/settings/context/SettingsContext";
|
import { useSettings } from "~/modules/settings/context/SettingsContext";
|
||||||
|
@ -10,8 +10,8 @@ import { EditorWrapper } from "../../components/EditorWrapper";
|
||||||
interface MarkdownEditorContainerProps {
|
interface MarkdownEditorContainerProps {
|
||||||
id: string;
|
id: string;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
startEditing: () => void;
|
startEditing: VoidCallback;
|
||||||
remove: () => void;
|
remove: VoidCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RichEditor = lazy(() =>
|
const RichEditor = lazy(() =>
|
||||||
|
@ -40,7 +40,7 @@ export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
||||||
const viewer = empty ? (
|
const viewer = empty ? (
|
||||||
<EmptyViewer startEditing={startEditing} />
|
<EmptyViewer startEditing={startEditing} />
|
||||||
) : (
|
) : (
|
||||||
<ReactMarkdownViewer value={value} startEditing={startEditing} />
|
<MarkdownViewer value={value} startEditing={startEditing} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const editor = (
|
const editor = (
|
||||||
|
@ -48,13 +48,17 @@ export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
||||||
{richEditorEnabled ? (
|
{richEditorEnabled ? (
|
||||||
<RichEditor value={value} onChange={setValue} locked={locked} />
|
<RichEditor value={value} onChange={setValue} locked={locked} />
|
||||||
) : (
|
) : (
|
||||||
<ReactMarkdownEditor value={value} onChange={setValue} />
|
<SimpleTextareaEditor
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
save={startEditing}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</EditorWrapper>
|
</EditorWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.editor}>
|
<div className={styles.editor} id={id}>
|
||||||
{hydrated && <Suspense>{locked ? viewer : editor}</Suspense>}
|
{hydrated && <Suspense>{locked ? viewer : editor}</Suspense>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,6 +44,15 @@ const DefaultLayout = ({
|
||||||
...panelProps.params,
|
...panelProps.params,
|
||||||
locked: !locked,
|
locked: !locked,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (panelProps.params.locked) {
|
||||||
|
setTimeout(() => {
|
||||||
|
document
|
||||||
|
.getElementById(panelProps.api.id)
|
||||||
|
?.querySelector("textarea")
|
||||||
|
?.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
}, [locked, panelProps.api, panelProps.params]);
|
}, [locked, panelProps.api, panelProps.params]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||||
import { RowGroup } from "~/components/containers/RowGroup";
|
import { RowGroup } from "~/components/containers/RowGroup";
|
||||||
import { SettingsRow } from "~/components/containers/SettingsRow";
|
import { SettingsRow } from "~/components/containers/SettingsRow";
|
||||||
import {
|
import {
|
||||||
ColorSettings,
|
ColorSettings as ColorSettingsValue,
|
||||||
SettingsValue,
|
SettingsValue,
|
||||||
useSettings,
|
useSettings,
|
||||||
} from "~/modules/settings/context/SettingsContext";
|
} from "~/modules/settings/context/SettingsContext";
|
||||||
|
@ -37,7 +37,7 @@ const ColorSettings: FC = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const setThemeColors = useCallback(
|
const setThemeColors = useCallback(
|
||||||
(val: ColorSettings) => {
|
(val: ColorSettingsValue) => {
|
||||||
update(fillThemeHeadings(val));
|
update(fillThemeHeadings(val));
|
||||||
},
|
},
|
||||||
[update]
|
[update]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue