Restructure files
This commit is contained in:
parent
b4b21561ed
commit
bdd46a530c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
|
drafts
|
||||||
node_modules
|
node_modules
|
||||||
mochawesome-report
|
mochawesome-report
|
||||||
lib
|
lib
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"sass": "^1.43.2",
|
"sass": "^1.43.2",
|
||||||
"sinon": "^11.1.2",
|
"sinon": "^11.1.2",
|
||||||
"sinon-chai": "^3.7.0",
|
"sinon-chai": "^3.7.0",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"tsconfig-paths": "^3.11.0",
|
"tsconfig-paths": "^3.11.0",
|
||||||
"typescript": "^4.4.3"
|
"typescript": "^4.4.3"
|
||||||
|
6
packages/adapters/package.json
Normal file
6
packages/adapters/package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "opvault-adapters",
|
||||||
|
"dependencies": {
|
||||||
|
"opvault.js": "*"
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import type { IAdapter, IFileSystem } from "./index"
|
import type { IAdapter, IFileSystem } from "opvault.js/src/adapter"
|
||||||
|
|
||||||
function normalize(path: string) {
|
function normalize(path: string) {
|
||||||
return path.replace(/^\//, "")
|
return path.replace(/^\//, "")
|
@ -1,5 +1,5 @@
|
|||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import type { IAdapter, IFileSystem } from "./index"
|
import type { IAdapter, IFileSystem } from "opvault.js/src/adapter"
|
||||||
|
|
||||||
export class FileSystem implements IFileSystem {
|
export class FileSystem implements IFileSystem {
|
||||||
private paths = new Set<string>()
|
private paths = new Set<string>()
|
@ -4,7 +4,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c; cp src/adapters/index.d.ts lib/adapters/; prettier --write lib >/dev/null",
|
"build": "rollup -c; prettier --write lib >/dev/null",
|
||||||
"build:docs": "typedoc --out docs src/index.ts --excludePrivate"
|
"build:docs": "typedoc --out docs src/index.ts --excludePrivate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -8,7 +8,6 @@ import { dependencies } from "./package.json"
|
|||||||
export default () => ({
|
export default () => ({
|
||||||
input: {
|
input: {
|
||||||
index: "./src/index.ts",
|
index: "./src/index.ts",
|
||||||
"adapters/node": "./src/adapters/node.ts",
|
|
||||||
},
|
},
|
||||||
external: builtinModules.concat(Object.keys(dependencies)),
|
external: builtinModules.concat(Object.keys(dependencies)),
|
||||||
output: {
|
output: {
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { promises as fs, existsSync } from "fs"
|
||||||
|
import { webcrypto } from "crypto"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that implements basic file system functionalities.
|
* An object that implements basic file system functionalities.
|
||||||
*/
|
*/
|
||||||
@ -53,3 +56,19 @@ export interface IAdapter {
|
|||||||
*/
|
*/
|
||||||
subtle: SubtleCrypto
|
subtle: SubtleCrypto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Node.js adapter. This can be used while using `opvault.js`
|
||||||
|
* in a Node.js environment.
|
||||||
|
*/
|
||||||
|
export const nodeAdapter: IAdapter = {
|
||||||
|
fs: {
|
||||||
|
readFile: path => fs.readFile(path, "utf-8"),
|
||||||
|
readBuffer: path => fs.readFile(path),
|
||||||
|
writeFile: fs.writeFile,
|
||||||
|
readdir: fs.readdir,
|
||||||
|
isDirectory: async path => fs.stat(path).then(x => x.isDirectory()),
|
||||||
|
exists: async path => existsSync(path),
|
||||||
|
},
|
||||||
|
subtle: (webcrypto as any).subtle,
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
import { promises as fs, existsSync } from "fs"
|
|
||||||
import { webcrypto } from "crypto"
|
|
||||||
|
|
||||||
import type { IAdapter } from "./index"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default Node.js adapter. This can be used while using `opvault.js`
|
|
||||||
* in a Node.js environment.
|
|
||||||
*/
|
|
||||||
export const nodeAdapter: IAdapter = {
|
|
||||||
fs: {
|
|
||||||
readFile: path => fs.readFile(path, "utf-8"),
|
|
||||||
readBuffer: path => fs.readFile(path),
|
|
||||||
writeFile: fs.writeFile,
|
|
||||||
readdir: fs.readdir,
|
|
||||||
isDirectory: async path => fs.stat(path).then(x => x.isDirectory()),
|
|
||||||
exists: async path => existsSync(path),
|
|
||||||
},
|
|
||||||
subtle: (webcrypto as any).subtle,
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import { decryptData } from "../decipher"
|
import { decryptData } from "./decipher"
|
||||||
import type { IAdapter } from "../adapters"
|
import type { IAdapter } from "./adapter"
|
||||||
import { createEventEmitter } from "../ee"
|
import { createEventEmitter } from "./ee"
|
||||||
import { HMACAssertionError } from "../errors"
|
import { HMACAssertionError } from "./errors"
|
||||||
import type { i18n } from "../i18n"
|
import type { i18n } from "./i18n"
|
||||||
import type { ItemDetails, Overview, Profile } from "../types"
|
import type { ItemDetails, Overview, Profile } from "./types"
|
||||||
import { setIfAbsent } from "../util"
|
import { setIfAbsent } from "./util"
|
||||||
import type { EncryptedItem } from "./item"
|
import type { EncryptedItem } from "./models/item"
|
||||||
|
|
||||||
/** Encryption and MAC */
|
/** Encryption and MAC */
|
||||||
export interface Cipher {
|
export interface Cipher {
|
@ -1,6 +1,6 @@
|
|||||||
import { resolve, extname, basename } from "path"
|
import { resolve, extname, basename } from "path"
|
||||||
import invariant from "tiny-invariant"
|
import invariant from "tiny-invariant"
|
||||||
import type { IFileSystem } from "./adapters"
|
import type { IFileSystem } from "./adapter"
|
||||||
import { once } from "./util"
|
import { once } from "./util"
|
||||||
|
|
||||||
export type OnePasswordFileManager = ReturnType<typeof OnePasswordFileManager>
|
export type OnePasswordFileManager = ReturnType<typeof OnePasswordFileManager>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
import { Vault } from "./models/vault"
|
import { Vault } from "./models/vault"
|
||||||
import type { IAdapter } from "./adapters"
|
import type { IAdapter } from "./adapter"
|
||||||
import { asyncMap } from "./util"
|
import { asyncMap } from "./util"
|
||||||
|
|
||||||
export type { Vault } from "./models/vault"
|
export type { Vault } from "./models/vault"
|
||||||
@ -18,7 +18,7 @@ interface IOptions {
|
|||||||
/**
|
/**
|
||||||
* Adapter used to interact with the file system and cryptography modules
|
* Adapter used to interact with the file system and cryptography modules
|
||||||
*/
|
*/
|
||||||
adapter?: IAdapter
|
adapter?: IAdapter | Promise<IAdapter>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,11 +26,11 @@ interface IOptions {
|
|||||||
*/
|
*/
|
||||||
export class OnePassword {
|
export class OnePassword {
|
||||||
readonly #path: string
|
readonly #path: string
|
||||||
readonly #adapter: IAdapter
|
readonly #adapter: IAdapter | Promise<IAdapter>
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
path,
|
path,
|
||||||
adapter = process.browser ? null : require("./adapters/node").nodeAdapter,
|
adapter = process.browser ? null! : import("./adapter").then(x => x.nodeAdapter),
|
||||||
}: IOptions) {
|
}: IOptions) {
|
||||||
this.#adapter = adapter
|
this.#adapter = adapter
|
||||||
this.#path = path
|
this.#path = path
|
||||||
@ -40,11 +40,11 @@ export class OnePassword {
|
|||||||
* @returns A list of names of profiles of the current vault.
|
* @returns A list of names of profiles of the current vault.
|
||||||
*/
|
*/
|
||||||
async getProfileNames() {
|
async getProfileNames() {
|
||||||
const [fs, path] = [this.#adapter.fs, this.#path]
|
const { fs } = await this.#adapter
|
||||||
const children = await fs.readdir(path)
|
const children = await fs.readdir(this.#path)
|
||||||
const profiles: string[] = []
|
const profiles: string[] = []
|
||||||
await asyncMap(children, async child => {
|
await asyncMap(children, async child => {
|
||||||
const fullPath = resolve(path, child)
|
const fullPath = resolve(this.#path, child)
|
||||||
if (
|
if (
|
||||||
(await fs.isDirectory(fullPath)) &&
|
(await fs.isDirectory(fullPath)) &&
|
||||||
(await fs.exists(resolve(fullPath, "profile.js")))
|
(await fs.exists(resolve(fullPath, "profile.js")))
|
||||||
@ -59,6 +59,6 @@ export class OnePassword {
|
|||||||
* @returns A OnePassword Vault instance.
|
* @returns A OnePassword Vault instance.
|
||||||
*/
|
*/
|
||||||
async getProfile(profileName: string) {
|
async getProfile(profileName: string) {
|
||||||
return await Vault.of(this.#path, profileName, this.#adapter)
|
return await Vault.of(this.#path, profileName, await this.#adapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import type { Crypto } from "./crypto"
|
import type { Crypto } from "../crypto"
|
||||||
import { invariant } from "../errors"
|
import { invariant } from "../errors"
|
||||||
|
|
||||||
type integer = number
|
type integer = number
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ItemDetails, Overview } from "../types"
|
import type { ItemDetails, Overview } from "../types"
|
||||||
import type { Crypto } from "./crypto"
|
import type { Crypto } from "../crypto"
|
||||||
import { Attachment } from "./attachment"
|
import { Attachment } from "./attachment"
|
||||||
import { NotUnlockedError } from "../errors"
|
import { NotUnlockedError } from "../errors"
|
||||||
import type { Category } from "../models"
|
import type { Category } from "../models"
|
||||||
@ -8,7 +8,7 @@ export interface EncryptedItem {
|
|||||||
category: string // "001"
|
category: string // "001"
|
||||||
/** Unix seconds */
|
/** Unix seconds */
|
||||||
created: integer
|
created: integer
|
||||||
d: string // "b3BkYXRhMbt"
|
d: string // details, bass64
|
||||||
folder: string // 32 chars
|
folder: string // 32 chars
|
||||||
hmac: string // base64
|
hmac: string // base64
|
||||||
k: string // base64
|
k: string // base64
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { IAdapter } from "../adapters"
|
import type { IAdapter } from "../adapter"
|
||||||
import { HMACAssertionError, invariant } from "../errors"
|
import { HMACAssertionError, invariant } from "../errors"
|
||||||
import { OnePasswordFileManager } from "../fs"
|
import { OnePasswordFileManager } from "../fs"
|
||||||
import { i18n } from "../i18n"
|
import { i18n } from "../i18n"
|
||||||
import type { EncryptedItem } from "./item"
|
import type { EncryptedItem } from "./item"
|
||||||
import { Crypto } from "./crypto"
|
import { Crypto } from "../crypto"
|
||||||
import { Item } from "./item"
|
import { Item } from "./item"
|
||||||
import type { Profile } from "../types"
|
import type { Profile } from "../types"
|
||||||
import { WeakValueMap } from "../weakMap"
|
import { WeakValueMap } from "../weakMap"
|
||||||
|
@ -130,6 +130,9 @@ export interface ItemDetails {
|
|||||||
}[]
|
}[]
|
||||||
/** Web form fields */
|
/** Web form fields */
|
||||||
fields?: ItemField[]
|
fields?: ItemField[]
|
||||||
|
/** Plain password items */
|
||||||
|
backupKeys?: string[]
|
||||||
|
password?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Folder {
|
export interface Folder {
|
||||||
|
@ -12,8 +12,13 @@ fs.writeFileSync(
|
|||||||
dtsPath,
|
dtsPath,
|
||||||
`type Translation = Record<string, string>;
|
`type Translation = Record<string, string>;
|
||||||
declare const exportee: {
|
declare const exportee: {
|
||||||
${Object.keys(json)
|
${Object.entries(json)
|
||||||
.map(x => `${x}: Translation;`)
|
.map(
|
||||||
|
([category, value]) =>
|
||||||
|
`${category}: {\n${Object.keys(value)
|
||||||
|
.map(key => ` ${key}: Translation;`)
|
||||||
|
.join("\n")}\n };`
|
||||||
|
)
|
||||||
.join("\n ")}
|
.join("\n ")}
|
||||||
};
|
};
|
||||||
export default exportee;
|
export default exportee;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import styled from "@emotion/styled"
|
import styled from "@emotion/styled"
|
||||||
import type { Attachment, AttachmentMetadata, Item, ItemField } from "opvault.js"
|
import type { Attachment, AttachmentMetadata, Item, ItemField } from "opvault.js"
|
||||||
import { useEffect, useState } from "react"
|
import type { ItemDetails } from "opvault.js/src/types"
|
||||||
|
import { memo, useEffect, useState } from "react"
|
||||||
import { useTranslate } from "../i18n"
|
import { useTranslate } from "../i18n"
|
||||||
import { CategoryIcon } from "./CategoryIcon"
|
import { CategoryIcon } from "./CategoryIcon"
|
||||||
import { ItemDates } from "./ItemDates"
|
import { ItemDates } from "./ItemDates"
|
||||||
@ -10,6 +11,7 @@ import {
|
|||||||
FieldTitle,
|
FieldTitle,
|
||||||
ItemDetailsFieldView,
|
ItemDetailsFieldView,
|
||||||
} from "./ItemField"
|
} from "./ItemField"
|
||||||
|
import { PasswordFieldView } from "./ItemFieldValue"
|
||||||
import { ItemWarning } from "./ItemWarning"
|
import { ItemWarning } from "./ItemWarning"
|
||||||
|
|
||||||
interface ItemViewProps {
|
interface ItemViewProps {
|
||||||
@ -57,6 +59,22 @@ const AttachmentContainer = styled.div`
|
|||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const SectionsView: React.FC<{ sections?: ItemDetails["sections"] }> = ({ sections }) =>
|
||||||
|
sections?.length ? (
|
||||||
|
<div style={{ marginBottom: 20 }}>
|
||||||
|
{sections
|
||||||
|
.filter(s => s.fields?.some(x => x.v != null))
|
||||||
|
.map((section, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
{section.title != null && <SectionTitle>{section.title}</SectionTitle>}
|
||||||
|
{section.fields?.map((field, j) => (
|
||||||
|
<ItemFieldView key={j} field={field} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
|
||||||
const FieldsView: React.FC<{ fields?: ItemField[] }> = ({ fields }) =>
|
const FieldsView: React.FC<{ fields?: ItemField[] }> = ({ fields }) =>
|
||||||
fields?.length ? (
|
fields?.length ? (
|
||||||
<div style={{ marginBottom: 20 }}>
|
<div style={{ marginBottom: 20 }}>
|
||||||
@ -71,7 +89,7 @@ const TagsView: React.FC<{ tags?: string[] }> = ({ tags }) => {
|
|||||||
if (!tags?.length) return null
|
if (!tags?.length) return null
|
||||||
return (
|
return (
|
||||||
<ExtraField>
|
<ExtraField>
|
||||||
<FieldTitle>{t.noun_tags}</FieldTitle>
|
<FieldTitle>{t.noun.tags}</FieldTitle>
|
||||||
<div>
|
<div>
|
||||||
{tags.map((tag, i) => (
|
{tags.map((tag, i) => (
|
||||||
<Tag key={i}>{tag}</Tag>
|
<Tag key={i}>{tag}</Tag>
|
||||||
@ -81,65 +99,69 @@ const TagsView: React.FC<{ tags?: string[] }> = ({ tags }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ItemView: React.FC<ItemViewProps> = ({ className, item }) => (
|
const JSONView = memo<{ item: Item }>(({ item }) => (
|
||||||
<Container className={className}>
|
<details>
|
||||||
<Inner>
|
<summary>JSON</summary>
|
||||||
<ItemWarning item={item} />
|
<pre>
|
||||||
<Header>
|
{JSON.stringify({ overview: item.overview, details: item.details }, null, 2)}
|
||||||
{item.details.fields == null}
|
</pre>
|
||||||
<Icon category={item.category} />
|
</details>
|
||||||
<ItemTitle>{item.overview.title}</ItemTitle>
|
))
|
||||||
</Header>
|
|
||||||
<details>
|
|
||||||
<summary>JSON</summary>
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify({ overview: item.overview, details: item.details }, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: 20 }}>
|
export const ItemView: React.FC<ItemViewProps> = ({ className, item }) => {
|
||||||
{item.details.sections
|
const t = useTranslate()
|
||||||
?.filter(s => s.fields?.some(x => x.v != null))
|
return (
|
||||||
.map((section, i) => (
|
<Container className={className}>
|
||||||
<div key={i}>
|
<Inner>
|
||||||
{section.title != null && <SectionTitle>{section.title}</SectionTitle>}
|
<ItemWarning item={item} />
|
||||||
{section.fields?.map((field, j) => (
|
<Header>
|
||||||
<ItemFieldView key={j} field={field} />
|
{item.details.fields == null}
|
||||||
|
<Icon category={item.category} />
|
||||||
|
<ItemTitle>{item.overview.title}</ItemTitle>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<JSONView item={item} />
|
||||||
|
<div style={{ height: 10 }}></div>
|
||||||
|
|
||||||
|
<SectionsView sections={item.details.sections} />
|
||||||
|
<FieldsView fields={item.details.fields} />
|
||||||
|
|
||||||
|
{item.details.notesPlain != null && (
|
||||||
|
<ExtraField>
|
||||||
|
<FieldTitle>notes</FieldTitle>
|
||||||
|
<div>
|
||||||
|
<p>{item.details.notesPlain}</p>
|
||||||
|
</div>
|
||||||
|
</ExtraField>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.details.password != null && (
|
||||||
|
<ExtraField>
|
||||||
|
<FieldTitle>{t.label.password}</FieldTitle>
|
||||||
|
<PasswordFieldView field={{ v: item.details.password }} />
|
||||||
|
</ExtraField>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TagsView tags={item.overview.tags} />
|
||||||
|
|
||||||
|
{item.attachments.length > 0 && (
|
||||||
|
<ExtraField>
|
||||||
|
<FieldTitle>attachments</FieldTitle>
|
||||||
|
<div>
|
||||||
|
{item.attachments.map((file, i) => (
|
||||||
|
<AttachmentView key={i} file={file} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</ExtraField>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<FieldsView fields={item.details.fields} />
|
|
||||||
|
|
||||||
{item.details.notesPlain != null && (
|
|
||||||
<ExtraField>
|
<ExtraField>
|
||||||
<FieldTitle>notes</FieldTitle>
|
<ItemDates item={item} />
|
||||||
<div>
|
|
||||||
<p>{item.details.notesPlain}</p>
|
|
||||||
</div>
|
|
||||||
</ExtraField>
|
</ExtraField>
|
||||||
)}
|
</Inner>
|
||||||
|
</Container>
|
||||||
<TagsView tags={item.overview.tags} />
|
)
|
||||||
|
}
|
||||||
{item.attachments.length > 0 && (
|
|
||||||
<ExtraField>
|
|
||||||
<FieldTitle>attachments</FieldTitle>
|
|
||||||
<div>
|
|
||||||
{item.attachments.map((file, i) => (
|
|
||||||
<AttachmentView key={i} file={file} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ExtraField>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ExtraField>
|
|
||||||
<ItemDates item={item} />
|
|
||||||
</ExtraField>
|
|
||||||
</Inner>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
|
|
||||||
function AttachmentView({ file }: { file: Attachment }) {
|
function AttachmentView({ file }: { file: Attachment }) {
|
||||||
const [metadata, setMetadata] = useState<AttachmentMetadata>()
|
const [metadata, setMetadata] = useState<AttachmentMetadata>()
|
||||||
|
@ -14,10 +14,10 @@ export const ItemDates: React.FC<{ item: Item }> = ({ item }) => {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div>
|
<div>
|
||||||
{t.label_last_updated}: {new Date(item.updatedAt).toLocaleString()}
|
{t.label.last_updated}: {new Date(item.updatedAt).toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{t.label_created_at}: {new Date(item.createdAt).toLocaleString()}
|
{t.label.created_at}: {new Date(item.createdAt).toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
@ -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: 120px;
|
min-width: 150px;
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -39,6 +39,7 @@ const Item = styled.div`
|
|||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
|
padding-right: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
|
@ -9,8 +9,10 @@ import { useItemFieldContextMenu } from "./ItemFieldContextMenu"
|
|||||||
|
|
||||||
const Container = styled.div``
|
const Container = styled.div``
|
||||||
|
|
||||||
|
export { Password as PasswordFieldView }
|
||||||
|
|
||||||
const Password: React.FC<{
|
const Password: React.FC<{
|
||||||
field: ItemSection.Concealed
|
field: Pick<ItemSection.Concealed, "v">
|
||||||
}> = ({ field }) => {
|
}> = ({ field }) => {
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const [bigText, showBigText] = useState(false)
|
const [bigText, showBigText] = useState(false)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createContext, memo, useContext, useEffect, useMemo, useState } from "react"
|
import { createContext, memo, useContext, useEffect, useMemo, useState } from "react"
|
||||||
import texts from "./texts.yml"
|
import texts from "./texts.yml"
|
||||||
|
|
||||||
type Keys = keyof typeof texts
|
const categories = Object.keys(texts)
|
||||||
|
|
||||||
const ALLOWED = new Set(["en", "fr"])
|
const ALLOWED = new Set(["en", "fr"])
|
||||||
const LOCALSTORAGE_KEY = "preferred-locale"
|
const LOCALSTORAGE_KEY = "preferred-locale"
|
||||||
@ -40,21 +40,29 @@ export function useTranslate() {
|
|||||||
const { locale } = useContext(LocaleContext)
|
const { locale } = useContext(LocaleContext)
|
||||||
const t = useMemo(
|
const t = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Proxy(
|
Object.fromEntries(
|
||||||
{},
|
categories.map(category => [
|
||||||
{
|
category,
|
||||||
get(_, p: string) {
|
new Proxy(
|
||||||
if (
|
{},
|
||||||
process.env.NODE_ENV === "development" &&
|
{
|
||||||
!Object.prototype.hasOwnProperty.call(texts, p)
|
get(_, p: string) {
|
||||||
) {
|
const obj = (texts as any)[category]
|
||||||
throw new Error(`t.${p} does not exist.`)
|
if (
|
||||||
|
process.env.NODE_ENV === "development" &&
|
||||||
|
!Object.prototype.hasOwnProperty.call(obj, p)
|
||||||
|
) {
|
||||||
|
throw new Error(`t.${p} does not exist.`)
|
||||||
|
}
|
||||||
|
return obj[p][locale]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return (texts as any)[p][locale]
|
),
|
||||||
},
|
])
|
||||||
}
|
|
||||||
) as {
|
) as {
|
||||||
[key in Keys]: string
|
[category in keyof typeof texts]: {
|
||||||
|
[key in keyof typeof texts[category]]: string
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[locale]
|
[locale]
|
||||||
)
|
)
|
||||||
|
@ -1,40 +1,51 @@
|
|||||||
# /* spellchecker: disable */
|
# /* spellchecker: disable */
|
||||||
label_choose_a_vault:
|
label:
|
||||||
en: Pick a vault
|
choose_a_vault:
|
||||||
fr: Choisir un coffre
|
en: Pick a vault
|
||||||
|
fr: Choisir un coffre
|
||||||
|
|
||||||
label_no_vault_selected:
|
no_vault_selected:
|
||||||
en: No vault is selected.
|
en: No vault is selected.
|
||||||
fr: Aucun coffre n’est sélectionné.
|
fr: Aucun coffre n’est sélectionné.
|
||||||
|
|
||||||
label_last_updated:
|
last_updated:
|
||||||
en: Last Updated
|
en: Last Updated
|
||||||
fr: Dernière modification
|
fr: Dernière modification
|
||||||
|
|
||||||
label_created_at:
|
created_at:
|
||||||
en: Created At
|
en: Created At
|
||||||
fr: Créé
|
fr: Créé
|
||||||
|
|
||||||
label_password_placeholder:
|
password_placeholder:
|
||||||
en: Master Password
|
en: Master Password
|
||||||
fr: Mot de passe principal
|
fr: Mot de passe principal
|
||||||
|
|
||||||
noun_vault:
|
username:
|
||||||
en: vault
|
en: Username
|
||||||
fr: coffre
|
fr: Nom d’utilisateur
|
||||||
|
|
||||||
noun_tags:
|
password:
|
||||||
en: tags
|
en: Password
|
||||||
fr: mots-clés
|
fr: Mot de passe
|
||||||
|
|
||||||
action_lock:
|
noun:
|
||||||
en: Lock
|
vault:
|
||||||
fr: Vérouiller
|
en: vault
|
||||||
|
fr: coffre
|
||||||
|
|
||||||
action_unlock:
|
tags:
|
||||||
en: Unlock
|
en: tags
|
||||||
fr: Déverouiller
|
fr: mots-clés
|
||||||
|
|
||||||
action_go_back:
|
action:
|
||||||
en: Back
|
lock:
|
||||||
fr: Revenir
|
en: Lock
|
||||||
|
fr: Vérouiller
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
en: Unlock
|
||||||
|
fr: Déverouiller
|
||||||
|
|
||||||
|
go_back:
|
||||||
|
en: Back
|
||||||
|
fr: Revenir
|
||||||
|
@ -102,17 +102,17 @@ export const Unlock: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div style={{ marginBottom: 10 }}>
|
<div style={{ marginBottom: 10 }}>
|
||||||
<BackButton onClick={onReturn} title={t.action_go_back}>
|
<BackButton onClick={onReturn} title={t.action.go_back}>
|
||||||
<IoMdArrowRoundBack />
|
<IoMdArrowRoundBack />
|
||||||
</BackButton>
|
</BackButton>
|
||||||
<Select
|
<Select
|
||||||
title={t.noun_vault}
|
title={t.noun.vault}
|
||||||
value={profile}
|
value={profile}
|
||||||
onChange={e => setProfile(e.currentTarget.value)}
|
onChange={e => setProfile(e.currentTarget.value)}
|
||||||
>
|
>
|
||||||
{profiles.map(p => (
|
{profiles.map(p => (
|
||||||
<option key={p} value={p}>
|
<option key={p} value={p}>
|
||||||
{t.noun_vault}: {p}
|
{t.noun.vault}: {p}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@ -122,14 +122,14 @@ export const Unlock: React.FC<{
|
|||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={e => setPassword(e.currentTarget.value)}
|
onChange={e => setPassword(e.currentTarget.value)}
|
||||||
placeholder={t.label_password_placeholder}
|
placeholder={t.label.password_placeholder}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
/>
|
/>
|
||||||
<Submit
|
<Submit
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!profile || !password}
|
disabled={!profile || !password}
|
||||||
onClick={unlock}
|
onClick={unlock}
|
||||||
title={t.action_unlock}
|
title={t.action.unlock}
|
||||||
>
|
>
|
||||||
<FaUnlock />
|
<FaUnlock />
|
||||||
</Submit>
|
</Submit>
|
||||||
|
@ -88,25 +88,36 @@ export const VaultView: React.FC<{ vault: Vault; onLock(): void }> = ({
|
|||||||
arrayFrom(vault.values()).then(setItems)
|
arrayFrom(vault.values()).then(setItems)
|
||||||
}, [vault])
|
}, [vault])
|
||||||
|
|
||||||
const filtered = useMemo(
|
const filtered = useMemo(() => {
|
||||||
() =>
|
const items = sortedItem.filter(x => x.category !== Category.Tombstone)
|
||||||
sortedItem
|
let res: Item[] = items
|
||||||
.filter(x => x.category !== Category.Tombstone)
|
if (search) {
|
||||||
.filter(
|
res = []
|
||||||
search
|
for (const x of items) {
|
||||||
? x =>
|
const compare = Math.max(
|
||||||
stringCompare(search, x.overview.title) ||
|
stringCompare(search, x.overview.title),
|
||||||
stringCompare(search, x.overview.ainfo)
|
stringCompare(search, x.overview.ainfo)
|
||||||
: () => true
|
) as CompareResult
|
||||||
),
|
switch (compare) {
|
||||||
[sortedItem, search]
|
case CompareResult.NoMatch:
|
||||||
)
|
continue
|
||||||
|
case CompareResult.Includes:
|
||||||
|
res.push(x)
|
||||||
|
break
|
||||||
|
case CompareResult.Equals:
|
||||||
|
res.unshift(x)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}, [sortedItem, search])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<ListContainer className={scrollbar}>
|
<ListContainer className={scrollbar}>
|
||||||
<div style={{ margin: "10px 10px", display: "flex" }}>
|
<div style={{ margin: "10px 10px", display: "flex" }}>
|
||||||
<LockButton onClick={onLock} title={t.action_lock}>
|
<LockButton onClick={onLock} title={t.action.lock}>
|
||||||
<FiLock />
|
<FiLock />
|
||||||
</LockButton>
|
</LockButton>
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
@ -147,8 +158,20 @@ async function arrayFrom<T>(generator: AsyncGenerator<T, void, unknown>) {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringCompare(search: string, source?: string) {
|
enum CompareResult {
|
||||||
if (!search) return true
|
NoMatch,
|
||||||
if (!source) return false
|
Includes,
|
||||||
return source.toLocaleLowerCase().includes(search.toLocaleLowerCase())
|
Equals,
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringCompare(search: string, source?: string) {
|
||||||
|
if (!search) return CompareResult.Includes
|
||||||
|
if (!source) return CompareResult.NoMatch
|
||||||
|
source = source.toLocaleLowerCase()
|
||||||
|
search = search.toLocaleUpperCase()
|
||||||
|
const includes = source.includes(search.toLocaleLowerCase())
|
||||||
|
if (includes) {
|
||||||
|
return source.length === search.length ? CompareResult.Equals : CompareResult.Includes
|
||||||
|
}
|
||||||
|
return CompareResult.NoMatch
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,8 @@ const PickOPVault: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PickOPVaultContainer>
|
<PickOPVaultContainer>
|
||||||
<button onClick={onClick}>{t.label_choose_a_vault}</button>
|
<button onClick={onClick}>{t.label.choose_a_vault}</button>
|
||||||
<PickOPVaultInfo>{t.label_no_vault_selected}</PickOPVaultInfo>
|
<PickOPVaultInfo>{t.label.no_vault_selected}</PickOPVaultInfo>
|
||||||
</PickOPVaultContainer>
|
</PickOPVaultContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@ -35,6 +35,7 @@ importers:
|
|||||||
sinon-chai: ^3.7.0
|
sinon-chai: ^3.7.0
|
||||||
ts-node: ^10.2.1
|
ts-node: ^10.2.1
|
||||||
tsconfig-paths: ^3.11.0
|
tsconfig-paths: ^3.11.0
|
||||||
|
tslib: ^2.3.1
|
||||||
typescript: ^4.4.3
|
typescript: ^4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/chai': 4.2.22
|
'@types/chai': 4.2.22
|
||||||
@ -68,8 +69,15 @@ importers:
|
|||||||
sinon-chai: 3.7.0_chai@4.3.4+sinon@11.1.2
|
sinon-chai: 3.7.0_chai@4.3.4+sinon@11.1.2
|
||||||
ts-node: 10.2.1_8304ecd715830f7c190b4d1dea90b100
|
ts-node: 10.2.1_8304ecd715830f7c190b4d1dea90b100
|
||||||
tsconfig-paths: 3.11.0
|
tsconfig-paths: 3.11.0
|
||||||
|
tslib: 2.3.1
|
||||||
typescript: 4.4.3
|
typescript: 4.4.3
|
||||||
|
|
||||||
|
packages/adapters:
|
||||||
|
specifiers:
|
||||||
|
opvault.js: '*'
|
||||||
|
dependencies:
|
||||||
|
opvault.js: link:../opvault.js
|
||||||
|
|
||||||
packages/opvault.js:
|
packages/opvault.js:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@rollup/plugin-json': ^4.1.0
|
'@rollup/plugin-json': ^4.1.0
|
||||||
@ -137,6 +145,9 @@ importers:
|
|||||||
typescript: 4.4.3
|
typescript: 4.4.3
|
||||||
vite: 2.6.13_sass@1.43.4
|
vite: 2.6.13_sass@1.43.4
|
||||||
|
|
||||||
|
packages/web/dist:
|
||||||
|
specifiers: {}
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
/7zip-bin/5.1.1:
|
/7zip-bin/5.1.1:
|
||||||
|
@ -2,8 +2,8 @@ import { resolve } from "path";
|
|||||||
import { describe, it, beforeEach } from "mocha";
|
import { describe, it, beforeEach } from "mocha";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import type { Vault } from "../packages/opvault.js/index";
|
import type { Vault } from "../packages/opvault.js/src/index";
|
||||||
import { OnePassword } from "../packages/opvault.js/index";
|
import { OnePassword } from "../packages/opvault.js/src/index";
|
||||||
|
|
||||||
describe("OnePassword", () => {
|
describe("OnePassword", () => {
|
||||||
const freddy = resolve(__dirname, "../freddy-2013-12-04.opvault");
|
const freddy = resolve(__dirname, "../freddy-2013-12-04.opvault");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { describe, it } from "mocha";
|
import { describe, it } from "mocha";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import { WeakValueMap } from "../packages/opvault.js/weakMap";
|
import { WeakValueMap } from "../packages/opvault.js/src/weakMap";
|
||||||
|
|
||||||
declare const gc: () => void;
|
declare const gc: () => void;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user