21 | {ctnLen > 0 ? (
~{wordCount} words, {ctnLen} characters
) : null}
22 |
Created: {fmtDatetime(note.created_at)}
23 |
Modified: {fmtDatetime(note.updated_at)}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/note/NoteMoveModal.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import useHotkeys from 'editor/hooks/useHotkeys';
3 | import MoveToInput from './NoteMoveInput';
4 |
5 | type Props = {
6 | noteId: string;
7 | setIsOpen: (isOpen: boolean) => void;
8 | };
9 |
10 | export default function MoveToModal(props: Props) {
11 | const { noteId, setIsOpen } = props;
12 |
13 | const hotkeys = useMemo(
14 | () => [
15 | {
16 | hotkey: 'esc',
17 | callback: () => setIsOpen(false),
18 | },
19 | ],
20 | [setIsOpen]
21 | );
22 | useHotkeys(hotkeys);
23 |
24 | return (
25 |
26 |
setIsOpen(false)}
29 | />
30 |
31 | setIsOpen(false)}
34 | className="z-30 w-full max-w-screen-sm bg-white rounded shadow-popover dark:bg-gray-800"
35 | />
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/note/NoteNewModal.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react';
2 | import useHotkeys from 'editor/hooks/useHotkeys';
3 | import { store } from 'lib/store';
4 | import FindOrCreateInput from './NoteNewInput';
5 |
6 | type Props = {
7 | setIsOpen: (isOpen: boolean) => void;
8 | };
9 |
10 | export default function FindOrCreateModal(props: Props) {
11 | const { setIsOpen } = props;
12 |
13 | const handleClose = useCallback(() => {
14 | store.getState().setCurrentCard(undefined);
15 | setIsOpen(false);
16 | }, [setIsOpen])
17 |
18 | const hotkeys = useMemo(
19 | () => [
20 | {
21 | hotkey: 'esc',
22 | callback: () => handleClose(),
23 | },
24 | ],
25 | [handleClose]
26 | );
27 | useHotkeys(hotkeys);
28 |
29 | return (
30 |
31 |
35 |
36 | setIsOpen(false)}
38 | className="z-30 w-full max-w-screen-sm bg-white rounded shadow-popover dark:bg-gray-800"
39 | />
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/note/NoteSumList.tsx:
--------------------------------------------------------------------------------
1 | import { IconPencil } from '@tabler/icons-react';
2 | import { parser, serializer } from "mdsmirror";
3 | import { useCurrentViewContext, DispatchType } from 'context/useCurrentView';
4 | import { Note } from 'types/model';
5 | import Tree from 'components/misc/Tree';
6 | import Tooltip from 'components/misc/Tooltip';
7 | import { openFilePath } from 'file/open';
8 |
9 | type Props = {
10 | anchor: string;
11 | notes: Note[];
12 | className?: string;
13 | isDate?: boolean;
14 | onClick?: (anchor: string) => void;
15 | };
16 |
17 | export default function NoteSumList(props: Props) {
18 | const { anchor, notes, className, isDate, onClick } = props;
19 | const currentView = useCurrentViewContext();
20 | const dispatch = currentView.dispatch;
21 |
22 | const nodeData = [
23 | {
24 | id: anchor,
25 | labelNode: (
26 |
27 | {anchor}
28 | {isDate ? (
29 |
30 |
33 |
34 | ) : null}
35 |
36 | ),
37 | children: notes.map(noteToTreeData(dispatch)),
38 | }
39 | ];
40 |
41 | const collapseAll = false;
42 |
43 | return (
44 |
45 | );
46 | }
47 |
48 | // eslint-disable-next-line react/display-name
49 | const noteToTreeData = (dispatch: DispatchType) => (note: Note) => {
50 | const doc = parser.parse(note.content);
51 | const value = doc.content.content
52 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
53 | .filter((node: any) => node.type.name === 'paragraph')
54 | .slice(0, 2);
55 | const sum: string = serializer.serialize(value);
56 |
57 | return {
58 | id: note.id,
59 | labelNode: (
60 |
61 |
72 |
{sum}
73 |
74 | ),
75 | showArrow: false,
76 | };
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/note/Title.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useEffect, useRef } from 'react';
2 | import { useStore } from 'lib/store';
3 |
4 | type Props = {
5 | initialTitle: string;
6 | onChange: (value: string) => void;
7 | className?: string;
8 | isDaily?: boolean;
9 | };
10 |
11 | function Title(props: Props) {
12 | const {
13 | initialTitle,
14 | onChange,
15 | className = '',
16 | isDaily = false,
17 | } = props;
18 | const titleRef = useRef
(null);
19 |
20 | const isCheckSpellOn = useStore((state) => state.isCheckSpellOn);
21 | const readMode = useStore((state) => state.readMode);
22 |
23 | const emitChange = () => {
24 | if (!titleRef.current) {
25 | return;
26 | }
27 | const title = titleRef.current.textContent ?? '';
28 | onChange(title);
29 | };
30 |
31 | // Set the initial title
32 | useEffect(() => {
33 | if (!titleRef.current) {
34 | return;
35 | }
36 | titleRef.current.textContent = initialTitle;
37 | }, [initialTitle]);
38 |
39 | return (
40 | {
46 | // Disallow newlines in the title field
47 | if (event.key === 'Enter') {
48 | event.preventDefault();
49 | }
50 | }}
51 | onPaste={(event) => {
52 | // Remove styling and newlines from the text
53 | event.preventDefault();
54 | let text = event.clipboardData.getData('text/plain');
55 | text = text.replace(/\r?\n|\r/g, ' ');
56 | document.execCommand('insertText', false, text);
57 | }}
58 | onBlur={emitChange}
59 | contentEditable={!(readMode || isDaily)}
60 | spellCheck={isCheckSpellOn}
61 | />
62 | );
63 | }
64 |
65 | export default memo(Title);
66 |
--------------------------------------------------------------------------------
/src/components/note/Toc.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { IconCaretRight, IconPoint } from '@tabler/icons-react';
3 |
4 | export type Heading = {
5 | title: string;
6 | level: number;
7 | id: string;
8 | };
9 |
10 | type Props = {
11 | headings: Heading[];
12 | metaInfo?: string;
13 | className?: string;
14 | };
15 |
16 | export default function Toc(props: Props) {
17 | const { headings, metaInfo = '', className = '' } = props;
18 | const [showTOC, setShowTOC] = useState
(false);
19 |
20 | return (
21 | <>
22 |
36 | {showTOC && headings.length ? (
37 |
38 | {headings.map((heading) => (
39 |
50 | ))}
51 |
52 | ) : null}
53 | >
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/note/backlinks/BacklinkBranch.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 |
3 | type BacklinkBranchProps = {
4 | title: string;
5 | };
6 |
7 | const BacklinkBranch = (props: BacklinkBranchProps) => {
8 | const { title } = props;
9 | return {title}
;
10 | };
11 |
12 | export default memo(BacklinkBranch);
13 |
--------------------------------------------------------------------------------
/src/components/note/backlinks/BacklinkMatchLeaf.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import MsEditor from "mdsmirror";
3 | import useOnNoteLinkClick from 'editor/hooks/useOnNoteLinkClick';
4 | import { useStore } from 'lib/store';
5 | import { shortenString } from 'utils/helper';
6 | import { BacklinkMatch } from './useBacklinks';
7 |
8 | type BacklinkMatchLeafProps = {
9 | noteId: string;
10 | match: BacklinkMatch;
11 | className?: string;
12 | };
13 |
14 | const BacklinkMatchLeaf = (props: BacklinkMatchLeafProps) => {
15 | const { noteId, match, className } = props;
16 | const { onClick: onNoteLinkClick } = useOnNoteLinkClick();
17 | const darkMode = useStore((state) => state.darkMode);
18 | const isRTL = useStore((state) => state.isRTL);
19 |
20 | const leafValue: string = match.context
21 | ? getContextString(match.context) || match.text
22 | : match.text;
23 | const editorValue = shortenString(leafValue, match.text);
24 |
25 | const containerClassName = `block text-left text-xs rounded p-2 my-1 w-full break-words ${className}`;
26 |
27 | return (
28 |
34 | );
35 | };
36 |
37 | export default memo(BacklinkMatchLeaf);
38 |
39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
40 | const getContextString = (nodes: any[]) =>
41 | nodes.reduce((res, node) => res + ' ' + (node.text || ''), '');
42 |
--------------------------------------------------------------------------------
/src/components/note/backlinks/BacklinkNoteBranch.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import useOnNoteLinkClick from 'editor/hooks/useOnNoteLinkClick';
3 | import { Backlink } from './useBacklinks';
4 |
5 | type BacklinkNoteBranchProps = {
6 | backlink: Backlink;
7 | };
8 |
9 | const BacklinkNoteBranch = (props: BacklinkNoteBranchProps) => {
10 | const { backlink } = props;
11 | const { onClick: onNoteLinkClick } = useOnNoteLinkClick();
12 |
13 | return (
14 |
23 | );
24 | };
25 |
26 | export default memo(BacklinkNoteBranch);
27 |
--------------------------------------------------------------------------------
/src/components/note/backlinks/Backlinks.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useCurrentViewContext } from 'context/useCurrentView';
3 | import Tree from 'components/misc/Tree';
4 | import useBacklinks from './useBacklinks';
5 | import type { Backlink, BacklinkMatch } from './useBacklinks';
6 | import BacklinkBranch from './BacklinkBranch';
7 | import BacklinkMatchLeaf from './BacklinkMatchLeaf';
8 | import BacklinkNoteBranch from './BacklinkNoteBranch';
9 |
10 | const MAX_EXPANDED_MATCHES = 42;
11 |
12 | type Props = {
13 | className?: string;
14 | isCollapse?: boolean;
15 | };
16 |
17 | export default function Backlinks(props: Props) {
18 | const { className, isCollapse = false } = props;
19 | const currentView = useCurrentViewContext();
20 | const params = currentView.state.params;
21 | const noteId = params?.noteId || '';
22 | const { linkedBacklinks, unlinkedBacklinks } = useBacklinks(noteId);
23 |
24 | const backlinkData = useMemo(
25 | () => getTreeData(linkedBacklinks, unlinkedBacklinks),
26 | [linkedBacklinks, unlinkedBacklinks]
27 | );
28 |
29 | const collapseAll = useMemo(() => {
30 | const numOfLinkedMatches = getNumOfMatches(linkedBacklinks);
31 | const numOfUnlinkedMatches = getNumOfMatches(unlinkedBacklinks);
32 | return (
33 | isCollapse ||
34 | numOfLinkedMatches > MAX_EXPANDED_MATCHES ||
35 | numOfUnlinkedMatches > MAX_EXPANDED_MATCHES
36 | );
37 | }, [linkedBacklinks, unlinkedBacklinks, isCollapse]);
38 |
39 | return (
40 |
41 | );
42 | }
43 |
44 | export const getNumOfMatches = (backlinks: Backlink[]) =>
45 | backlinks.reduce(
46 | (numOfMatches, backlink) => numOfMatches + backlink.matches.length,
47 | 0
48 | );
49 |
50 | const getTreeData = (
51 | linkedBacklinks: Backlink[],
52 | unlinkedBacklinks: Backlink[]
53 | ) => {
54 | const numOfLinkedMatches = getNumOfMatches(linkedBacklinks);
55 | const numOfUnlinkedMatches = getNumOfMatches(unlinkedBacklinks);
56 |
57 | return [
58 | {
59 | id: 'linked-backlinks',
60 | labelNode: (),
61 | children: linkedBacklinks.map(backlinkToTreeData(true)),
62 | },
63 | {
64 | id: 'unlinked-backlinks',
65 | labelNode: (),
66 | children: unlinkedBacklinks.map(backlinkToTreeData(false)),
67 | },
68 | ];
69 | };
70 |
71 | // eslint-disable-next-line react/display-name
72 | const backlinkToTreeData = (isLinked: boolean) => (backlink: Backlink) => {
73 | const matches: Array = [];
74 | const linePaths: Record = {};
75 |
76 | // Only keep matches with unique line paths
77 | for (const match of backlink.matches) {
78 | const linePathKey = `${match.from}-${match.to}`;
79 | if (!linePaths[linePathKey]) {
80 | matches.push(match);
81 | linePaths[linePathKey] = true;
82 | }
83 | }
84 |
85 | const idPrefix = isLinked ? 'linked' : 'unlinked';
86 |
87 | return {
88 | id: `${idPrefix}-${backlink.id}`,
89 | labelNode: ,
90 | children: matches.map((match) => ({
91 | id: `${idPrefix}-${backlink.id}-${match.from}-${match.to}`,
92 | labelNode: (
93 |
98 | ),
99 | showArrow: false,
100 | })),
101 | };
102 | };
103 |
--------------------------------------------------------------------------------
/src/components/note/backlinks/updateBacklinks.ts:
--------------------------------------------------------------------------------
1 | import { store } from 'lib/store';
2 | import { isUrl } from 'utils/helper';
3 | import { LINK_REGEX, WIKILINK_REGEX } from 'components/view/ForceGraph'
4 | import { writeFile } from 'file/write';
5 | import { loadDir } from 'file/open';
6 | import { computeLinkedBacklinks } from './useBacklinks';
7 |
8 | /**
9 | * Updates the backlink properties of notes on the current note title changed.
10 | * the current note is the note other notes link to
11 | * @param noteTitle of current note
12 | * @param newTitle of current note, it is undefined on delete note
13 | */
14 | const updateBacklinks = async (noteTitle: string, newTitle?: string) => {
15 | const isLoaded = store.getState().isLoaded;
16 | const setIsLoaded = store.getState().setIsLoaded;
17 | const initDir = store.getState().initDir;
18 | // console.log("updateBackLinks loaded?", isLoaded);
19 | if (!isLoaded && initDir) {
20 | loadDir(initDir).then(() => setIsLoaded(true));
21 | }
22 |
23 | const notes = store.getState().notes;
24 | const updateNote = store.getState().updateNote;
25 | const backlinks = computeLinkedBacklinks(notes, noteTitle);
26 | for (const backlink of backlinks) {
27 | const note = notes[backlink.id];
28 | if (!note) {
29 | continue;
30 | }
31 |
32 | let content = note.content;
33 | // CASE: []()
34 | const link_array: RegExpMatchArray[] = [...note.content.matchAll(LINK_REGEX)];
35 | for (const match of link_array) {
36 | const href = match[2];
37 | if (!isUrl(href)) {
38 | const title = decodeURI(href);
39 | if (noteTitle === title) {
40 | newTitle = newTitle?.trim();
41 | const replaceTo = newTitle
42 | ? `[${match[1]}](${encodeURI(newTitle)})` // rename
43 | : match[1] // delete
44 | content = content.replaceAll(match[0], replaceTo);
45 | }
46 | }
47 | }
48 | // CASE: [[]]
49 | const wiki_array: RegExpMatchArray[] = [...note.content.matchAll(WIKILINK_REGEX)];
50 | // console.log("wiki arr", wiki_array, noteTitle, newTitle)
51 | for (const match of wiki_array) {
52 | const href = match[1];
53 | if (!isUrl(href)) {
54 | const title = href;
55 | if (noteTitle === title) {
56 | newTitle = newTitle?.trim();
57 | const replaceTo = newTitle
58 | ? `[[${newTitle}]]` // rename
59 | : match[1] // delete
60 | content = content.replaceAll(match[0], replaceTo);
61 | }
62 | }
63 | }
64 |
65 | // update content and write file
66 | updateNote({ id: note.id, content });
67 | await writeFile(note?.file_path, content);
68 | }
69 | };
70 |
71 | export default updateBacklinks;
72 |
--------------------------------------------------------------------------------
/src/components/settings/AboutModal.tsx:
--------------------------------------------------------------------------------
1 | import { getVersion, getTauriVersion } from '@tauri-apps/api/app';
2 | import { writeText } from '@tauri-apps/api/clipboard';
3 | import { useState, useEffect } from 'react';
4 | import { openUrl } from 'file/open';
5 | import { getLog, clearLog } from 'file/storage';
6 | import { BaseModal } from './BaseModal';
7 |
8 |
9 | type Props = {
10 | isOpen: boolean;
11 | handleClose: () => void;
12 | }
13 |
14 | export default function AboutModal({ isOpen, handleClose }: Props) {
15 | const [appVersion, setAppVersion] = useState('');
16 | const [tauriVersion, setTauriVersion] = useState('');
17 | const [hasVersion, setHasVersion] = useState(false);
18 |
19 | useEffect(() => {
20 | if (!hasVersion) {
21 | getVersion().then(ver => setAppVersion(ver)).catch(() => {/**/});
22 | getTauriVersion().then(ver => setTauriVersion(ver)).catch(() => {/**/});
23 | }
24 | return () => { setHasVersion(true); };
25 | }, [hasVersion]);
26 |
27 | return (
28 |
29 |
30 |
mdSilo Desktop
31 |
App Version: {appVersion}
32 |
Tauri Version: {tauriVersion}
33 |
34 |
40 |
48 |
58 |
59 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/settings/BaseModal.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | The following was modified from the source code:
3 | https://github.com/cwackerfuss/react-wordle/blob/main/src/components/modals/BaseModal.tsx
4 |
5 | MIT License Copyright (c) 2022 Hannah Park
6 | */
7 |
8 | import { Fragment } from 'react';
9 | import { Dialog, Transition } from '@headlessui/react';
10 | import { IconX } from '@tabler/icons-react';
11 |
12 | type Props = {
13 | title: string;
14 | children: React.ReactNode;
15 | isOpen: boolean;
16 | handleClose: () => void;
17 | }
18 |
19 | export const BaseModal = ({ title, children, isOpen, handleClose }: Props) => {
20 | return (
21 |
22 |
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsToggle.tsx:
--------------------------------------------------------------------------------
1 | import Toggle from 'components/misc/Toggle';
2 |
3 | type Props = {
4 | name: string;
5 | descript?: string;
6 | check: boolean;
7 | handleCheck: (isChecked: boolean) => void;
8 | optionLeft?: string;
9 | optionRight?: string;
10 | }
11 |
12 | export const SettingsToggle = (props: Props) => {
13 | const {
14 | name,
15 | descript,
16 | check,
17 | handleCheck,
18 | optionLeft = 'Off',
19 | optionRight = 'On',
20 | } = props;
21 |
22 | return (
23 |
24 |
25 |
{name}
26 |
{descript}
27 |
28 |
29 | {optionLeft}
30 |
36 | {optionRight}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/sidebar/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import { useTransition, animated, SpringConfig } from '@react-spring/web';
3 | import { isMobile } from 'utils/helper';
4 | import { useStore } from 'lib/store';
5 | import SidebarContent from './SidebarContent';
6 | import SidebarHeader from './SidebarHeader';
7 |
8 | const SPRING_CONFIG: SpringConfig = {
9 | mass: 1,
10 | tension: 170,
11 | friction: 10,
12 | clamp: true,
13 | } as const;
14 |
15 | type Props = {
16 | className?: string;
17 | };
18 |
19 | function Sidebar(props: Props) {
20 | const { className='' } = props;
21 |
22 | const isSidebarOpen = useStore((state) => state.isSidebarOpen);
23 | const setIsSidebarOpen = useStore((state) => state.setIsSidebarOpen);
24 |
25 | const transition = useTransition<
26 | boolean,
27 | {
28 | transform: string;
29 | dspl: number;
30 | backgroundOpacity: number;
31 | backgroundColor: string;
32 | }
33 | >(isSidebarOpen, {
34 | initial: {
35 | transform: 'translateX(0%)',
36 | dspl: 1,
37 | backgroundOpacity: 0.3,
38 | backgroundColor: 'black',
39 | },
40 | from: {
41 | transform: 'translateX(-100%)',
42 | dspl: 0,
43 | backgroundOpacity: 0,
44 | backgroundColor: 'transparent',
45 | },
46 | enter: {
47 | transform: 'translateX(0%)',
48 | dspl: 1,
49 | backgroundOpacity: 0.3,
50 | backgroundColor: 'black',
51 | },
52 | leave: {
53 | transform: 'translateX(-100%)',
54 | dspl: 0,
55 | backgroundOpacity: 0,
56 | backgroundColor: 'transparent',
57 | },
58 | config: SPRING_CONFIG,
59 | expires: (item) => !item,
60 | });
61 |
62 | return transition(
63 | (styles, item) =>
64 | item && (
65 | <>
66 | {isMobile() ? (
67 |
73 | displ === 0 ? 'none' : 'initial'
74 | ),
75 | }}
76 | onClick={() => setIsSidebarOpen(false)}
77 | />
78 | ) : null}
79 |
84 | displ === 0 ? 'none' : 'initial'
85 | ),
86 | }}
87 | >
88 |
91 |
92 |
93 |
94 |
95 | >
96 | )
97 | );
98 | }
99 |
100 | export default memo(Sidebar);
101 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarContent.tsx:
--------------------------------------------------------------------------------
1 | import { IconFolder, IconHash, IconPlaylist, IconSearch } from '@tabler/icons-react';
2 | import Tooltip from 'components/misc/Tooltip';
3 | import { SidebarTab as SidebarTabType, useStore } from 'lib/store';
4 | import SidebarNotes from './SidebarNotes';
5 | import SidebarPlaylist from './SidebarPlaylist';
6 | import SidebarSearch from './SidebarSearch';
7 | import SidebarTab from './SidebarTab';
8 | import SidebarTags from './SidebarTags';
9 |
10 | type Props = {
11 | className?: string;
12 | };
13 |
14 | export default function SidebarContent(props: Props) {
15 | const { className } = props;
16 | const activeTab = useStore((state) => state.sidebarTab);
17 | const setActiveTab = useStore((state) => state.setSidebarTab);
18 |
19 | return (
20 |
21 |
22 |
23 | {activeTab === SidebarTabType.Silo ? : null}
24 | {activeTab === SidebarTabType.Search ? : null}
25 | {activeTab === SidebarTabType.Hashtag ? : null}
26 | {activeTab === SidebarTabType.Playlist ? : null}
27 |
28 |
29 | );
30 | }
31 |
32 | type TabsProps = {
33 | activeTab: SidebarTabType;
34 | setActiveTab: (tab: SidebarTabType) => void;
35 | };
36 |
37 | const Tabs = (props: TabsProps) => {
38 | const { activeTab, setActiveTab } = props;
39 |
40 | return (
41 |
42 |
43 | setActiveTab(SidebarTabType.Silo)}
46 | Icon={IconFolder}
47 | />
48 |
49 |
50 | setActiveTab(SidebarTabType.Search)}
53 | Icon={IconSearch}
54 | />
55 |
56 |
57 | setActiveTab(SidebarTabType.Hashtag)}
60 | Icon={IconHash}
61 | />
62 |
63 |
64 | setActiveTab(SidebarTabType.Playlist)}
67 | Icon={IconPlaylist}
68 | />
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarHeader.tsx:
--------------------------------------------------------------------------------
1 | import { invoke } from '@tauri-apps/api';
2 | import { Menu } from '@headlessui/react';
3 | import {
4 | IconChevronsDown, IconChevronLeft, IconSettings, IconBrowser,
5 | IconPizza, IconInfoCircle, IconCurrentLocation
6 | } from '@tabler/icons-react';
7 | import { useStore } from 'lib/store';
8 | import Tooltip from 'components/misc/Tooltip';
9 | import { DropdownItem } from 'components/misc/Dropdown';
10 | import { isMobile } from 'utils/helper';
11 |
12 |
13 | export default function SidebarHeader() {
14 | const setIsSidebarOpen = useStore((state) => state.setIsSidebarOpen);
15 | const setIsSettingsOpen = useStore((state) => state.setIsSettingsOpen);
16 | const setIsAboutOpen = useStore((state) => state.setIsAboutOpen);
17 |
18 | return (
19 |
20 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import { ForwardedRef, forwardRef, HTMLAttributes, memo } from 'react';
2 |
3 | interface SidebarItemProps extends HTMLAttributes {
4 | isHighlighted?: boolean;
5 | }
6 |
7 | function SidebarItem(
8 | props: SidebarItemProps,
9 | forwardedRef: ForwardedRef
10 | ) {
11 | const { children, className = '', isHighlighted, ...otherProps } = props;
12 | const itemClassName = `w-full overflow-x-hidden overflow-ellipsis whitespace-nowrap text-gray-800 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700 ${isHighlighted ? 'bg-gray-300 dark:bg-gray-600' : 'bg-gray-50 dark:bg-gray-800'} ${className}`;
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | }
20 |
21 | export default memo(forwardRef(SidebarItem));
22 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarNotes.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useMemo } from 'react';
2 | import { NoteTreeItem, useStore } from 'lib/store';
3 | import { Sort } from 'lib/userSettings';
4 | import { ciStringCompare, dateCompare } from 'utils/helper';
5 | import { onOpenFile, onListDir } from 'editor/hooks/useOpen';
6 | import ErrorBoundary from '../misc/ErrorBoundary';
7 | import SidebarNotesBar from './SidebarNotesBar';
8 | import SidebarNotesTree from './SidebarNotesTree';
9 | import SidebarHistory from './SidebarHistory';
10 |
11 | type SidebarNotesProps = {
12 | className?: string;
13 | };
14 |
15 | function SidebarNotes(props: SidebarNotesProps) {
16 | const { className='' } = props;
17 |
18 | const currentDir = useStore((state) => state.currentDir);
19 |
20 | const noteTree = useStore((state) => state.noteTree);
21 | const noteSort = useStore((state) => state.noteSort);
22 | // console.log("note tree", noteTree)
23 | const [sortedNoteTree, numOfNotes] = useMemo(() => {
24 | if (currentDir) {
25 | const treeList = noteTree[currentDir] || [];
26 | return [sortNoteTree(treeList, noteSort), treeList.length];
27 | } else {
28 | return [[], 0];
29 | }
30 | }, [noteTree, currentDir, noteSort]);
31 |
32 | // console.log("tree", numOfNotes, sortedNoteTree, currentDir)
33 |
34 | const btnClass = "p-1 mt-4 mx-4 text-white rounded bg-blue-500 hover:bg-blue-800";
35 |
36 | return (
37 |
38 |
39 | {currentDir ? (
40 |
44 | ) : null }
45 | {sortedNoteTree && sortedNoteTree.length > 0 ? (
46 |
50 | ) : currentDir ? null : (
51 | <>
52 |
53 |
54 |
55 | >
56 | )}
57 |
58 |
59 | );
60 | }
61 |
62 | /**
63 | * Sorts the tree item with the given noteSort.
64 | */
65 | const sortNoteTree = (
66 | tree: NoteTreeItem[],
67 | noteSort: Sort
68 | ): NoteTreeItem[] => {
69 | // Copy tree shallowly
70 | const newTree = [...tree];
71 | // Sort tree items (one level)
72 | if (newTree.length >= 2) {
73 | newTree.sort((n1, n2) => {
74 | switch (noteSort) {
75 | case Sort.DateModifiedAscending:
76 | return dateCompare(n1.updated_at, n2.updated_at);
77 | case Sort.DateModifiedDescending:
78 | return dateCompare(n2.updated_at, n1.updated_at);
79 | case Sort.DateCreatedAscending:
80 | return dateCompare(n1.created_at, n2.created_at);
81 | case Sort.DateCreatedDescending:
82 | return dateCompare(n2.created_at, n1.created_at);
83 | case Sort.TitleAscending:
84 | return ciStringCompare(n1.title, n2.title);
85 | case Sort.TitleDescending:
86 | return ciStringCompare(n2.title, n1.title);
87 | default:
88 | return ciStringCompare(n1.title, n2.title);
89 | }
90 | });
91 | newTree.sort((n1, n2) => Number(Boolean(n2.is_dir)) - Number(Boolean(n1.is_dir)));
92 | }
93 |
94 | return newTree;
95 | };
96 |
97 | export default memo(SidebarNotes);
98 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarNotesBar.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import { IconArrowBarToUp } from '@tabler/icons-react';
3 | import { useStore } from 'lib/store';
4 | import { Sort } from 'lib/userSettings';
5 | import Tooltip from 'components/misc/Tooltip';
6 | import { normalizeSlash, getParentDir } from 'file/util';
7 | import { listDirPath } from 'editor/hooks/useOpen';
8 | import SidebarNotesSortDropdown from './SidebarNotesSortDropdown';
9 | import { SidebarDirDropdown } from './SidebarDropdown';
10 |
11 | type Props = {
12 | noteSort: Sort;
13 | numOfNotes: number;
14 | };
15 |
16 | function SidebarNotesBar(props: Props) {
17 | const { noteSort, numOfNotes} = props;
18 | const setNoteSort = useStore((state) => state.setNoteSort);
19 |
20 | const initDir = useStore((state) => state.initDir);
21 | const currentDir = useStore((state) => state.currentDir);
22 | const checkInit: boolean = !currentDir || currentDir === initDir;
23 |
24 | const currentFolder = currentDir
25 | ? normalizeSlash(currentDir).split('/').pop() || '/'
26 | : 'md';
27 | const barClass = `px-2 text-sm bg-blue-500 text-white rounded overflow-hidden`;
28 |
29 | return (
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 | {currentDir ? (
41 |
47 | ) : (
48 |
49 | {currentFolder}: {numOfNotes}
50 |
51 | )}
52 |
53 |
54 |
55 |
56 |
68 |
69 |
70 | );
71 | }
72 |
73 | export default memo(SidebarNotesBar);
74 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarNotesSortDropdown.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState, memo } from 'react';
2 | import { Menu } from '@headlessui/react';
3 | import { IconSortDescending, IconCheck } from '@tabler/icons-react';
4 | import { usePopper } from 'react-popper';
5 | import { ReadableNameBySort, Sort } from 'lib/userSettings';
6 | import Tooltip from 'components/misc/Tooltip';
7 | import Portal from 'components/misc/Portal';
8 |
9 | type Props = {
10 | currentSort: Sort;
11 | setCurrentSort: (sort: Sort) => void;
12 | };
13 |
14 | const SidebarNotesSortDropdown = (props: Props) => {
15 | const { currentSort, setCurrentSort } = props;
16 |
17 | const buttonRef = useRef(null);
18 | const [popperElement, setPopperElement] = useState(
19 | null
20 | );
21 | const { styles, attributes } = usePopper(buttonRef.current, popperElement, {
22 | placement: 'bottom-start',
23 | });
24 |
25 | return (
26 |
90 | );
91 | };
92 |
93 | export default memo(SidebarNotesSortDropdown);
94 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarNotesTree.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useCallback, memo } from 'react';
2 | import List from 'react-virtualized/dist/commonjs/List';
3 | import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
4 | import { NoteTreeItem } from 'lib/store';
5 | import { useCurrentViewContext } from 'context/useCurrentView';
6 | import SidebarNoteLink from './SidebarNoteLink';
7 |
8 | type Props = {
9 | data: NoteTreeItem[];
10 | className?: string;
11 | };
12 |
13 | function SidebarNotesTree(props: Props) {
14 | const { data, className } = props;
15 |
16 | const currentView = useCurrentViewContext();
17 | const params = currentView.state.params;
18 | const noteId = params?.noteId || '';
19 |
20 | const currentNoteId = useMemo(() => {
21 | const id = noteId;
22 | return id && typeof id === 'string' ? id : undefined;
23 | }, [noteId]);
24 |
25 | const Row = useCallback(
26 | ({ index, style }: {index: number; style: React.CSSProperties}) => {
27 | const node = data[index];
28 | return (
29 |
35 | );
36 | },
37 | [currentNoteId, data]
38 | );
39 |
40 | return (
41 |
42 |
43 | {({ width, height }) => (
44 |
51 | )}
52 |
53 |
54 | );
55 | }
56 |
57 | export default memo(SidebarNotesTree);
58 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarTab.tsx:
--------------------------------------------------------------------------------
1 | import { ForwardedRef, forwardRef, memo } from 'react';
2 | import { Icon } from '@tabler/icons-react';
3 |
4 | type Props = {
5 | isActive: boolean;
6 | setActive: () => void;
7 | Icon: Icon;
8 | className?: string;
9 | };
10 |
11 | const SidebarTab = (
12 | props: Props,
13 | forwardedRef: ForwardedRef
14 | ) => {
15 | const { isActive, setActive, Icon, className = '' } = props;
16 | return (
17 |
31 | );
32 | };
33 |
34 | export default memo(forwardRef(SidebarTab));
35 |
--------------------------------------------------------------------------------
/src/components/sidebar/StatusBar.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useCallback, useState } from 'react';
2 | import { IconHeadphones } from '@tabler/icons-react';
3 | import { store, useStore } from 'lib/store';
4 | import AudioPlayer from 'components/feed/AudioPlayer';
5 | import * as dataAgent from 'components/feed/dataAgent';
6 | import { useCurrentViewContext } from 'context/useCurrentView';
7 |
8 | type Props = {
9 | className?: string;
10 | };
11 |
12 | function Statusbar(props: Props) {
13 | const { className = '' } = props;
14 |
15 | const currentPod = useStore((state) => state.currentPod);
16 | const [hide, setHide] = useState(false);
17 | // console.log("current pod: ", currentPod)
18 | const currentView = useCurrentViewContext();
19 | const dispatch = currentView.dispatch;
20 | const toArticle = useCallback(async (url: string) => {
21 | const article = await dataAgent.getArticleByUrl(url);
22 | if (article) {
23 | store.getState().setCurrentArticle(article);
24 | dispatch({view: 'feed'});
25 | }
26 | }, [dispatch])
27 |
28 | return (
29 |
30 |
33 | {currentPod && (
34 |
35 |
await toArticle(currentPod.article_url)}
38 | >
39 | {currentPod.title}
40 |
41 |
42 |
43 | )}
44 |
45 | );
46 | }
47 |
48 | export default memo(Statusbar);
49 |
--------------------------------------------------------------------------------
/src/components/view/MainView.tsx:
--------------------------------------------------------------------------------
1 | import MsEditor from "mdsmirror";
2 | import ErrorBoundary from 'components/misc/ErrorBoundary';
3 | import { useCurrentViewContext } from 'context/useCurrentView';
4 | import Chronicle from './chronicle';
5 | import Journals from './journals';
6 | import Tasks from './tasks';
7 | import Kanban from './kanban';
8 | import Graph from './graph';
9 | import NotePage from './md';
10 | import HashTags from "./hashtags";
11 | import Feed from "./feed";
12 |
13 | export default function MainView() {
14 | const currentView = useCurrentViewContext();
15 | const viewTy = currentView.state.view;
16 | //
17 | return (
18 | <>
19 | {viewTy === 'default' ? (
20 |
21 | ) : viewTy === 'feed' ? (
22 |
23 | ) : viewTy === 'chronicle' ? (
24 |
25 | ) : viewTy === 'task' ? (
26 |
27 | ) : viewTy === 'kanban' ? (
28 |
29 | ) : viewTy === 'graph' ? (
30 |
31 | ) : viewTy === 'journal' ? (
32 |
33 | ) : viewTy === 'tag' ? (
34 |
35 | ) : (
36 |
37 | )}
38 | >
39 | );
40 | }
41 |
42 | function DefaultView() {
43 | return (
44 |
45 |
46 |
47 | Hello, welcome to mdSilo Desktop.
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
55 | const defaultValue = `
56 | A lightweight, local-first personal Wiki and knowledge base for storing ideas, thought, knowledge with the powerful all-in-one reading/writing tool. Use it to organize writing, network thoughts and build a Second Brain on top of local plain text Markdown files.
57 |
58 | ## Features
59 | - ➰ I/O: Feed & Podcast client(Input) and Personal Wiki(Output);
60 | - 🔀 All-In-One Editor: Markdown, WYSIWYG, Mind Map...
61 | - 📝 Markdown and extensions: Math/Chemical Equation, Diagram, Hashtag...
62 | - 🗄️ Build personal wiki with bidirectional wiki links
63 | - ⌨️ Slash commands, Hotkeys and Hovering toolbar...
64 | - 📋 Kanban board to manage the process of knowledge growing
65 | - 🕸️ Graph view to visualize the networked writing
66 | - 📅 Chronicle view and Daily activities graph
67 | - ✔️ Task view to track todo/doing/done
68 | - 🔍 Full-text search
69 | - ✨ Available for Windows, macOS, Linux and Web
70 |
71 | For human brain, Reading and Writing is the I/O: the communication between the information processing system and the outside world. mdSilo is here to boost your daily I/O, it is tiny yet powerful, free for everyone.
72 | \\
73 | `;
74 |
--------------------------------------------------------------------------------
/src/components/view/graph.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import ErrorBoundary from 'components/misc/ErrorBoundary';
3 | import ForceGraph from 'components/view/ForceGraph';
4 | import { useStore } from 'lib/store';
5 | import { loadDir } from 'file/open';
6 |
7 | export default function Graph() {
8 | const isLoaded = useStore((state) => state.isLoaded);
9 | const setIsLoaded = useStore((state) => state.setIsLoaded);
10 | const initDir = useStore((state) => state.initDir);
11 | // console.log("g loaded?", isLoaded);
12 | useEffect(() => {
13 | if (!isLoaded && initDir) {
14 | loadDir(initDir).then(() => setIsLoaded(true));
15 | }
16 | }, [initDir, isLoaded, setIsLoaded]);
17 |
18 | return (
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/view/hashtags.tsx:
--------------------------------------------------------------------------------
1 | import ErrorBoundary from 'components/misc/ErrorBoundary';
2 | import { useCurrentViewContext } from 'context/useCurrentView';
3 | import { SearchTree } from 'components/sidebar/SidebarSearch';
4 |
5 | export default function HashTags() {
6 | const className = '';
7 | const currentView = useCurrentViewContext();
8 | const tag = currentView.state.tag || '';
9 |
10 | return (
11 |
12 |
13 |
14 | {`#${tag}`}
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/view/journals.tsx:
--------------------------------------------------------------------------------
1 | import MsEditor from "mdsmirror";
2 | import { useCurrentViewContext } from 'context/useCurrentView';
3 | import { useStore } from 'lib/store';
4 | import { Note } from 'types/model';
5 | import ErrorBoundary from 'components/misc/ErrorBoundary';
6 | import FindOrCreateInput from 'components/note/NoteNewInput';
7 | import { dateCompare, regDateStr, strToDate } from 'utils/helper';
8 | import { openFilePath } from "file/open";
9 |
10 | export default function Journals() {
11 | const initDir = useStore((state) => state.initDir);
12 | const notes = useStore((state) => state.notes);
13 | const notesArr = Object.values(notes);
14 | const dailyNotes = notesArr.filter(n => regDateStr.test(n.title));
15 | dailyNotes.sort(
16 | (n1, n2) => dateCompare(strToDate(n2.title), strToDate(n1.title))
17 | );
18 |
19 | return (
20 |
21 |
22 |
23 | {initDir ? (
24 |
27 | ) : null}
28 |
29 |
30 | {dailyNotes.map((n) => ())}
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | type NoteItemProps = {
38 | note: Note;
39 | };
40 |
41 | function NoteItem(props: NoteItemProps) {
42 | const { note } = props;
43 | const currentView = useCurrentViewContext();
44 | const dispatch = currentView.dispatch;
45 | const darkMode = useStore((state) => state.darkMode);
46 | const isRTL = useStore((state) => state.isRTL);
47 |
48 | return (
49 |
50 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/view/kanban.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect, useMemo } from 'react';
2 | import { useStore } from 'lib/store';
3 | import ErrorBoundary from 'components/misc/ErrorBoundary';
4 | import KanbanBoard from 'components/kanban/Board';
5 | import { Card, Column, Kanbans } from 'components/kanban/types';
6 | import { joinPath } from 'file/util';
7 | import FileAPI from 'file/files';
8 |
9 | export default function Kanban() {
10 | const initDir = useStore((state) => state.initDir);
11 | const currentKanban = useStore((state) => state.currentBoard);
12 | const setCurrentKanban = useStore((state) => state.setCurrentBoard);
13 | const [kanbans, setKanbans] = useState({});
14 |
15 | // console.log("currentKanban", currentKanban, kanbans);
16 |
17 | useEffect(() => {
18 | const kanbanJsonPath = initDir ? joinPath(initDir, `kanban.json`) : '';
19 | if (kanbanJsonPath) {
20 | const jsonFile = new FileAPI(kanbanJsonPath);
21 | jsonFile.readFile().then(json => {
22 | const kanbans: Kanbans = JSON.parse(json || "{}");
23 | // console.log("effect Kanbans", kanbans);
24 | setKanbans(kanbans);
25 | });
26 | }
27 | }, [initDir]);
28 |
29 | const onKanbanChange = useCallback(
30 | async (columns: Column[], cards: Card[], bgColor?: string, bgImg?: string) => {
31 | const saveFile = new FileAPI('kanban.json', initDir);
32 | const name = currentKanban || "default";
33 | const oldData = kanbans[name];
34 | const newData = {
35 | columns,
36 | cards,
37 | bgColor: bgColor || oldData?.bgColor,
38 | bgImg: bgImg || oldData?.bgImg,
39 | };
40 | kanbans[name] = newData;
41 | // console.log("to save kanba", kanbans);
42 | await saveFile.writeFile(JSON.stringify(kanbans));
43 | },
44 | [currentKanban, initDir, kanbans]
45 | );
46 |
47 | const [newKanban, setNewKanban] = useState("new kanban");
48 |
49 | const kanbanData = useMemo(() => {
50 | const name = currentKanban || "default";
51 | return kanbans[name] ?? {columns: [], cards: []};
52 | }, [currentKanban, kanbans]);
53 |
54 | return (
55 |
56 |
57 |
58 | {Object.keys(kanbans).map((k, index) => (
59 |
64 | ))}
65 | {setNewKanban(ev.target.value);}}
71 | onKeyDown={(ev) => {
72 | if (ev.key !== "Enter") return;
73 | setCurrentKanban(newKanban);
74 | }}
75 | />
76 |
81 |
82 | {kanbanData && (
83 |
88 | )}
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/view/md.tsx:
--------------------------------------------------------------------------------
1 | import { useCurrentViewContext } from 'context/useCurrentView';
2 | import Note from 'components/note/Note';
3 |
4 | export default function NotePage() {
5 | const currentView = useCurrentViewContext();
6 | const params = currentView.state.params;
7 | const noteId = params?.noteId || '';
8 | // const hlHash = params?.hash || '';
9 |
10 | if (!noteId || typeof noteId !== 'string') {
11 | return (
12 |
13 |
14 | This note does not exists!
15 |
16 |
17 | );
18 | }
19 |
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/context/useCurrentMd.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from 'react';
2 | import { useContext, createContext, Dispatch } from 'react';
3 | import { ViewAction, ViewState } from './viewReducer';
4 |
5 | type CurrentMd = {
6 | ty: string;
7 | id: string;
8 | state: ViewState;
9 | dispatch: Dispatch;
10 | };
11 |
12 | const CurrContext = createContext(undefined);
13 |
14 | type Props = {
15 | children: ReactNode;
16 | value: CurrentMd;
17 | };
18 |
19 | export function ProvideCurrentMd({ children, value }: Props) {
20 | return {children};
21 | }
22 |
23 | export const useCurrentMdContext = () => {
24 | const context = useContext(CurrContext);
25 | if (context === undefined) {
26 | throw new Error('useCurrentContext must be used within a provider');
27 | }
28 | return context;
29 | };
30 |
--------------------------------------------------------------------------------
/src/context/useCurrentView.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from 'react';
2 | import { useContext, createContext, useReducer, Dispatch } from 'react';
3 | import { viewReducer, initialState, ViewAction, ViewState } from './viewReducer';
4 |
5 | export type DispatchType = Dispatch;
6 |
7 | type CurrentView = {
8 | state: ViewState;
9 | dispatch: DispatchType;
10 | };
11 |
12 | const CurrentViewContext = createContext(undefined);
13 |
14 | export function ProvideCurrentView({ children }: {children: ReactNode;}) {
15 | const [state, dispatch] = useReducer(viewReducer, initialState);
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | }
22 |
23 | export const useCurrentViewContext = () => {
24 | const context = useContext(CurrentViewContext);
25 | if (context === undefined) {
26 | throw new Error('useCurrentViewContext must be used within a provider');
27 | }
28 | return context;
29 | };
30 |
--------------------------------------------------------------------------------
/src/context/viewReducer.ts:
--------------------------------------------------------------------------------
1 | import { store } from 'lib/store';
2 | import { setWindowTitle } from 'file/util';
3 |
4 | export const initialState = {view: 'default'};
5 |
6 | type ViewParams = { noteId: string; stackIds?: string[], hash?: string };
7 |
8 | export interface ViewState {
9 | view: string;
10 | params?: ViewParams;
11 | tag?: string;
12 | }
13 |
14 | export type ViewAction =
15 | | { view: 'default' }
16 | | { view: 'feed' }
17 | | { view: 'chronicle' }
18 | | { view: 'task' }
19 | | { view: 'graph' }
20 | | { view: 'journal' }
21 | | { view: 'kanban' }
22 | | {
23 | view: 'md';
24 | params: ViewParams;
25 | }
26 | | {
27 | view: 'tag';
28 | tag: string;
29 | };
30 |
31 | export function viewReducer(state: ViewState, action: ViewAction): ViewState {
32 | const actionView = action.view;
33 | if (actionView === 'md') {
34 | store.getState().setCurrentNoteId(action.params.noteId);
35 | } else {
36 | store.getState().setCurrentNoteId('');
37 | setWindowTitle(`: ${actionView} - mdSilo`);
38 | }
39 |
40 | switch (actionView) {
41 | case 'default':
42 | return {...state, view: 'default'};
43 | case 'feed':
44 | return {...state, view: 'feed'};
45 | case 'chronicle':
46 | return {...state, view: 'chronicle'};
47 | case 'task':
48 | return {...state, view: 'task'};
49 | case 'graph':
50 | return {...state, view: 'graph'};
51 | case 'kanban':
52 | return {...state, view: 'kanban'};
53 | case 'journal':
54 | return {...state, view: 'journal'};
55 | case 'md':
56 | return {view: 'md', params: action.params};
57 | case 'tag':
58 | return {view: 'tag', tag: action.tag};
59 | default:
60 | throw new Error();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/editor/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction, useEffect, useState } from 'react';
2 |
3 | export default function useDebounce(
4 | value: T,
5 | delay: number
6 | ): [T, Dispatch>] {
7 | const [debouncedValue, setDebouncedValue] = useState(value);
8 |
9 | useEffect(() => {
10 | const handler = setTimeout(() => {
11 | setDebouncedValue(value);
12 | }, delay);
13 |
14 | return () => {
15 | clearTimeout(handler);
16 | };
17 | }, [value, delay]);
18 |
19 | return [debouncedValue, setDebouncedValue];
20 | }
21 |
--------------------------------------------------------------------------------
/src/editor/hooks/useDeleteNote.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useCurrentViewContext } from 'context/useCurrentView';
3 | import { store } from 'lib/store';
4 | import updateBacklinks from 'components/note/backlinks/updateBacklinks';
5 | import { deleteFile } from 'file/write';
6 |
7 | export default function useDeleteNote(noteId: string, noteTitle: string) {
8 | const currentView = useCurrentViewContext();
9 | const dispatch = currentView.dispatch;
10 |
11 | const onDeleteClick = useCallback(async () => {
12 | dispatch({view: 'default'});
13 | doDeleteNote(noteId, noteTitle);
14 | }, [dispatch, noteId, noteTitle]);
15 |
16 | return onDeleteClick;
17 | }
18 |
19 | export async function doDeleteNote(noteId: string, noteTitle: string) {
20 | // delete in store
21 | store.getState().deleteNote(noteId);
22 | // delete backlinks
23 | await updateBacklinks(noteTitle, undefined);
24 | // delete in disk,
25 | await deleteFile(noteId);
26 | }
27 |
--------------------------------------------------------------------------------
/src/editor/hooks/useExport.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { invoke } from '@tauri-apps/api';
3 | import { jsPDF } from "jspdf";
4 | import html2canvas from 'html2canvas';
5 |
6 | export function ExportAs(as: string, filePath: string) {
7 | const pixelRatio = window.devicePixelRatio;
8 | const note = document.getElementById("note-content");
9 | if (!note) return;
10 |
11 | html2canvas(note).then(async function (canvas) {
12 | const imgData = canvas.toDataURL("image/png");
13 | requestAnimationFrame(() => {
14 | if (as === 'pdf') {
15 | handlePdf(imgData, canvas, pixelRatio, filePath);
16 | } else {
17 | handleImg(imgData, filePath);
18 | }
19 | });
20 | await invoke('msg_dialog', { title: "Export", msg: filePath });
21 | });
22 | }
23 |
24 | async function handleImg(imgData: string, filePath: string) {
25 | const binaryData = atob(imgData.split("base64,")[1]);
26 | const data = [];
27 | for (let i = 0; i < binaryData.length; i++) {
28 | data.push(binaryData.charCodeAt(i));
29 | }
30 | await invoke('download_file', { filePath, blob: data });
31 | }
32 |
33 | async function handlePdf(
34 | imgData: string,
35 | canvas: HTMLCanvasElement,
36 | pixelRatio: number,
37 | filePath: string,
38 | ) {
39 | const orientation = canvas.width > canvas.height ? "l" : "p";
40 | const pdf = new jsPDF(orientation, "pt", [
41 | canvas.width / pixelRatio,
42 | canvas.height / pixelRatio,
43 | ]);
44 | const pdfWidth = pdf.internal.pageSize.getWidth();
45 | const pdfHeight = pdf.internal.pageSize.getHeight();
46 | pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
47 |
48 | const data = (pdf as any).__private__.getArrayBuffer(
49 | (pdf as any).__private__.buildDocument()
50 | );
51 | await invoke('download_file', { filePath, blob: Array.from(new Uint8Array(data)) });
52 | }
53 |
--------------------------------------------------------------------------------
/src/editor/hooks/useHotkeys.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import isHotkey from 'is-hotkey';
3 |
4 | /**
5 | mod+n: new note
6 | mod+p: new page(item) // reserve
7 | mod+s: save note // reserve
8 | Esc, esc quite
9 | ...
10 | **/
11 | export default function useHotkeys(
12 | hotkeys: { hotkey: string; callback: () => void }[]
13 | ) {
14 | useEffect(() => {
15 | const handleKeyboardShortcuts = (event: KeyboardEvent) => {
16 | for (const { hotkey, callback } of hotkeys) {
17 | if (isHotkey(hotkey, event)) {
18 | event.preventDefault();
19 | callback();
20 | }
21 | }
22 | };
23 | document.addEventListener('keydown', handleKeyboardShortcuts);
24 | return () =>
25 | document.removeEventListener('keydown', handleKeyboardShortcuts);
26 | }, [hotkeys]);
27 | }
28 |
--------------------------------------------------------------------------------
/src/editor/hooks/useOnNoteLinkClick.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useCurrentViewContext } from 'context/useCurrentView';
3 | import { openFilePath } from 'file/open';
4 |
5 | export default function useOnNoteLinkClick() {
6 | const currentView = useCurrentViewContext();
7 | const dispatch = currentView.dispatch;
8 |
9 | const onClick = useCallback(
10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
11 | async (toId: string, highlightedPath?: any) => {
12 | const note = await openFilePath(toId, true);
13 | if (!note) return;
14 | const noteId = note.id;
15 | const hash = highlightedPath ? `0-${highlightedPath}` : ''; // TODO
16 | dispatch({view: 'md', params: {noteId, hash}});
17 | return;
18 | },
19 | [dispatch]
20 | );
21 |
22 | return { onClick };
23 | }
24 |
--------------------------------------------------------------------------------
/src/editor/hooks/useTasks.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useEffect } from 'react';
2 | import { parser, Task, getTasks } from "mdsmirror";
3 | import { Notes, useStore } from 'lib/store';
4 | import { loadDir } from 'file/open';
5 | import { Note } from 'types/model';
6 |
7 | type TaskWithID = { title: string} & Task;
8 |
9 | export type DocTask = {
10 | note: Note;
11 | tasks: TaskWithID[];
12 | };
13 |
14 | export default function useTasks() {
15 | const isLoaded = useStore((state) => state.isLoaded);
16 | const setIsLoaded = useStore((state) => state.setIsLoaded);
17 | const initDir = useStore((state) => state.initDir);
18 | // console.log("tk loaded?", isLoaded);
19 | useEffect(() => {
20 | if (!isLoaded && initDir) {
21 | loadDir(initDir).then(() => setIsLoaded(true));
22 | }
23 | }, [initDir, isLoaded, setIsLoaded]);
24 |
25 | const notes = useStore((state) => state.notes);
26 |
27 | const docTasks = useMemo(
28 | () => computeTasks(notes),
29 | [notes]
30 | );
31 |
32 | return docTasks;
33 | }
34 |
35 | export const computeTasks = (notes: Notes): DocTask[] => {
36 | const result: DocTask[] = [];
37 | const myNotes = Object.values(notes);
38 | for (const note of myNotes) {
39 | const tasks = computeNoteTasks(note.content);
40 |
41 | if (tasks.length > 0) {
42 | const newTasks: TaskWithID[] = tasks.map(
43 | t => {return {title: note.title, text: t.text, completed: t.completed}}
44 | );
45 | result.push({ note, tasks: newTasks });
46 | }
47 | }
48 | return result;
49 | };
50 |
51 | const computeNoteTasks = (content: string) => {
52 | const doc = parser.parse(content);
53 | // console.log(">> doc: ", doc, content)
54 | const tasks = getTasks(doc);
55 |
56 | return tasks;
57 | };
58 |
--------------------------------------------------------------------------------
/src/file/process.ts:
--------------------------------------------------------------------------------
1 | import { NotesData } from 'lib/store';
2 | import { regDateStr } from 'utils/helper';
3 | import { Note, defaultNote } from 'types/model';
4 | import { FileMetaData } from 'file/directory';
5 |
6 | export function processJson(content: string): NotesData {
7 | try {
8 | const notesData: NotesData = JSON.parse(content);
9 | return notesData;
10 | } catch (e) {
11 | console.log('Please Check the JSON file: ', e);
12 | return {isloaded: false, notesobj: {}, notetree: {}};
13 | }
14 | }
15 |
16 | /**
17 | * on Process Files:
18 | */
19 | export function processFiles(fileList: FileMetaData[]) {
20 | const newNotesData: Note[] = [];
21 | const nonNotesData: Note[] = [];
22 |
23 | for (const file of fileList) {
24 | const fileName = file.file_name;
25 |
26 | if (!fileName || !file.is_file) {
27 | continue;
28 | }
29 | const fileContent = file.file_text;
30 | const filePath = file.file_path;
31 |
32 | const checkMd = checkFileIsMd(fileName);
33 | // new note from file
34 | const newNoteTitle = checkMd ? rmFileNameExt(fileName) : fileName;
35 | const lastModDate = new Date(file.last_modified.secs_since_epoch * 1000).toISOString();
36 | const createdDate = new Date(file.created.secs_since_epoch * 1000).toISOString();
37 | const isDaily = checkMd ? regDateStr.test(newNoteTitle) : false;
38 | const newNoteObj = {
39 | id: filePath,
40 | title: newNoteTitle,
41 | content: fileContent,
42 | created_at: createdDate,
43 | updated_at: lastModDate,
44 | is_daily: isDaily,
45 | file_path: filePath,
46 | };
47 | const newProcessed = {...defaultNote, ...newNoteObj};
48 |
49 | // push to Array
50 | checkMd ? newNotesData.push(newProcessed) : nonNotesData.push(newProcessed);
51 | }
52 |
53 | return [newNotesData, nonNotesData];
54 | }
55 |
56 | export function processDirs(fileList: FileMetaData[]) {
57 | const newDirsData: Note[] = [];
58 |
59 | for (const file of fileList) {
60 | const fileName = file.file_name;
61 |
62 | if (!fileName || !file.is_dir ) {
63 | continue;
64 | }
65 |
66 | const filePath = file.file_path;
67 | const lastModDate = new Date(file.last_modified.secs_since_epoch * 1000).toISOString();
68 | const createdDate = new Date(file.created.secs_since_epoch * 1000).toISOString();
69 | const newDirObj = {
70 | id: filePath,
71 | title: fileName,
72 | created_at: createdDate,
73 | updated_at: lastModDate,
74 | file_path: filePath,
75 | is_dir: true,
76 | };
77 | const newProcessedDir = {...defaultNote, ...newDirObj};
78 |
79 | // push to Array
80 | newDirsData.push(newProcessedDir);
81 | }
82 |
83 | return newDirsData;
84 | }
85 |
86 | /* #endregion: import process */
87 |
88 | /**
89 | * remove file name extension
90 | *
91 | * @param {string} fname, file name.
92 | */
93 | export const rmFileNameExt = (fname: string) => {
94 | return fname.replace(/\.[^/.]+$/, '');
95 | }
96 |
97 | export const getFileExt = (fname: string) => {
98 | return fname.slice((fname.lastIndexOf(".") - 1 >>> 0) + 2);
99 | }
100 |
101 | export const checkFileIsMd = (fname: string) => {
102 | const check = /\.(text|txt|md|mkdn|mdwn|mdown|markdown){1}$/i.test(fname);
103 | return check;
104 | }
105 |
--------------------------------------------------------------------------------
/src/file/storage.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { invoke } from '@tauri-apps/api/tauri'
3 | import { isTauri } from './util';
4 |
5 | interface StorageData {
6 | status: boolean;
7 | data: JSON;
8 | }
9 |
10 | /**
11 | * Set data to local storage
12 | * @param {string} key
13 | * @param {any} value
14 | * @returns {Promise}
15 | */
16 | export const set = async (key: string, value: any): Promise => {
17 | if (isTauri) {
18 | return await invoke('set_data', { key, value });
19 | } else {
20 | localStorage.setItem(key, JSON.stringify(value));
21 | }
22 | };
23 |
24 | /**
25 | * Get data from local storage
26 | * @param {string} key
27 | * @returns {Promise}
28 | */
29 | export const get = async (key: string): Promise => {
30 | if (isTauri) {
31 | const storeData: StorageData = await invoke('get_data', { key });
32 | return storeData.status ? storeData.data : {};
33 | } else {
34 | const storeData = localStorage.getItem(key);
35 | return storeData ? JSON.parse(storeData) : {};
36 | }
37 | };
38 |
39 | /**
40 | * Remove data in local storage
41 | * @param {string} key
42 | * @returns {any}
43 | */
44 | export const remove = async (key: string): Promise => {
45 | if (isTauri) {
46 | await invoke('delete_data', { key });
47 | } else {
48 | localStorage.removeItem(key);
49 | }
50 | };
51 |
52 | // eslint-disable-next-line import/no-anonymous-default-export
53 | export default { set, get, remove };
54 |
55 |
56 | // for Log
57 | //
58 | type LogItem = {
59 | ty: string;
60 | info: string;
61 | timestamp: string; // Date
62 | };
63 |
64 | /**
65 | * Write a log
66 | * @param {string} ty - log type: info, error, warning, ..
67 | * @param {string} info - log information
68 | * @returns {Promise}
69 | */
70 | export const setLog = async (ty: string, info: string): Promise => {
71 | const logData: LogItem[] = [{ ty, info, timestamp: new Date().toLocaleString() }];
72 | return await invoke('set_log', { logData });
73 | };
74 |
75 | /**
76 | * Get the logs
77 | * @returns {Promise}
78 | */
79 | export const getLog = async (): Promise => {
80 | return await invoke('get_log');
81 | };
82 |
83 | /**
84 | * clear the logs
85 | * @returns void
86 | */
87 | export const clearLog = async (): Promise => {
88 | return await invoke('del_log');
89 | };
90 |
--------------------------------------------------------------------------------
/src/file/write.ts:
--------------------------------------------------------------------------------
1 | // import { invoke } from '@tauri-apps/api';
2 | import { Notes } from 'lib/store';
3 | import { buildNotesJson, joinPaths } from './util';
4 | import FileAPI from './files';
5 |
6 | /**
7 | * Write file to disk
8 | * @param filePath
9 | * @param content
10 | */
11 | export async function writeFile(filePath: string, content: string) {
12 | const file = new FileAPI(filePath);
13 | await file.writeFile(content);
14 | }
15 |
16 | /**
17 | * Write json containing all data to folder
18 | * @param parentDir
19 | * @param json optional
20 | */
21 | export async function writeJsonFile(parentDir: string, json = '') {
22 | const jsonFile = new FileAPI('mdsilo.json', parentDir);
23 | const notesJson = json || buildNotesJson();
24 | await jsonFile.writeFile(notesJson);
25 | // await invoke('save_notes', { dir: parentDir, content: notesJson });
26 | }
27 |
28 | /**
29 | * Delete a file
30 | * @param filePath string
31 | */
32 | export async function deleteFile(filePath: string) {
33 | const file = new FileAPI(filePath);
34 | await file.deleteFiles();
35 | }
36 |
37 | /**
38 | * save all mds and json to a folder
39 | * @param dirPath string
40 | */
41 | export async function writeAllFile(dirPath: string, notesObj: Notes) {
42 | // save mds
43 | const myNotes = Object.values(notesObj);
44 | for (const note of myNotes) {
45 | const fileName = `${note.title}.md`;
46 | const notePath = await joinPaths(dirPath, [fileName]);
47 | const content = note.content;
48 | await writeFile(notePath, content);
49 | }
50 | // save json with all notes, data
51 | const notesJson = buildNotesJson();
52 | await writeJsonFile(dirPath, notesJson);
53 | }
54 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import App from './components/App';
5 |
6 | // eslint-disable-next-line import/no-named-as-default-member
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/styles/fonts/IBMPlexSerif-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/IBMPlexSerif-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_AMS-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_AMS-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_AMS-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_AMS-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_AMS-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_AMS-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Bold.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Bold.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Bold.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Caligraphic-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Caligraphic-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Bold.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Bold.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Bold.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Fraktur-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Fraktur-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Bold.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Bold.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Bold.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-BoldItalic.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Italic.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Italic.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Italic.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Main-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Main-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-BoldItalic.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-Italic.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-Italic.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Math-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Math-Italic.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Bold.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Bold.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Bold.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Italic.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Italic.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Italic.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_SansSerif-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_SansSerif-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Script-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Script-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Script-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Script-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Script-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Script-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size1-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size1-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size1-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size1-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size1-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size1-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size2-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size2-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size2-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size2-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size2-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size2-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size3-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size3-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size3-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size3-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size3-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size3-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size4-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size4-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size4-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size4-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Size4-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Size4-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Typewriter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Typewriter-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Typewriter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Typewriter-Regular.woff
--------------------------------------------------------------------------------
/src/styles/fonts/KaTeX_Typewriter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/KaTeX_Typewriter-Regular.woff2
--------------------------------------------------------------------------------
/src/styles/fonts/RobotoMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/RobotoMono-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/fonts/Sniglet-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdSilo/mdSilo-app/a18ceddca4bbedfd7fd42d863bc1af99e2ceb74e/src/styles/fonts/Sniglet-Regular.ttf
--------------------------------------------------------------------------------
/src/styles/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | h1 {
7 | font-size: 2.5em;
8 | }
9 | h2 {
10 | font-size: 2em;
11 | }
12 | h3 {
13 | font-size: 1.6em;
14 | }
15 | h4 {
16 | font-size: 1.2em;
17 | }
18 | }
19 |
20 | @layer components {
21 | .btn {
22 | @apply px-5 py-3 font-medium text-white transition duration-200 ease-in-out rounded shadow bg-primary-600 hover:bg-opacity-75;
23 | }
24 |
25 | .pop-btn {
26 | @apply px-2 py-2 text-sm text-black rounded shadow bg-green-200 hover:bg-green-100;
27 | }
28 |
29 | .m-btn0 {
30 | @apply px-2 py-1 text-xs text-black rounded shadow bg-green-200 hover:bg-green-100;
31 | }
32 |
33 | .m-btn1 {
34 | @apply px-2 py-1 text-xs text-black rounded shadow bg-blue-200 hover:bg-blue-100;
35 | }
36 |
37 | .m-btn2 {
38 | @apply px-2 py-1 text-xs text-black rounded shadow bg-orange-200 hover:bg-orange-100;
39 | }
40 |
41 | .card {
42 | @apply max-w-md p-8 overflow-hidden bg-white rounded shadow-md;
43 | }
44 |
45 | .input {
46 | @apply block py-1 text-gray-500 border-gray-300 rounded shadow-sm focus:ring-yellow-300 focus:border-primary-50;
47 | }
48 |
49 | .link {
50 | @apply cursor-pointer text-primary-600 hover:text-primary-500 dark:text-primary-400 dark:hover:text-primary-300;
51 | }
52 | .splash-bg {
53 | background: linear-gradient(55deg, #414b61 0 45%, #2d3343 45% 100%);
54 | }
55 | .img-effect {
56 | --m:
57 | radial-gradient(circle farthest-side at right,#000 99%,#0000) 0 100%/46% 92% no-repeat,
58 | radial-gradient(circle farthest-side at left,#000 99%,#0000) 100% 0/46% 92% no-repeat;
59 | -webkit-mask: var(--m);
60 | mask: var(--m);
61 | }
62 |
63 | .content {
64 | line-height: 1.8;
65 | }
66 | .content a {
67 | color: green;
68 | }
69 | .content a:hover {
70 | text-decoration: underline;
71 | }
72 | .content h1, .content h2, .content h3, .content h4, .content h5 {
73 | padding: 12px 0;
74 | line-height: 1.2em;
75 | }
76 | .content pre {
77 | white-space: break-spaces;
78 | }
79 | .content img, .content video {
80 | display: block;
81 | max-width: 100% !important;
82 | height: auto;
83 | margin: 10px auto;
84 | }
85 | .content blockquote {
86 | font-weight: 400;
87 | margin: 2rem 0;
88 | padding-left: 1rem;
89 | border-left: 6px solid greenyellow;
90 | }
91 | .content p {
92 | line-height: 1.6;
93 | font-weight: 400;
94 | font-size: 18px;
95 | margin: 1.2rem 0;
96 | }
97 | .content table, .content th, .content td {
98 | border: 1px solid;
99 | padding: 6px;
100 | }
101 | .content hr {
102 | margin: 2.4rem 0;
103 | }
104 | .content figcaption {
105 | text-align: center;
106 | color: #f4f4f5;
107 | }
108 | }
109 |
110 | :root {
111 | /* This is used for the spinner and progress bar */
112 | --color-primary-500: #00bfd8;
113 | }
114 |
115 | @media (pointer: fine) {
116 | ::-webkit-scrollbar {
117 | width: 10px;
118 | height: 10px;
119 | }
120 |
121 | /* Track */
122 | ::-webkit-scrollbar-track {
123 | @apply bg-gray-100 dark:bg-gray-700;
124 | }
125 |
126 | /* Handle */
127 | ::-webkit-scrollbar-thumb {
128 | @apply bg-gray-300 dark:bg-gray-500;
129 | }
130 |
131 | /* Handle on hover */
132 | ::-webkit-scrollbar-thumb:hover {
133 | @apply bg-gray-400 dark:bg-gray-400;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/types/model.ts:
--------------------------------------------------------------------------------
1 | // READING
2 | //
3 | export interface ChannelType {
4 | id: number;
5 | title: string;
6 | link: string;
7 | description?: string;
8 | published?: string; // iso date string
9 | ty: string; // podcast | rss
10 | unread: number;
11 | }
12 |
13 | export interface ArticleType {
14 | id: number;
15 | title: string;
16 | url: string;
17 | feed_link: string;
18 | audio_url: string;
19 | description: string;
20 | published?: Date;
21 | read_status: number;
22 | star_status: number;
23 | content?: string;
24 | author?: string;
25 | image?: string;
26 | source?: string;
27 | links?: string[];
28 | ttr?: number;
29 | }
30 |
31 | export interface PodType {
32 | title: string;
33 | url: string;
34 | published?: Date;
35 | article_url: string;
36 | feed_link: string;
37 | }
38 |
39 | // WRITING
40 | //
41 | export type Note = {
42 | id: string; // !!Important!! id === file_path
43 | title: string;
44 | content: string;
45 | file_path: string;
46 | cover: string | null;
47 | created_at: string;
48 | updated_at: string;
49 | is_daily: boolean;
50 | is_dir?: boolean;
51 | };
52 |
53 | export const defaultNote = {
54 | title: 'untitled',
55 | content: ' ',
56 | file_path: '',
57 | cover: '',
58 | created_at: new Date().toISOString(),
59 | updated_at: new Date().toISOString(),
60 | is_daily: false,
61 | };
62 |
--------------------------------------------------------------------------------
/src/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | export type PickPartial = Pick> &
2 | Partial>;
3 |
--------------------------------------------------------------------------------
/src/utils/file-extensions.ts:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) Arthur Verschaeve
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | export const imageExtensions = [
26 | 'ase',
27 | 'art',
28 | 'bmp',
29 | 'blp',
30 | 'cd5',
31 | 'cit',
32 | 'cpt',
33 | 'cr2',
34 | 'cut',
35 | 'dds',
36 | 'dib',
37 | 'djvu',
38 | 'egt',
39 | 'exif',
40 | 'gif',
41 | 'gpl',
42 | 'grf',
43 | 'icns',
44 | 'ico',
45 | 'iff',
46 | 'jng',
47 | 'jpeg',
48 | 'jpg',
49 | 'jfif',
50 | 'jp2',
51 | 'jps',
52 | 'lbm',
53 | 'max',
54 | 'miff',
55 | 'mng',
56 | 'msp',
57 | 'nitf',
58 | 'ota',
59 | 'pbm',
60 | 'pc1',
61 | 'pc2',
62 | 'pc3',
63 | 'pcf',
64 | 'pcx',
65 | 'pdn',
66 | 'pgm',
67 | 'PI1',
68 | 'PI2',
69 | 'PI3',
70 | 'pict',
71 | 'pct',
72 | 'pnm',
73 | 'pns',
74 | 'ppm',
75 | 'psb',
76 | 'psd',
77 | 'pdd',
78 | 'psp',
79 | 'px',
80 | 'pxm',
81 | 'pxr',
82 | 'qfx',
83 | 'raw',
84 | 'rle',
85 | 'sct',
86 | 'sgi',
87 | 'rgb',
88 | 'int',
89 | 'bw',
90 | 'tga',
91 | 'tiff',
92 | 'tif',
93 | 'vtf',
94 | 'xbm',
95 | 'xcf',
96 | 'xpm',
97 | '3dv',
98 | 'amf',
99 | 'ai',
100 | 'awg',
101 | 'cgm',
102 | 'cdr',
103 | 'cmx',
104 | 'dxf',
105 | 'e2d',
106 | 'egt',
107 | 'eps',
108 | 'fs',
109 | 'gbr',
110 | 'odg',
111 | 'svg',
112 | 'stl',
113 | 'vrml',
114 | 'x3d',
115 | 'sxd',
116 | 'v2d',
117 | 'vnd',
118 | 'wmf',
119 | 'emf',
120 | 'art',
121 | 'xar',
122 | 'png',
123 | 'webp',
124 | 'jxr',
125 | 'hdp',
126 | 'wdp',
127 | 'cur',
128 | 'ecw',
129 | 'iff',
130 | 'lbm',
131 | 'liff',
132 | 'nrrd',
133 | 'pam',
134 | 'pcx',
135 | 'pgf',
136 | 'sgi',
137 | 'rgb',
138 | 'rgba',
139 | 'bw',
140 | 'int',
141 | 'inta',
142 | 'sid',
143 | 'ras',
144 | 'sun',
145 | ];
146 |
147 | export const docExtensions = [
148 | 'pdf',
149 | 'doc',
150 | 'docx',
151 | 'xls',
152 | 'xlsx',
153 | 'ppt',
154 | 'pptx',
155 | 'odt',
156 | 'ods',
157 | 'odp',
158 | 'xps',
159 | 'pages',
160 | 'numbers',
161 | 'key',
162 | 'zip',
163 | ];
164 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const colors = require('tailwindcss/colors');
3 | const { gray } = require('tailwindcss/colors');
4 |
5 | module.exports = {
6 | mode: 'jit',
7 | content: ['./src/**/*.{html,js,ts,jsx,tsx}', './public/**/*.{html,js,ts,jsx,tsx}'],
8 | darkMode: 'class',
9 | plugins: [
10 | require('@tailwindcss/typography'),
11 | require('@tailwindcss/forms'),
12 | ],
13 | theme: {
14 | container: {
15 | center: true,
16 | screens: {
17 | sm: '640px',
18 | md: '768px',
19 | lg: '1024px',
20 | xl: '1280px',
21 | '2xl': '1280px',
22 | },
23 | },
24 | extend: {
25 | spacing: {
26 | 0.25: '0.0625rem',
27 | 128: '32rem',
28 | 160: '40rem',
29 | 176: '44rem',
30 | 192: '48rem',
31 | 240: '60rem',
32 | 'screen-10': '10vh',
33 | 'screen-80': '80vh',
34 | },
35 | colors: {
36 | primary: colors.emerald,
37 | gray: colors.neutral,
38 | orange: colors.orange,
39 | },
40 | boxShadow: {
41 | popover:
42 | 'rgb(15 15 15 / 10%) 0px 3px 6px, rgb(15 15 15 / 20%) 0px 9px 24px',
43 | },
44 | opacity: {
45 | 0.1: '0.001',
46 | 85: '.85',
47 | },
48 | zIndex: {
49 | '-10': '-10',
50 | },
51 | cursor: {
52 | alias: 'alias',
53 | },
54 | animation: {
55 | 'bounce-x': 'bounce-x 1s infinite',
56 | },
57 | keyframes: {
58 | 'bounce-x': {
59 | '0%, 100%': {
60 | transform: 'translateX(0)',
61 | animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)',
62 | },
63 | '50%': {
64 | transform: 'translateX(25%)',
65 | animationTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
66 | },
67 | },
68 | },
69 | typography: {
70 | DEFAULT: {
71 | css: {
72 | b: {
73 | fontWeight: 600,
74 | },
75 | h1: {
76 | fontWeight: 600,
77 | color: gray,
78 | },
79 | h2: {
80 | fontWeight: 600,
81 | color: gray,
82 | },
83 | h3: {
84 | fontWeight: 600,
85 | color: gray,
86 | },
87 | h4: {
88 | fontWeight: 600,
89 | },
90 | h5: {
91 | fontWeight: 600,
92 | },
93 | h6: {
94 | fontWeight: 600,
95 | },
96 | a: {
97 | textDecoration: 'none',
98 | fontWeight: 'normal',
99 | '&:hover': {
100 | color: colors.emerald[500],
101 | },
102 | },
103 | blockquote: {
104 | color: colors.emerald[100],
105 | }
106 | },
107 | },
108 | },
109 | },
110 | },
111 | };
112 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 |
22 | "downlevelIteration": true,
23 | "incremental": true
24 | },
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | , "src/components/sidebar/SidebarTagstsx" ],
30 | "exclude": [
31 | "node_modules",
32 | "**/*.spec.ts"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------