Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.

Commit 9719467

Browse files
chore: promote main to stable
2 parents 3ccf748 + bb9323c commit 9719467

File tree

9 files changed

+411
-136
lines changed

9 files changed

+411
-136
lines changed

.github/workflows/release-package.yml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,22 @@ jobs:
5252
run: |
5353
pnpm dlx semantic-release
5454
55-
- name: Sync stable back to main
55+
- name: Create sync PR to main
5656
if: github.ref == 'refs/heads/stable'
5757
env:
5858
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
59+
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
5960
run: |
60-
git config user.name "github-actions[bot]"
61-
git config user.email "github-actions[bot]@users.noreply.github.com"
62-
git fetch origin main stable
63-
git checkout main
64-
git merge origin/stable -m "chore: sync stable release to main"
65-
git push origin main
61+
pr_number=$(gh pr list --base main --head stable --state open --json number --jq '.[0].number')
62+
if [ -z "$pr_number" ]; then
63+
git fetch origin main
64+
if git diff --quiet origin/main...HEAD; then
65+
echo "Main is already up to date with stable; skipping PR."
66+
exit 0
67+
fi
68+
pr_number=$(gh pr create --base main --head stable --title "chore: sync stable release to main" --body "Automated PR to merge the latest stable changes back into main." --json number --jq '.number')
69+
echo "Created PR #$pr_number to sync stable into main."
70+
else
71+
echo "PR #$pr_number already open to sync stable into main."
72+
fi
73+
gh pr merge "$pr_number" --merge --auto || echo "Auto-merge setup skipped (conditions not met)."

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ examples/**/yarn.lock
2525

