vue-legal-docs-import 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # vue-legal-docs-import
2
+
3
+ Bringing documents into a platform: pick or drop files, read them in the
4
+ browser, hand them to whoever mounted the component.
5
+
6
+ ```bash
7
+ npm install vue-legal-docs-import
8
+ ```
9
+
10
+ ```vue
11
+ <script setup lang="ts">
12
+ import { LegalDocsImport } from 'vue-legal-docs-import'
13
+ import type { ImportedDocument } from 'vue-legal-docs-import'
14
+ import 'vue-legal-docs-import/style.css'
15
+
16
+ async function save(documents: ImportedDocument[]) {
17
+ await fetch('/api/datasets', {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json' },
20
+ body: JSON.stringify({ name: 'My documents', documents }),
21
+ })
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <LegalDocsImport :on-import="save" />
27
+ </template>
28
+ ```
29
+
30
+ Each document comes back as `{ name, source, full_text }` — the filename
31
+ without its extension, the filename, and the text.
32
+
33
+ ## It stores nothing and sends nothing
34
+
35
+ Files are read in the browser, because a file somebody picked is already
36
+ theirs: no upload, no round trip, nothing to authenticate. What happens next
37
+ is the host's decision, and this component does not know or care whether the
38
+ documents become rows in a database, a session in a tab, or a download.
39
+
40
+ That is the same arrangement `vue-legal-query-builder` uses for search. A
41
+ component that talks to a server needs to know which server and often needs a
42
+ credential, and neither belongs in a package that renders a file picker.
43
+
44
+ ## Doing the work on a backend
45
+
46
+ There are two places a server belongs, and they are not the same place.
47
+
48
+ **Extracting text from a format** is a reader. `read` is async and the
49
+ component never calls anything itself, so a reader is free to post the file to
50
+ an endpoint you own and let a real parser handle it — a Go or Python library
51
+ will do more with a PDF than anything that fits in a page, and OCR is not
52
+ happening in a browser at all:
53
+
54
+ ```ts
55
+ const pdfReader: FormatReader = {
56
+ extensions: ['.pdf'],
57
+ label: 'PDF',
58
+ async read(file) {
59
+ const body = new FormData()
60
+ body.append('file', file)
61
+ const res = await fetch('/api/services/docs/extract', { method: 'POST', body })
62
+ if (!res.ok) throw new Error('could not read this PDF')
63
+ // Return metadata alongside the text when the parser knows more.
64
+ return res.json() as Promise<{ text: string; metadata: { pages: number } }>
65
+ },
66
+ }
67
+ ```
68
+
69
+ **Doing something to the text** is `onPrepare`. Summarising, cleaning
70
+ boilerplate, splitting a long judgment into sections, detecting a language,
71
+ redacting names. It receives every document at once, so a backend can do it in
72
+ one request rather than one per file — which matters most for exactly the
73
+ expensive steps worth sending away:
74
+
75
+ ```vue
76
+ <LegalDocsImport :readers="[...defaultReaders, pdfReader]" :on-prepare="summarise" :on-import="save" />
77
+ ```
78
+
79
+ ```ts
80
+ async function summarise(documents: ImportedDocument[]) {
81
+ const res = await fetch('/api/services/docs/summarise', {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ documents }),
85
+ })
86
+ if (!res.ok) throw new Error('the summariser is not answering')
87
+ return res.json() as Promise<ImportedDocument[]>
88
+ }
89
+ ```
90
+
91
+ It returns documents rather than changing them in place, so it may return fewer
92
+ — dropping what turned out to be unusable — or more, one file becoming a
93
+ document per section.
94
+
95
+ The result is shown in the list *before* anything is imported, which is why
96
+ this is separate from `onImport`. Whoever is importing gets to see what the
97
+ backend made of their files and remove what came back wrong, while it is still
98
+ a decision rather than a correction.
99
+
100
+ If `onPrepare` throws, the documents are kept as they were read and the reason
101
+ is shown. A summariser being down is not a reason to lose somebody's upload.
102
+
103
+ **The component still makes no requests.** Every one of these is a function you
104
+ write, pointed at an endpoint you own — the same arrangement
105
+ `vue-legal-query-builder` uses for search, and for the same reason: a package
106
+ that renders a file picker should not be choosing a server or holding a
107
+ credential.
108
+
109
+ ## Adding a format
110
+
111
+ Only plain text is read today. The formats people actually have are PDF and
112
+ DOCX, and each needs a parser large enough that it should not load until a file
113
+ of that kind turns up — so the component takes readers rather than knowing how
114
+ to read anything itself:
115
+
116
+ ```ts
117
+ import type { FormatReader } from 'vue-legal-docs-import'
118
+ import { defaultReaders } from 'vue-legal-docs-import'
119
+
120
+ const pdfReader: FormatReader = {
121
+ extensions: ['.pdf'],
122
+ label: 'PDF',
123
+ async read(file) {
124
+ const { extractText } = await import('./my-pdf-parser') // loaded on demand
125
+ return extractText(await file.arrayBuffer())
126
+ },
127
+ }
128
+ ```
129
+
130
+ ```vue
131
+ <LegalDocsImport :readers="[...defaultReaders, pdfReader]" :on-import="save" />
132
+ ```
133
+
134
+ A file nothing can read is listed as skipped with the reason, rather than
135
+ disappearing — files vanishing without explanation is what makes an import feel
136
+ broken.
137
+
138
+ ## Props
139
+
140
+ | Prop | Type | Description |
141
+ |---|---|---|
142
+ | `readers` | `FormatReader[]` | Formats to accept. Defaults to plain text. A reader may call your server. |
143
+ | `onPrepare` | `(docs) => Promise<docs>` | Runs over everything read, before it is shown as ready. Where a backend step goes. |
144
+ | `onImport` | `(docs) => Promise<void>` | Called with everything read. Without it, no button is shown and `v-model:documents` is how the host reads the list. |
145
+ | `importLabel` | `string` | The button's text. |
146
+ | `clearOnImport` | `boolean` | Empty the list once the host has taken them. Default `true`. |
147
+ | `v-model:documents` | `ImportedDocument[]` | The list, for a host that would rather drive it. |
@@ -0,0 +1,73 @@
1
+ import type { DefineComponent, Plugin } from "vue";
2
+
3
+ /** A document read from a file, ready for the host to store. */
4
+ export interface ImportedDocument {
5
+ name: string;
6
+ source: string;
7
+ full_text: string;
8
+ /** Whatever a reader or a preparation step attached. Passed through. */
9
+ metadata?: Record<string, unknown>;
10
+ }
11
+
12
+ /** What a reader produces. A bare string is the common case. */
13
+ export interface ReadResult {
14
+ text: string;
15
+ metadata?: Record<string, unknown>;
16
+ }
17
+
18
+ /** A file that could not be read, kept so the person importing can see why. */
19
+ export interface ImportFailure {
20
+ name: string;
21
+ reason: string;
22
+ }
23
+
24
+ /**
25
+ * How one kind of file becomes a document. Adding PDF or DOCX means adding a
26
+ * reader, not changing the component.
27
+ */
28
+ export interface FormatReader {
29
+ extensions: string[];
30
+ label: string;
31
+ read(file: File): Promise<string | ReadResult>;
32
+ }
33
+
34
+ /**
35
+ * Works over everything that was read, before any of it is imported — where a
36
+ * backend step goes when it is about the text rather than the file format.
37
+ * Given every document at once so it can be done in one request.
38
+ */
39
+ export type PrepareDocuments = (
40
+ documents: ImportedDocument[],
41
+ ) => Promise<ImportedDocument[]>;
42
+
43
+ export declare const textReader: FormatReader;
44
+ export declare const defaultReaders: FormatReader[];
45
+ export declare function readerFor(readers: FormatReader[], filename: string): FormatReader | undefined;
46
+ export declare function extensionOf(filename: string): string;
47
+ export declare function baseName(filename: string): string;
48
+ /** Strips a BOM and makes line endings LF, so offsets are stable. */
49
+ export declare function normalise(text: string): string;
50
+
51
+ export interface LegalDocsImportProps {
52
+ /** Formats to accept. Defaults to plain text. */
53
+ readers?: FormatReader[];
54
+ /**
55
+ * Runs over everything read, before it is shown as ready. A failure here
56
+ * keeps the documents as they were read rather than losing the upload.
57
+ */
58
+ onPrepare?: PrepareDocuments;
59
+ /**
60
+ * Called with everything read. The host decides what to keep — this
61
+ * component stores nothing and sends nothing anywhere.
62
+ */
63
+ onImport?: (documents: ImportedDocument[]) => Promise<void> | void;
64
+ importLabel?: string;
65
+ clearOnImport?: boolean;
66
+ /** v-model:documents, for a host that would rather drive the list itself. */
67
+ documents?: ImportedDocument[];
68
+ }
69
+
70
+ export declare const LegalDocsImport: DefineComponent<LegalDocsImportProps, {}, any>;
71
+
72
+ export declare const VueLegalDocsImportPlugin: Plugin;
73
+ export default VueLegalDocsImportPlugin;
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .ldi[data-v-df8ee0fe]{font-family:inherit;color-scheme:light;color:#1f2937}.ldi-drop[data-v-df8ee0fe]{border:2px dashed #d1d5db;border-radius:8px;padding:28px 16px;text-align:center;background:#f9fafb;transition:border-color .15s,background-color .15s}.ldi-drop--over[data-v-df8ee0fe]{border-color:#3b82f6;background:#eff6ff}.ldi-drop--busy[data-v-df8ee0fe]{opacity:.7}.ldi-picker[data-v-df8ee0fe]{display:none}.ldi-drop-title[data-v-df8ee0fe]{margin:0;font-size:14px;color:#1f2937}.ldi-drop-hint[data-v-df8ee0fe]{margin:6px 0 0;font-size:12px;color:#6b7280}.ldi-link[data-v-df8ee0fe]{background:none;border:none;padding:0;font:inherit;color:#2563eb;cursor:pointer;text-decoration:underline}.ldi-status[data-v-df8ee0fe]{margin:8px 0 0;font-size:13px;color:#6b7280}.ldi-failures[data-v-df8ee0fe]{margin:10px 0 0;padding:10px 12px;list-style:none;border:1px solid #fecaca;background:#fef2f2;border-radius:6px;font-size:13px;color:#991b1b}.ldi-list[data-v-df8ee0fe]{margin-top:14px;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden;background:#fff}.ldi-list ul[data-v-df8ee0fe]{margin:0;padding:0;list-style:none;max-height:260px;overflow-y:auto}.ldi-list-head[data-v-df8ee0fe]{color:#1f2937;display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:#f3f4f6;font-size:13px;font-weight:600}.ldi-item[data-v-df8ee0fe]{color:#1f2937;display:flex;align-items:center;gap:10px;padding:8px 12px;border-top:1px solid #e5e7eb;font-size:13px}.ldi-item-name[data-v-df8ee0fe]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ldi-item-size[data-v-df8ee0fe]{color:#6b7280;font-size:12px}.ldi-remove[data-v-df8ee0fe]{background:none;border:none;cursor:pointer;font-size:18px;line-height:1;color:#9ca3af;padding:0 4px}.ldi-remove[data-v-df8ee0fe]:hover{color:#dc2626}.ldi-actions[data-v-df8ee0fe]{display:flex;align-items:center;gap:12px;margin-top:14px}.ldi-button[data-v-df8ee0fe]{background:#2563eb;color:#fff;border:none;border-radius:6px;padding:8px 16px;font:inherit;font-size:14px;cursor:pointer}.ldi-button[data-v-df8ee0fe]:disabled{opacity:.6;cursor:default}.ldi-error[data-v-df8ee0fe]{color:#b91c1c;font-size:13px}.ldi-error--standalone[data-v-df8ee0fe]{margin:10px 0 0}
@@ -0,0 +1,234 @@
1
+ import { defineComponent as z, useModel as F, ref as v, computed as x, openBlock as r, createElementBlock as i, createElementVNode as l, withModifiers as I, normalizeClass as S, createTextVNode as P, toDisplayString as u, createCommentVNode as h, Fragment as E, renderList as M, mergeModels as q } from "vue";
2
+ const G = {
3
+ extensions: [".txt", ".text", ".md"],
4
+ label: "Text files",
5
+ async read(e) {
6
+ return H(await e.text());
7
+ }
8
+ };
9
+ function H(e) {
10
+ return e.replace(/^\ufeff/, "").replace(/\r\n/g, `
11
+ `).replace(/\r/g, `
12
+ `);
13
+ }
14
+ const J = [G];
15
+ function K(e, o) {
16
+ const s = O(o);
17
+ return e.find((f) => f.extensions.includes(s));
18
+ }
19
+ function O(e) {
20
+ const o = e.lastIndexOf(".");
21
+ return o === -1 ? "" : e.slice(o).toLowerCase();
22
+ }
23
+ function Q(e) {
24
+ const o = O(e);
25
+ return o ? e.slice(0, -o.length) : e;
26
+ }
27
+ function U(e) {
28
+ return e.flatMap((o) => o.extensions).join(",");
29
+ }
30
+ const W = { class: "ldi" }, X = ["accept"], Y = { class: "ldi-drop-title" }, Z = { class: "ldi-drop-hint" }, ee = {
31
+ key: 0,
32
+ class: "ldi-status"
33
+ }, te = {
34
+ key: 1,
35
+ class: "ldi-status"
36
+ }, ne = {
37
+ key: 2,
38
+ class: "ldi-failures"
39
+ }, ae = {
40
+ key: 3,
41
+ class: "ldi-list"
42
+ }, oe = { class: "ldi-list-head" }, se = { class: "ldi-item-name" }, le = { class: "ldi-item-size" }, re = ["aria-label", "onClick"], ie = {
43
+ key: 4,
44
+ class: "ldi-error ldi-error--standalone"
45
+ }, ue = {
46
+ key: 5,
47
+ class: "ldi-actions"
48
+ }, ce = ["disabled"], de = {
49
+ key: 0,
50
+ class: "ldi-error"
51
+ }, pe = /* @__PURE__ */ z({
52
+ __name: "LegalDocsImport",
53
+ props: /* @__PURE__ */ q({
54
+ readers: {},
55
+ onPrepare: {},
56
+ onImport: {},
57
+ importLabel: { default: "Import documents" },
58
+ clearOnImport: { type: Boolean, default: !0 }
59
+ }, {
60
+ documents: { default: () => [] },
61
+ documentsModifiers: {}
62
+ }),
63
+ emits: ["update:documents"],
64
+ setup(e) {
65
+ const o = e, s = F(e, "documents"), f = v(null), g = v(!1), y = v(""), k = v(!1), _ = v(!1), c = v(""), m = v([]), b = x(() => o.readers ?? J), $ = x(() => U(b.value)), N = x(() => b.value.map((n) => n.label).join(", "));
66
+ function R(n) {
67
+ return n.trim() ? n.trim().split(/\s+/).length : 0;
68
+ }
69
+ async function C(n) {
70
+ m.value = [], c.value = "";
71
+ const a = [];
72
+ for (const t of n) {
73
+ const p = K(b.value, t.name);
74
+ if (!p) {
75
+ m.value.push({ name: t.name, reason: "not a kind of file this can read" });
76
+ continue;
77
+ }
78
+ y.value = t.name;
79
+ try {
80
+ const d = await p.read(t), D = typeof d == "string" ? d : d.text, L = typeof d == "string" ? void 0 : d.metadata;
81
+ if (!D.trim()) {
82
+ m.value.push({ name: t.name, reason: "the file is empty" });
83
+ continue;
84
+ }
85
+ a.push({
86
+ name: Q(t.name),
87
+ source: t.name,
88
+ full_text: D,
89
+ ...L ? { metadata: L } : {}
90
+ });
91
+ } catch (d) {
92
+ m.value.push({
93
+ name: t.name,
94
+ reason: d instanceof Error ? d.message : "could not be read"
95
+ });
96
+ } finally {
97
+ y.value = "";
98
+ }
99
+ }
100
+ s.value = [...s.value, ...await V(a)];
101
+ }
102
+ async function V(n) {
103
+ if (!o.onPrepare || n.length === 0) return n;
104
+ k.value = !0;
105
+ try {
106
+ return await o.onPrepare(n);
107
+ } catch (a) {
108
+ return c.value = (a instanceof Error ? a.message : "preparing these documents failed") + " — importing them as they were read", n;
109
+ } finally {
110
+ k.value = !1;
111
+ }
112
+ }
113
+ function A(n) {
114
+ const a = n.target;
115
+ C(Array.from(a.files ?? [])), a.value = "";
116
+ }
117
+ function B(n) {
118
+ var a;
119
+ g.value = !1, C(Array.from(((a = n.dataTransfer) == null ? void 0 : a.files) ?? []));
120
+ }
121
+ function T(n) {
122
+ s.value = s.value.filter((a, t) => t !== n);
123
+ }
124
+ function w() {
125
+ s.value = [], m.value = [], c.value = "";
126
+ }
127
+ async function j() {
128
+ if (o.onImport) {
129
+ _.value = !0, c.value = "";
130
+ try {
131
+ await o.onImport(s.value), o.clearOnImport && w();
132
+ } catch (n) {
133
+ c.value = n instanceof Error ? n.message : "could not import these documents";
134
+ } finally {
135
+ _.value = !1;
136
+ }
137
+ }
138
+ }
139
+ return (n, a) => (r(), i("div", W, [
140
+ l("div", {
141
+ class: S(["ldi-drop", { "ldi-drop--over": g.value, "ldi-drop--busy": y.value }]),
142
+ onDragover: a[1] || (a[1] = I((t) => g.value = !0, ["prevent"])),
143
+ onDragleave: a[2] || (a[2] = I((t) => g.value = !1, ["prevent"])),
144
+ onDrop: I(B, ["prevent"])
145
+ }, [
146
+ l("input", {
147
+ ref_key: "picker",
148
+ ref: f,
149
+ type: "file",
150
+ multiple: "",
151
+ class: "ldi-picker",
152
+ accept: $.value,
153
+ onChange: A
154
+ }, null, 40, X),
155
+ l("p", Y, [
156
+ l("button", {
157
+ type: "button",
158
+ class: "ldi-link",
159
+ onClick: a[0] || (a[0] = (t) => {
160
+ var p;
161
+ return (p = f.value) == null ? void 0 : p.click();
162
+ })
163
+ }, " Choose files "),
164
+ a[3] || (a[3] = P(" or drop them here ", -1))
165
+ ]),
166
+ l("p", Z, u(N.value), 1)
167
+ ], 34),
168
+ y.value ? (r(), i("p", ee, "Reading " + u(y.value) + "…", 1)) : k.value ? (r(), i("p", te, "Preparing documents…")) : h("", !0),
169
+ m.value.length ? (r(), i("ul", ne, [
170
+ (r(!0), i(E, null, M(m.value, (t) => (r(), i("li", {
171
+ key: t.name
172
+ }, [
173
+ l("strong", null, u(t.name), 1),
174
+ P(" — " + u(t.reason), 1)
175
+ ]))), 128))
176
+ ])) : h("", !0),
177
+ s.value.length ? (r(), i("div", ae, [
178
+ l("div", oe, [
179
+ l("span", null, u(s.value.length) + " document" + u(s.value.length === 1 ? "" : "s") + " ready", 1),
180
+ l("button", {
181
+ type: "button",
182
+ class: "ldi-link",
183
+ onClick: w
184
+ }, "Clear")
185
+ ]),
186
+ l("ul", null, [
187
+ (r(!0), i(E, null, M(s.value, (t, p) => (r(), i("li", {
188
+ key: t.source + p,
189
+ class: "ldi-item"
190
+ }, [
191
+ l("span", se, u(t.name), 1),
192
+ l("span", le, u(R(t.full_text)) + " words", 1),
193
+ l("button", {
194
+ type: "button",
195
+ class: "ldi-remove",
196
+ "aria-label": `Remove ${t.name}`,
197
+ onClick: (d) => T(p)
198
+ }, " × ", 8, re)
199
+ ]))), 128))
200
+ ])
201
+ ])) : h("", !0),
202
+ c.value && !s.value.length ? (r(), i("p", ie, u(c.value), 1)) : h("", !0),
203
+ s.value.length && e.onImport ? (r(), i("div", ue, [
204
+ l("button", {
205
+ type: "button",
206
+ class: "ldi-button",
207
+ disabled: _.value,
208
+ onClick: j
209
+ }, u(_.value ? "Importing…" : e.importLabel), 9, ce),
210
+ c.value ? (r(), i("span", de, u(c.value), 1)) : h("", !0)
211
+ ])) : h("", !0)
212
+ ]));
213
+ }
214
+ }), me = (e, o) => {
215
+ const s = e.__vccOpts || e;
216
+ for (const [f, g] of o)
217
+ s[f] = g;
218
+ return s;
219
+ }, ve = /* @__PURE__ */ me(pe, [["__scopeId", "data-v-df8ee0fe"]]), ge = {
220
+ install(e) {
221
+ e.component("LegalDocsImport", ve);
222
+ }
223
+ };
224
+ export {
225
+ ve as LegalDocsImport,
226
+ ge as VueLegalDocsImportPlugin,
227
+ Q as baseName,
228
+ ge as default,
229
+ J as defaultReaders,
230
+ O as extensionOf,
231
+ H as normalise,
232
+ K as readerFor,
233
+ G as textReader
234
+ };
@@ -0,0 +1,3 @@
1
+ (function(s,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(s=typeof globalThis<"u"?globalThis:s||self,e(s.VueLegalDocsImport={},s.Vue))})(this,function(s,e){"use strict";const _={extensions:[".txt",".text",".md"],label:"Text files",async read(t){return E(await t.text())}};function E(t){return t.replace(/^\ufeff/,"").replace(/\r\n/g,`
2
+ `).replace(/\r/g,`
3
+ `)}const V=[_];function b(t,a){const r=y(a);return t.find(p=>p.extensions.includes(r))}function y(t){const a=t.lastIndexOf(".");return a===-1?"":t.slice(a).toLowerCase()}function B(t){const a=y(t);return a?t.slice(0,-a.length):t}function w(t){return t.flatMap(a=>a.extensions).join(",")}const L={class:"ldi"},M=["accept"],P={class:"ldi-drop-title"},O={class:"ldi-drop-hint"},T={key:0,class:"ldi-status"},R={key:1,class:"ldi-status"},$={key:2,class:"ldi-failures"},j={key:3,class:"ldi-list"},F={class:"ldi-list-head"},A={class:"ldi-item-name"},v={class:"ldi-item-size"},z=["aria-label","onClick"],q={key:4,class:"ldi-error ldi-error--standalone"},G={key:5,class:"ldi-actions"},H=["disabled"],J={key:0,class:"ldi-error"},N=((t,a)=>{const r=t.__vccOpts||t;for(const[p,u]of a)r[p]=u;return r})(e.defineComponent({__name:"LegalDocsImport",props:e.mergeModels({readers:{},onPrepare:{},onImport:{},importLabel:{default:"Import documents"},clearOnImport:{type:Boolean,default:!0}},{documents:{default:()=>[]},documentsModifiers:{}}),emits:["update:documents"],setup(t){const a=t,r=e.useModel(t,"documents"),p=e.ref(null),u=e.ref(!1),f=e.ref(""),k=e.ref(!1),g=e.ref(!1),i=e.ref(""),m=e.ref([]),h=e.computed(()=>a.readers??V),K=e.computed(()=>w(h.value)),Q=e.computed(()=>h.value.map(o=>o.label).join(", "));function U(o){return o.trim()?o.trim().split(/\s+/).length:0}async function C(o){m.value=[],i.value="";const l=[];for(const n of o){const d=b(h.value,n.name);if(!d){m.value.push({name:n.name,reason:"not a kind of file this can read"});continue}f.value=n.name;try{const c=await d.read(n),x=typeof c=="string"?c:c.text,S=typeof c=="string"?void 0:c.metadata;if(!x.trim()){m.value.push({name:n.name,reason:"the file is empty"});continue}l.push({name:B(n.name),source:n.name,full_text:x,...S?{metadata:S}:{}})}catch(c){m.value.push({name:n.name,reason:c instanceof Error?c.message:"could not be read"})}finally{f.value=""}}r.value=[...r.value,...await W(l)]}async function W(o){if(!a.onPrepare||o.length===0)return o;k.value=!0;try{return await a.onPrepare(o)}catch(l){return i.value=(l instanceof Error?l.message:"preparing these documents failed")+" — importing them as they were read",o}finally{k.value=!1}}function X(o){const l=o.target;C(Array.from(l.files??[])),l.value=""}function Y(o){var l;u.value=!1,C(Array.from(((l=o.dataTransfer)==null?void 0:l.files)??[]))}function Z(o){r.value=r.value.filter((l,n)=>n!==o)}function I(){r.value=[],m.value=[],i.value=""}async function ee(){if(a.onImport){g.value=!0,i.value="";try{await a.onImport(r.value),a.clearOnImport&&I()}catch(o){i.value=o instanceof Error?o.message:"could not import these documents"}finally{g.value=!1}}}return(o,l)=>(e.openBlock(),e.createElementBlock("div",L,[e.createElementVNode("div",{class:e.normalizeClass(["ldi-drop",{"ldi-drop--over":u.value,"ldi-drop--busy":f.value}]),onDragover:l[1]||(l[1]=e.withModifiers(n=>u.value=!0,["prevent"])),onDragleave:l[2]||(l[2]=e.withModifiers(n=>u.value=!1,["prevent"])),onDrop:e.withModifiers(Y,["prevent"])},[e.createElementVNode("input",{ref_key:"picker",ref:p,type:"file",multiple:"",class:"ldi-picker",accept:K.value,onChange:X},null,40,M),e.createElementVNode("p",P,[e.createElementVNode("button",{type:"button",class:"ldi-link",onClick:l[0]||(l[0]=n=>{var d;return(d=p.value)==null?void 0:d.click()})}," Choose files "),l[3]||(l[3]=e.createTextVNode(" or drop them here ",-1))]),e.createElementVNode("p",O,e.toDisplayString(Q.value),1)],34),f.value?(e.openBlock(),e.createElementBlock("p",T,"Reading "+e.toDisplayString(f.value)+"…",1)):k.value?(e.openBlock(),e.createElementBlock("p",R,"Preparing documents…")):e.createCommentVNode("",!0),m.value.length?(e.openBlock(),e.createElementBlock("ul",$,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(m.value,n=>(e.openBlock(),e.createElementBlock("li",{key:n.name},[e.createElementVNode("strong",null,e.toDisplayString(n.name),1),e.createTextVNode(" — "+e.toDisplayString(n.reason),1)]))),128))])):e.createCommentVNode("",!0),r.value.length?(e.openBlock(),e.createElementBlock("div",j,[e.createElementVNode("div",F,[e.createElementVNode("span",null,e.toDisplayString(r.value.length)+" document"+e.toDisplayString(r.value.length===1?"":"s")+" ready",1),e.createElementVNode("button",{type:"button",class:"ldi-link",onClick:I},"Clear")]),e.createElementVNode("ul",null,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(r.value,(n,d)=>(e.openBlock(),e.createElementBlock("li",{key:n.source+d,class:"ldi-item"},[e.createElementVNode("span",A,e.toDisplayString(n.name),1),e.createElementVNode("span",v,e.toDisplayString(U(n.full_text))+" words",1),e.createElementVNode("button",{type:"button",class:"ldi-remove","aria-label":`Remove ${n.name}`,onClick:c=>Z(d)}," × ",8,z)]))),128))])])):e.createCommentVNode("",!0),i.value&&!r.value.length?(e.openBlock(),e.createElementBlock("p",q,e.toDisplayString(i.value),1)):e.createCommentVNode("",!0),r.value.length&&t.onImport?(e.openBlock(),e.createElementBlock("div",G,[e.createElementVNode("button",{type:"button",class:"ldi-button",disabled:g.value,onClick:ee},e.toDisplayString(g.value?"Importing…":t.importLabel),9,H),i.value?(e.openBlock(),e.createElementBlock("span",J,e.toDisplayString(i.value),1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0)]))}}),[["__scopeId","data-v-df8ee0fe"]]),D={install(t){t.component("LegalDocsImport",N)}};s.LegalDocsImport=N,s.VueLegalDocsImportPlugin=D,s.baseName=B,s.default=D,s.defaultReaders=V,s.extensionOf=y,s.normalise=E,s.readerFor=b,s.textReader=_,Object.defineProperties(s,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "vue-legal-docs-import",
3
+ "version": "1.1.0",
4
+ "description": "A Vue component for bringing documents into a platform: pick or drop files, read them in the browser, hand them to the host.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/MaastrichtU-BISS/vue-legal-docs-import.git"
8
+ },
9
+ "homepage": "https://github.com/MaastrichtU-BISS/vue-legal-docs-import#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/MaastrichtU-BISS/vue-legal-docs-import/issues"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/vue-legal-docs-import.umd.js",
15
+ "module": "./dist/vue-legal-docs-import.es.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/vue-legal-docs-import.es.js",
21
+ "require": "./dist/vue-legal-docs-import.umd.js"
22
+ },
23
+ "./style.css": "./dist/style.css"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "scripts": {
29
+ "build": "vite build && node scripts/copy-types.mjs",
30
+ "dev": "vite build --watch",
31
+ "type-check": "vue-tsc --noEmit",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "keywords": [
35
+ "legal",
36
+ "vue",
37
+ "import",
38
+ "upload"
39
+ ],
40
+ "license": "MIT",
41
+ "peerDependencies": {
42
+ "vue": "^3.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@vitejs/plugin-vue": "^5.0.0",
46
+ "typescript": "^5.9.3",
47
+ "vite": "^5.2.0",
48
+ "vue": "^3.4.0",
49
+ "vue-tsc": "^2.0.0"
50
+ }
51
+ }