diff --git a/package.json b/package.json index 732783c0ec..59523f85b9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "scripts": { "dev": "env-cmd cross-env NODE_ENV=development TS_NODE_PROJECT=\"tsconfig-webpack.json\" webpack-dev-server --mode development --open-page \"app\"", + "dev:app": "npm run build:electron && npm run start", "start": "electron app/index.js", "lint": "eslint src/* --ext .ts,.tsx", "format": "prettier --write \"src/**/*\"", diff --git a/src/components/NotePage/NoteList/NoteList.tsx b/src/components/NotePage/NoteList/NoteList.tsx index 4f9fafcbf1..450459d8d7 100644 --- a/src/components/NotePage/NoteList/NoteList.tsx +++ b/src/components/NotePage/NoteList/NoteList.tsx @@ -1,11 +1,4 @@ -import React, { - useCallback, - KeyboardEvent, - useRef, - ChangeEventHandler, - useMemo, - useState -} from 'react' +import React, { useCallback, useRef, ChangeEventHandler } from 'react' import NoteItem from './NoteItem' import { PopulatedNoteDoc } from '../../../lib/db/types' import styled from '../../../lib/styled' @@ -17,6 +10,11 @@ import { } from '../../../lib/styled/styleFunctions' import { IconEdit, IconLoupe, IconArrowSingleDown } from '../../icons' import { useTranslation } from 'react-i18next' +import { + useGlobalKeyDownHandler, + isWithGeneralCtrlKey +} from '../../../lib/keyboard' +import { NoteListSortOptions } from '../NotePage' export const StyledNoteListContainer = styled.div` display: flex; @@ -98,8 +96,6 @@ export const StyledNoteListContainer = styled.div` } ` -type SortProps = 'createdAt' | 'title' | 'updatedAt' - type NoteListProps = { currentStorageId?: string currentNoteId?: string @@ -111,6 +107,7 @@ type NoteListProps = { navigateUp: () => void basePathname: string lastCreatedNoteId: string + setSort: (option: NoteListSortOptions) => void } const NoteList = ({ @@ -123,7 +120,8 @@ const NoteList = ({ setSearchInput, navigateDown, navigateUp, - lastCreatedNoteId + lastCreatedNoteId, + setSort }: NoteListProps) => { const { t } = useTranslation() const updateSearchInput: ChangeEventHandler = useCallback( @@ -132,31 +130,35 @@ const NoteList = ({ }, [setSearchInput] ) - const [sort, setSort] = useState('updatedAt') - const sortedNotes = useMemo(() => { - return notes.sort((first, second) => { - return sort === 'title' - ? first[sort].localeCompare(second[sort]) - : second[sort].localeCompare(first[sort]) - }) - }, [notes, sort]) + const listRef = useRef(null) + const searchRef = useRef(null) - const handleListKeyDown = useCallback( - (event: KeyboardEvent) => { - switch (event.key) { - case 'ArrowDown': + useGlobalKeyDownHandler(e => { + switch (e.key) { + case 's': + if (isWithGeneralCtrlKey(e) && !e.shiftKey) { + searchRef.current!.focus() + } + break + case 'j': + if (isWithGeneralCtrlKey(e)) { + e.preventDefault() + e.stopPropagation() navigateDown() - break - case 'ArrowUp': + } + break + case 'k': + if (isWithGeneralCtrlKey(e)) { + e.preventDefault() + e.stopPropagation() navigateUp() - break - } - }, - [navigateUp, navigateDown] - ) - - const listRef = useRef(null) + } + break + default: + break + } + }) const focusList = useCallback(() => { listRef.current!.focus() @@ -166,6 +168,7 @@ const NoteList = ({
-
    - {sortedNotes.map(note => { +
      + {notes.map(note => { const noteIsCurrentNote = note._id === currentNoteId return (
    • diff --git a/src/components/NotePage/NotePage.tsx b/src/components/NotePage/NotePage.tsx index 67709f0d27..5abfdaf07f 100644 --- a/src/components/NotePage/NotePage.tsx +++ b/src/components/NotePage/NotePage.tsx @@ -19,6 +19,10 @@ import { useGeneralStatus, ViewModeType } from '../../lib/generalStatus' import { useDialog, DialogIconTypes } from '../../lib/dialog' import { escapeRegExp } from '../../lib/regex' import { useTranslation } from 'react-i18next' +import { + useGlobalKeyDownHandler, + isWithGeneralCtrlKey +} from '../../lib/keyboard' export const StyledNoteDetailNoNote = styled.div` text-align: center; @@ -31,6 +35,8 @@ export type BreadCrumbs = { folderIsActive: boolean }[] +export type NoteListSortOptions = 'createdAt' | 'title' | 'updatedAt' + export default () => { const db = useDb() @@ -46,12 +52,12 @@ export default () => { return db.storageMap[storageId] }, [db.storageMap, storageId]) const router = useRouter() + const { t } = useTranslation() const [search, setSearchInput] = useState('') const currentPathnameWithoutNoteId = usePathnameWithoutNoteId() const { push } = useRouter() const [lastCreatedNoteId, setLastCreatedNoteId] = useState('') - - const { t } = useTranslation() + const [sort, setSort] = useState('updatedAt') useEffect(() => { setLastCreatedNoteId('') @@ -119,15 +125,22 @@ export default () => { }, [db.storageMap, currentStorage, routeParams]) const filteredNotes = useMemo(() => { - if (search.trim() === '') return notes - const regex = new RegExp(escapeRegExp(search), 'i') - return notes.filter( - note => - note.tags.join().match(regex) || - note.title.match(regex) || - note.content.match(regex) - ) - }, [search, notes]) + let filteredNotes = notes + if (search.trim() != '') { + const regex = new RegExp(escapeRegExp(search), 'i') + filteredNotes = notes.filter( + note => + note.tags.join().match(regex) || + note.title.match(regex) || + note.content.match(regex) + ) + } + return filteredNotes.sort((first, second) => { + return sort === 'title' + ? first[sort].localeCompare(second[sort]) + : second[sort].localeCompare(first[sort]) + }) + }, [search, notes, sort]) const currentNoteIndex = useMemo(() => { for (let i = 0; i < filteredNotes.length; i++) { @@ -139,7 +152,9 @@ export default () => { }, [filteredNotes, noteId]) const currentNote: PopulatedNoteDoc | undefined = useMemo(() => { - return filteredNotes[currentNoteIndex] + return filteredNotes[currentNoteIndex] != null + ? filteredNotes[currentNoteIndex] + : undefined }, [filteredNotes, currentNoteIndex]) const createNote = useCallback(async () => { @@ -245,6 +260,41 @@ export default () => { } }, [filteredNotes, currentNoteIndex, router, currentPathnameWithoutNoteId]) + useGlobalKeyDownHandler(e => { + switch (e.key) { + case 'Backspace': + if (currentNote != null && isWithGeneralCtrlKey(e)) { + if (!currentNote.trashed) { + db.trashNote(currentNote.storageId, currentNote._id) + } else { + purgeNote(currentNote.storageId, currentNote._id) + } + } + break + case 'n': + if (isWithGeneralCtrlKey(e)) { + createNote() + } + break + case 'T': + case 't': + if (isWithGeneralCtrlKey(e) && e.shiftKey) { + switch (generalStatus['noteViewMode']) { + case 'edit': + toggleViewMode('split') + break + case 'split': + toggleViewMode('preview') + break + default: + toggleViewMode('edit') + break + } + } + break + } + }) + return ( { navigateUp={navigateUp} currentNoteId={currentNote ? currentNote._id : undefined} lastCreatedNoteId={lastCreatedNoteId} + setSort={setSort} /> } right={ diff --git a/src/components/PreferencesModal/PreferencesModal.tsx b/src/components/PreferencesModal/PreferencesModal.tsx index a0eab04886..cd2ddb48ac 100644 --- a/src/components/PreferencesModal/PreferencesModal.tsx +++ b/src/components/PreferencesModal/PreferencesModal.tsx @@ -56,9 +56,10 @@ const CloseButton = styled.button` ` const PreferencesModal = () => { + const { t } = useTranslation() const { closed, toggleClosed } = usePreferences() const [tab, setTab] = useState('general') - const { t } = useTranslation() + const keydownHandler = useMemo(() => { return (event: KeyboardEvent) => { if (!closed && event.key === 'Escape') { diff --git a/src/components/Tutorials/TutorialsNoteList.tsx b/src/components/Tutorials/TutorialsNoteList.tsx index b552f0a632..a3c1ef653c 100644 --- a/src/components/Tutorials/TutorialsNoteList.tsx +++ b/src/components/Tutorials/TutorialsNoteList.tsx @@ -1,8 +1,12 @@ -import React, { useCallback, KeyboardEvent, useRef } from 'react' +import React, { useCallback, useRef } from 'react' import { TutorialsNavigatorTreeItem } from '../../lib/tutorials' import TutorialsNoteItem from './TutorialsNoteItem' import { StyledNoteListContainer } from '../NotePage/NoteList/NoteList' import { useTranslation } from 'react-i18next' +import { + isWithGeneralCtrlKey, + useGlobalKeyDownHandler +} from '../../lib/keyboard' type TutorialsNoteListProps = { currentTree: TutorialsNavigatorTreeItem @@ -21,19 +25,26 @@ const TutorialsNoteList = ({ basePathname, selectedNote }: TutorialsNoteListProps) => { - const handleListKeyDown = useCallback( - (event: KeyboardEvent) => { - switch (event.key) { - case 'ArrowDown': + useGlobalKeyDownHandler(e => { + switch (e.key) { + case 'j': + if (isWithGeneralCtrlKey(e)) { + e.preventDefault() + e.stopPropagation() navigateDown() - break - case 'ArrowUp': + } + break + case 'k': + if (isWithGeneralCtrlKey(e)) { + e.preventDefault() + e.stopPropagation() navigateUp() - break - } - }, - [navigateUp, navigateDown] - ) + } + break + default: + break + } + }) const listRef = useRef(null) @@ -52,7 +63,7 @@ const TutorialsNoteList = ({ return ( -
        +
          {notes.map(note => { const noteIsCurrentNote = selectedNote != null && diff --git a/src/lib/i18n/enUS.ts b/src/lib/i18n/enUS.ts index c53e200bff..7bb673b458 100644 --- a/src/lib/i18n/enUS.ts +++ b/src/lib/i18n/enUS.ts @@ -58,8 +58,8 @@ export default { 'note.nothingMessage': 'No notes could be found.', 'note.noTitle': 'No title', 'note.date': 'ago', - 'note.createKeyMac': 'Mac: Command(⌘) + Enter', - 'note.createKeyWinLin': 'Windows/Linux: Ctrl + Enter', + 'note.createKeyMac': 'Mac: Command(⌘) + n', + 'note.createKeyWinLin': 'Windows/Linux: Ctrl + n', 'note.createkeymessage1': 'to create a new note', 'note.createkeymessage2': 'Select a storage', 'note.createkeymessage3': 'to create a new note', diff --git a/src/lib/i18n/ja.ts b/src/lib/i18n/ja.ts index 381b7e05b7..70b8ae500a 100644 --- a/src/lib/i18n/ja.ts +++ b/src/lib/i18n/ja.ts @@ -58,8 +58,8 @@ export default { 'note.nothingMessage': 'ノートが見つかりません', 'note.noTitle': '無題', 'note.date': '前', - 'note.createKeyMac': 'Mac: Command(⌘) + Enter', - 'note.createKeyWinLin': 'Windows/Linux: Ctrl + Enter', + 'note.createKeyMac': 'Mac: Command(⌘) + n', + 'note.createKeyWinLin': 'Windows/Linux: Ctrl + n', 'note.createkeymessage1': 'ノート作成', 'note.createkeymessage2': 'ストレージを選択しましょう', 'note.createkeymessage3': 'ノート作成', @@ -70,8 +70,7 @@ export default { //About 'about.about': '概要', - 'about.boostnoteDescription': - 'オープンソースの開発者のためのノートアプリ', + 'about.boostnoteDescription': 'オープンソースの開発者のためのノートアプリ', 'about.website': 'ウェブサイト', 'about.boostWiki': 'チームのためのBoost Note', 'about.platform': 'クロスプラットフォーム', @@ -86,7 +85,8 @@ export default { //Billing 'billing.billing': '課金', - 'billing.message': 'プランをアップグレードするためにはサインインしてください', + 'billing.message': + 'プランをアップグレードするためにはサインインしてください', 'billing.basic': '無料', 'billing.current': '今のプラン', 'billing.premium': 'プレミアム', @@ -125,7 +125,8 @@ export default { 'Boost Noteは、改善のためにエンドポイント数等のみデータを取得しています。あなたのノートの中身を見たり、データを取得することは絶対にありません。Boost Noteはオープンソースであるため、どのように動いているかGitHubから確認することができます。', 'preferences.analyticsDescription2': 'データ取得をオンにするかオフにするか、選ぶことができます。', - 'preferences.analyticsLabel': 'Boost Noteの改善のために、データ取得をオンにする', + 'preferences.analyticsLabel': + 'Boost Noteの改善のために、データ取得をオンにする', 'preferences.displayTutorialsLabel': 'チュートリアルを表示', // Preferences EditorTab @@ -148,7 +149,8 @@ export default { // Preferences ImportTab 'preferences.import': 'データ移行', 'preferences.description': '旧Boost Noteアプリからデータを移行する', - 'preferences.importFlow1': '1. あなたのPCで、Boost Noteのファイルが入っているフォルダを選択します', + 'preferences.importFlow1': + '1. あなたのPCで、Boost Noteのファイルが入っているフォルダを選択します', 'preferences.importFlow2': '2. .csonファイルを選択し、下のフォームにドラッグ&ドロップしてください', 'preferences.importFlow3': diff --git a/src/lib/keyboard.ts b/src/lib/keyboard.ts index 66d14ab2e0..343f40c7dc 100644 --- a/src/lib/keyboard.ts +++ b/src/lib/keyboard.ts @@ -1,10 +1,15 @@ import { useEffect } from 'react' import { osName } from './utils' +import isElectron from 'is-electron' export const useGlobalKeyDownHandler = ( handler: (event: KeyboardEvent) => void ) => { return useEffect(() => { + if (!isElectron()) { + return + } + window.addEventListener('keydown', handler) return () => { window.removeEventListener('keydown', handler)