From de478b30fc0c5ab26e7c511e7e80c2a47f5949b9 Mon Sep 17 00:00:00 2001 From: kjirou Date: Wed, 16 Jun 2021 23:32:35 +0900 Subject: [PATCH 1/6] Exec `npm install react react-dom` --- package-lock.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++++ 2 files changed, 50 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3f130e3..4f65b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,6 +610,11 @@ } } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -643,6 +648,14 @@ "p-locate": "^4.1.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -710,6 +723,11 @@ "path-key": "^3.0.0" } }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -802,6 +820,25 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, "rechoir": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", @@ -842,6 +879,15 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", diff --git a/package.json b/package.json index d45e8f1..16958ed 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,9 @@ "typescript": "^4.3.2", "webpack": "^5.39.0", "webpack-cli": "^4.7.2" + }, + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" } } From 1a547d700801a3aa4582e9b4326e782c481ce9af Mon Sep 17 00:00:00 2001 From: kjirou Date: Wed, 16 Jun 2021 23:33:05 +0900 Subject: [PATCH 2/6] Exec `npm install -D @types/react @types/react-dom` --- package-lock.json | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 40 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4f65b62..4a26ffe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,38 @@ "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", "dev": true }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz", + "integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.7.tgz", + "integrity": "sha512-Wd5xvZRlccOrCTej8jZkoFZuZRKHzanDDv1xglI33oBNFMWrqOSzrvWFw7ngSiZjrpJAzPKFtX7JvuXpkNmQHA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", + "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", @@ -358,6 +390,12 @@ "which": "^2.0.1" } }, + "csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==", + "dev": true + }, "electron-to-chromium": { "version": "1.3.752", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", diff --git a/package.json b/package.json index 16958ed..f2ae379 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "homepage": "https://github.com/kjirou/recalldoc#readme", "private": true, "devDependencies": { + "@types/react": "^17.0.11", + "@types/react-dom": "^17.0.7", "ts-loader": "^9.2.3", "typescript": "^4.3.2", "webpack": "^5.39.0", From c0136f96849523660420b09f0d57dc615f42435a Mon Sep 17 00:00:00 2001 From: kjirou Date: Thu, 17 Jun 2021 00:29:13 +0900 Subject: [PATCH 3/6] Error when building with webpack --- src/components/Searcher.tsx | 11 +++++++++++ src/index.ts | 8 ++++++++ tsconfig.json | 2 ++ webpack.config.js | 4 ++-- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/components/Searcher.tsx diff --git a/src/components/Searcher.tsx b/src/components/Searcher.tsx new file mode 100644 index 0000000..faf440e --- /dev/null +++ b/src/components/Searcher.tsx @@ -0,0 +1,11 @@ +import { VFC } from 'react' + +export type Props = { +} + +export const Searcher: VFC = (props) => { + return
+ +
    +
    +} diff --git a/src/index.ts b/src/index.ts index f3bf932..0a1d39e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,7 @@ +import { createElement } from 'react' +import { render } from 'react-dom' +import { Searcher } from './components/Searcher' + type Footprint = { title: string; url: string; @@ -65,6 +69,10 @@ const renderSearcher = (state: State, itemList: HTMLUListElement): void => { } const initializeSearcher = (footprints: Footprint[]): void => { + render( + createElement(Searcher, {}), + document.body, + ) const container = document.createElement('div') container.classList.add('recalldoc-searcher') container.style.position = 'fixed' diff --git a/tsconfig.json b/tsconfig.json index bc47be5..59dbdc4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,11 @@ { "compilerOptions": { + "jsx": "react-jsx", "lib": [ "dom", "es2020" ], + "moduleResolution": "node", "outDir": "./dist", "strict": true, "target": "esnext" diff --git a/webpack.config.js b/webpack.config.js index 42a17e4..408bd81 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,12 +13,12 @@ module.exports = { module: { rules: [ { - test: /\.ts$/, + test: /\.tsx?$/, use: 'ts-loader', }, ], }, resolve: { - extensions: ['.ts'], + extensions: ['.ts', '.tsx'], }, } From 9ec58a07e7e5aa3ecf492a6c7f22a21ec6dff4e9 Mon Sep 17 00:00:00 2001 From: kjirou Date: Thu, 17 Jun 2021 00:56:01 +0900 Subject: [PATCH 4/6] Fix the building error --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 408bd81..04620af 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,6 +19,6 @@ module.exports = { ], }, resolve: { - extensions: ['.ts', '.tsx'], + extensions: ['.ts', '.tsx', '.js'], }, } From 85767179b4285a11840547f8861cd221b712ea5c Mon Sep 17 00:00:00 2001 From: kjirou Date: Thu, 17 Jun 2021 02:15:00 +0900 Subject: [PATCH 5/6] Rewrite code using with React --- src/components/Searcher.tsx | 65 ++++++++++++++- src/index.ts | 153 +++++++++++++++--------------------- 2 files changed, 126 insertions(+), 92 deletions(-) diff --git a/src/components/Searcher.tsx b/src/components/Searcher.tsx index faf440e..a93af05 100644 --- a/src/components/Searcher.tsx +++ b/src/components/Searcher.tsx @@ -1,11 +1,68 @@ -import { VFC } from 'react' +import { KeyboardEvent, VFC } from 'react' + +export type FootprintProps = { + highlighted: boolean; + title: string; + url: string; +} export type Props = { + footprints: FootprintProps[]; + onInput: (inputValue: string) => void, + onKeyDown: (event: KeyboardEvent) => void, } +// TODO: mount 時に focus する。 +// TODO: 最大表示件数を設定する。 +// TODO: 検索キーワードがマッチしている箇所をハイライトする。 export const Searcher: VFC = (props) => { - return
    - -
      + return
      + { + props.onInput(event.currentTarget.value) + }} + onKeyDown={props.onKeyDown} + style={{ + display: 'block', + textAlign: 'right', + width: '100%', + }} + /> + { + props.footprints.length > 0 && + }
      } diff --git a/src/index.ts b/src/index.ts index 0a1d39e..d7b3947 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,10 @@ import { createElement } from 'react' -import { render } from 'react-dom' -import { Searcher } from './components/Searcher' +import { createPortal, render } from 'react-dom' +import { + FootprintProps as SearcherFootprintProps, + Props as SearcherProps, + Searcher, +} from './components/Searcher' type Footprint = { title: string; @@ -47,103 +51,76 @@ const rotateIndex = (length: number, index: number): number => { return (length * 1000 + index) % length } -const renderSearcher = (state: State, itemList: HTMLUListElement): void => { - const searchedFootprints = searchFootprints(state.footprints, state.inputValue) - const actualCursoredIndex = rotateIndex(searchedFootprints.length, state.cursoredIndex) - itemList.innerHTML = '' - // TODO: マッチしている箇所をハイライトする。 - // TODO: 全件出力してしまう。 - for (const [index, footprint] of searchedFootprints.entries()) { - const itemLi = document.createElement('li') - itemLi.style.lineHeight = '1' - if (index === actualCursoredIndex) { - itemLi.style.backgroundColor = '#ffff00' - } - const itemAnchor = document.createElement('a') - itemAnchor.textContent = footprint.title - itemAnchor.href = footprint.url - itemAnchor.style.fontSize = '12px' - itemLi.appendChild(itemAnchor) - itemList.appendChild(itemLi) - } +const pageKind = getPageKind(document.URL) +// TODO: 全画面でデータを読み込んでいる。 +const footprints = loadFootprints() +let state: State = { + cursoredIndex: 0, + inputValue: '', + footprints, } +const searcherRootElement = document.createElement('div') +searcherRootElement.style.display = 'none' +document.body.appendChild(searcherRootElement) -const initializeSearcher = (footprints: Footprint[]): void => { +const renderSearcher = (props: SearcherProps): void => { render( - createElement(Searcher, {}), - document.body, + createPortal( + createElement(Searcher, props), + document.body, + ), + searcherRootElement, ) - const container = document.createElement('div') - container.classList.add('recalldoc-searcher') - container.style.position = 'fixed' - container.style.width = '600px' - container.style.top = '20px' - container.style.left = 'calc(50% - 600px/2)' - container.style.zIndex = '1' - const itemList = document.createElement('ul') - itemList.style.padding = '5px' - itemList.style.border = '1px solid #cccccc' - itemList.style.backgroundColor = '#ffffff' - const searchField: HTMLInputElement = document.createElement('input') - searchField.style.display = 'block' - searchField.style.width = '100%' - searchField.style.textAlign = 'right' - let state: State = { - cursoredIndex: 0, - inputValue: '', - footprints, - } - searchField.addEventListener('input', (event) => { - state = { - ...state, - // TODO: 型が雑だけど、Reactにするときにどうせ変わるはず。 - // TODO: event.currentTarget でも searchField: HTMLInputElement の型を引き継げないのはなぜ? - inputValue: (event.currentTarget as HTMLInputElement).value, - cursoredIndex: 0, - } - renderSearcher(state, itemList) - }) - searchField.addEventListener('keydown', (event) => { - if (event.key === 'ArrowUp') { - event.preventDefault() - state = { - ...state, - cursoredIndex: state.cursoredIndex - 1, - } - renderSearcher(state, itemList) - } else if (event.key === 'ArrowDown') { - event.preventDefault() +} + +const generateSearcherProps = (state: State): SearcherProps => { + const searchedFootprints = searchFootprints(state.footprints, state.inputValue) + const actualCursoredIndex = rotateIndex(searchedFootprints.length, state.cursoredIndex) + return { + footprints: searchedFootprints.map((footprint, index) => ({ + ...footprint, + highlighted: index === actualCursoredIndex, + })), + onInput: ((inputValue: string) => { state = { ...state, - cursoredIndex: state.cursoredIndex + 1, + inputValue, + cursoredIndex: 0, } - renderSearcher(state, itemList) - // TODO: IMEの変換決定でここが動いてしまう。 - } else if (event.key === 'Enter') { - event.preventDefault() - const searchedFootprints = searchFootprints(state.footprints, state.inputValue) - const cursoredFootprint = searchedFootprints[rotateIndex(searchedFootprints.length, state.cursoredIndex)] - if (cursoredFootprint) { - window.location.href = cursoredFootprint.url + renderSearcher(generateSearcherProps(state)) + }), + // TODO: ESCキーで Searcher を閉じる。 + onKeyDown: ((event) => { + if (event.key === 'ArrowUp') { + event.preventDefault() + state = { + ...state, + cursoredIndex: state.cursoredIndex - 1, + } + renderSearcher(generateSearcherProps(state)) + } else if (event.key === 'ArrowDown') { + event.preventDefault() + state = { + ...state, + cursoredIndex: state.cursoredIndex + 1, + } + renderSearcher(generateSearcherProps(state)) + // TODO: IMEの変換決定でここが動いてしまう。 + } else if (event.key === 'Enter') { + event.preventDefault() + const searchedFootprints = searchFootprints(state.footprints, state.inputValue) + const cursoredFootprint = searchedFootprints[rotateIndex(searchedFootprints.length, state.cursoredIndex)] + if (cursoredFootprint) { + window.location.href = cursoredFootprint.url + } } - } - }) - container.appendChild(searchField) - container.appendChild(itemList) - document.body.appendChild(container) - searchField.focus() + }) + } } -const pageKind = getPageKind(document.URL) -// TODO: 全画面でデータを読み込んでいる。 -const footprints = loadFootprints() - window.addEventListener('keydown', (event) => { - if ( - (event.ctrlKey || event.metaKey) && event.shiftKey && event.key === 'l' && - !document.querySelector('.recalldoc-searcher') - ) { - initializeSearcher(footprints) + if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key === 'l') { + renderSearcher(generateSearcherProps(state)) } }) From 09189e93f68b0e9c972f87752399c10ca647bc99 Mon Sep 17 00:00:00 2001 From: kjirou Date: Thu, 17 Jun 2021 02:22:00 +0900 Subject: [PATCH 6/6] Restore `searchField.focus()` function --- src/components/Searcher.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/Searcher.tsx b/src/components/Searcher.tsx index a93af05..bd38e94 100644 --- a/src/components/Searcher.tsx +++ b/src/components/Searcher.tsx @@ -1,4 +1,9 @@ -import { KeyboardEvent, VFC } from 'react' +import { + KeyboardEvent, + VFC, + useEffect, + useRef, +} from 'react' export type FootprintProps = { highlighted: boolean; @@ -12,10 +17,15 @@ export type Props = { onKeyDown: (event: KeyboardEvent) => void, } -// TODO: mount 時に focus する。 // TODO: 最大表示件数を設定する。 // TODO: 検索キーワードがマッチしている箇所をハイライトする。 export const Searcher: VFC = (props) => { + const searchFieldRef = useRef(null) + + useEffect(() => { + searchFieldRef.current?.focus() + }, []) + return
      = (props) => { }} > { props.onInput(event.currentTarget.value) }}