Skip to content

Commit 0bed33f

Browse files
author
Corey B
committed
fix(extension): patch template iterable renderer and inject File System API linux native fallback via downloads
1 parent cd7333b commit 0bed33f

2 files changed

Lines changed: 131 additions & 44 deletions

File tree

examples/court_judgment.fdd

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,77 @@
33
<head>
44
<title>Court Judgment</title>
55
</head>
6-
<body style="background: #e5e5e5; display: flex; padding: 2rem; justify-content: center;">
6+
<body style="background: #e5e5e5; display: flex; padding: 2rem; justify-content: center; margin: 0; min-height: 100vh;">
77
<fdd-container extension=".fdd">
88
<script type="application/vc+json" id="fdd-data">
99
{
1010
"issuer": "did:web:courts.mn.gov",
11-
"credentialSubject": { "caseNumber": "25-CV-1234", "judge": "Hon. Sarah Johnson", "decision": "Judgment entered for Plaintiff in the amount of $5,000." },
12-
"proof": { "signature": "sig_abcd1234...", "type": "Ed25519Signature2020" }
11+
"credentialSubject": {
12+
"caseNumber": "25-CV-1234",
13+
"judge": "Hon. Sarah Johnson",
14+
"decision": "Judgment entered for Plaintiff in the amount of $5,000.",
15+
"plaintiff": "Alice Tech LLC",
16+
"defendant": "Bob Solutions Inc",
17+
"filingDate": "2025-10-14",
18+
"jurisdiction": "Hennepin County District Court",
19+
"history": [
20+
{"date": "2025-10-14", "event": "Initial Complaint Filed by Plaintiff"},
21+
{"date": "2025-11-02", "event": "Defendant's Motion to Dismiss Denied"},
22+
{"date": "2025-12-15", "event": "Final Judgment Delivered by Presiding Judge"}
23+
]
24+
},
25+
"proof": { "signature": "sig_abcd123498765432...", "type": "Ed25519Signature2020" }
1326
}
1427
</script>
15-
<template shadowrootmode="open" bind="#fdd-data">
28+
<script type="application/json" id="user-notes">
29+
{ "clerkNotation": "Case closed. Awaiting payment receipt." }
30+
</script>
31+
<template shadowrootmode="open" bind="#fdd-data, #user-notes">
1632
<style>
1733
:host {
18-
display:block; font-family: 'Times New Roman', serif; width: 600px;
34+
display:block; font-family: 'Times New Roman', serif; width: 650px;
1935
background: white; padding: 3rem; border: double 6px #1a1a1a;
2036
text-align: center; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
2137
}
22-
.seal { font-size: 4rem; margin: 1rem 0; color: #b91c1c; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); }
23-
.content { text-align: left; margin-top: 2rem; font-size: 1.3rem; line-height: 1.8; color: #111; }
24-
.verified { border: 2px dashed #059669; background: #ecfdf5; padding: 12px; color: #059669; font-weight: bold; margin-bottom: 2rem; border-radius: 4px; }
38+
.seal { font-size: 5rem; margin: 1.5rem 0; color: #b91c1c; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); }
39+
.content { text-align: left; margin-top: 1.5rem; font-size: 1.25rem; line-height: 1.7; color: #111; }
40+
.verified { border: 2px dashed #059669; background: #ecfdf5; padding: 12px; color: #059669; font-weight: bold; margin-bottom: 2rem; border-radius: 4px; font-family: sans-serif;}
41+
.metadata { font-size: 1rem; color: #555; background: #f9f9f9; padding: 1rem; border-radius: 6px; border: 1px solid #ddd; margin-bottom: 1.5rem; }
42+
.history { list-style-type: none; padding: 0; font-size: 1.1rem; }
43+
.history li { border-left: 3px solid #ccc; margin-bottom: 10px; padding-left: 15px; }
44+
.history .date { font-weight: bold; color: #444; margin-right: 15px;}
45+
.notes-area { width: 100%; height: 80px; margin-top: 5px; box-sizing: border-box; border-radius: 8px; border: 1px dashed #cbd5e1; padding: 10px; font-family: Arial; resize: none; background: #fdfdfd;}
2546
</style>
26-
<div class="verified">✓ Official Court Record • Cryptographically Sealed</div>
27-
<h1 style="text-transform: uppercase;">State of Minnesota District Court</h1>
28-
<h2 style="color: #444;">Case No: {{caseNumber}}</h2>
47+
<div class="verified">✓ Official Court Record • Cryptographically Sealed Header</div>
48+
<h1 style="text-transform: uppercase;">State of Minnesota</h1>
49+
<h2 style="color: #444;">{{jurisdiction}}</h2>
50+
<h3 style="color: #666; margin-top:0;">Case No: {{caseNumber}}</h3>
51+
52+
<div class="metadata">
53+
<strong>Plaintiff:</strong> {{plaintiff}} <br>
54+
<strong>Defendant:</strong> {{defendant}} <br>
55+
<strong>Action Date:</strong> {{filingDate}}
56+
</div>
57+
2958
<div class="seal">⚖️</div>
3059
<div class="content">
3160
<p><strong>Presiding Judge:</strong> {{judge}}</p>
32-
<p style="border-top:1px solid #ccc; padding-top: 1rem; margin-top:2rem;">
61+
<p><strong>Record of Actions:</strong></p>
62+
<ul class="history">
63+
{{#each history}}
64+
<li><span class="date">{{date}}</span> <span class="event">{{event}}</span></li>
65+
{{/each}}
66+
</ul>
67+
68+
<p style="border-top:1px solid #333; padding-top: 1rem; margin-top:2rem;">
3369
<strong>Decree:</strong> {{decision}}
3470
</p>
3571
</div>
72+
73+
<div style="margin-top: 2rem; text-align: left; font-family: sans-serif;">
74+
<label style="font-weight: bold; color: #475569; font-size: 0.9rem;">Internal Clerk Log (Local Edits Save to File):</label>
75+
<textarea name="user-notes.clerkNotation" class="notes-area" autosave>{{clerkNotation}}</textarea>
76+
</div>
3677
</template>
3778
</fdd-container>
3879
</body>

extension/content.js

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,36 @@ class ExtensionFDD {
4545
let html = this.template.innerHTML;
4646

4747
// Advanced Handlebars-style Arrays: {{#each list}}...{{/each}}
48-
html = html.replace(/\{\{#each\s+(.*?)\}\}([\s\S]*?)\{\{\/each\}\}/g, (match, arrayKey, loopBody) => {
49-
const arr = context[arrayKey.trim()];
50-
if (!Array.isArray(arr)) return '';
51-
return arr.map(item => {
52-
return loopBody.replace(/\{\{(.*?)\}\}/g, (m, k) => {
53-
const key = k.trim();
54-
return (item[key] !== undefined && item[key] !== null) ? item[key] : '';
48+
html = html.replace(/\{\{#each\s+(.*?)\}\}([\s\S]*?)\{\{\/each\}\}/g, (match, param, body) => {
49+
const key = param.trim();
50+
const arr = context[key];
51+
if (!arr || !Array.isArray(arr)) {
52+
console.warn("FDD Array not found for", key);
53+
return '';
54+
}
55+
let out = "";
56+
for (let i = 0; i < arr.length; i++) {
57+
let itemBody = body;
58+
Object.keys(arr[i]).forEach(k => {
59+
const val = arr[i][k] !== null && arr[i][k] !== undefined ? arr[i][k] : '';
60+
itemBody = itemBody.replace(new RegExp(`\\{\\{\\s*${k}\\s*\\}\\}`, 'g'), val);
5561
});
56-
}).join('');
62+
out += itemBody;
63+
}
64+
return out;
5765
});
5866

59-
// Basic Handlebars Binding: {{field}}
60-
html = html.replace(/\{\{(.*?)\}\}/g, (match, key) => {
61-
const k = key.trim();
62-
return (context[k] !== undefined && context[k] !== null) ? context[k] : '';
67+
// Global Key Binding: {{field}}
68+
Object.keys(context).forEach(k => {
69+
if (typeof context[k] !== 'object') {
70+
const val = context[k] !== null && context[k] !== undefined ? context[k] : '';
71+
html = html.replace(new RegExp(`\\{\\{\\s*${k}\\s*\\}\\}`, 'g'), val);
72+
}
6373
});
6474

75+
// Any remaining hanging mustache brackets will just be stripped to keep the UI clean
76+
html = html.replace(/\{\{(.*?)\}\}/g, '');
77+
6578
const shadowRoot = this.container.attachShadow({ mode: 'open' });
6679
shadowRoot.innerHTML = html;
6780

@@ -70,7 +83,6 @@ class ExtensionFDD {
7083
}
7184

7285
setupInteractivity(root) {
73-
// Allows declarative CSS filtering by driving native filtering through data attributes over the Shadow Root
7486
const searchInputs = root.querySelectorAll('input[data-filter-target]');
7587
searchInputs.forEach(input => {
7688
input.addEventListener('input', e => {
@@ -92,10 +104,9 @@ class ExtensionFDD {
92104
setupAutoSave(root) {
93105
const inputs = root.querySelectorAll('[autosave]');
94106
inputs.forEach(input => {
95-
96-
// Request persistent file write-handle purely on interaction intent
97-
input.addEventListener('focus', async () => {
98-
if (!this.fileHandle && window.showOpenFilePicker) {
107+
// Prompt user for local file persistence capability on intersection/interaction click
108+
input.addEventListener('pointerdown', async () => {
109+
if (!this.fileHandle && window.showOpenFilePicker && document.contentType !== 'text/plain') {
99110
try {
100111
const [handle] = await window.showOpenFilePicker({
101112
types: [{ description: 'Formatted Data Document', accept: {'text/html': ['.fdd']} }],
@@ -108,14 +119,22 @@ class ExtensionFDD {
108119
this.fileHandle = handle;
109120
console.log("FDD: Auto-save File Handle persistently bound!");
110121
}
111-
} catch(e) { console.warn("FDD Interaction missing valid write handle confirmation."); }
122+
} catch(e) { console.warn("FDD Permission denied by user. Download fallback will be used."); }
112123
}
113124
});
114125

115126
// Commit to persistent disk layer via handler mechanism
127+
input.addEventListener('change', async (e) => {
128+
const path = e.target.name.split('.');
129+
if (path[0] === 'user-notes' && path[1]) {
130+
this.mutableData[path[1]] = e.target.value;
131+
await this.syncFile();
132+
}
133+
});
134+
116135
input.addEventListener('blur', async (e) => {
117136
const path = e.target.name.split('.');
118-
if (path[0] === 'user-notes' && path[1] && this.fileHandle) {
137+
if (path[0] === 'user-notes' && path[1]) {
119138
this.mutableData[path[1]] = e.target.value;
120139
await this.syncFile();
121140
}
@@ -133,20 +152,47 @@ class ExtensionFDD {
133152
mutableScript.textContent = "\n " + JSON.stringify(this.mutableData, null, 2) + "\n ";
134153
}
135154

136-
try {
137-
const writable = await this.fileHandle.createWritable();
138-
const content = "<!DOCTYPE html>\n" + originalDocParser.documentElement.outerHTML;
139-
await writable.write(content);
140-
await writable.close();
141-
142-
const badge = document.createElement('div');
143-
badge.style.cssText = "position:fixed;bottom:20px;right:20px;background:#10b981;color:white;padding:8px 16px;border-radius:999px;z-index:9999;font-family:system-ui;box-shadow:0 4px 6px rgba(0,0,0,0.1);font-weight:bold;";
144-
badge.innerText = "✓ FDD Synced to Disk";
145-
document.body.appendChild(badge);
146-
setTimeout(() => badge.remove(), 2500);
147-
} catch (e) {
148-
console.error("FDD Persist Error", e);
155+
const content = "<!DOCTYPE html>\n" + originalDocParser.documentElement.outerHTML;
156+
157+
// Primary Write Strategy (Windows / macOS / Chromium Standards)
158+
if (this.fileHandle) {
159+
try {
160+
const writable = await this.fileHandle.createWritable();
161+
await writable.write(content);
162+
await writable.close();
163+
this.showBadge("✓ FDD Synced Directly to Disk", "#10b981");
164+
return;
165+
} catch (e) {
166+
console.error("Direct FDD Persist Error, falling back...", e);
167+
}
149168
}
169+
170+
// Secondary Linux / Missing FileURL Permission Fallback Strategy
171+
console.warn("Executing native Download Fallback mapping for persistent storage");
172+
const blob = new Blob([content], { type: 'text/html' });
173+
const url = URL.createObjectURL(blob);
174+
const a = document.createElement('a');
175+
a.href = url;
176+
a.download = 'updated.fdd';
177+
document.body.appendChild(a);
178+
a.click();
179+
document.body.removeChild(a);
180+
URL.revokeObjectURL(url);
181+
this.showBadge("⚠️ Downloaded New FDD Backup", "#fbbf24");
182+
}
183+
184+
showBadge(msg, color) {
185+
const badge = document.createElement('div');
186+
badge.style.cssText = `position:fixed;bottom:20px;right:20px;background:${color};color:white;padding:10px 18px;border-radius:999px;z-index:9999;font-family:system-ui;font-weight:bold;box-shadow:0 10px 15px -3px rgba(0,0,0,0.1);`;
187+
badge.innerText = msg;
188+
189+
// Since container is a shadowRoot, we append directly to the host body
190+
document.body.appendChild(badge);
191+
setTimeout(() => {
192+
badge.style.transition = 'opacity 0.5s ease';
193+
badge.style.opacity = '0';
194+
setTimeout(() => badge.remove(), 500);
195+
}, 3000);
150196
}
151197
}
152198

0 commit comments

Comments
 (0)