Features/shadcn #1

Merged
rehlert merged 17 commits from features/shadcn into main 2026-07-22 19:15:48 +00:00
3 changed files with 144 additions and 16 deletions
Showing only changes of commit 15a3fc5877 - Show all commits
+15 -6
View File
@@ -2,8 +2,8 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="89f9fc3a-63ad-41cb-8d25-f07df352eb98" name="Changes" comment=""> <list default="true" id="89f9fc3a-63ad-41cb-8d25-f07df352eb98" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/lib/zip-processing.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/zip-processing.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/routes/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/routes/+page.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/routes/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/routes/+page.svelte" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@@ -34,7 +34,7 @@
"RunOnceActivity.typescript.service.memoryLimit.init": "true", "RunOnceActivity.typescript.service.memoryLimit.init": "true",
"cidr.known.project.marker": "true", "cidr.known.project.marker": "true",
"codeWithMe.voiceChat.enabledByDefault": "false", "codeWithMe.voiceChat.enabledByDefault": "false",
"git-widget-placeholder": "main", "git-widget-placeholder": "features/zip-processing",
"last_opened_file_path": "/home/rehlert/repos/juri-merger", "last_opened_file_path": "/home/rehlert/repos/juri-merger",
"nodejs_package_manager_path": "pnpm", "nodejs_package_manager_path": "pnpm",
"settings.editor.selected.configurable": "preferences.pluginManager", "settings.editor.selected.configurable": "preferences.pluginManager",
@@ -50,7 +50,7 @@
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1781169139290</updated> <updated>1781169139290</updated>
<workItem from="1781169141138" duration="309000" /> <workItem from="1781169141138" duration="309000" />
<workItem from="1781251694220" duration="5995000" /> <workItem from="1781251694220" duration="7259000" />
</task> </task>
<task id="LOCAL-00001" summary="adapts styling of rvg.legal"> <task id="LOCAL-00001" summary="adapts styling of rvg.legal">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -68,7 +68,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1781257473070</updated> <updated>1781257473070</updated>
</task> </task>
<option name="localTasksCounter" value="3" /> <task id="LOCAL-00003" summary="zips are getting extracted">
<option name="closed" value="true" />
<created>1781258303003</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1781258303003</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -80,7 +88,8 @@
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" /> <option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="adapts styling of rvg.legal" /> <MESSAGE value="adapts styling of rvg.legal" />
<MESSAGE value="adds dropzone to add zips" /> <MESSAGE value="adds dropzone to add zips" />
<option name="LAST_COMMIT_MESSAGE" value="adds dropzone to add zips" /> <MESSAGE value="zips are getting extracted" />
<option name="LAST_COMMIT_MESSAGE" value="zips are getting extracted" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
+129 -6
View File
@@ -10,9 +10,8 @@ export type ZipAttachment = {
}; };
export type ProcessedZipArchive = { export type ProcessedZipArchive = {
name: string; name: string
attachments: ZipAttachment[]; attachments: ZipAttachment[]
xjustizNachrichtXml: Uint8Array | null;
}; };
const IMAGE_EXTENSIONS = new Set([ const IMAGE_EXTENSIONS = new Set([
@@ -30,6 +29,7 @@ const IMAGE_EXTENSIONS = new Set([
]); ]);
const ZIP_META_FILE_NAME = "xjustiz_nachricht.xml"; const ZIP_META_FILE_NAME = "xjustiz_nachricht.xml";
const XML_TEXT_DECODER = new TextDecoder();
const unzipArchive = (data: Uint8Array) => const unzipArchive = (data: Uint8Array) =>
new Promise<Record<string, Uint8Array>>((resolve, reject) => { new Promise<Record<string, Uint8Array>>((resolve, reject) => {
@@ -56,6 +56,126 @@ const isPdf = (path: string) => getExtension(path) === ".pdf";
const isImage = (path: string) => IMAGE_EXTENSIONS.has(getExtension(path)); const isImage = (path: string) => IMAGE_EXTENSIONS.has(getExtension(path));
const normalizeKey = (value: string) => value.replaceAll("\\", "/").toLowerCase().trim();
const matchesElementName = (element: Element, requestedName: string) => {
const requestedLocalName = requestedName.split(".").pop() ?? requestedName;
return (
element.localName === requestedName ||
element.localName === requestedLocalName ||
element.tagName === requestedName ||
element.tagName === requestedLocalName ||
element.tagName.endsWith(`:${requestedLocalName}`)
);
};
const getDirectChildByLocalName = (element: Element, localName: string) =>
Array.from(element.children).find((child) => matchesElementName(child, localName)) ?? null;
const getFirstDescendantByLocalName = (root: Document | Element, localName: string) =>
Array.from(root.querySelectorAll("*")).find((element) => matchesElementName(element, localName)) ?? null;
const getTextContentByPath = (root: Element, path: string[]) => {
let current: Element | null = root;
for (const localName of path) {
current = current ? getDirectChildByLocalName(current, localName) : null;
if (!current) {
return null;
}
}
const textContent = current.textContent?.trim();
return textContent ? textContent : null;
};
const parseXmlDocument = (xmlBytes: Uint8Array) => {
const parser = new DOMParser();
const document = parser.parseFromString(XML_TEXT_DECODER.decode(xmlBytes), "application/xml");
if (document.querySelector("parsererror")) {
return null;
}
return document;
};
const getXJustizDocumentNames = (xmlBytes: Uint8Array) => {
const document = parseXmlDocument(xmlBytes);
if (!document) {
return [];
}
const schriftgutobjekte = getFirstDescendantByLocalName(document, "schriftgutobjekte");
if (!schriftgutobjekte) {
return [];
}
const dokumentNodes = Array.from(schriftgutobjekte.children).filter((child) =>
matchesElementName(child, "dokument"),
);
return dokumentNodes
.map((dokument) => getTextContentByPath(dokument, ["xjustiz.fachspezifischeDaten", "datei", "dateiname"]))
.filter((name): name is string => Boolean(name));
};
const orderAttachmentsByDocumentNames = (attachments: ZipAttachment[], documentNames: string[]) => {
const buckets = new Map<string, ZipAttachment[]>();
for (const attachment of attachments) {
const keys = new Set([normalizeKey(attachment.path), normalizeKey(attachment.name)]);
for (const key of keys) {
const bucket = buckets.get(key) ?? [];
bucket.push(attachment);
buckets.set(key, bucket);
}
}
const usedAttachments = new Set<ZipAttachment>();
const orderedAttachments: ZipAttachment[] = [];
const takeAttachment = (key: string) => {
const bucket = buckets.get(key);
if (!bucket) {
return null;
}
const nextAttachment = bucket.find((attachment) => !usedAttachments.has(attachment)) ?? null;
if (nextAttachment) {
usedAttachments.add(nextAttachment);
}
return nextAttachment;
};
for (const documentName of documentNames) {
const normalizedName = normalizeKey(documentName);
const normalizedBaseName = normalizeKey(getBaseName(documentName));
const matchedAttachment = takeAttachment(normalizedName) ?? takeAttachment(normalizedBaseName);
if (matchedAttachment) {
orderedAttachments.push(matchedAttachment);
}
}
for (const attachment of attachments) {
if (!usedAttachments.has(attachment)) {
orderedAttachments.push(attachment);
}
}
return orderedAttachments;
};
export const extractZipArchive = async (file: File): Promise<ProcessedZipArchive> => { export const extractZipArchive = async (file: File): Promise<ProcessedZipArchive> => {
const archiveBytes = new Uint8Array(await file.arrayBuffer()); const archiveBytes = new Uint8Array(await file.arrayBuffer());
const files = await unzipArchive(archiveBytes); const files = await unzipArchive(archiveBytes);
@@ -91,9 +211,12 @@ export const extractZipArchive = async (file: File): Promise<ProcessedZipArchive
} }
} }
const documentNames = xjustizNachrichtXml ? getXJustizDocumentNames(xjustizNachrichtXml) : [];
const orderedAttachments = orderAttachmentsByDocumentNames(attachments, documentNames);
const archiveName = documentNames[0] ? getBaseName(documentNames[0]) : file.name;
return { return {
name: file.name, name: archiveName,
attachments, attachments: orderedAttachments
xjustizNachrichtXml,
}; };
}; };
-4
View File
@@ -20,7 +20,6 @@
try { try {
const processedZipArchive = await extractZipArchive(file); const processedZipArchive = await extractZipArchive(file);
processedZipArchive.xjustizNachrichtXml
processedZipFiles = [...processedZipFiles, processedZipArchive]; processedZipFiles = [...processedZipFiles, processedZipArchive];
} catch (error) { } catch (error) {
console.error(`Failed to process ${file.name}`, error); console.error(`Failed to process ${file.name}`, error);
@@ -53,9 +52,6 @@
{#each processedZipFiles as file} {#each processedZipFiles as file}
<li class="rounded-container bg-primary-50 px-4 py-3 text-[14px] font-medium text-primary-900"> <li class="rounded-container bg-primary-50 px-4 py-3 text-[14px] font-medium text-primary-900">
{file.name} - {file.attachments.length} relevante Datei{file.attachments.length === 1 ? "" : "en"} {file.name} - {file.attachments.length} relevante Datei{file.attachments.length === 1 ? "" : "en"}
{#if file.xjustizNachrichtXml}
- xjustiz_nachricht.xml vorhanden
{/if}
</li> </li>
{/each} {/each}
</ul> </ul>