General improvements and bug fixes

This commit is contained in:
aet 2021-11-23 03:13:01 -05:00
parent bdd46a530c
commit 8f9ec73caf
26 changed files with 908 additions and 2122 deletions

View File

@ -14,35 +14,35 @@
"@types/chai-as-promised": "^7.1.4", "@types/chai-as-promised": "^7.1.4",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/mocha": "github:whitecolor/mocha-types#da22474cf43f48a56c86f8c23a5a0ea36e295768", "@types/mocha": "github:whitecolor/mocha-types#da22474cf43f48a56c86f8c23a5a0ea36e295768",
"@types/node": "^16.10.3", "@types/node": "^16.11.9",
"@types/sinon": "^10.0.4", "@types/sinon": "^10.0.6",
"@types/sinon-chai": "^3.2.5", "@types/sinon-chai": "^3.2.5",
"@types/wicg-file-system-access": "^2020.9.4", "@types/wicg-file-system-access": "^2020.9.4",
"@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/eslint-plugin": "5.4.0",
"@typescript-eslint/parser": "4.33.0", "@typescript-eslint/parser": "5.4.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"eslint": "7.32.0", "eslint": "8.3.0",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0", "eslint-import-resolver-typescript": "2.5.0",
"eslint-plugin-import": "2.24.2", "eslint-plugin-import": "2.25.3",
"eslint-plugin-react": "7.26.1", "eslint-plugin-react": "7.27.1",
"eslint-plugin-react-hooks": "4.2.0", "eslint-plugin-react-hooks": "4.3.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"marked": "^3.0.8", "marked": "^4.0.4",
"mocha": "^9.1.2", "mocha": "^9.1.3",
"mochawesome": "^6.3.0", "mochawesome": "^7.0.1",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"sass": "^1.43.2", "sass": "^1.43.4",
"sinon": "^11.1.2", "sinon": "^12.0.1",
"sinon-chai": "^3.7.0", "sinon-chai": "^3.7.0",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"ts-node": "^10.2.1", "ts-node": "^10.4.0",
"tsconfig-paths": "^3.11.0", "tsconfig-paths": "^3.12.0",
"typescript": "^4.4.3" "typescript": "^4.5.2"
}, },
"prettier": { "prettier": {
"arrowParens": "avoid", "arrowParens": "avoid",

View File

@ -16,8 +16,8 @@
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-replace": "^3.0.0",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"rollup": "^2.58.0", "rollup": "^2.60.1",
"rollup-plugin-ts": "^1.4.7", "rollup-plugin-ts": "^2.0.4",
"typedoc": "^0.22.7" "typedoc": "^0.22.9"
} }
} }

View File

@ -21,6 +21,8 @@ export default () => ({
preventAssignment: true, preventAssignment: true,
values: { values: {
"process.env.NODE_ENV": '"production"', "process.env.NODE_ENV": '"production"',
'require("./adapter").nodeAdapter':
'import("./adapter").then(x => x.nodeAdapter)',
}, },
}), }),
], ],

View File

@ -30,7 +30,7 @@ export class OnePassword {
constructor({ constructor({
path, path,
adapter = process.browser ? null! : import("./adapter").then(x => x.nodeAdapter), adapter = process.browser ? null! : require("./adapter").nodeAdapter,
}: IOptions) { }: IOptions) {
this.#adapter = adapter this.#adapter = adapter
this.#path = path this.#path = path

View File

@ -8,7 +8,7 @@ icon: dist/512x512.png
directories: directories:
output: bundle output: bundle
app: dist app: dist
buildResources: build buildResources: dist
mac: mac:
category: public.app-category.productivity category: public.app-category.productivity
target: target:

View File

@ -14,24 +14,25 @@
}, },
"devDependencies": { "devDependencies": {
"@emotion/css": "^11.5.0", "@emotion/css": "^11.5.0",
"@emotion/react": "^11.5.0", "@emotion/react": "^11.6.0",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.6.0",
"@rollup/plugin-yaml": "^3.1.0", "@rollup/plugin-yaml": "^3.1.0",
"@types/react": "^17.0.0", "@types/react": "^17.0.36",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.11",
"@vitejs/plugin-react": "^1.0.0", "@vitejs/plugin-react": "^1.1.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"electron": "^15.2.0", "electron": "^16.0.1",
"electron-builder": "^22.13.1", "electron-builder": "^22.14.5",
"esbuild": "^0.13.6", "esbuild": "^0.13.15",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"opvault.js": "*", "opvault.js": "*",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"react": "^17.0.0", "react": "^17.0.2",
"react-dom": "^17.0.0", "react-dom": "^17.0.2",
"react-idle-timer": "4.6.4",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"sass": "^1.43.4", "sass": "^1.43.4",
"typescript": "^4.3.2", "typescript": "^4.5.2",
"vite": "^2.6.4" "vite": "^2.6.14"
} }
} }