2626
# Dev
2727
dev/**
28+
CLAUDE.md

README.md

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ function TemplateEditor() {
2424

2525
fields={{
2626
available: [
27-
{ id: 'customer_name', label: 'Customer Name', category: 'Contact' },
28-
{ id: 'invoice_date', label: 'Invoice Date', category: 'Invoice' },
29-
{ id: 'amount', label: 'Amount', category: 'Invoice' }
27+
{ id: '1324567890', label: 'Customer Name', category: 'Contact' },
28+
{ id: '1324567891', label: 'Invoice Date', category: 'Invoice' },
29+
{ id: '1324567892', label: 'Amount', category: 'Invoice' }
3030
]
3131
}}
3232

@@ -47,8 +47,8 @@ function TemplateEditor() {
4747
```javascript
4848
{
4949
fields: [
50-
{ id: "field_123", alias: "Customer Name", tag: "contact" },
51-
{ id: "field_124", alias: "Invoice Date", tag: "invoice" }
50+
{ id: "1324567890", alias: "Customer Name", tag: "contact" },
51+
{ id: "1324567891", alias: "Invoice Date", tag: "invoice" }
5252
],
5353
document: { /* ProseMirror document JSON */ }
5454
}
@@ -206,25 +206,77 @@ function TemplateEditor() {
206206
207207
## Export Template
208208
209-
Get the complete template data for saving:
209+
The `exportTemplate` method supports two modes of operation via the `ExportConfig` interface:
210+
211+
### 1. Download Mode (Default)
212+
213+
Automatically downloads the template as a file in the browser:
214+
215+
```jsx
216+
const handleDownload = async () => {
217+
// Download with default filename "document.docx"
218+
await ref.current?.exportTemplate();
219+
220+
// Or with custom filename
221+
await ref.current?.exportTemplate({
222+
fileName: 'invoice-template.docx'
223+
});
224+
};
225+
```
226+
227+
### 2. Blob Mode (for Database/API)
228+
229+
Get the template as a Blob for saving to your database or API:
210230
211231
```jsx
212232
const handleSave = async () => {
213-
await ref.current?.exportTemplate({ fileName: 'invoice.docx' });
233+
// Get the blob without triggering download
234+
const blob = await ref.current?.exportTemplate({
235+
fileName: 'invoice-template.docx',
236+
triggerDownload: false
237+
});
238+
239+
if (blob) {
240+
// Send to your API/database
241+
const formData = new FormData();
242+
formData.append('template', blob, 'invoice-template.docx');
243+
244+
await fetch('/api/templates', {
245+
method: 'POST',
246+
body: formData
247+
});
248+
}
214249
};
215250
```
216251
252+
### ExportConfig Interface
253+
254+
```typescript
255+
interface ExportConfig {
256+
fileName?: string; // Default: "document"
257+
triggerDownload?: boolean; // Default: true
258+
}
259+
260+
// Method signature
261+
exportTemplate(config?: ExportConfig): Promise<void | Blob>
262+
```
263+
264+
**Return value:**
265+
- `Promise<void>` when `triggerDownload: true` (download happens automatically)
266+
- `Promise<Blob>` when `triggerDownload: false` (returns the docx data)
267+
217268
## TypeScript
218269
219270
Full TypeScript support included:
220271
221272
```typescript
222273
import SuperDocTemplateBuilder from '@superdoc-dev/template-builder';
223-
import type {
274+
import type {
224275
TemplateField,
225276
FieldDefinition,
226277
TriggerEvent,
227-
SuperDocTemplateBuilderHandle
278+
ExportConfig,
279+
SuperDocTemplateBuilderHandle
228280
} from '@superdoc-dev/template-builder';
229281

230282
const ref = useRef<SuperDocTemplateBuilderHandle>(null);

demo/src/App.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ header {
8383
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
8484
}
8585

86+
.toolbar-right {
87+
display: flex;
88+
align-items: center;
89+
gap: 0.75rem;
90+
}
91+
8692
.toolbar-left {
8793
display: flex;
8894
align-items: center;
@@ -113,6 +119,38 @@ header {
113119
background: #0051d5;
114120
}
115121

122+
.export-button:disabled,
123+
.import-button:disabled {
124+
cursor: not-allowed;
125+
opacity: 0.7;
126+
}
127+
128+
.import-button {
129+
padding: 0.5rem 1.25rem;
130+
background: white;
131+
color: #007aff;
132+
border: 1px solid #b0d4ff;
133+
border-radius: 8px;
134+
font-weight: 600;
135+
cursor: pointer;
136+
transition: background 0.2s, color 0.2s, border-color 0.2s;
137+
}
138+
139+
.import-button:hover {
140+
background: #e8f2ff;
141+
border-color: #007aff;
142+
}
143+
144+
.toolbar-error {
145+
margin-top: 0.75rem;
146+
padding: 0.75rem 1rem;
147+
background: #fef2f2;
148+
color: #b91c1c;
149+
border: 1px solid #fecaca;
150+
border-radius: 8px;
151+
font-size: 0.875rem;
152+
}
153+
116154
/* Template Builder Container */
117155
.superdoc-template-builder {
118156
background: white;

demo/src/App.tsx

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,38 @@ import "superdoc/dist/style.css";
99
import "./App.css";
1010

1111
const availableFields: FieldDefinition[] = [
12-
// Contact Information
13-
{ id: "customer_name", label: "Customer Name", category: "Contact" },
14-
{ id: "customer_email", label: "Customer Email", category: "Contact" },
15-
{ id: "customer_phone", label: "Customer Phone", category: "Contact" },
16-
{ id: "customer_address", label: "Customer Address", category: "Contact" },
17-
18-
// Company Information
19-
{ id: 'company_name', label: 'Company Name', category: 'Company' },
20-
{ id: 'company_address', label: 'Company Address', category: 'Company' },
21-
{ id: 'company_phone', label: 'Company Phone', category: 'Company' },
22-
{ id: 'company_email', label: 'Company Email', category: 'Company' },
23-
24-
// Invoice/Order
25-
{ id: 'invoice_number', label: 'Invoice Number', category: 'Invoice' },
26-
{ id: 'invoice_date', label: 'Invoice Date', category: 'Invoice' },
27-
{ id: 'due_date', label: 'Due Date', category: 'Invoice' },
28-
{ id: 'total_amount', label: 'Total Amount', category: 'Invoice' },
29-
{ id: 'tax_amount', label: 'Tax Amount', category: 'Invoice' },
30-
{ id: 'subtotal', label: 'Subtotal', category: 'Invoice' },
12+
// Agreement
13+
{ id: '1242142770', label: 'Agreement Date', category: 'Agreement' },
14+
15+
// Parties
16+
{ id: '1242142771', label: 'User Name', category: 'Parties' },
17+
{ id: '1242142772', label: 'Company Name', category: 'Parties' },
18+
19+
// Scope
20+
{ id: '1242142773', label: 'Service Type', category: 'Scope' },
3121

3222
// Legal
33-
{ id: 'effective_date', label: 'Effective Date', category: 'Legal' },
34-
{ id: 'termination_date', label: 'Termination Date', category: 'Legal' },
35-
{ id: 'jurisdiction', label: 'Jurisdiction', category: 'Legal' },
36-
{ id: 'governing_law', label: 'Governing Law', category: 'Legal' },
37-
38-
// Product/Service
39-
{ id: 'product_name', label: 'Product Name', category: 'Product' },
40-
{ id: 'product_description', label: 'Product Description', category: 'Product' },
41-
{ id: 'quantity', label: 'Quantity', category: 'Product' },
42-
{ id: 'unit_price', label: 'Unit Price', category: 'Product' },
23+
{ id: '1242142774', label: 'Agreement Jurisdiction', category: 'Legal' },
24+
25+
// Company Details
26+
{ id: '1242142775', label: 'Company Address', category: 'Company' },
27+
28+
// Signatures
29+
{ id: '1242142776', label: 'Signature', category: 'Signatures' },
4330
];
4431

4532
export function App() {
4633
const [fields, setFields] = useState<TemplateField[]>([]);
4734
const [events, setEvents] = useState<string[]>([]);
4835
const [isDownloading, setIsDownloading] = useState(false);
36+
const [isImporting, setIsImporting] = useState(false);
37+
const [importError, setImportError] = useState<string | null>(null);
38+
const [documentSource, setDocumentSource] = useState<string | File>(
39+
"https://storage.googleapis.com/public_static_hosting/public_demo_docs/new_service_agreement.docx",
40+
);
4941
const builderRef = useRef<SuperDocTemplateBuilderHandle>(null);
42+
const fileInputRef = useRef<HTMLInputElement>(null);
43+
const importingRef = useRef(false);
5044

5145
const log = useCallback((msg: string) => {
5246
const time = new Date().toLocaleTimeString();
@@ -63,7 +57,7 @@ export function App() {
6357
log(`✓ Inserted: ${field.alias}`);
6458
}, [log]);
6559

66-
const handleFieldDelete = useCallback((fieldId: string) => {
60+
const handleFieldDelete = useCallback((fieldId: string | number) => {
6761
log(`✗ Deleted: ${fieldId}`);
6862
}, [log]);
6963

@@ -75,6 +69,12 @@ export function App() {
7569

7670
const handleReady = useCallback(() => {
7771
log('✓ Template builder ready');
72+
if (importingRef.current) {
73+
log('📄 Document imported');
74+
importingRef.current = false;
75+
setImportError(null);
76+
setIsImporting(false);
77+
}
7878
}, [log]);
7979

8080
const handleTrigger = useCallback(() => {
@@ -114,9 +114,35 @@ export function App() {
114114
};
115115

116116
const documentConfig = useMemo(() => ({
117-
source: "https://storage.googleapis.com/public_static_hosting/public_demo_docs/service_agreement.docx",
117+
source: documentSource,
118118
mode: 'editing' as const
119-
}), []);
119+
}), [documentSource]);
120+
121+
const handleImportButtonClick = useCallback(() => {
122+
if (isImporting) return;
123+
fileInputRef.current?.click();
124+
}, [isImporting]);
125+
126+
const handleFileInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
127+
const file = event.target.files?.[0];
128+
event.target.value = "";
129+
130+
if (!file) return;
131+
132+
const extension = file.name.split('.').pop()?.toLowerCase();
133+
if (extension !== 'docx') {
134+
const message = 'Invalid file type. Please choose a .docx file.';
135+
setImportError(message);
136+
log('⚠️ ' + message);
137+
return;
138+
}
139+
140+
importingRef.current = true;
141+
setImportError(null);
142+
setIsImporting(true);
143+
setDocumentSource(file);
144+
log(`📥 Importing "${file.name}"`);
145+
}, [log]);
120146

121147
const fieldsConfig = useMemo(() => ({
122148
available: availableFields,
@@ -160,16 +186,36 @@ export function App() {
160186
<span className="hint">Tab/Shift+Tab to navigate</span>
161187
</div>
162188
<div className="toolbar-right">
189+
<input
190+
type="file"
191+
accept=".docx"
192+
ref={fileInputRef}
193+
style={{ display: 'none' }}
194+
onChange={handleFileInputChange}
195+
/>
196+
<button
197+
onClick={handleImportButtonClick}
198+
className="import-button"
199+
disabled={isImporting || isDownloading}
200+
>
201+
{isImporting ? 'Importing…' : 'Import File'}
202+
</button>
163203
<button
164204
onClick={handleExportTemplate}
165205
className="export-button"
166-
disabled={isDownloading}
206+
disabled={isDownloading || isImporting}
167207
>
168208
{isDownloading ? "Exporting..." : "Export Template"}
169209
</button>
170210
</div>
171211
</div>
172212

213+
{importError && (
214+
<div className="toolbar-error" role="alert">
215+
{importError}
216+
</div>
217+
)}
218+
173219
<SuperDocTemplateBuilder
174220
ref={builderRef}
175221
document={documentConfig}

0 commit comments

Comments
 (0)