diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..16e3dce618 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +/src/lib/tutorials/files \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a51eb476d6..4552846076 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2762,7 +2762,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -6159,8 +6159,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -6181,14 +6180,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6203,20 +6200,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -6333,8 +6327,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -6346,7 +6339,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6361,7 +6353,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6369,14 +6360,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6395,7 +6384,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6476,8 +6464,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6489,7 +6476,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6575,8 +6561,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -6612,7 +6597,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6632,7 +6616,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6676,14 +6659,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -8271,8 +8252,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -8293,14 +8273,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8315,20 +8293,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -8445,8 +8420,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -8458,7 +8432,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8473,7 +8446,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8481,14 +8453,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8507,7 +8477,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8588,8 +8557,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -8601,7 +8569,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8687,8 +8654,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -8724,7 +8690,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8744,7 +8709,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8788,14 +8752,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -9843,7 +9805,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mississippi": { @@ -11614,6 +11576,54 @@ } } }, + "raw-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", + "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^2.5.0" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "react": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", @@ -12522,7 +12532,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -13509,7 +13519,7 @@ }, "readable-stream": { "version": "1.0.33", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", "integrity": "sha1-OjYN1mwbHX/UcFOJhg7aHQ9hEmw=", "requires": { "core-util-is": "~1.0.0", diff --git a/package.json b/package.json index 177b29ba24..407d6c2c90 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "pouchdb-mapreduce": "^7.1.1", "pouchdb-replication": "^7.1.1", "ramda": "^0.26.1", + "raw-loader": "^4.0.0", "react": "^16.9.0", "react-dom": "^16.9.0", "react-i18next": "^11.0.1", diff --git a/src/components/NotePage/NoteDetail/NoteDetail.tsx b/src/components/NotePage/NoteDetail/NoteDetail.tsx index 85e7f4c318..93778b03f2 100644 --- a/src/components/NotePage/NoteDetail/NoteDetail.tsx +++ b/src/components/NotePage/NoteDetail/NoteDetail.tsx @@ -24,7 +24,7 @@ import { } from '../../../lib/styled/styleFunctions' import ToolbarExportButton from '../../atoms/ToolbarExportButton' -const StyledNoteDetailContainer = styled.div` +export const StyledNoteDetailContainer = styled.div` ${secondaryBackgroundColor} display: flex; flex-direction: column; diff --git a/src/components/NotePage/NoteList/NoteItem.tsx b/src/components/NotePage/NoteList/NoteItem.tsx index d2d51d8bd4..25e1ace762 100644 --- a/src/components/NotePage/NoteList/NoteItem.tsx +++ b/src/components/NotePage/NoteList/NoteItem.tsx @@ -12,7 +12,7 @@ import { import cc from 'classcat' import { setTransferrableNoteData } from '../../../lib/dnd' -const StyledNoteListItem = styled.div` +export const StyledNoteListItem = styled.div` margin: 0; border-left: 2px solid transparent; ${uiTextColor} diff --git a/src/components/NotePage/NoteList/NoteList.tsx b/src/components/NotePage/NoteList/NoteList.tsx index 449a03aaf0..a2ffccc55d 100644 --- a/src/components/NotePage/NoteList/NoteList.tsx +++ b/src/components/NotePage/NoteList/NoteList.tsx @@ -10,7 +10,7 @@ import { uiTextColor } from '../../../lib/styled/styleFunctions' -const StyledContainer = styled.div` +export const StyledNoteListContainer = styled.div` display: flex; flex-direction: column; overflow: hidden; @@ -107,7 +107,7 @@ const NoteList = ({ }, []) return ( - +
No notes} - + ) } diff --git a/src/components/PreferencesModal/GeneralTab.tsx b/src/components/PreferencesModal/GeneralTab.tsx index dfa8f35a3c..a4d1855631 100644 --- a/src/components/PreferencesModal/GeneralTab.tsx +++ b/src/components/PreferencesModal/GeneralTab.tsx @@ -12,7 +12,8 @@ import { usePreferences, GeneralThemeOptions, GeneralLanguageOptions, - GeneralNoteSortingOptions + GeneralNoteSortingOptions, + GeneralTutorialsOptions } from '../../lib/preferences' import { useTranslation } from 'react-i18next' import { SelectChangeEventHandler } from '../../lib/events' @@ -51,6 +52,15 @@ const GeneralTab = () => { [setPreferences] ) + const selectTutorialsDisplay: SelectChangeEventHandler = useCallback( + event => { + setPreferences({ + 'general.tutorials': event.target.value as GeneralTutorialsOptions + }) + }, + [setPreferences] + ) + const { t } = useTranslation() return ( @@ -126,6 +136,18 @@ const GeneralTab = () => { +
+ {t('preferences.displayTutorialsLabel')} + + + + + + +
) } diff --git a/src/components/Router.tsx b/src/components/Router.tsx index ad9337dedc..d4f02092c4 100644 --- a/src/components/Router.tsx +++ b/src/components/Router.tsx @@ -4,10 +4,14 @@ import { useRouteParams } from '../lib/router' import { StyledNotFoundPage } from './styled' import { StorageEdit, StorageCreate } from './Storage' import { useDb } from '../lib/db' +import TutorialsPage from './Tutorials/TutorialsPage' +import useRedirectHandler from '../lib/router/redirect' export default () => { const routeParams = useRouteParams() const db = useDb() + useRedirectHandler() + switch (routeParams.name) { case 'storages.allNotes': case 'storages.notes': @@ -16,6 +20,9 @@ export default () => { return case 'storages.create': return + case 'tutorials.show': + return + break case 'storages.edit': const storage = db.storageMap[routeParams.storageId] if (storage != null) { diff --git a/src/components/SideNavigator/SideNavigator.tsx b/src/components/SideNavigator/SideNavigator.tsx index 42d7366333..3f43dd3e5a 100644 --- a/src/components/SideNavigator/SideNavigator.tsx +++ b/src/components/SideNavigator/SideNavigator.tsx @@ -19,6 +19,7 @@ import { useGeneralStatus } from '../../lib/generalStatus' import ControlButton from './ControlButton' import FolderListFragment from './FolderListFragment' import TagListFragment from './TagListFragment' +import TutorialsNavigator from '../Tutorials/TutorialsNavigator' const StyledSideNavContainer = styled.nav` display: flex; @@ -126,7 +127,7 @@ export default () => { [popup, prompt, createStorage] ) - const { toggleClosed } = usePreferences() + const { toggleClosed, preferences } = usePreferences() const { toggleSideNavOpenedItem, sideNavOpenedItemSet, @@ -260,6 +261,9 @@ export default () => { {storageEntries.length === 0 && (
No storages
)} + {preferences['general.tutorials'] === 'display' && ( + + )}
{ + const routeParams = useRouteParams() + const { push } = useRouter() + const currentHref = currentTutorialPathname() + const { toggleSideNavOpenedItem, sideNavOpenedItemSet } = useGeneralStatus() + + const tutorials = tutorialsTree + + function getNavigatorNodesFromTreeItem( + tree: TutorialsNavigatorTreeItem, + currentDepth: number, + parentNode?: NavigatorNode, + parentComponentPathname?: string + ): NavigatorNode | undefined { + if (tree.type === 'note') { + return + } + + const componentPathname = `${parentComponentPathname != null && + parentComponentPathname}/${tree.absolutePath}` + const nodeHref = `${parentNode != null ? parentNode.href : '/app'}/${ + tree.slug + }` + + const folderIsActive = currentHref.split('/notes/note:')[0] === nodeHref + + const notesUnderCurrentNode = tree.children.filter( + child => child.type === 'note' + ) + + const nodeId = `TF-${nodeHref.split('/app')[1]}` + const currentNode = { + id: nodeId, + name: `${tree.label} ${ + notesUnderCurrentNode.length > 0 + ? `(${notesUnderCurrentNode.length})` + : '' + }`, + iconPath: + tree.type === 'folder' + ? folderIsActive + ? mdiFolderOpenOutline + : mdiFolderOutline + : mdiHelpCircleOutline, + href: nodeHref, + active: folderIsActive, + depth: currentDepth, + opened: sideNavOpenedItemSet.has(nodeId), + children: [] + } + + const childrenNodes = + tree.children.length === 0 + ? [] + : (tree.children + .map(childrenTree => + getNavigatorNodesFromTreeItem( + childrenTree, + currentDepth + 1, + currentNode, + componentPathname + ) + ) + .filter(node => node != null) as NavigatorNode[]) + return { + ...currentNode, + children: childrenNodes + } + } + + const nodes = useMemo(() => { + return tutorials + .map(tutorial => getNavigatorNodesFromTreeItem(tutorial, 0)) + .filter(node => node != null) as NavigatorNode[] + }, [routeParams, tutorials, toggleSideNavOpenedItem]) + + const redirectToTutorialNode = (node: NavigatorNode) => { + push(node.href) + } + + const renderNode = ( + node: NavigatorNode, + parentNodeIsOpened: boolean + ): JSX.Element | null => { + if (node.depth > 0 && !parentNodeIsOpened) { + return null + } + return ( + + redirectToTutorialNode(node)} + onFoldButtonClick={() => toggleSideNavOpenedItem(node.id)} + folded={node.children.length === 0 ? undefined : !node.opened} + /> + {node.children.map(child => renderNode(child, node.opened))} + + ) + } + + return <>{nodes.map(node => renderNode(node, true))} +} + +export default TutorialsNavigator diff --git a/src/components/Tutorials/TutorialsNoteDetail.tsx b/src/components/Tutorials/TutorialsNoteDetail.tsx new file mode 100644 index 0000000000..aadfd227e2 --- /dev/null +++ b/src/components/Tutorials/TutorialsNoteDetail.tsx @@ -0,0 +1,128 @@ +import React from 'react' +import CustomizedCodeEditor from '../atoms/CustomizedCodeEditor' +import CustomizedMarkdownPreviewer from '../atoms/CustomizedMarkdownPreviewer' +import { mdiEyeOutline, mdiArrowSplitVertical } from '@mdi/js' +import ToolbarIconButton from '../atoms/ToolbarIconButton' +import Toolbar from '../atoms/Toolbar' +import ToolbarSeparator from '../atoms/ToolbarSeparator' +import { TutorialsNavigatorTreeItem } from '../../lib/tutorials' +import { StyledNoteDetailContainer } from '../NotePage/NoteDetail/NoteDetail' + +type TutorialsNoteDetailProps = { + note: TutorialsNavigatorTreeItem + splitMode: boolean + previewMode: boolean + toggleSplitMode: () => void + togglePreviewMode: () => void +} + +type TutorialsNoteDetailState = { + noteComponent: string + noteContent: string +} + +export default class TutorialsNoteDetail extends React.Component< + TutorialsNoteDetailProps +> { + state: TutorialsNoteDetailState = { + noteComponent: this.props.note.slug, + noteContent: '' + } + + async componentDidMount() { + this.setState({ noteContent: await this.fetchNoteContentFromTreeItem() }) + } + + async fetchNoteContentFromTreeItem() { + try { + const doc = await import( + `../../lib/tutorials/files${this.props.note.absolutePath}` + ) + return doc.default + } catch (error) { + console.log(error) + return `Could not load the file` + } + } + + async componentDidUpdate( + _prevProps: TutorialsNoteDetailProps, + prevState: TutorialsNoteDetailState + ) { + const { note } = this.props + if (note.absolutePath !== prevState.noteComponent) { + this.setState({ + noteComponent: note.absolutePath, + noteContent: await this.fetchNoteContentFromTreeItem() + }) + } + } + + codeMirror?: CodeMirror.EditorFromTextArea + codeMirrorRef = (codeMirror: CodeMirror.EditorFromTextArea) => { + this.codeMirror = codeMirror + } + + render() { + const { + note, + splitMode, + previewMode, + toggleSplitMode, + togglePreviewMode + } = this.props + + const codeEditor = ( + {}} + readonly={true} + /> + ) + const markdownPreviewer = ( + + ) + + return ( + + {note == null ? ( +

No note is selected

+ ) : ( + <> +
+ +
+
+ {previewMode ? ( + markdownPreviewer + ) : splitMode ? ( + <> +
{codeEditor}
+
{markdownPreviewer}
+ + ) : ( + codeEditor + )} +
+ + + + + + + )} +
+ ) + } +} diff --git a/src/components/Tutorials/TutorialsNoteItem.tsx b/src/components/Tutorials/TutorialsNoteItem.tsx new file mode 100644 index 0000000000..bb88e107bb --- /dev/null +++ b/src/components/Tutorials/TutorialsNoteItem.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { Link } from '../../lib/router' +import cc from 'classcat' +import { TutorialsNavigatorTreeItem } from '../../lib/tutorials' +import { StyledNoteListItem } from '../NotePage/NoteList/NoteItem' + +type TutorialsNoteItemProps = { + note: TutorialsNavigatorTreeItem + active: boolean + basePathname: string + focusList: () => void +} + +export default ({ + note, + active, + basePathname, + focusList +}: TutorialsNoteItemProps) => { + const href = `${basePathname}/notes/note:${note.slug}` + + return ( + + +
+
{note.label}
+
+ +
+ ) +} diff --git a/src/components/Tutorials/TutorialsNoteList.tsx b/src/components/Tutorials/TutorialsNoteList.tsx new file mode 100644 index 0000000000..623398cf57 --- /dev/null +++ b/src/components/Tutorials/TutorialsNoteList.tsx @@ -0,0 +1,74 @@ +import React, { useCallback, KeyboardEvent, useRef } from 'react' +import { TutorialsNavigatorTreeItem } from '../../lib/tutorials' +import TutorialsNoteItem from './TutorialsNoteItem' +import { StyledNoteListContainer } from '../NotePage/NoteList/NoteList' + +type TutorialsNoteListProps = { + currentTree: TutorialsNavigatorTreeItem + basePathname: string + parentTree?: TutorialsNavigatorTreeItem + selectedNote?: TutorialsNavigatorTreeItem + navigateUp: () => void + navigateDown: () => void +} + +const TutorialsNoteList = ({ + currentTree, + parentTree, + navigateUp, + navigateDown, + basePathname, + selectedNote +}: TutorialsNoteListProps) => { + const handleListKeyDown = useCallback( + (event: KeyboardEvent) => { + switch (event.key) { + case 'ArrowDown': + navigateDown() + break + case 'ArrowUp': + navigateUp() + break + } + }, + [navigateUp, navigateDown] + ) + + const listRef = useRef(null) + + const focusList = useCallback(() => { + listRef.current!.focus() + }, []) + + const notes = + currentTree.type !== 'note' + ? currentTree.children.filter(child => child.type === 'note') + : parentTree == null + ? [] + : parentTree.children.filter(child => child.type === 'note') + + return ( + +
    + {notes.map(note => { + const noteIsCurrentNote = + selectedNote != null && + note.absolutePath === selectedNote.absolutePath + return ( +
  • + +
  • + ) + })} + {notes.length === 0 &&
  • No notes
  • } +
+
+ ) +} + +export default TutorialsNoteList diff --git a/src/components/Tutorials/TutorialsPage.tsx b/src/components/Tutorials/TutorialsPage.tsx new file mode 100644 index 0000000000..067283ea83 --- /dev/null +++ b/src/components/Tutorials/TutorialsPage.tsx @@ -0,0 +1,278 @@ +import React, { useEffect, useCallback, useState } from 'react' +import { tutorialsTree, TutorialsNavigatorTreeItem } from '../../lib/tutorials' +import TwoPaneLayout from '../atoms/TwoPaneLayout' +import { useGeneralStatus } from '../../lib/generalStatus' +import { useRouter } from '../../lib/router' +import TutorialsNoteList from './TutorialsNoteList' +import TutorialsNoteDetail from './TutorialsNoteDetail' + +interface TutorialsPageProps { + pathname: string +} + +type TutoriasPagePicker = { + parentTree?: TutorialsNavigatorTreeItem + currentTree: TutorialsNavigatorTreeItem +} + +export default ({ pathname }: TutorialsPageProps) => { + const [ + currentTutorialBranch, + setCurrentTutorialBranch + ] = useState(getCurrentNodeFromTutorialTrees) + + const [currentFolderPathname, setCurrentFolderPathname] = useState( + getCurrentFolderPathname + ) + const [selectedNote, setSelectedNote] = useState< + TutorialsNavigatorTreeItem | undefined + >() + + const { generalStatus, setGeneralStatus } = useGeneralStatus() + const router = useRouter() + const updateNoteListWidth = useCallback( + (leftWidth: number) => { + setGeneralStatus({ + noteListWidth: leftWidth + }) + }, + [setGeneralStatus] + ) + + const toggleSplitMode = useCallback(() => { + setGeneralStatus(prevState => ({ + noteSplitMode: !prevState.noteSplitMode + })) + }, [setGeneralStatus]) + + const togglePreviewMode = useCallback(() => { + setGeneralStatus(prevState => ({ + notePreviewMode: !prevState.notePreviewMode + })) + }, [setGeneralStatus]) + + useEffect(() => { + setCurrentTutorialBranch(getCurrentNodeFromTutorialTrees) + setCurrentFolderPathname(getCurrentFolderPathname) + }, [pathname, tutorialsTree]) + + useEffect(() => { + setSelectedNote(getCurrentNote) + }, [currentTutorialBranch]) + + function getCurrentNote(): TutorialsNavigatorTreeItem | undefined { + if (currentTutorialBranch == null) { + return undefined + } + + if (currentTutorialBranch.currentTree.type === 'note') { + return currentTutorialBranch.currentTree + } + + const notesChildren = currentTutorialBranch.currentTree.children.filter( + node => node.type === 'note' + ) + if (notesChildren.length > 0) { + return notesChildren[0] + } + return undefined + } + + function getCurrentFolderPathname() { + return pathname.split('/notes')[0] + } + + function getCurrentNodeFromTutorialTrees() { + let match = null + for (let i = 0; i < tutorialsTree.length; i++) { + match = searchThroughTreeForIdenticalNode( + pathname, + '/app', + '', + tutorialsTree[i] + ) + if (match != null) { + break + } + } + return match + } + + function searchThroughTreeForIdenticalNode( + pathToSearch: string, + parentDepthPath: string, + parentAbsolutePath: string, + tree: TutorialsNavigatorTreeItem, + parentTree?: TutorialsNavigatorTreeItem + ): TutoriasPagePicker | null { + let match = null + const currentDepthPath = `${parentDepthPath}/${ + tree.type === 'note' ? 'notes/note:' : '' + }${tree.slug}` + + const currentAbsolutePath = parentAbsolutePath + '/' + tree.absolutePath + if (currentDepthPath === pathToSearch) { + const currentTreeWithDepthAbsolutePath = { + ...tree, + absolutePath: currentAbsolutePath, + children: Object.entries(tree.children).map(obj => { + return { + ...obj[1], + absolutePath: currentAbsolutePath + '/' + obj[1].absolutePath + } + }) as TutorialsNavigatorTreeItem[] + } + + const parentTreeWithDepthAbsolutePath = + parentTree != null + ? { + ...parentTree, + absolutePath: parentAbsolutePath, + children: Object.entries(parentTree.children).map(obj => { + return { + ...obj[1], + absolutePath: parentAbsolutePath + '/' + obj[1].absolutePath + } + }) + } + : undefined + + return { + currentTree: currentTreeWithDepthAbsolutePath, + parentTree: parentTreeWithDepthAbsolutePath + } + } + + for (let i = 0; i < tree.children.length; i++) { + match = searchThroughTreeForIdenticalNode( + pathToSearch, + currentDepthPath, + currentAbsolutePath, + tree.children[i], + tree + ) + if (match != null) { + break + } + } + + return match + } + + const navigateUp = useCallback(() => { + if (currentTutorialBranch == null) { + return + } + + if (selectedNote == null) { + return + } + + const notes = + currentTutorialBranch.currentTree.type !== 'note' + ? currentTutorialBranch.currentTree.children.filter( + node => node.type === 'note' + ) + : currentTutorialBranch.parentTree != null + ? currentTutorialBranch.parentTree.children.filter( + node => node.type === 'note' + ) + : [] + + if (notes.length < 1) { + return + } + + let currentNoteIndex = 0 + for (let i = 0; i < notes.length; i++) { + if (selectedNote.absolutePath === notes[i].absolutePath) { + currentNoteIndex = i + break + } + } + + if (currentNoteIndex - 1 >= 0) { + router.push( + currentFolderPathname + + '/notes/note:' + + notes[currentNoteIndex - 1].slug + ) + } + return + }, [selectedNote]) + + const navigateDown = useCallback(() => { + if (currentTutorialBranch == null) { + return + } + + if (selectedNote == null) { + return + } + + const notes = + currentTutorialBranch.currentTree.type !== 'note' + ? currentTutorialBranch.currentTree.children.filter( + node => node.type === 'note' + ) + : currentTutorialBranch.parentTree != null + ? currentTutorialBranch.parentTree.children.filter( + node => node.type === 'note' + ) + : [] + + if (notes.length < 1) { + return + } + + let currentNoteIndex = 0 + for (let i = 0; i < notes.length; i++) { + if (selectedNote.absolutePath === notes[i].absolutePath) { + currentNoteIndex = i + break + } + } + + if (currentNoteIndex + 1 >= 0 && currentNoteIndex + 1 < notes.length) { + router.push( + currentFolderPathname + + '/notes/note:' + + notes[currentNoteIndex + 1].slug + ) + } + return + }, [selectedNote]) + + return ( + + ) + } + right={ + selectedNote == null ? ( +
No note selected
+ ) : ( + + ) + } + onResizeEnd={updateNoteListWidth} + /> + ) +} diff --git a/src/components/atoms/CodeEditor.tsx b/src/components/atoms/CodeEditor.tsx index c9c642d9c5..85cc9a46fd 100644 --- a/src/components/atoms/CodeEditor.tsx +++ b/src/components/atoms/CodeEditor.tsx @@ -33,6 +33,7 @@ interface CodeEditorProps { indentSize?: EditorIndentSizeOptions keyMap?: EditorKeyMapOptions mode?: string + readonly?: boolean } class CodeEditor extends React.Component { @@ -52,7 +53,8 @@ class CodeEditor extends React.Component { indentUnit: indentSize, tabSize: indentSize, keyMap, - mode: this.props.mode || 'markdown' + mode: this.props.mode || 'markdown', + readOnly: this.props.readonly === true }) this.codeMirror.on('change', this.handleCodeMirrorChange) window.addEventListener('codemirror-mode-load', this.reloadMode) diff --git a/src/components/atoms/CustomizedCodeEditor.tsx b/src/components/atoms/CustomizedCodeEditor.tsx index 0a498b6cfe..143ae3848e 100644 --- a/src/components/atoms/CustomizedCodeEditor.tsx +++ b/src/components/atoms/CustomizedCodeEditor.tsx @@ -11,6 +11,7 @@ interface CustomizedCodeEditor { codeMirrorRef?: (codeMirror: CodeMirror.EditorFromTextArea) => void className?: string mode?: string + readonly?: boolean } const CustomizedCodeEditor = ({ @@ -18,7 +19,8 @@ const CustomizedCodeEditor = ({ value, codeMirrorRef, className, - mode + mode, + readonly }: CustomizedCodeEditor) => { const { preferences } = usePreferences() return ( @@ -34,6 +36,7 @@ const CustomizedCodeEditor = ({ indentSize={preferences['editor.indentSize']} keyMap={preferences['editor.keyMap']} mode={mode} + readonly={readonly} /> ) } diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index b4736721d5..6b141368dc 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -80,7 +80,6 @@ export function createDbStoreCreator( storageDataList.map(storageData => prepareStorage(storageData, adapter)) ) - setInitialized(true) setStorageMap( storages.reduce( (map, storage) => { @@ -90,6 +89,7 @@ export function createDbStoreCreator( {} as ObjectMap ) ) + setInitialized(true) }, []) const createStorage = useCallback(async (name: string) => { diff --git a/src/lib/i18n/enUS.ts b/src/lib/i18n/enUS.ts index 9bf71046f8..f4462b7021 100644 --- a/src/lib/i18n/enUS.ts +++ b/src/lib/i18n/enUS.ts @@ -21,6 +21,7 @@ export default { 'preferences.analyticsDescription2': 'You can choose to enable or disable this option.', 'preferences.analyticsLabel': 'Enable analytics to help improve Boostnote', + 'preferences.displayTutorialsLabel': 'Tutorials and FAQ', // Preferences EditorTab 'preferences.editorTheme': 'Editor Theme', diff --git a/src/lib/i18n/ja.ts b/src/lib/i18n/ja.ts index a2cbcc51fd..951fb17c77 100644 --- a/src/lib/i18n/ja.ts +++ b/src/lib/i18n/ja.ts @@ -21,6 +21,7 @@ export default { 'preferences.analyticsDescription2': 'You can choose to enable or disable this option.', 'preferences.analyticsLabel': 'Enable analytics to help improve Boostnote', + 'preferences.displayTutorialsLabel': 'チュートリアルを表示', // Preferences EditorTab 'preferences.editorTheme': 'エディターテーマ', diff --git a/src/lib/preferences/store.ts b/src/lib/preferences/store.ts index 6967ab1202..463a8de626 100644 --- a/src/lib/preferences/store.ts +++ b/src/lib/preferences/store.ts @@ -30,6 +30,7 @@ const basePreferences: Preferences = { 'general.theme': 'dark', 'general.noteSorting': 'date-updated', 'general.enableAnalytics': true, + 'general.tutorials': 'display', // Editor 'editor.theme': 'default', diff --git a/src/lib/preferences/types.ts b/src/lib/preferences/types.ts index c6cbc3e941..ab6a660eb1 100644 --- a/src/lib/preferences/types.ts +++ b/src/lib/preferences/types.ts @@ -6,6 +6,7 @@ export type GeneralNoteSortingOptions = | 'date-updated' | 'date-created' | 'title' +export type GeneralTutorialsOptions = 'display' | 'hide' export type EditorIndentTypeOptions = 'tab' | 'spaces' export type EditorIndentSizeOptions = 2 | 4 | 8 @@ -18,6 +19,7 @@ export interface Preferences { 'general.theme': GeneralThemeOptions 'general.noteSorting': GeneralNoteSortingOptions 'general.enableAnalytics': boolean + 'general.tutorials': GeneralTutorialsOptions // Editor 'editor.theme': string diff --git a/src/lib/router/index.tsx b/src/lib/router/index.tsx index 35b09ace34..418afe3e52 100644 --- a/src/lib/router/index.tsx +++ b/src/lib/router/index.tsx @@ -2,3 +2,4 @@ export * from './types' export * from './store' export * from './utils' export * from './Link' +export * from './redirect' diff --git a/src/lib/router/redirect.ts b/src/lib/router/redirect.ts new file mode 100644 index 0000000000..51e0828ff9 --- /dev/null +++ b/src/lib/router/redirect.ts @@ -0,0 +1,24 @@ +import { useDb } from '../db' +import { useRouter } from './store' +import { useEffect } from 'react' +import { usePreferences } from '../preferences' + +export default function useRedirectHandler() { + const db = useDb() + const { preferences } = usePreferences() + const { push, pathname } = useRouter() + + useEffect(() => { + if (!db.initialized || db.storageMap == null) { + return + } + if ( + pathname === '/app' && + preferences['general.tutorials'] === 'display' && + Object.keys(db.storageMap).length === 0 + ) { + push('/app/tutorials/welcome-pack/guides/notes/note:storage-guide') + } + return + }, [pathname, db.initialized, preferences['general.displayTutorials']]) +} diff --git a/src/lib/router/store.ts b/src/lib/router/store.ts index e34f0eaa09..0408d745b7 100644 --- a/src/lib/router/store.ts +++ b/src/lib/router/store.ts @@ -22,15 +22,18 @@ function useRouteStore(): RouterStore { setLocation(location) history.pushState(null, document.title, urlStr) }, []) + const replace = useCallback((urlStr: string) => { const location = parseUrl(urlStr) setLocation(location) history.pushState(null, document.title, urlStr) }, []) + const go = useCallback((count: number) => { history.go(count) }, []) + const goBack = useCallback(() => go(-1), [go]) const goForward = useCallback(() => go(1), [go]) diff --git a/src/lib/router/utils.ts b/src/lib/router/utils.ts index 4855cef4aa..d881c01b09 100644 --- a/src/lib/router/utils.ts +++ b/src/lib/router/utils.ts @@ -77,6 +77,11 @@ export interface StorageTagsRouteParams extends BaseRouteParams { noteId?: string } +export interface TutorialsRouteParams extends BaseRouteParams { + name: 'tutorials.show' + path: string +} + export interface UnknownRouteparams extends BaseRouteParams { name: 'unknown' } @@ -89,6 +94,7 @@ export type AllRouteParams = | StorageTrashCanRouteParams | StorageTagsRouteParams | UnknownRouteparams + | TutorialsRouteParams export const useRouteParams = () => { const { pathname } = useRouter() @@ -104,6 +110,13 @@ export const useRouteParams = () => { } } + if (names[0] === 'tutorials') { + return { + name: 'tutorials.show', + path: pathname + } + } + if (names[0] !== 'storages' || names[1] == null) { return { name: 'unknown' @@ -187,3 +200,14 @@ export const usePathnameWithoutNoteId = () => { return '/app' }, [routeParams]) } + +export const currentTutorialPathname = () => { + const routeParams = useRouteParams() + return useMemo(() => { + switch (routeParams.name) { + case 'tutorials.show': + return routeParams.path + } + return '/app' + }, [routeParams]) +} diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Guides/AboutOurCommunity.md b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/AboutOurCommunity.md new file mode 100644 index 0000000000..76c8646f01 --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/AboutOurCommunity.md @@ -0,0 +1,21 @@ +# About Our Community +Here is some additional info on our community! We are always welcoming newcomers, so don't hesitate to say hello ;) + +## Connect with Us +- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LWQxZTQwNjBlMDI4YjkyYjg2MTRiZGJhNzA1YjQ5ODA5M2M0M2NlMjI5YjhiYWQzNzgzYmU0MDMwOTlmZmZmMGE) +- [Facebook Group](https://www.facebook.com/groups/boostnote/) +- [Twitter](https://twitter.com/boostnoteapp) +- [Blog](https://medium.com/boostnote) +- [Reddit](https://www.reddit.com/r/Boostnote/) + +## Supporting Boostnote +Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers. + +Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer: + +[![Let's fund issues in this repository](https://issuehunt.io/static/embed/issuehunt-button-v1.svg)](https://issuehunt.io/repos/53266139) + +## More Information +* [Website](https://boostnote.io) +* [Newsletters](https://boostnote.io/#subscribe) +* [Development Configurations](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md) \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Guides/KeyboardShortcuts.md b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/KeyboardShortcuts.md new file mode 100644 index 0000000000..a6603e0811 --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/KeyboardShortcuts.md @@ -0,0 +1,140 @@ +# Keyboard Shortcuts + + +## Basic Shortcuts +Here are other keyboard shortcuts which can be useful for you :) + + +### Preferences + +* Open Preference +-> `Command + ,` + +--- + +* Close Preference +-> `Esc` + + +### Notes + +* New Note +-> `Command + N` + +--- + +* Clone Note +-> `Command + D` + +--- + +* Next Note +-> `Command + ]` + +--- + +* Previous Note +-> `Command + [` + + +### Adding Stuff + +* Add Tag +-> `Command + Shift + T` + +--- + +* Add Emoji & Symbol +-> `Command + Control + Space` + +--- + +* Generate/Update Markdown TOC +-> `Control + Shift + T` + + +### Toggle Views + +* Toggle Full Screen +-> `Command + Control + F` + +--- + +* Toggle Sidebar +-> `Command + B` + +--- + +* Toggle Note Info +-> `Command + Option + I` + + +### Zoom + +* Zoom In +-> `Command + =` + +--- + +* Zoom Out +-> `Command + -` + +--- + +* Actual Size +-> `Command + 0` + + +### Other Useful Shortcuts + +* Focus Search +-> `Command + Shift + L` + +--- + +* Reload +-> `Command + R` + + +## Hotkeys +Hotkeys are set like below by default, but you can change them in the preference modal. + +--- + +* Show/Hide Boostnote +-> `Command + Alt + L` + +--- + +* Show/Hide Menu Bar +-> `Alt` + +--- + +* Toggle Editor Mode +-> `Command + Alt + M` + +--- + +* Delete Note +-> `Command + Shift + Delete` + +--- + +* Paste HTML +-> `Command + Shift + V` + +--- + +* Prettify Markdown +-> `Command + Shift + F` + +--- + +* Insert Current Date +-> `Command + /` + +--- + +* Insert Current Date and Time +-> `Command + Alt + /` \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Guides/StorageGuide.md b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/StorageGuide.md new file mode 100644 index 0000000000..0e424d1e1b --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Guides/StorageGuide.md @@ -0,0 +1,18 @@ +# Storage guide +Storages and folders can help you to organize your notes. Check this guide and start organizing to make your life easier! + +## Adding a Storage +One of the first thing you want to do in order to use Boostnote is to create a storage. In order to do so, you can click on the link in the sidebar. + +## Storage Can Have Folders +Each storage can have as many folders as you want, and you can create even subfolders inside there. + +You can check the Sample Storage on the sidebar and play around with it so you can get some ideas on how you want to organize with this feature! + +## Local Storage & Cloud Storage +This feature has two storage types, local and cloud. There are no limitations for local storage number (like older version Boostnote), but if you want to create more than two cloud storage, you need to upgrade your plan to do so. [Follow this link](https://boostnote.io/) to upgrade the plan :) + +## Sync Your Cloud Storage +The merit of cloud storage is syncing your notes on every platform. Even when you are offline, Boostnote will start syncing once it became online. + +If there is something problem and couldn't sync your notes, this app will tell you how to solve it. \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Playground/GetStarted.md b/src/lib/tutorials/files/tutorials/WelcomePack/Playground/GetStarted.md new file mode 100644 index 0000000000..0eee0f6b01 --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Playground/GetStarted.md @@ -0,0 +1,92 @@ +# Get Started +Welcome to Boostnote :) This is a page for you to play around with. + +## 👨‍💻 Markdown Cheat Sheet 👩‍💻 + +--- +### 1️⃣ Headings +--- + +#### Heading 1 +`# H1` + +#### Heading 2 +`## H2` + +#### Heading 3 +`### H3` + +#### Heading 4 +`#### H4` + +#### Heading 5 +`##### H5` + +#### Heading 6 +`###### H6` + +--- +### 2️⃣ Text Decoration +--- + +#### Bold +`**bold**` + +#### Italic +`*italicized text*` + +#### Line Through +`~~line through~~` + +--- +### 3️⃣ List +--- + +#### Ordered List +``` +1. First Item +2. Second Item +3. Third Item +``` + +#### Unordered List +``` +* First Item +* Second Item +* Third Item +``` + +--- +### 4️⃣ Code Decoration +--- + +#### Code +`code` + +#### Code Block + +```html +Hello World! + +``` + +--- +### 5️⃣ Others +--- + +#### Checkbox +``` +* [x] First Item +* [ ] Second item +``` + +#### Horizontal Rule +`---` + +#### Link +`[Boostnote](https://boostnote.io/)` + +#### Quote +`> This is a quote from somewhere!` + +--- \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Playground/TodaysTask.md b/src/lib/tutorials/files/tutorials/WelcomePack/Playground/TodaysTask.md new file mode 100644 index 0000000000..6c5a12cc23 --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Playground/TodaysTask.md @@ -0,0 +1,11 @@ +# Today's Task +Let's write your today's tasks to get used to Boostnote! + +## Sample Tasks +* [x] Download Boostnote +* [ ] Write down today's task +* [ ] Get a cup of coffee +* [ ] Turn on my favorite music + +## Write Your Own :) +* [ ] Your first tasks is...? \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Templates/Brainstorm.md b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/Brainstorm.md new file mode 100644 index 0000000000..90cbc47f8a --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/Brainstorm.md @@ -0,0 +1,29 @@ +# Template 4: Brainstorm + +## Goals/Issues + +* Point 1 +* Point 2 +* Point 3 + +## Research + +### [Boostnote](https://boostnote.io/) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus turpis nunc, tempor et purus tempus, condimentum accumsan massa. + +## Suggestions/Ideas + +* Idea 1 +* Idea 2 +* Idea 3 + +## Final Decision + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus turpis nunc, tempor et purus tempus, condimentum accumsan massa. + +## Action Items + +* [ ] Item 1 +* [ ] Item 2 +* [ ] Item 3 \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Templates/BugfixReport.md b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/BugfixReport.md new file mode 100644 index 0000000000..f46408386d --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/BugfixReport.md @@ -0,0 +1,21 @@ +# Template 3: Bugfix Report + +## Summary of the Bug + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus turpis nunc, tempor et purus tempus, condimentum accumsan massa. + +## Why Did It Happen? + +* Lorem ipsum dolor sit amet, consectetur adipiscing elit. +* Lorem ipsum dolor sit amet, consectetur adipiscing elit. +* Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +## How Did I Fix? + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus turpis nunc, tempor et purus tempus, condimentum accumsan massa. + +## How to Prevent Next Time? + +* [ ] Idea 1 +* [ ] Idea 2 +* [ ] Idea 3 \ No newline at end of file diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Templates/MeetingNotes.md b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/MeetingNotes.md new file mode 100644 index 0000000000..d588ed5ded --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/MeetingNotes.md @@ -0,0 +1,33 @@ +# Template 1: Meeting Notes + +## Basic Info + +* Project Name +* Date, Time, Location +* Goal of This Meeting +* Attendees + +## Background + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus turpis nunc, tempor et purus tempus, condimentum accumsan massa. + +## Agenda + +* Topic 1 +* Topic 2 +* Topic 3 + +## Decisions + +* Decision 1 +* Decision 2 +* Decision 3 + +## Next Actions + +* [x] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Closure +Summary & Takeaways diff --git a/src/lib/tutorials/files/tutorials/WelcomePack/Templates/WeeklyPlanner.md b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/WeeklyPlanner.md new file mode 100644 index 0000000000..b4bba4cfc1 --- /dev/null +++ b/src/lib/tutorials/files/tutorials/WelcomePack/Templates/WeeklyPlanner.md @@ -0,0 +1,55 @@ +# Template 2: Weekly Planner + +## This Week's Goal + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Monday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Tuesday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Wednesday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Thursday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Friday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Saturday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Sunday + +* [ ] Task 1 +* [ ] Task 2 +* [ ] Task 3 + +## Memo + +* I learned ... +* Memo 2 +* Memo 3 \ No newline at end of file diff --git a/src/lib/tutorials/index.tsx b/src/lib/tutorials/index.tsx new file mode 100644 index 0000000000..aec59884a9 --- /dev/null +++ b/src/lib/tutorials/index.tsx @@ -0,0 +1 @@ +export * from './tree' diff --git a/src/lib/tutorials/tree.ts b/src/lib/tutorials/tree.ts new file mode 100644 index 0000000000..03d64c94ae --- /dev/null +++ b/src/lib/tutorials/tree.ts @@ -0,0 +1,113 @@ +export type TutorialsNavigatorTreeItem = { + label: string + slug: string + absolutePath: string + type: 'folder' | 'note' | 'storage' + children: TutorialsNavigatorTreeItem[] +} + +export const tutorialsTree: TutorialsNavigatorTreeItem[] = [ + { + label: 'Tutorials', + absolutePath: 'tutorials', + slug: 'tutorials', + type: 'storage', + children: [ + { + label: 'Welcome Pack', + absolutePath: 'WelcomePack', + slug: 'welcome-pack', + type: 'folder', + children: [ + { + label: 'Playground', + absolutePath: 'Playground', + slug: 'playground', + type: 'folder', + children: [ + { + label: 'Get Started!', + absolutePath: 'GetStarted.md', + slug: 'get-started', + type: 'note', + children: [] + }, + { + label: "Today's Task", + absolutePath: 'TodaysTask.md', + slug: 'daily-task', + type: 'note', + children: [] + } + ] + }, + { + label: 'Guides', + absolutePath: 'Guides', + slug: 'guides', + type: 'folder', + children: [ + { + label: 'About our community', + absolutePath: 'AboutOurCommunity.md', + slug: 'community', + type: 'note', + children: [] + }, + { + label: 'Keyboard shortcuts', + absolutePath: 'KeyboardShortcuts.md', + slug: 'keyboard-shortcuts', + type: 'note', + children: [] + }, + { + label: 'Storage guide', + absolutePath: 'StorageGuide.md', + slug: 'storage-guide', + type: 'note', + children: [] + } + ] + }, + { + label: 'Templates', + absolutePath: 'Templates', + slug: 'templates', + type: 'folder', + children: [ + { + label: 'Brainstorm', + absolutePath: 'Brainstorm.md', + slug: 'brainstorm', + type: 'note', + children: [] + }, + { + label: 'Bugfix Report', + absolutePath: 'BugfixReport.md', + slug: 'bugfix-report', + type: 'note', + children: [] + }, + { + label: 'Meeting Notes', + absolutePath: 'MeetingNotes.md', + slug: 'meeting-notes', + type: 'note', + children: [] + }, + { + label: 'Weekly Planner', + absolutePath: 'WeeklyPlanner.md', + slug: 'weekly-planner', + type: 'note', + children: [] + } + ] + } + ] + } + ] + } +] diff --git a/typings/markdowns.d.ts b/typings/markdowns.d.ts new file mode 100644 index 0000000000..d8024e92b1 --- /dev/null +++ b/typings/markdowns.d.ts @@ -0,0 +1 @@ +declare module '*.md' diff --git a/webpack.config.ts b/webpack.config.ts index 335075d7c6..e47d49ccb7 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -51,6 +51,14 @@ module.exports = { test: /\.tsx?$/, use: [{ loader: 'ts-loader' }], exclude: /node_modules/ + }, + { + test: /\.md$/, + use: [ + { + loader: 'raw-loader' + } + ] } ] },