feat(frontend): hide outdated annotations when file version changes
Some checks failed
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
Some checks failed
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
- Regenerate API types with fileHash on Document and DocumentAnnotation - PdfViewer accepts documentFileHash prop; filters visibleAnnotations to those whose hash matches (or is null) and shows an amber notice banner when any annotations are hidden due to a hash mismatch - Document detail page passes doc.fileHash to PdfViewer - Add i18n key annotation_outdated_notice in de/en/es - E2E: two new tests covering hide-on-reupload and restore-on-original-reupload scenarios; add minimal2.pdf fixture for a different-hash upload Closes #55 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #58.
This commit is contained in:
@@ -347,6 +347,135 @@ test.describe('PDF annotations — admin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── PDF Annotations — file hash (version awareness) ─────────────────────────
|
||||||
|
|
||||||
|
test.describe('PDF annotations — file hash versioning', () => {
|
||||||
|
const baseURL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||||
|
const PDF_FIXTURE2 = path.resolve(__dirname, 'fixtures/minimal2.pdf');
|
||||||
|
|
||||||
|
test('annotations are hidden after a different file is uploaded', async ({ page, request }) => {
|
||||||
|
test.setTimeout(90_000);
|
||||||
|
|
||||||
|
// 1. Create document and upload original PDF
|
||||||
|
const createRes = await request.post('/api/documents', {
|
||||||
|
multipart: { title: 'E2E Hash Test — version' }
|
||||||
|
});
|
||||||
|
if (!createRes.ok()) throw new Error(`Create failed: ${createRes.status()}`);
|
||||||
|
const doc = await createRes.json();
|
||||||
|
|
||||||
|
const uploadRes = await request.put(`/api/documents/${doc.id}`, {
|
||||||
|
multipart: {
|
||||||
|
title: doc.title,
|
||||||
|
file: {
|
||||||
|
name: 'minimal.pdf',
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
buffer: fs.readFileSync(PDF_FIXTURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!uploadRes.ok()) throw new Error(`Upload failed: ${uploadRes.status()}`);
|
||||||
|
|
||||||
|
// 2. Create an annotation via API
|
||||||
|
const annotRes = await request.post(`/api/documents/${doc.id}/annotations`, {
|
||||||
|
data: { pageNumber: 1, x: 0.1, y: 0.1, width: 0.2, height: 0.2, color: '#ff0000' }
|
||||||
|
});
|
||||||
|
if (!annotRes.ok()) throw new Error(`Create annotation failed: ${annotRes.status()}`);
|
||||||
|
|
||||||
|
// 3. Verify annotation appears before re-upload
|
||||||
|
await page.goto(`${baseURL}/documents/${doc.id}`);
|
||||||
|
await page.waitForSelector('[data-hydrated]');
|
||||||
|
await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });
|
||||||
|
await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
|
||||||
|
timeout: 8000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Upload a different file (different hash)
|
||||||
|
const reuploadRes = await request.put(`/api/documents/${doc.id}`, {
|
||||||
|
multipart: {
|
||||||
|
title: doc.title,
|
||||||
|
file: {
|
||||||
|
name: 'minimal2.pdf',
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
buffer: fs.readFileSync(PDF_FIXTURE2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!reuploadRes.ok()) throw new Error(`Re-upload failed: ${reuploadRes.status()}`);
|
||||||
|
|
||||||
|
// 5. Reload — annotation must be hidden and notice shown
|
||||||
|
await page.reload();
|
||||||
|
await page.waitForSelector('[data-hydrated]');
|
||||||
|
await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid^="annotation-"]')).toHaveCount(0, { timeout: 8000 });
|
||||||
|
await expect(page.locator('[data-testid="annotation-outdated-notice"]')).toBeVisible({
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/annotation-hidden-after-reupload.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('annotations reappear after re-uploading the original file', async ({ page, request }) => {
|
||||||
|
test.setTimeout(90_000);
|
||||||
|
|
||||||
|
// 1. Create document and upload original PDF
|
||||||
|
const createRes = await request.post('/api/documents', {
|
||||||
|
multipart: { title: 'E2E Hash Test — restore' }
|
||||||
|
});
|
||||||
|
if (!createRes.ok()) throw new Error(`Create failed: ${createRes.status()}`);
|
||||||
|
const doc = await createRes.json();
|
||||||
|
|
||||||
|
const originalBytes = fs.readFileSync(PDF_FIXTURE);
|
||||||
|
const uploadRes = await request.put(`/api/documents/${doc.id}`, {
|
||||||
|
multipart: {
|
||||||
|
title: doc.title,
|
||||||
|
file: { name: 'minimal.pdf', mimeType: 'application/pdf', buffer: originalBytes }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!uploadRes.ok()) throw new Error(`Upload failed: ${uploadRes.status()}`);
|
||||||
|
|
||||||
|
// 2. Create annotation
|
||||||
|
const annotRes = await request.post(`/api/documents/${doc.id}/annotations`, {
|
||||||
|
data: { pageNumber: 1, x: 0.1, y: 0.1, width: 0.2, height: 0.2, color: '#0000ff' }
|
||||||
|
});
|
||||||
|
if (!annotRes.ok()) throw new Error(`Create annotation failed: ${annotRes.status()}`);
|
||||||
|
|
||||||
|
// 3. Replace with different file
|
||||||
|
const replaceRes = await request.put(`/api/documents/${doc.id}`, {
|
||||||
|
multipart: {
|
||||||
|
title: doc.title,
|
||||||
|
file: {
|
||||||
|
name: 'minimal2.pdf',
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
buffer: fs.readFileSync(PDF_FIXTURE2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!replaceRes.ok()) throw new Error(`Replace failed: ${replaceRes.status()}`);
|
||||||
|
|
||||||
|
// 4. Re-upload original file (restoring the hash)
|
||||||
|
const restoreRes = await request.put(`/api/documents/${doc.id}`, {
|
||||||
|
multipart: {
|
||||||
|
title: doc.title,
|
||||||
|
file: { name: 'minimal.pdf', mimeType: 'application/pdf', buffer: originalBytes }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!restoreRes.ok()) throw new Error(`Restore failed: ${restoreRes.status()}`);
|
||||||
|
|
||||||
|
// 5. Verify annotation reappears and notice is gone
|
||||||
|
await page.goto(`${baseURL}/documents/${doc.id}`);
|
||||||
|
await page.waitForSelector('[data-hydrated]');
|
||||||
|
await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
|
||||||
|
timeout: 8000
|
||||||
|
});
|
||||||
|
await expect(page.locator('[data-testid="annotation-outdated-notice"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/annotation-restored.png' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ─── PDF Annotations (read-only user) ─────────────────────────────────────────
|
// ─── PDF Annotations (read-only user) ─────────────────────────────────────────
|
||||||
|
|
||||||
test.describe('PDF annotations — read-only user', () => {
|
test.describe('PDF annotations — read-only user', () => {
|
||||||
|
|||||||
21
frontend/e2e/fixtures/minimal2.pdf
Normal file
21
frontend/e2e/fixtures/minimal2.pdf
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
1 0 obj
|
||||||
|
<</Type/Catalog/Pages 2 0 R>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<</Type/Pages/Kids[3 0 R]/Count 1>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<</Type/Page/MediaBox[0 0 3 3]/Parent 2 0 R>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 4
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000009 00000 n
|
||||||
|
0000000058 00000 n
|
||||||
|
0000000115 00000 n
|
||||||
|
trailer
|
||||||
|
<</Size 4/Root 1 0 R>>
|
||||||
|
startxref
|
||||||
|
190
|
||||||
|
%%EOF
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
"error_annotation_not_found": "Die Annotation wurde nicht gefunden.",
|
"error_annotation_not_found": "Die Annotation wurde nicht gefunden.",
|
||||||
"error_annotation_overlap": "Die Annotation überschneidet sich mit einer vorhandenen.",
|
"error_annotation_overlap": "Die Annotation überschneidet sich mit einer vorhandenen.",
|
||||||
|
"annotation_outdated_notice": "Einige Annotationen beziehen sich auf eine frühere Dateiversion und werden nicht angezeigt.",
|
||||||
"error_document_not_found": "Das Dokument wurde nicht gefunden.",
|
"error_document_not_found": "Das Dokument wurde nicht gefunden.",
|
||||||
"error_document_no_file": "Diesem Dokument ist noch keine Datei zugeordnet.",
|
"error_document_no_file": "Diesem Dokument ist noch keine Datei zugeordnet.",
|
||||||
"error_file_not_found": "Die Datei konnte im Speicher nicht gefunden werden.",
|
"error_file_not_found": "Die Datei konnte im Speicher nicht gefunden werden.",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
"error_annotation_not_found": "Annotation not found.",
|
"error_annotation_not_found": "Annotation not found.",
|
||||||
"error_annotation_overlap": "The annotation overlaps an existing one.",
|
"error_annotation_overlap": "The annotation overlaps an existing one.",
|
||||||
|
"annotation_outdated_notice": "Some annotations refer to an earlier file version and are not shown.",
|
||||||
"error_document_not_found": "Document not found.",
|
"error_document_not_found": "Document not found.",
|
||||||
"error_document_no_file": "No file is associated with this document.",
|
"error_document_no_file": "No file is associated with this document.",
|
||||||
"error_file_not_found": "The file could not be found in storage.",
|
"error_file_not_found": "The file could not be found in storage.",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
"error_annotation_not_found": "Anotación no encontrada.",
|
"error_annotation_not_found": "Anotación no encontrada.",
|
||||||
"error_annotation_overlap": "La anotación se superpone con una existente.",
|
"error_annotation_overlap": "La anotación se superpone con una existente.",
|
||||||
|
"annotation_outdated_notice": "Algunas anotaciones hacen referencia a una versión anterior del archivo y no se muestran.",
|
||||||
"error_document_not_found": "Documento no encontrado.",
|
"error_document_not_found": "Documento no encontrado.",
|
||||||
"error_document_no_file": "No hay ningún archivo asociado a este documento.",
|
"error_document_no_file": "No hay ningún archivo asociado a este documento.",
|
||||||
"error_file_not_found": "El archivo no pudo encontrarse en el almacenamiento.",
|
"error_file_not_found": "El archivo no pudo encontrarse en el almacenamiento.",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { SvelteMap } from 'svelte/reactivity';
|
|||||||
import type { PDFDocumentProxy, PDFPageProxy, RenderTask } from 'pdfjs-dist';
|
import type { PDFDocumentProxy, PDFPageProxy, RenderTask } from 'pdfjs-dist';
|
||||||
import AnnotationLayer from './AnnotationLayer.svelte';
|
import AnnotationLayer from './AnnotationLayer.svelte';
|
||||||
import AnnotationCommentPanel from './AnnotationCommentPanel.svelte';
|
import AnnotationCommentPanel from './AnnotationCommentPanel.svelte';
|
||||||
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
@@ -11,7 +12,8 @@ let {
|
|||||||
canAnnotate = false,
|
canAnnotate = false,
|
||||||
canComment,
|
canComment,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
canAdmin
|
canAdmin,
|
||||||
|
documentFileHash
|
||||||
}: {
|
}: {
|
||||||
url: string;
|
url: string;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
@@ -19,6 +21,7 @@ let {
|
|||||||
canComment?: boolean;
|
canComment?: boolean;
|
||||||
currentUserId?: string | null;
|
currentUserId?: string | null;
|
||||||
canAdmin?: boolean;
|
canAdmin?: boolean;
|
||||||
|
documentFileHash?: string | null;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let pdfDoc = $state<PDFDocumentProxy | null>(null);
|
let pdfDoc = $state<PDFDocumentProxy | null>(null);
|
||||||
@@ -51,6 +54,7 @@ type Annotation = {
|
|||||||
height: number;
|
height: number;
|
||||||
color: string;
|
color: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
fileHash?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
let annotations = $state<Annotation[]>([]);
|
let annotations = $state<Annotation[]>([]);
|
||||||
@@ -59,6 +63,11 @@ let annotateColor = $state('#ffff00');
|
|||||||
let commentCounts = new SvelteMap<string, number>();
|
let commentCounts = new SvelteMap<string, number>();
|
||||||
let activeAnnotationId = $state<string | null>(null);
|
let activeAnnotationId = $state<string | null>(null);
|
||||||
|
|
||||||
|
const visibleAnnotations = $derived(
|
||||||
|
annotations.filter((a) => !a.fileHash || !documentFileHash || a.fileHash === documentFileHash)
|
||||||
|
);
|
||||||
|
const outdatedCount = $derived(annotations.length - visibleAnnotations.length);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Dynamic import keeps pdfjs out of the SSR bundle entirely
|
// Dynamic import keeps pdfjs out of the SSR bundle entirely
|
||||||
const [lib, { default: workerUrl }] = await Promise.all([
|
const [lib, { default: workerUrl }] = await Promise.all([
|
||||||
@@ -298,6 +307,27 @@ function zoomOut() {
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-full w-full flex-col bg-[#2A2A2A]">
|
<div class="flex h-full w-full flex-col bg-[#2A2A2A]">
|
||||||
|
{#if outdatedCount > 0}
|
||||||
|
<div
|
||||||
|
class="flex shrink-0 items-center gap-2 border-b border-amber-500/30 bg-amber-500/10 px-4 py-2"
|
||||||
|
data-testid="annotation-outdated-notice"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-4 w-4 shrink-0 text-amber-400"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="font-sans text-xs text-amber-300">{m.annotation_outdated_notice()}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<!-- Controls -->
|
<!-- Controls -->
|
||||||
<div
|
<div
|
||||||
class="flex shrink-0 items-center justify-between gap-2 border-b border-white/10 px-4 py-2"
|
class="flex shrink-0 items-center justify-between gap-2 border-b border-white/10 px-4 py-2"
|
||||||
@@ -439,7 +469,7 @@ function zoomOut() {
|
|||||||
style="position: absolute; top: 0; left: 0; overflow: hidden; pointer-events: none; line-height: 1;"
|
style="position: absolute; top: 0; left: 0; overflow: hidden; pointer-events: none; line-height: 1;"
|
||||||
></div>
|
></div>
|
||||||
<AnnotationLayer
|
<AnnotationLayer
|
||||||
annotations={annotations.filter((a) => a.pageNumber === currentPage)}
|
annotations={visibleAnnotations.filter((a) => a.pageNumber === currentPage)}
|
||||||
canAnnotate={annotateMode}
|
canAnnotate={annotateMode}
|
||||||
color={annotateColor}
|
color={annotateColor}
|
||||||
onDraw={handleAnnotationDraw}
|
onDraw={handleAnnotationDraw}
|
||||||
|
|||||||
@@ -180,6 +180,86 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/documents/{documentId}/comments": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getDocumentComments"];
|
||||||
|
put?: never;
|
||||||
|
post: operations["postDocumentComment"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/documents/{documentId}/comments/{commentId}/replies": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post: operations["replyToDocumentComment"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/documents/{documentId}/annotations": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["listAnnotations"];
|
||||||
|
put?: never;
|
||||||
|
post: operations["createAnnotation"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/documents/{documentId}/annotations/{annotationId}/comments": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getAnnotationComments"];
|
||||||
|
put?: never;
|
||||||
|
post: operations["postAnnotationComment"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/documents/{documentId}/annotations/{annotationId}/comments/{commentId}/replies": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post: operations["replyToAnnotationComment"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/auth/reset-password": {
|
"/api/auth/reset-password": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -260,6 +340,22 @@ export interface paths {
|
|||||||
patch: operations["updateGroup"];
|
patch: operations["updateGroup"];
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/documents/{documentId}/comments/{commentId}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete: operations["deleteComment"];
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch: operations["editComment"];
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/tags": {
|
"/api/tags": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -420,6 +516,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/documents/{documentId}/annotations/{annotationId}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete: operations["deleteAnnotation"];
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
@@ -510,6 +622,7 @@ export interface components {
|
|||||||
title: string;
|
title: string;
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
|
fileHash?: string;
|
||||||
originalFilename: string;
|
originalFilename: string;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
status: "PLACEHOLDER" | "UPLOADED" | "TRANSCRIBED" | "REVIEWED" | "ARCHIVED";
|
status: "PLACEHOLDER" | "UPLOADED" | "TRANSCRIBED" | "REVIEWED" | "ARCHIVED";
|
||||||
@@ -548,6 +661,63 @@ export interface components {
|
|||||||
name?: string;
|
name?: string;
|
||||||
permissions?: string[];
|
permissions?: string[];
|
||||||
};
|
};
|
||||||
|
CreateCommentDTO: {
|
||||||
|
content?: string;
|
||||||
|
};
|
||||||
|
DocumentComment: {
|
||||||
|
/** Format: uuid */
|
||||||
|
id: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
documentId: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
annotationId?: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
parentId?: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
authorId?: string;
|
||||||
|
authorName: string;
|
||||||
|
content: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAt: string;
|
||||||
|
replies: components["schemas"]["DocumentComment"][];
|
||||||
|
};
|
||||||
|
CreateAnnotationDTO: {
|
||||||
|
/** Format: int32 */
|
||||||
|
pageNumber?: number;
|
||||||
|
/** Format: double */
|
||||||
|
x?: number;
|
||||||
|
/** Format: double */
|
||||||
|
y?: number;
|
||||||
|
/** Format: double */
|
||||||
|
width?: number;
|
||||||
|
/** Format: double */
|
||||||
|
height?: number;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
DocumentAnnotation: {
|
||||||
|
/** Format: uuid */
|
||||||
|
id: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
documentId: string;
|
||||||
|
/** Format: int32 */
|
||||||
|
pageNumber: number;
|
||||||
|
/** Format: double */
|
||||||
|
x: number;
|
||||||
|
/** Format: double */
|
||||||
|
y: number;
|
||||||
|
/** Format: double */
|
||||||
|
width: number;
|
||||||
|
/** Format: double */
|
||||||
|
height: number;
|
||||||
|
color: string;
|
||||||
|
fileHash?: string;
|
||||||
|
/** Format: uuid */
|
||||||
|
createdBy?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
};
|
||||||
ResetPasswordRequest: {
|
ResetPasswordRequest: {
|
||||||
token?: string;
|
token?: string;
|
||||||
newPassword?: string;
|
newPassword?: string;
|
||||||
@@ -1062,6 +1232,205 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
getDocumentComments: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
postDocumentComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateCommentDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
replyToDocumentComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
commentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateCommentDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
listAnnotations: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentAnnotation"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
createAnnotation: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateAnnotationDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentAnnotation"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
getAnnotationComments: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
annotationId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
postAnnotationComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
annotationId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateCommentDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
replyToAnnotationComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
commentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateCommentDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
resetPassword: {
|
resetPassword: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -1192,6 +1561,54 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deleteComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
commentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description No Content */
|
||||||
|
204: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
editComment: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
commentId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateCommentDTO"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["DocumentComment"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
searchTags: {
|
searchTags: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
@@ -1422,4 +1839,25 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deleteAnnotation: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
documentId: string;
|
||||||
|
annotationId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description No Content */
|
||||||
|
204: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -908,6 +908,7 @@ function versionLabel(v: VersionSummary, index: number): string {
|
|||||||
canComment={canComment}
|
canComment={canComment}
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
canAdmin={canAdmin}
|
canAdmin={canAdmin}
|
||||||
|
documentFileHash={doc.fileHash ?? null}
|
||||||
/>
|
/>
|
||||||
{:else if fileUrl}
|
{:else if fileUrl}
|
||||||
<div class="flex h-full w-full items-center justify-center overflow-auto p-8">
|
<div class="flex h-full w-full items-center justify-center overflow-auto p-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user