View File

@ -1,5 +1,6 @@
import { useCallback, useState } from "react" import { useCallback, useEffect, useState } from "react"
import type { Vault, OnePassword } from "opvault.js" import type { Vault, OnePassword } from "opvault.js"
import { useIdleTimer } from "react-idle-timer/modern"
import { VaultView } from "./pages/Vault" import { VaultView } from "./pages/Vault"
import { VaultPicker } from "./pages/VaultPicker" import { VaultPicker } from "./pages/VaultPicker"
@ -12,6 +13,19 @@ export const App: React.FC = () => {
setVault(undefined) setVault(undefined)
}, [vault]) }, [vault])
const { reset, pause } = useIdleTimer({
timeout: 60_000,
onIdle: onLock,
})
useEffect(() => {
if (vault) {
reset()
} else {
pause()
}
}, [vault])
if (!vault) { if (!vault) {
return ( return (
<VaultPicker <VaultPicker

View File

@ -0,0 +1,14 @@
import { useEffect, memo } from "react"
import { useLocaleContext, useTranslate } from "./i18n"
export const SideEffect = memo(() => {
const { locale } = useLocaleContext()
const t = useTranslate()
useEffect(() => {
document.documentElement.lang = locale
document.title = t.label.app_name
}, [locale])
return null
})

View File

@ -11,10 +11,12 @@ const Container = styled.div`
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
font-size: 8em; font-size: 6em;
text-align: center; text-align: center;
padding: 20px 25px; padding: 20px 25px;
word-break: break-word; word-break: break-word;
min-width: 75vw;
z-index: 2;
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
} }

View File

@ -1,3 +1,4 @@
import { memo } from "react"
import { Category } from "opvault.js" import { Category } from "opvault.js"
import { cx, css } from "@emotion/css" import { cx, css } from "@emotion/css"
import { BsBank2, BsPeopleFill } from "react-icons/bs" import { BsBank2, BsPeopleFill } from "react-icons/bs"
@ -77,14 +78,11 @@ interface CategoryIconProps {
category: Category category: Category
} }
export const CategoryIcon: React.FC<CategoryIconProps> = ({ export const CategoryIcon = memo<CategoryIconProps>(
className, ({ className, category, style, fill }) => {
category, const Component = getComponent(category)
style, return Component ? (
fill, <Component className={cx(reactIconClass, className)} fill={fill} style={style} />
}) => { ) : null
const Component = getComponent(category) }
return Component ? ( )
<Component className={cx(reactIconClass, className)} fill={fill} style={style} />
) : null
}

View File

@ -3,6 +3,7 @@ import type { Attachment, AttachmentMetadata, Item, ItemField } from "opvault.js
import type { ItemDetails } from "opvault.js/src/types" import type { ItemDetails } from "opvault.js/src/types"
import { memo, useEffect, useState } from "react" import { memo, useEffect, useState } from "react"
import { useTranslate } from "../i18n" import { useTranslate } from "../i18n"
import { ItemNoTitle } from "../styles"
import { CategoryIcon } from "./CategoryIcon" import { CategoryIcon } from "./CategoryIcon"
import { ItemDates } from "./ItemDates" import { ItemDates } from "./ItemDates"
import { import {
@ -59,7 +60,7 @@ const AttachmentContainer = styled.div`
margin: 5px 0; margin: 5px 0;
` `
const SectionsView: React.FC<{ sections?: ItemDetails["sections"] }> = ({ sections }) => const SectionsView = memo<{ sections?: ItemDetails["sections"] }>(({ sections }) =>
sections?.length ? ( sections?.length ? (
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
{sections {sections
@ -74,8 +75,9 @@ const SectionsView: React.FC<{ sections?: ItemDetails["sections"] }> = ({ sectio
))} ))}
</div> </div>
) : null ) : null
)
const FieldsView: React.FC<{ fields?: ItemField[] }> = ({ fields }) => const FieldsView = memo<{ fields?: ItemField[] }>(({ fields }) =>
fields?.length ? ( fields?.length ? (
<div style={{ marginBottom: 20 }}> <div style={{ marginBottom: 20 }}>
{fields.map((field, i) => ( {fields.map((field, i) => (
@ -83,8 +85,9 @@ const FieldsView: React.FC<{ fields?: ItemField[] }> = ({ fields }) =>
))} ))}
</div> </div>
) : null ) : null
)
const TagsView: React.FC<{ tags?: string[] }> = ({ tags }) => { const TagsView = memo<{ tags?: string[] }>(({ tags }) => {
const t = useTranslate() const t = useTranslate()
if (!tags?.length) return null if (!tags?.length) return null
return ( return (
@ -97,7 +100,7 @@ const TagsView: React.FC<{ tags?: string[] }> = ({ tags }) => {
</div> </div>
</ExtraField> </ExtraField>
) )
} })
const JSONView = memo<{ item: Item }>(({ item }) => ( const JSONView = memo<{ item: Item }>(({ item }) => (
<details> <details>
@ -108,7 +111,7 @@ const JSONView = memo<{ item: Item }>(({ item }) => (
</details> </details>
)) ))
export const ItemView: React.FC<ItemViewProps> = ({ className, item }) => { export const ItemView = memo<ItemViewProps>(({ className, item }) => {
const t = useTranslate() const t = useTranslate()
return ( return (
<Container className={className}> <Container className={className}>
@ -117,7 +120,9 @@ export const ItemView: React.FC<ItemViewProps> = ({ className, item }) => {
<Header> <Header>
{item.details.fields == null} {item.details.fields == null}
<Icon category={item.category} /> <Icon category={item.category} />
<ItemTitle>{item.overview.title}</ItemTitle> <ItemTitle>
{item.overview.title || <ItemNoTitle>{t.label.no_title}</ItemNoTitle>}
</ItemTitle>
</Header> </Header>
<JSONView item={item} /> <JSONView item={item} />
@ -161,7 +166,7 @@ export const ItemView: React.FC<ItemViewProps> = ({ className, item }) => {
</Inner> </Inner>
</Container> </Container>
) )
} })
function AttachmentView({ file }: { file: Attachment }) { function AttachmentView({ file }: { file: Attachment }) {
const [metadata, setMetadata] = useState<AttachmentMetadata>() const [metadata, setMetadata] = useState<AttachmentMetadata>()

View File

@ -1,3 +1,4 @@
import { memo } from "react"
import styled from "@emotion/styled" import styled from "@emotion/styled"
import type { Item } from "opvault.js" import type { Item } from "opvault.js"
import { useTranslate } from "../i18n" import { useTranslate } from "../i18n"
@ -9,7 +10,7 @@ const Container = styled.div`
opacity: 0.5; opacity: 0.5;
` `
export const ItemDates: React.FC<{ item: Item }> = ({ item }) => { export const ItemDates = memo<{ item: Item }>(({ item }) => {
const t = useTranslate() const t = useTranslate()
return ( return (
<Container> <Container>
@ -21,4 +22,4 @@ export const ItemDates: React.FC<{ item: Item }> = ({ item }) => {
</div> </div>
</Container> </Container>
) )
} })

View File

@ -1,3 +1,4 @@
import { memo } from "react"
import styled from "@emotion/styled" import styled from "@emotion/styled"
import type { ItemField, ItemSection } from "opvault.js" import type { ItemField, ItemSection } from "opvault.js"
import { ErrorBoundary } from "./ErrorBoundary" import { ErrorBoundary } from "./ErrorBoundary"
@ -13,9 +14,9 @@ export const FieldTitle: React.FC = styled.div`
margin-bottom: 3px; margin-bottom: 3px;
` `
export const ItemFieldView: React.FC<{ export const ItemFieldView = memo<{
field: ItemSection.Any field: ItemSection.Any
}> = ({ field }) => { }>(({ field }) => {
if (field.v == null) { if (field.v == null) {
return null return null
} }
@ -28,11 +29,11 @@ export const ItemFieldView: React.FC<{
</Container> </Container>
</ErrorBoundary> </ErrorBoundary>
) )
} })
export const ItemDetailsFieldView: React.FC<{ export const ItemDetailsFieldView = memo<{
field: ItemField field: ItemField
}> = ({ field }) => { }>(({ field }) => {
if (field.value == null) { if (field.value == null) {
return null return null
} }
@ -45,4 +46,4 @@ export const ItemDetailsFieldView: React.FC<{
</Container> </Container>
</ErrorBoundary> </ErrorBoundary>
) )
} })

View File

@ -7,7 +7,7 @@ const Container = styled.menu`
box-shadow: #0004 0px 1px 4px; box-shadow: #0004 0px 1px 4px;
left: 99%; left: 99%;
margin-block-start: 0; margin-block-start: 0;
min-width: 150px; min-width: 180px;
padding-inline-start: 0; padding-inline-start: 0;
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -9,6 +9,12 @@ import { useItemFieldContextMenu } from "./ItemFieldContextMenu"
const Container = styled.div`` const Container = styled.div``
function useCopy(text: string) {
return useCallback(() => {
navigator.clipboard.writeText(text)
}, [text])
}
export { Password as PasswordFieldView } export { Password as PasswordFieldView }
const Password: React.FC<{ const Password: React.FC<{
@ -19,9 +25,7 @@ const Password: React.FC<{
const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu() const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
const onToggle = useCallback(() => setShow(x => !x), []) const onToggle = useCallback(() => setShow(x => !x), [])
const onCopy = useCallback(() => { const onCopy = useCopy(field.v)
navigator.clipboard.writeText(field.v)
}, [field.v])
const onOpenBigText = useCallback(() => { const onOpenBigText = useCallback(() => {
showBigText(true) showBigText(true)
}, []) }, [])
@ -67,9 +71,7 @@ const DateView: React.FC<{ field: ItemSection.Date }> = ({ field }) => {
const TextView: React.FC<{ value: string }> = ({ value }) => { const TextView: React.FC<{ value: string }> = ({ value }) => {
const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu() const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
const onCopy = useCallback(() => { const onCopy = useCopy(value)
navigator.clipboard.writeText(value)
}, [value])
return ( return (
<> <>
@ -126,7 +128,7 @@ export const ItemDetailsFieldValue: React.FC<{
return ( return (
<ErrorBoundary> <ErrorBoundary>
<Container>{field.value}</Container> <TextView value={field.value!} />
</ErrorBoundary> </ErrorBoundary>
) )
} }

View File

@ -1,7 +1,10 @@
import { memo } from "react"
import styled from "@emotion/styled" import styled from "@emotion/styled"
import { cx } from "@emotion/css" import { cx } from "@emotion/css"
import type { Item } from "opvault.js" import type { Item } from "opvault.js"
import { CategoryIcon } from "./CategoryIcon" import { CategoryIcon } from "./CategoryIcon"
import { useTranslate } from "../i18n"
import { ItemNoTitle } from "../styles"
interface ListProps { interface ListProps {
items: Item[] items: Item[]
@ -36,6 +39,7 @@ const ItemTitle = styled.div`
font-weight: 600; font-weight: 600;
margin-bottom: 2px; margin-bottom: 2px;
` `
const ItemDescription = styled.div` const ItemDescription = styled.div`
font-size: 95%; font-size: 95%;
white-space: nowrap; white-space: nowrap;
@ -47,25 +51,30 @@ const Icon = styled(CategoryIcon)`
font-size: 1.5em; font-size: 1.5em;
` `
export const ItemList: React.FC<ListProps> = ({ items, onSelect, selected }) => ( export const ItemList = memo<ListProps>(({ items, onSelect, selected }) => {
<Container> const t = useTranslate()
<List> return (
{items.map(item => ( <Container>
<ItemView <List>
key={item.uuid} {items.map(item => (
onClick={() => onSelect(item)} <ItemView
className={cx({ key={item.uuid}
selected: selected?.uuid === item.uuid, onClick={() => onSelect(item)}
trashed: item.isDeleted, className={cx({
})} selected: selected?.uuid === item.uuid,
> trashed: item.isDeleted,
<Icon fill="#FFF" category={item.category} /> })}
<div> >
<ItemTitle>{item.overview.title!}</ItemTitle> <Icon fill="#FFF" category={item.category} />
<ItemDescription>{item.overview.ainfo || " "}</ItemDescription> <div>
</div> <ItemTitle>
</ItemView> {item.overview.title || <ItemNoTitle>{t.label.no_title}</ItemNoTitle>}
))} </ItemTitle>
</List> <ItemDescription>{item.overview.ainfo || " "}</ItemDescription>
</Container> </div>
) </ItemView>
))}
</List>
</Container>
)
})

View File

@ -1,6 +1,6 @@
import styled from "@emotion/styled" import styled from "@emotion/styled"
import type { Item } from "opvault.js" import type { Item } from "opvault.js"
import { useMemo } from "react" import { useMemo, memo } from "react"
import { parseMonthYear } from "../utils" import { parseMonthYear } from "../utils"
const Container = styled.div` const Container = styled.div`
@ -12,7 +12,7 @@ const Container = styled.div`
} }
` `
export const ItemWarning: React.FC<{ item: Item }> = ({ item }) => { export const ItemWarning = memo<{ item: Item }>(({ item }) => {
const isExpired = useMemo(() => { const isExpired = useMemo(() => {
const fields = item.details.sections?.flatMap(x => x.fields ?? []) const fields = item.details.sections?.flatMap(x => x.fields ?? [])
if (!fields?.length) return false if (!fields?.length) return false
@ -38,4 +38,4 @@ export const ItemWarning: React.FC<{ item: Item }> = ({ item }) => {
} }
return null return null
} })

View File

@ -1,3 +1,4 @@
import { memo } from "react"
import styled from "@emotion/styled" import styled from "@emotion/styled"
const Container = styled.div` const Container = styled.div`
@ -16,8 +17,8 @@ const Title = styled.div`
flex-grow: 1; flex-grow: 1;
` `
export const TitleBar = () => ( export const TitleBar = memo(() => (
<Container> <Container>
<Title>OPVault Viewer</Title> <Title>OPVault Viewer</Title>
</Container> </Container>
) ))

View File

@ -1,9 +1,18 @@
import { createContext, memo, useContext, useEffect, useMemo, useState } from "react" import {
createContext,
memo,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import texts from "./texts.yml" import texts from "./texts.yml"
const categories = Object.keys(texts) const categories = Object.keys(texts)
const ALLOWED = new Set(["en", "fr"]) const ALLOWED = new Set(["en", "fr"])
const SKIP_ITALIC = new Set(["zh", "ko", "ja"])
const LOCALSTORAGE_KEY = "preferred-locale" const LOCALSTORAGE_KEY = "preferred-locale"
function getLocaleFromStorage() { function getLocaleFromStorage() {
@ -38,33 +47,33 @@ export const useLocaleContext = () => useContext(LocaleContext)
export function useTranslate() { export function useTranslate() {
const { locale } = useContext(LocaleContext) const { locale } = useContext(LocaleContext)
const t = useMemo( const getter = useCallback(
() => (category: string, key: string) => {
const obj = (texts as any)[category]
if (
process.env.NODE_ENV === "development" &&
!Object.prototype.hasOwnProperty.call(obj, key)
) {
throw new Error(`t.${key} does not exist.`)
}
return obj[key][locale]
},
[locale]
)
const t: {
[category in keyof typeof texts]: {
[key in keyof typeof texts[category]]: string
}
} = useMemo(
(): any =>
Object.fromEntries( Object.fromEntries(
categories.map(category => [ categories.map(category => [
category, category,
new Proxy( new Proxy({}, { get: (_, p: string) => getter(category, p) }),
{},
{
get(_, p: string) {
const obj = (texts as any)[category]
if (
process.env.NODE_ENV === "development" &&
!Object.prototype.hasOwnProperty.call(obj, p)
) {
throw new Error(`t.${p} does not exist.`)
}
return obj[p][locale]
},
}
),
]) ])
) as { ),
[category in keyof typeof texts]: { [getter]
[key in keyof typeof texts[category]]: string
}
},
[locale]
) )
return t return t
} }

View File

@ -1,5 +1,9 @@
# /* spellchecker: disable */ # /* spellchecker: disable */
label: label:
app_name:
en: OPVault Viewer
fr: Lecteur de coffre OPVault
choose_a_vault: choose_a_vault:
en: Pick a vault en: Pick a vault
fr: Choisir un coffre fr: Choisir un coffre
@ -28,6 +32,23 @@ label:
en: Password en: Password
fr: Mot de passe fr: Mot de passe
no_title:
en: Untitled
fr: Sans titre
options:
sort_by_name:
en: Sort by Name
fr: Trier par nom
sort_by_created_at:
en: Sort by date created
fr: Trier par date de création
sort_by_updated_at:
en: Sort by date modified
fr: Trier par date de modification
noun: noun:
vault: vault:
en: vault en: vault

View File

@ -2,18 +2,21 @@ import React from "react"
import { render } from "react-dom" import { render } from "react-dom"
import { App } from "./App" import { App } from "./App"
import { LocaleContextProvider } from "./i18n" import { LocaleContextProvider } from "./i18n"
import { SideEffect } from "./SideEffect"
import "./index.scss" import "./index.scss"
if (navigator.platform === "MacIntel") { if (navigator.platform === "MacIntel") {
document.documentElement.classList.add("mac") document.documentElement.classList.add("mac")
} }
render( const Root: React.FC = () => (
<React.StrictMode> <React.StrictMode>
{/* <TitleBar /> */} {/* <TitleBar /> */}
<LocaleContextProvider> <LocaleContextProvider>
<SideEffect />
<App /> <App />
</LocaleContextProvider> </LocaleContextProvider>
</React.StrictMode>, </React.StrictMode>
document.getElementById("app")
) )
render(<Root />, document.getElementById("app"))

3
packages/web/src/modules.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module "react-idle-timer/modern" {
export * from "react-idle-timer/dist/modern"
}

View File

@ -136,9 +136,9 @@ export const VaultView: React.FC<{ vault: Vault; onLock(): void }> = ({
value={sortBy} value={sortBy}
onChange={e => setSortBy(+e.currentTarget.value)} onChange={e => setSortBy(+e.currentTarget.value)}
> >
<option value={SortBy.Name}>Sort by Name</option> <option value={SortBy.Name}>{t.options.sort_by_name}</option>
<option value={SortBy.CreatedAt}>Sort by Created Time</option> <option value={SortBy.CreatedAt}>{t.options.sort_by_created_at}</option>
<option value={SortBy.UpdatedAt}>Sort by Updated Time</option> <option value={SortBy.UpdatedAt}>{t.options.sort_by_updated_at}</option>
</select> </select>
</SortContainer> </SortContainer>
<ItemList items={filtered} onSelect={setItem} selected={item} /> <ItemList items={filtered} onSelect={setItem} selected={item} />

View File

@ -1,4 +1,15 @@
import { css } from "@emotion/css" import { css } from "@emotion/css"
import styled from "@emotion/styled"
export const ItemNoTitle = styled.span`
font-weight: normal;
font-style: italic;
[lang^="zh"],
[lang="ko"],
[lang="ja"] & {
font-style: normal;
}
`
export const scrollbar = css` export const scrollbar = css`
&&::-webkit-scrollbar { &&::-webkit-scrollbar {

View File

@ -13,6 +13,9 @@ export default defineConfig({
}, },
build: { build: {
outDir: "dist/web", outDir: "dist/web",
rollupOptions: {
external: ["fs", ""],
},
}, },
resolve: { resolve: {
alias: { alias: {

2678
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff