-}> = ({ field }) => {
- const t = useTranslate()
- const [show, setShow] = useState(false)
- const [bigText, showBigText] = useState(false)
-
- const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
- const onToggle = useCallback(() => setShow(x => !x), [])
- const onCopy = useCopy(field.v)
- const onOpenBigText = useCallback(() => {
- showBigText(true)
- }, [])
- const onCloseBigText = useCallback(() => {
- showBigText(false)
- }, [])
-
- return (
- <>
- setShow(x => !x)}
- onClick={onCopy}
- style={{
- fontFamily: "var(--monospace)",
- ...(!show && { userSelect: "none" }),
- }}
- >
- {show ? field.v : "·".repeat(10)}
-
- {bigText && {field.v}}
-
- - {t.action.copy}
- - {show ? t.action.hide : t.action.show}
- {!bigText && (
- - {t.action.show_in_big_characters}
- )}
-
- >
- )
-}
-
-const MonthYear: React.FC<{ field: ItemSection.MonthYear }> = ({ field }) => {
- const { year, month } = parseMonthYear(field.v)
- return (
-
- {month.toString().padStart(2, "0")}/{year.toString().padStart(4, "0")}
-
- )
-}
-
-const DateView: React.FC<{ field: ItemSection.Date }> = ({ field }) => {
- const date = useMemo(() => new Date(field.v * 1000), [field.v])
- return {date.toLocaleDateString()}
-}
-
-const TextView: React.FC<{ value: string }> = ({ value }) => {
- const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
- const onCopy = useCopy(value)
-
- return (
- <>
-
- {value}
-
-
- - Copier
-
- >
- )
-}
-
-export const ItemFieldValue: React.FC<{
- field: ItemSection.Any
-}> = ({ field }) => {
- if (field.v == null) {
- return null
- }
-
- switch (field.k) {
- case "concealed":
- return
- case "monthYear":
- return
- case "date":
- return
- case "address":
- return (
-
- {field.v.street}
-
- {field.v.city}, {field.v.state} ({field.v.zip})
-
- {field.v.country}
-
- )
- }
-
- return (
-
-
-
- )
-}
-
-export const ItemDetailsFieldValue: React.FC<{
- field: ItemField
-}> = ({ field }) => {
- if (field.type === FieldType.Password || field.designation === "password") {
- return
- }
-
- return (
-
-
-
- )
-}
diff --git a/packages/web/src/components/ItemFieldValue/Address.tsx b/packages/web/src/components/ItemFieldValue/Address.tsx
new file mode 100644
index 0000000..4b41f6a
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/Address.tsx
@@ -0,0 +1,12 @@
+import type { ItemSection } from "opvault.js"
+import { Container } from "./Container"
+
+export const Address: React.FC<{ field: ItemSection.Address }> = ({ field }) => (
+
+ {field.v.street}
+
+ {field.v.city}, {field.v.state} ({field.v.zip})
+
+ {field.v.country}
+
+)
diff --git a/packages/web/src/components/ItemFieldValue/Container.ts b/packages/web/src/components/ItemFieldValue/Container.ts
new file mode 100644
index 0000000..d783d06
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/Container.ts
@@ -0,0 +1,9 @@
+import styled from "@emotion/styled"
+
+export const Container = styled.div`
+ cursor: pointer;
+ &:hover {
+ color: #6fa9ff;
+ text-decoration: underline;
+ }
+`
diff --git a/packages/web/src/components/ItemFieldValue/DateView.tsx b/packages/web/src/components/ItemFieldValue/DateView.tsx
new file mode 100644
index 0000000..01c28e9
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/DateView.tsx
@@ -0,0 +1,8 @@
+import type { ItemSection } from "opvault.js"
+import { useMemo } from "react"
+import { Container } from "./Container"
+
+export const DateView: React.FC<{ field: ItemSection.Date }> = ({ field }) => {
+ const date = useMemo(() => new Date(field.v * 1000), [field.v])
+ return {date.toLocaleDateString()}
+}
diff --git a/packages/web/src/components/ItemFieldValue/MonthYear.tsx b/packages/web/src/components/ItemFieldValue/MonthYear.tsx
new file mode 100644
index 0000000..408d707
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/MonthYear.tsx
@@ -0,0 +1,12 @@
+import type { ItemSection } from "opvault.js"
+import { parseMonthYear } from "../../utils"
+import { Container } from "./Container"
+
+export const MonthYear: React.FC<{ field: ItemSection.MonthYear }> = ({ field }) => {
+ const { year, month } = parseMonthYear(field.v)
+ return (
+
+ {month.toString().padStart(2, "0")}/{year.toString().padStart(4, "0")}
+
+ )
+}
diff --git a/packages/web/src/components/ItemFieldValue/OTP.tsx b/packages/web/src/components/ItemFieldValue/OTP.tsx
new file mode 100644
index 0000000..82047bb
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/OTP.tsx
@@ -0,0 +1,64 @@
+import styled from "@emotion/styled"
+import type { ItemSection } from "opvault.js"
+import { useCallback, useState } from "react"
+import { useTranslate } from "../../i18n"
+import { useItemFieldContextMenu } from "../ItemFieldContextMenu"
+import { Container } from "./Container"
+import { useCopy } from "./hooks"
+
+const OTPItemContainer = styled(Container)`
+ margin: 5px 0;
+`
+
+const OTPItem = ({ children }: { children: string }) => {
+ const { onRightClick } = useItemFieldContextMenu()
+ const onCopy = useCopy(children)
+
+ return (
+
+ {children}
+
+ )
+}
+
+const ItemCount = styled(Container)`
+ opacity: 0.5;
+ user-select: none;
+`
+
+export const OTP: React.FC<{
+ field: Pick
+}> = ({ field }) => {
+ const t = useTranslate()
+ const [show, setShow] = useState(false)
+
+ const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
+ const onToggle = useCallback(() => setShow(x => !x), [])
+ const fields = field.v.split(" ")
+
+ return (
+ <>
+ {show ? (
+ setShow(x => !x)}
+ style={{
+ fontFamily: "var(--monospace)",
+ paddingTop: 5,
+ }}
+ >
+ {fields.map((item, i) => (
+ {item}
+ ))}
+
+ ) : (
+
+ {fields.length} {fields.length === 1 ? t.noun.item : t.noun.items}
+
+ )}
+
+ - {show ? t.action.hide : t.action.show}
+
+ >
+ )
+}
diff --git a/packages/web/src/components/ItemFieldValue/Password.tsx b/packages/web/src/components/ItemFieldValue/Password.tsx
new file mode 100644
index 0000000..11dddd1
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/Password.tsx
@@ -0,0 +1,49 @@
+import type { ItemSection } from "opvault.js"
+import { useCallback, useState } from "react"
+import { useTranslate } from "../../i18n"
+import { BigTextView } from "../BigTextView"
+import { useItemFieldContextMenu } from "../ItemFieldContextMenu"
+import { Container } from "./Container"
+import { useCopy } from "./hooks"
+
+export const Password: React.FC<{
+ field: Pick
+}> = ({ field }) => {
+ const t = useTranslate()
+ const [show, setShow] = useState(false)
+ const [bigText, showBigText] = useState(false)
+
+ const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
+ const onToggle = useCallback(() => setShow(x => !x), [])
+ const onCopy = useCopy(field.v)
+ const onOpenBigText = useCallback(() => {
+ showBigText(true)
+ }, [])
+ const onCloseBigText = useCallback(() => {
+ showBigText(false)
+ }, [])
+
+ return (
+ <>
+ setShow(x => !x)}
+ onClick={onCopy}
+ style={{
+ fontFamily: "var(--monospace)",
+ ...(!show && { userSelect: "none" }),
+ }}
+ >
+ {show ? field.v : "·".repeat(10)}
+
+ {bigText && {field.v}}
+
+ - {t.action.copy}
+ - {show ? t.action.hide : t.action.show}
+ {!bigText && (
+ - {t.action.show_in_big_characters}
+ )}
+
+ >
+ )
+}
diff --git a/packages/web/src/components/ItemFieldValue/Text.tsx b/packages/web/src/components/ItemFieldValue/Text.tsx
new file mode 100644
index 0000000..44583b3
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/Text.tsx
@@ -0,0 +1,19 @@
+import { useItemFieldContextMenu } from "../ItemFieldContextMenu"
+import { Container } from "./Container"
+import { useCopy } from "./hooks"
+
+export const TextView: React.FC<{ value: string }> = ({ value }) => {
+ const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
+ const onCopy = useCopy(value)
+
+ return (
+ <>
+
+ {value}
+
+
+ - Copier
+
+ >
+ )
+}
diff --git a/packages/web/src/components/ItemFieldValue/hooks.ts b/packages/web/src/components/ItemFieldValue/hooks.ts
new file mode 100644
index 0000000..6a08862
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/hooks.ts
@@ -0,0 +1,14 @@
+import { useCallback } from "react"
+import { useTranslate } from "../../i18n"
+import { toast, ToastType } from "../Toast"
+
+export function useCopy(text: string) {
+ const t = useTranslate()
+ return useCallback(() => {
+ navigator.clipboard.writeText(text)
+ toast({
+ type: ToastType.Secondary,
+ message: t.tips.copied_to_clipboard,
+ })
+ }, [text, t])
+}
diff --git a/packages/web/src/components/ItemFieldValue/index.tsx b/packages/web/src/components/ItemFieldValue/index.tsx
new file mode 100644
index 0000000..62c3fa2
--- /dev/null
+++ b/packages/web/src/components/ItemFieldValue/index.tsx
@@ -0,0 +1,52 @@
+import type { ItemSection, ItemField } from "opvault.js"
+import { FieldType } from "opvault.js"
+import { ErrorBoundary } from "../ErrorBoundary"
+import { Password } from "./Password"
+import { OTP } from "./OTP"
+import { MonthYear } from "./MonthYear"
+import { DateView } from "./DateView"
+import { TextView } from "./Text"
+import { Address } from "./Address"
+
+export const ItemFieldValue: React.FC<{
+ field: ItemSection.Any
+}> = ({ field }) => {
+ if (field.v == null) {
+ return null
+ }
+
+ switch (field.k) {
+ case "concealed":
+ return field.n.startsWith("TOTP_") ? (
+
+ ) : (
+
+ )
+ case "monthYear":
+ return
+ case "date":
+ return
+ case "address":
+ return
+ }
+
+ return (
+
+
+
+ )
+}
+
+export const ItemDetailsFieldValue: React.FC<{
+ field: ItemField
+}> = ({ field }) => {
+ if (field.type === FieldType.Password || field.designation === "password") {
+ return
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/web/src/i18n/texts.yml b/packages/web/src/i18n/texts.yml
index 6aaf808..dc840d5 100644
--- a/packages/web/src/i18n/texts.yml
+++ b/packages/web/src/i18n/texts.yml
@@ -192,6 +192,16 @@ noun:
fr: secondes
ja: 秒
+ item:
+ en: item
+ fr: élément
+ ja: アイテム
+
+ items:
+ en: items
+ fr: éléments
+ ja: アイテム
+
action:
lock:
en: Lock
diff --git a/packages/web/src/settings/index.tsx b/packages/web/src/settings/index.tsx
index dfbcc78..dd34cc2 100644
--- a/packages/web/src/settings/index.tsx
+++ b/packages/web/src/settings/index.tsx
@@ -75,6 +75,7 @@ export const Settings: React.FC<{
value={autolockAfter}
onChange={e => setAutolockAfter(e.target.valueAsNumber)}
disabled={!enableAutoLock}
+ min={5}
/>
{autolockAfter}