Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions paperless/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "paperless",
"name": "Paperless-ngx",
"version": "1.1.1",
"version": "1.2.0",
"author": "Bulwark Mail Community",
"description": "Send attachments and messages to Paperless-ngx, find your documents there and attach them to new mail.",
"type": "ui-extension",
Expand All @@ -20,7 +20,7 @@
"ui:download-file"
],
"entrypoint": "index.js",
"minAppVersion": "1.15.0",
"minAppVersion": "1.16.0",
"icon": "media/icon.svg",
"banner": "media/banner.svg",
"configSchema": {
Expand Down
4 changes: 2 additions & 2 deletions paperless/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion paperless/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bulwark-plugin-paperless",
"version": "1.1.1",
"version": "1.2.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
Binary file modified paperless/paperless.zip
Binary file not shown.
67 changes: 40 additions & 27 deletions paperless/src/ui/common.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, Badge, Button, Checkbox, EmptyState, Field, LinkButton, Row, Select, Stack, Text } from '@plugin-host/ui';
import { Alert, Badge, Button, Combobox, EmptyState, Field, LinkButton, Stack } from '@plugin-host/ui';
import { getApi, t, hostOf } from './host.js';
import { clientFor, connectPersonal, resolveConnection } from '../lib/connection.js';
import { errorText } from '../lib/errors.js';
Expand Down Expand Up @@ -73,41 +73,54 @@ export function ConnectEmptyState({ connection, onConnected }) {
);
}

/** A select over Paperless objects, with an empty "none" entry. */
export function PickOne({ label, items, value, onChange, noneLabel, suggested, block = true }) {
/** A searchable combobox over Paperless objects, with an empty "none" entry. */
export function PickOne({ label, items, value, onChange, noneLabel, suggested, block = true, placeholder }) {
const options = useMemo(() => [
{ value: '', label: noneLabel || t('common.none') },
...(items || []).map((it) => ({ value: String(it.id), label: it.name })),
], [items, noneLabel]);

const handleChange = useCallback((v) => {
onChange?.(v === '' ? null : Number(v));
}, [onChange]);

const strValue = value == null ? '' : String(value);
const fieldLabel = suggested ? <>{label} <Badge tone="success">{t('send.suggested')}</Badge></> : label;

return (
<Field label={suggested ? <>{label} <Badge tone="success">{t('send.suggested')}</Badge></> : label}>
<Select
<Field label={fieldLabel}>
<Combobox
block={block}
value={value == null ? '' : String(value)}
onChange={(v) => onChange(v === '' ? null : Number(v))}
options={[{ value: '', label: noneLabel || t('common.none') }, ...(items || []).map((it) => ({ value: String(it.id), label: it.name }))]}
value={strValue}
onChange={handleChange}
options={options}
placeholder={placeholder || noneLabel || t('common.none')}
/>
</Field>
);
}

/** Tags as a row of check boxes. */
export function TagPicker({ tags, value, onChange, label }) {
const on = new Set(value || []);
if (!tags?.length) return null;
const toggle = (id) => {
const next = new Set(on);
if (next.has(id)) next.delete(id); else next.add(id);
onChange([...next]);
};

/** Tags selector using multiselect Combobox. */
export function TagPicker({ tags, value = [], onChange, label }) {
const options = useMemo(() => (tags || []).map((t) => ({ value: String(t.id), label: t.name })), [tags]);
const stringValues = useMemo(() => (value || []).map(String), [value]);
const handleChange = useCallback((newVals) => {
onChange?.(newVals.map(Number));
}, [onChange]);
const name = label || t('field.tags');
if (!tags?.length) return null;
return (
<div role="group" aria-label={name}>
<Stack gap={2}>
<Text weight="medium">{name}</Text>
<Row gap={4}>
{tags.map((tag) => (
<Checkbox key={tag.id} label={tag.name} checked={on.has(tag.id)} onChange={() => toggle(tag.id)} />
))}
</Row>
</Stack>
</div>
<Field label={name}>
<Combobox
multiple
block
options={options}
value={stringValues}
onChange={handleChange}
placeholder={name}
/>
</Field>
);
}

Expand Down
Loading