add hotkeys

This commit is contained in:
Fedor Katurov 2024-10-02 20:50:30 +07:00
parent 6d00bffbec
commit db911e51e4
14 changed files with 91 additions and 73 deletions

4
.gitignore vendored
View file

@ -22,4 +22,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
web-ext-artifacts web-ext-artifacts
output
*.tgz

View file

@ -22,4 +22,4 @@ yarn dev
## TO-DO ## TO-DO
- Use Remirror as editor - Use HyperMD editor

View file

@ -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"
}, },

View file

@ -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"],

View file

@ -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

View file

@ -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;

View file

@ -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 };

View file

@ -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 };

View file

@ -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 };

View file

@ -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>
); );

View file

@ -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(() => {

View file

@ -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]