This commit is contained in:
2025-10-30 14:41:15 +01:00
parent 86be6256a4
commit 67fd787a14
9 changed files with 23 additions and 60 deletions
+1 -18
View File
@@ -49,24 +49,7 @@ services:
command: > command: >
/bin/sh -c " /bin/sh -c "
echo 'Running database migrations' && echo 'Running database migrations' &&
diesel migration run && diesel migration run
echo 'Ensuring tenant admin exists' &&
if papercrate-admin list-tenants | grep -q '^admin '; then
echo 'tenant admin already exists';
else
papercrate-admin create-tenant admin;
fi &&
ADMIN_TENANT_ID=$(papercrate-admin list-tenants | awk '/^admin / {print $2; exit}') &&
if [ -z "$ADMIN_TENANT_ID" ]; then
echo 'failed to resolve admin tenant id' >&2;
exit 1;
fi &&
echo 'Ensuring demo user credentials' &&
(papercrate-admin create-user admin adminadmin || papercrate-admin set-password admin adminadmin) &&
echo 'Ensuring demo membership' &&
papercrate-admin add-user-to-tenant admin "$ADMIN_TENANT_ID" &&
echo 'Ensuring Quickwit index for admin tenant' &&
papercrate-admin quickwit-create-index "$ADMIN_TENANT_ID"
" "
user: root user: root
restart: "no" restart: "no"
+1 -18
View File
@@ -84,24 +84,7 @@ services:
command: > command: >
/bin/sh -c " /bin/sh -c "
echo 'Running database migrations' && echo 'Running database migrations' &&
diesel migration run && diesel migration run
echo 'Ensuring tenant admin exists' &&
if papercrate-admin list-tenants | grep -q '^admin '; then
echo 'tenant admin already exists';
else
papercrate-admin create-tenant admin;
fi &&
ADMIN_TENANT_ID=$(papercrate-admin list-tenants | awk '/^admin / {print $2; exit}') &&
if [ -z "$ADMIN_TENANT_ID" ]; then
echo 'failed to resolve admin tenant id' >&2;
exit 1;
fi &&
echo 'Ensuring demo user credentials' &&
(papercrate-admin create-user admin adminadmin || papercrate-admin set-password admin adminadmin) &&
echo 'Ensuring demo membership' &&
papercrate-admin add-user-to-tenant admin "$ADMIN_TENANT_ID" &&
echo 'Ensuring Quickwit index for admin tenant' &&
papercrate-admin quickwit-create-index "$ADMIN_TENANT_ID"
" "
user: root user: root
restart: "no" restart: "no"
+1 -1
View File
@@ -39,7 +39,7 @@ npm run build
- Drag-and-drop moves (documents between folders) and file uploads (window-wide or onto a folder) - Drag-and-drop moves (documents between folders) and file uploads (window-wide or onto a folder)
- Search box plus tag chips filter documents across the selected folder and all descendants - Search box plus tag chips filter documents across the selected folder and all descendants
- Tag management (create/assign/remove) from the detail panel - Tag management (create/assign/remove) from the detail panel
- Login via the seeded admin account (`admin` / `adminadmin`) with stored JWT session - Login with a WebAuthn passkey created through the signup flow (no baked-in demo account)
- Inline status banner for quick feedback on API interactions - Inline status banner for quick feedback on API interactions
## Assets ## Assets
+1 -4
View File
@@ -23,9 +23,6 @@ const DEFAULT_CANVAS_WIDTH = 1024;
const DEFAULT_CANVAS_HEIGHT = 680; const DEFAULT_CANVAS_HEIGHT = 680;
const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag']; const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'];
const resolveSizeKey = (doc) =>
doc?.id || doc?.document_id || doc?.uuid || doc?.original_name || doc?.title || 'doc';
const CARD_MIN = 240; const CARD_MIN = 240;
const CARD_MAX = 340; const CARD_MAX = 340;
const TAG_REMOVE_DISTANCE = 160; const TAG_REMOVE_DISTANCE = 160;
@@ -1170,7 +1167,7 @@ const syncLayoutSnapshot = useCallback(() => {
id: doc.id, id: doc.id,
width: docWidth, width: docWidth,
height: docHeight, height: docHeight,
seedKey: resolveSizeKey(doc), seedKey: doc.id,
}); });
}); });
+6 -6
View File
@@ -125,7 +125,7 @@ const TagSection = ({
{datalistId ? ( {datalistId ? (
<datalist id={datalistId}> <datalist id={datalistId}>
{datalistOptions.map((option) => ( {datalistOptions.map((option) => (
<option key={option.id || option.label || option} value={option.label || option} /> <option key={option.id} />
))} ))}
</datalist> </datalist>
) : null} ) : null}
@@ -456,7 +456,7 @@ const DetailPanel = ({
id: doc.id, id: doc.id,
url, url,
orientation, orientation,
alt: doc.title || doc.original_name || 'Document preview', alt: doc.title,
}; };
}, },
[ensureAssetUrl, getDocumentAsset], [ensureAssetUrl, getDocumentAsset],
@@ -500,7 +500,7 @@ const DetailPanel = ({
id: singleDoc.id, id: singleDoc.id,
url, url,
orientation, orientation,
alt: singleDoc.title || singleDoc.original_name || 'Document preview', alt: singleDoc.title,
}, },
]; ];
}, [singleDoc, singlePreviewNavigator.currentUrl, singlePreviewNavigator.currentMetadata]); }, [singleDoc, singlePreviewNavigator.currentUrl, singlePreviewNavigator.currentMetadata]);
@@ -522,7 +522,7 @@ const DetailPanel = ({
id: doc.id, id: doc.id,
url, url,
orientation, orientation,
alt: doc.title || doc.original_name || 'Document preview', alt: doc.title,
}; };
} }
return makePreviewItem(doc, 1); return makePreviewItem(doc, 1);
@@ -724,7 +724,7 @@ const DetailPanel = ({
) { ) {
return { return {
url: singlePreviewNavigator.currentUrl, url: singlePreviewNavigator.currentUrl,
alt: singleDoc.title || singleDoc.original_name || 'Document preview', alt: singleDoc.title,
canGoPrev: canGoPrev:
singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoPrev), singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoPrev),
canGoNext: canGoNext:
@@ -743,7 +743,7 @@ const DetailPanel = ({
) { ) {
return { return {
url: stackPreviewNavigator.currentUrl, url: stackPreviewNavigator.currentUrl,
alt: stackTopDocument.title || stackTopDocument.original_name || 'Document preview', alt: stackTopDocument.title,
canGoPrev: canGoPrev:
topEffectiveCardinality > 1 && Boolean(stackPreviewNavigator.canGoPrev), topEffectiveCardinality > 1 && Boolean(stackPreviewNavigator.canGoPrev),
canGoNext: canGoNext:
+5 -5
View File
@@ -34,8 +34,8 @@ const resolveCorrespondents = (doc) => {
doc.correspondents.forEach((entry, index) => { doc.correspondents.forEach((entry, index) => {
if (!entry) return; if (!entry) return;
const id = entry.id ?? entry.correspondent_id ?? null; const id = entry.id;
const name = (entry.name || entry.label || entry.slug || '').trim(); const name = entry.name.trim();
if (!name) return; if (!name) return;
if (id && seen.has(id)) { if (id && seen.has(id)) {
@@ -951,7 +951,7 @@ const DocumentsTable = ({
</td> </td>
<td> <td>
{(() => { {(() => {
const issuedAt = doc.issued_at || doc.updated_at || null; const issuedAt = doc.issued_at || null;
if (!issuedAt) { if (!issuedAt) {
return '—'; return '—';
} }
@@ -974,13 +974,13 @@ const DocumentsTable = ({
event.stopPropagation(); event.stopPropagation();
const nextName = window.prompt( const nextName = window.prompt(
'Rename document', 'Rename document',
doc.title || doc.original_name || '', doc.title
); );
if (!nextName) { if (!nextName) {
return; return;
} }
const trimmed = nextName.trim(); const trimmed = nextName.trim();
if (!trimmed || trimmed === (doc.title || doc.original_name)) { if (!trimmed || trimmed === doc.title) {
return; return;
} }
onDocumentRename(doc.id, trimmed); onDocumentRename(doc.id, trimmed);
+1 -1
View File
@@ -4498,7 +4498,7 @@ const AppLayout = () => {
} }
const doc = documentLookup.get(documentId) || null; const doc = documentLookup.get(documentId) || null;
const label = doc?.title || doc?.original_name || 'this document'; const label = doc?.title;
const confirmed = window.confirm(`Move "${label}" to trash? You can restore it from trash later.`); const confirmed = window.confirm(`Move "${label}" to trash? You can restore it from trash later.`);
if (!confirmed) { if (!confirmed) {
+6 -6
View File
@@ -11,14 +11,14 @@ const PreviewWorkspace = ({
return null; return null;
} }
const title = document.title || document.original_name || 'Document'; const title = document.title;
const mime = previewEntry?.contentType || document.content_type || 'application/pdf'; const mime = document.content_type;
const sizeBytes = Number(document.current_version?.size_bytes) || 0; const sizeBytes = Number(document.current_version?.size_bytes) || 0;
const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : null; const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : null;
const metadata = const metadata =
document.metadata && Object.keys(document.metadata).length > 0 ? document.metadata : null; document.metadata && Object.keys(document.metadata).length > 0 ? document.metadata : null;
const folderName = document.folder_path || document.folder_name || null; const folderName = document.folder_path || document.folder_name || null;
const issuedAt = document.issued_at || document.current_version?.issued_at || null; const issuedAt = document.issued_at || null;
const createdAt = document.created_at || null; const createdAt = document.created_at || null;
const updatedAt = document.updated_at || null; const updatedAt = document.updated_at || null;
const tags = Array.isArray(document.tags) ? document.tags : []; const tags = Array.isArray(document.tags) ? document.tags : [];
@@ -33,13 +33,13 @@ const PreviewWorkspace = ({
if (updatedAt) rows.push(['Updated', new Date(updatedAt).toLocaleString()]); if (updatedAt) rows.push(['Updated', new Date(updatedAt).toLocaleString()]);
if (folderName) rows.push(['Folder', folderName]); if (folderName) rows.push(['Folder', folderName]);
if (tags.length) { if (tags.length) {
rows.push(['Tags', tags.map((tag) => tag.label || tag.name || tag.slug).filter(Boolean).join(', ')]); rows.push(['Tags', tags.map((tag) => tag.label).filter(Boolean).join(', ')]);
} }
if (correspondents.length) { if (correspondents.length) {
rows.push([ rows.push([
'Correspondents', 'Correspondents',
correspondents correspondents
.map((entry) => entry.name || entry.label || entry.slug) .map((entry) => entry.name)
.filter(Boolean) .filter(Boolean)
.join(', '), .join(', '),
]); ]);
@@ -173,7 +173,7 @@ export const createPreviewSurface = ({
return null; return null;
} }
const title = document.title || document.original_name || 'Document preview'; const title = document.title;
const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null; const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null;
const closeButton = onClose const closeButton = onClose
? ( ? (
+1 -1
View File
@@ -382,7 +382,7 @@ const Sidebar = ({
{tenants.map((tenant) => { {tenants.map((tenant) => {
const tenantId = tenant?.id || null; const tenantId = tenant?.id || null;
const isActive = tenantId === activeTenantId; const isActive = tenantId === activeTenantId;
const tenantLabel = tenant?.name || tenant?.slug || tenantId || 'Tenant'; const tenantLabel = tenant?.name;
return ( return (
<button <button
key={tenantId || tenantLabel} key={tenantId || tenantLabel}