domma-cms 0.54.2 → 0.66.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/CLAUDE.md +151 -77
- package/README.md +4 -4
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +3 -3
- package/admin/js/lib/page-picker.js +1 -0
- package/admin/js/lib/plugin-accent.js +1 -0
- package/admin/js/lib/plugin-chrome.js +1 -0
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/sidebar-grouping.js +1 -1
- package/admin/js/lib/sidebar-grouping.test.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/lib/slideover-resizable.js +1 -0
- package/admin/js/templates/context-menu-editor.html +212 -0
- package/admin/js/templates/context-menus.html +16 -0
- package/admin/js/templates/menu-editor.html +19 -25
- package/admin/js/templates/plugin-code.html +1 -1
- package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
- package/admin/js/templates/settings.html +27 -97
- package/admin/js/templates/theme.html +173 -0
- package/admin/js/views/context-menu-editor.js +55 -0
- package/admin/js/views/context-menus.js +5 -0
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/menu-editor.js +13 -13
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +25 -23
- package/admin/js/views/search.js +1 -0
- package/admin/js/views/settings.js +3 -3
- package/admin/js/views/theme.js +1 -0
- package/bin/cli.js +11 -2
- package/bin/lib/smtp-defaults.js +53 -0
- package/bin/update.js +13 -2
- package/config/menus/admin-sidebar.json +129 -23
- package/config/plugins.json +5 -5
- package/config/search.json +13 -0
- package/config/theme.json +18 -0
- package/package.json +12 -4
- package/plugins/_lib/admin/mail/compose-window.js +914 -0
- package/plugins/_lib/admin/mail/contacts.js +301 -0
- package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
- package/plugins/_lib/admin/mail/folder-tree.js +254 -0
- package/plugins/_lib/admin/mail/identity.js +480 -0
- package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
- package/plugins/_lib/admin/mail/keyboard.js +136 -0
- package/plugins/_lib/admin/mail/mail.css +1 -0
- package/plugins/_lib/admin/mail/mail.html +71 -0
- package/plugins/_lib/admin/mail/panes.js +253 -0
- package/plugins/_lib/admin/mail/reader-view.js +4453 -0
- package/plugins/_lib/admin/mail/resizable.js +26 -0
- package/plugins/_lib/admin/mail/rules.js +343 -0
- package/plugins/_lib/admin/mail/scheduling.js +203 -0
- package/plugins/_lib/admin/mail/section-kit.js +277 -0
- package/plugins/_lib/admin/mail/templates.js +238 -0
- package/plugins/_lib/admin/mail/threads.js +200 -0
- package/plugins/_lib/admin/mail/vacation.js +269 -0
- package/plugins/_lib/admin/ui/help.css +1 -0
- package/plugins/_lib/admin/ui/help.js +174 -0
- package/plugins/_lib/admin/ui/resizable.js +151 -0
- package/plugins/_lib/dataStore.js +117 -0
- package/plugins/_lib/mail/accounts.js +919 -0
- package/plugins/_lib/mail/bodyTokens.js +101 -0
- package/plugins/_lib/mail/compose.js +256 -0
- package/plugins/_lib/mail/defaults.js +57 -0
- package/plugins/_lib/mail/diagnostics.js +154 -0
- package/plugins/_lib/mail/envelope.js +161 -0
- package/plugins/_lib/mail/folders.js +192 -0
- package/plugins/_lib/mail/handoff.js +274 -0
- package/plugins/_lib/mail/imapPool.js +293 -0
- package/plugins/_lib/mail/mbox.js +74 -0
- package/plugins/_lib/mail/pollSchedule.js +79 -0
- package/plugins/_lib/mail/poller.js +135 -0
- package/plugins/_lib/mail/priority.js +138 -0
- package/plugins/_lib/mail/readRoutes.js +680 -0
- package/plugins/_lib/mail/render.js +291 -0
- package/plugins/_lib/mail/ruleRunner.js +152 -0
- package/plugins/_lib/mail/scheduler.js +254 -0
- package/plugins/_lib/mail/secretbox.js +229 -0
- package/plugins/_lib/mail/send.js +396 -0
- package/plugins/_lib/mail/store.js +1002 -0
- package/plugins/_lib/mail/sync.js +292 -0
- package/plugins/_lib/mail/syncPlan.js +126 -0
- package/plugins/_lib/mail/syncSelection.js +82 -0
- package/plugins/_lib/mail/unsubscribe.js +183 -0
- package/plugins/_lib/mail/vacationRunner.js +114 -0
- package/plugins/_lib/mail/write.js +473 -0
- package/plugins/_lib/schemaSync.js +83 -0
- package/plugins/_template/admin/css/index.css +0 -0
- package/plugins/_template/admin/templates/index.html +4 -4
- package/plugins/_template/admin/views/index.js +7 -0
- package/plugins/analytics/admin/css/index.css +1 -0
- package/plugins/analytics/admin/templates/analytics.html +22 -13
- package/plugins/analytics/plugin.json +3 -0
- package/plugins/blog/admin/css/index.css +1 -0
- package/plugins/blog/admin/templates/blog.html +30 -18
- package/plugins/blog/admin/templates/categories.html +2 -2
- package/plugins/blog/admin/templates/comments.html +2 -2
- package/plugins/blog/admin/templates/post-editor.html +34 -34
- package/plugins/blog/admin/templates/settings.html +6 -3
- package/plugins/blog/admin/views/blog.js +8 -5
- package/plugins/blog/admin/views/categories.js +5 -10
- package/plugins/blog/admin/views/comments.js +5 -5
- package/plugins/blog/admin/views/post-editor.js +39 -20
- package/plugins/blog/admin/views/settings.js +52 -50
- package/plugins/blog/collections/categories/schema.json +7 -6
- package/plugins/blog/collections/comments/schema.json +11 -10
- package/plugins/blog/collections/posts/schema.json +14 -13
- package/plugins/blog/plugin.js +36 -13
- package/plugins/blog/plugin.json +13 -5
- package/plugins/blog/plugin.public.js +312 -0
- package/plugins/contacts/admin/css/index.css +1 -0
- package/plugins/contacts/admin/templates/contacts.html +128 -0
- package/plugins/contacts/admin/views/contacts.js +237 -4
- package/plugins/contacts/collections/user-contacts/schema.json +108 -0
- package/plugins/contacts/plugin.js +214 -27
- package/plugins/contacts/plugin.json +4 -1
- package/plugins/invoice/admin/css/index.css +1 -0
- package/plugins/invoice/admin/templates/editor.html +140 -49
- package/plugins/invoice/admin/templates/index.html +153 -23
- package/plugins/invoice/admin/templates/issuers.html +2 -5
- package/plugins/invoice/admin/templates/receivers.html +2 -5
- package/plugins/invoice/admin/views/contacts-source.js +266 -0
- package/plugins/invoice/admin/views/editor.js +366 -199
- package/plugins/invoice/admin/views/export.js +199 -0
- package/plugins/invoice/admin/views/help-content.js +61 -0
- package/plugins/invoice/admin/views/index.js +582 -94
- package/plugins/invoice/admin/views/issuers.js +24 -17
- package/plugins/invoice/admin/views/media.js +172 -0
- package/plugins/invoice/admin/views/party-view.js +305 -67
- package/plugins/invoice/admin/views/payments.js +127 -0
- package/plugins/invoice/admin/views/print.js +130 -0
- package/plugins/invoice/admin/views/receivers.js +49 -16
- package/plugins/invoice/admin/views/send.js +212 -0
- package/plugins/invoice/admin/views/settings.js +594 -0
- package/plugins/invoice/admin/views/view-lifecycle.js +33 -0
- package/plugins/invoice/collections/invoice-issuers/schema.json +77 -11
- package/plugins/invoice/collections/invoice-receivers/schema.json +10 -9
- package/plugins/invoice/collections/invoices/schema.json +19 -13
- package/plugins/invoice/config.js +27 -6
- package/plugins/invoice/pdf.js +164 -0
- package/plugins/invoice/plugin.js +1217 -44
- package/plugins/invoice/plugin.json +10 -9
- package/plugins/invoice/templates/_base.css +1 -0
- package/plugins/invoice/templates/classic-nologo.html +100 -0
- package/plugins/invoice/templates/classic.html +91 -0
- package/plugins/invoice/templates/invoice-print.html +24 -0
- package/plugins/invoice/templates/minimal.html +99 -0
- package/plugins/invoice/templates/modern-nologo.html +114 -0
- package/plugins/invoice/templates/modern.html +113 -0
- package/plugins/invoice/templates/templates.json +11 -0
- package/plugins/mail-reader/admin/views/mail.js +19 -0
- package/plugins/mail-reader/config.js +7 -0
- package/plugins/mail-reader/plugin.js +48 -0
- package/plugins/mail-reader/plugin.json +33 -0
- package/plugins/notes/admin/views/notes.js +1 -1
- package/plugins/notes/plugin.json +2 -2
- package/plugins/surveys/lib/audience.js +37 -0
- package/plugins/surveys/lib/campaigns.js +43 -0
- package/plugins/surveys/lib/ledger.js +110 -0
- package/plugins/surveys/lib/sending.js +106 -0
- package/plugins/surveys/lib/stats.js +62 -0
- package/plugins/surveys/lib/submit.js +95 -0
- package/plugins/surveys/lib/tokens.js +28 -0
- package/plugins/surveys/plugin.public.js +149 -0
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +1 -1
- package/public/css/forms.css +1 -1
- package/public/css/menu-highlight.css +1 -1
- package/public/css/search.css +1 -0
- package/public/css/site.css +1 -1
- package/public/js/collection-context.js +2 -2
- package/public/js/context-menus.js +1 -0
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -2
- package/public/js/menu-decor.mjs +1 -1
- package/public/js/search.js +1 -0
- package/public/js/site.js +1 -1
- package/scripts/build.js +37 -3
- package/scripts/copy-domma.js +48 -0
- package/scripts/seed.js +1996 -0
- package/scripts/setup.js +8 -0
- package/server/routes/api/collections.js +34 -0
- package/server/routes/api/context-menus.js +104 -0
- package/server/routes/api/forms.js +42 -3
- package/server/routes/api/notifications.js +69 -19
- package/server/routes/api/plugins.js +50 -6
- package/server/routes/api/search.js +43 -0
- package/server/routes/api/theme.js +69 -0
- package/server/routes/public.js +42 -7
- package/server/server.js +74 -0
- package/server/services/adapters/FileAdapter.js +6 -1
- package/server/services/content.js +26 -0
- package/server/services/contextMenus.js +477 -0
- package/server/services/email.js +29 -3
- package/server/services/health.js +23 -2
- package/server/services/markdown.js +70 -9
- package/server/services/menuRender.js +28 -4
- package/server/services/menus.js +25 -1
- package/server/services/permissionRegistry.js +24 -0
- package/server/services/pluginFiles.js +52 -11
- package/server/services/plugins.js +229 -6
- package/server/services/renderer.js +148 -22
- package/server/services/roles.js +1 -1
- package/server/services/search-migration.js +82 -0
- package/server/services/search.js +413 -0
- package/server/services/sidebar-migration.js +1 -0
- package/server/services/themeSettings.js +541 -0
- package/server/services/users.js +8 -0
- package/server/templates/page.html +4 -2
- package/plugins/contacts/data/contacts.json +0 -20
- package/plugins/notes/data/notes.json +0 -1
- package/plugins/site-search/admin/views/site-search.js +0 -116
- package/plugins/site-search/config.js +0 -15
- package/plugins/site-search/plugin.js +0 -188
- package/plugins/site-search/plugin.json +0 -40
- package/plugins/site-search/public/inject-body.html +0 -17
- package/plugins/site-search/public/inject-head.html +0 -1
- package/plugins/site-search/public/search.css +0 -1
- package/plugins/site-search/public/search.js +0 -1
- package/plugins/todo/data/todos.json +0 -1
|
@@ -21,6 +21,69 @@ async function api(url, method = 'GET', body) {
|
|
|
21
21
|
return text ? JSON.parse(text) : {};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The billing fields, as a map of contact key to the input that edits it.
|
|
26
|
+
*
|
|
27
|
+
* One list rather than a load/save pair spelled out twice - the way a field
|
|
28
|
+
* gets added to the form and quietly never saved.
|
|
29
|
+
*/
|
|
30
|
+
const BILLING_FIELDS = {
|
|
31
|
+
company: '#contact-company',
|
|
32
|
+
jobTitle: '#contact-job-title',
|
|
33
|
+
website: '#contact-website',
|
|
34
|
+
billingEmail: '#contact-billing-email',
|
|
35
|
+
vatNumber: '#contact-vat-number',
|
|
36
|
+
companyNumber: '#contact-company-number',
|
|
37
|
+
reference: '#contact-reference',
|
|
38
|
+
paymentTerms: '#contact-payment-terms',
|
|
39
|
+
currency: '#contact-currency',
|
|
40
|
+
accountName: '#contact-account-name',
|
|
41
|
+
bankName: '#contact-bank-name',
|
|
42
|
+
sortCode: '#contact-sort-code',
|
|
43
|
+
accountNumber: '#contact-account-number',
|
|
44
|
+
iban: '#contact-iban',
|
|
45
|
+
bic: '#contact-bic'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The repeatable lists, and the columns each row is made of.
|
|
50
|
+
*
|
|
51
|
+
* `wide` gets a full-width input; `check` is the single radio-ish flag that
|
|
52
|
+
* says which address an invoice should use.
|
|
53
|
+
*/
|
|
54
|
+
const REPEATERS = {
|
|
55
|
+
addresses: {
|
|
56
|
+
listId: 'addresses-list',
|
|
57
|
+
emptyId: 'addresses-empty',
|
|
58
|
+
columns: [
|
|
59
|
+
{key: 'label', placeholder: 'Label (Office, Home…)'},
|
|
60
|
+
{key: 'line1', placeholder: 'Address line 1', wide: true},
|
|
61
|
+
{key: 'line2', placeholder: 'Address line 2', wide: true},
|
|
62
|
+
{key: 'city', placeholder: 'City'},
|
|
63
|
+
{key: 'region', placeholder: 'County / Region'},
|
|
64
|
+
{key: 'postcode', placeholder: 'Postcode'},
|
|
65
|
+
{key: 'country', placeholder: 'Country'},
|
|
66
|
+
{key: 'isBilling', label: 'Use for invoices', check: true}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
emails: {
|
|
70
|
+
listId: 'emails-list',
|
|
71
|
+
emptyId: 'emails-empty',
|
|
72
|
+
columns: [
|
|
73
|
+
{key: 'label', placeholder: 'Label (Accounts, Personal…)'},
|
|
74
|
+
{key: 'address', placeholder: 'name@example.com', wide: true}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
phones: {
|
|
78
|
+
listId: 'phones-list',
|
|
79
|
+
emptyId: 'phones-empty',
|
|
80
|
+
columns: [
|
|
81
|
+
{key: 'label', placeholder: 'Label (Mobile, Office…)'},
|
|
82
|
+
{key: 'number', placeholder: '+44 …', wide: true}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
24
87
|
export const contactsView = {
|
|
25
88
|
templateUrl: '/plugins/contacts/admin/templates/contacts.html',
|
|
26
89
|
|
|
@@ -239,6 +302,141 @@ export const contactsView = {
|
|
|
239
302
|
// Editor
|
|
240
303
|
// ---------------------------------------------------------------
|
|
241
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Draw one repeatable list.
|
|
307
|
+
*
|
|
308
|
+
* Built with DOM APIs rather than innerHTML because these rows carry
|
|
309
|
+
* inputs, and Domma's .html() sanitises interactive elements away.
|
|
310
|
+
*
|
|
311
|
+
* @param {string} field addresses | emails | phones
|
|
312
|
+
* @param {object[]} rows
|
|
313
|
+
*/
|
|
314
|
+
function renderRepeater(field, rows) {
|
|
315
|
+
const spec = REPEATERS[field];
|
|
316
|
+
const listEl = $container.find('#' + spec.listId).get(0);
|
|
317
|
+
const emptyEl = $container.find('#' + spec.emptyId).get(0);
|
|
318
|
+
if (!listEl) return;
|
|
319
|
+
|
|
320
|
+
while (listEl.firstChild) listEl.removeChild(listEl.firstChild);
|
|
321
|
+
if (emptyEl) emptyEl.style.display = rows.length ? 'none' : '';
|
|
322
|
+
|
|
323
|
+
rows.forEach((row, index) => {
|
|
324
|
+
const rowEl = document.createElement('div');
|
|
325
|
+
rowEl.className = 'contacts-row';
|
|
326
|
+
rowEl.dataset.index = String(index);
|
|
327
|
+
|
|
328
|
+
for (const col of spec.columns) {
|
|
329
|
+
if (col.check) {
|
|
330
|
+
const wrap = document.createElement('label');
|
|
331
|
+
wrap.className = 'form-check contacts-row-check';
|
|
332
|
+
const box = document.createElement('input');
|
|
333
|
+
box.type = 'checkbox';
|
|
334
|
+
box.className = 'form-check-input';
|
|
335
|
+
box.dataset.key = col.key;
|
|
336
|
+
box.checked = row[col.key] === true;
|
|
337
|
+
// Only one address can be the billing one.
|
|
338
|
+
box.addEventListener('change', () => {
|
|
339
|
+
if (!box.checked) return;
|
|
340
|
+
listEl.querySelectorAll(`input[data-key="${col.key}"]`).forEach((other) => {
|
|
341
|
+
if (other !== box) other.checked = false;
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
const text = document.createElement('span');
|
|
345
|
+
text.className = 'form-check-label';
|
|
346
|
+
text.textContent = col.label;
|
|
347
|
+
wrap.appendChild(box);
|
|
348
|
+
wrap.appendChild(text);
|
|
349
|
+
rowEl.appendChild(wrap);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const input = document.createElement('input');
|
|
354
|
+
input.type = 'text';
|
|
355
|
+
input.className = 'form-input' + (col.wide ? ' contacts-row-wide' : '');
|
|
356
|
+
input.dataset.key = col.key;
|
|
357
|
+
input.placeholder = col.placeholder ?? '';
|
|
358
|
+
input.value = row[col.key] ?? '';
|
|
359
|
+
rowEl.appendChild(input);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const remove = document.createElement('button');
|
|
363
|
+
remove.type = 'button';
|
|
364
|
+
remove.className = 'btn btn-sm btn-ghost contacts-row-remove';
|
|
365
|
+
remove.title = 'Remove';
|
|
366
|
+
remove.textContent = '\u00d7';
|
|
367
|
+
remove.addEventListener('click', () => {
|
|
368
|
+
const next = readRepeater(field);
|
|
369
|
+
next.splice(index, 1);
|
|
370
|
+
renderRepeater(field, next);
|
|
371
|
+
updateBillingSummary();
|
|
372
|
+
});
|
|
373
|
+
rowEl.appendChild(remove);
|
|
374
|
+
|
|
375
|
+
listEl.appendChild(rowEl);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Read a repeatable list back out of the DOM.
|
|
381
|
+
*
|
|
382
|
+
* @param {string} field
|
|
383
|
+
* @returns {object[]}
|
|
384
|
+
*/
|
|
385
|
+
function readRepeater(field) {
|
|
386
|
+
const spec = REPEATERS[field];
|
|
387
|
+
const listEl = $container.find('#' + spec.listId).get(0);
|
|
388
|
+
if (!listEl) return [];
|
|
389
|
+
|
|
390
|
+
return [...listEl.querySelectorAll('.contacts-row')].map((rowEl) => {
|
|
391
|
+
const row = {};
|
|
392
|
+
for (const col of spec.columns) {
|
|
393
|
+
const el = rowEl.querySelector(`[data-key="${col.key}"]`);
|
|
394
|
+
row[col.key] = col.check ? el?.checked === true : (el?.value ?? '').trim();
|
|
395
|
+
}
|
|
396
|
+
return row;
|
|
397
|
+
}).filter((row) => Object.entries(row).some(([k, v]) => k !== 'label' && v !== '' && v !== false));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** A one-line "what is in here" for the folded billing section. */
|
|
401
|
+
function updateBillingSummary() {
|
|
402
|
+
const el = $container.find('#billing-summary').get(0);
|
|
403
|
+
if (!el) return;
|
|
404
|
+
const bits = [];
|
|
405
|
+
const company = $container.find('#contact-company').get(0)?.value?.trim();
|
|
406
|
+
if (company) bits.push(company);
|
|
407
|
+
const addresses = readRepeater('addresses').length;
|
|
408
|
+
if (addresses) bits.push(`${addresses} address${addresses === 1 ? '' : 'es'}`);
|
|
409
|
+
const extras = readRepeater('emails').length + readRepeater('phones').length;
|
|
410
|
+
if (extras) bits.push(`${extras} more contact${extras === 1 ? '' : 's'}`);
|
|
411
|
+
if ($container.find('#contact-sort-code').get(0)?.value?.trim()
|
|
412
|
+
|| $container.find('#contact-iban').get(0)?.value?.trim()) bits.push('bank details');
|
|
413
|
+
el.textContent = bits.length ? bits.join(' \u00b7 ') : '';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Fill the billing inputs from a contact, or clear them for a new one. */
|
|
417
|
+
function loadBilling(contact) {
|
|
418
|
+
for (const [key, sel] of Object.entries(BILLING_FIELDS)) {
|
|
419
|
+
const el = $container.find(sel).get(0);
|
|
420
|
+
if (!el) continue;
|
|
421
|
+
const value = contact?.[key];
|
|
422
|
+
el.value = value === null || value === undefined ? '' : String(value);
|
|
423
|
+
}
|
|
424
|
+
for (const field of Object.keys(REPEATERS)) {
|
|
425
|
+
renderRepeater(field, Array.isArray(contact?.[field]) ? contact[field] : []);
|
|
426
|
+
}
|
|
427
|
+
updateBillingSummary();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Read the billing inputs back into the body a save posts. */
|
|
431
|
+
function readBilling() {
|
|
432
|
+
const body = {};
|
|
433
|
+
for (const [key, sel] of Object.entries(BILLING_FIELDS)) {
|
|
434
|
+
body[key] = ($container.find(sel).get(0)?.value ?? '').trim();
|
|
435
|
+
}
|
|
436
|
+
for (const field of Object.keys(REPEATERS)) body[field] = readRepeater(field);
|
|
437
|
+
return body;
|
|
438
|
+
}
|
|
439
|
+
|
|
242
440
|
function openEditor(id) {
|
|
243
441
|
currentEditId = id ?? null;
|
|
244
442
|
selectedGroups = [];
|
|
@@ -269,6 +467,7 @@ export const contactsView = {
|
|
|
269
467
|
if (notesEl) notesEl.value = c.notes ?? '';
|
|
270
468
|
if (favEl) favEl.checked = c.favourite === true;
|
|
271
469
|
selectedGroups = Array.isArray(c.groups) ? [...c.groups] : [];
|
|
470
|
+
loadBilling(c);
|
|
272
471
|
} else {
|
|
273
472
|
if (titleEl) titleEl.textContent = 'Add Contact';
|
|
274
473
|
if (saveLbl) saveLbl.textContent = 'Save Contact';
|
|
@@ -284,6 +483,7 @@ export const contactsView = {
|
|
|
284
483
|
if (notesEl) notesEl.value = '';
|
|
285
484
|
if (favEl) favEl.checked = false;
|
|
286
485
|
selectedGroups = [];
|
|
486
|
+
loadBilling(null);
|
|
287
487
|
}
|
|
288
488
|
|
|
289
489
|
updateGroupsDisplay();
|
|
@@ -337,7 +537,8 @@ export const contactsView = {
|
|
|
337
537
|
phone: ($container.find('#contact-phone').get(0)?.value ?? '').trim(),
|
|
338
538
|
notes: ($container.find('#contact-notes').get(0)?.value ?? '').trim(),
|
|
339
539
|
favourite: $container.find('#contact-favourite').get(0)?.checked === true,
|
|
340
|
-
groups: selectedGroups
|
|
540
|
+
groups: selectedGroups,
|
|
541
|
+
...readBilling()
|
|
341
542
|
};
|
|
342
543
|
|
|
343
544
|
try {
|
|
@@ -562,6 +763,36 @@ export const contactsView = {
|
|
|
562
763
|
const addBtn = $container.find('#add-contact-btn').get(0);
|
|
563
764
|
if (addBtn) addBtn.addEventListener('click', () => openEditor(null));
|
|
564
765
|
|
|
766
|
+
// Billing section: fold, unfold, and the three Add buttons.
|
|
767
|
+
const billingToggle = $container.find('#billing-toggle').get(0);
|
|
768
|
+
const billingBody = $container.find('#billing-body').get(0);
|
|
769
|
+
if (billingToggle && billingBody) {
|
|
770
|
+
billingToggle.addEventListener('click', () => {
|
|
771
|
+
const open = billingBody.classList.toggle('is-open');
|
|
772
|
+
// The chevron is ROTATED rather than swapped for a different
|
|
773
|
+
// icon: I.scan replaces the span with a fresh <svg>, so the
|
|
774
|
+
// element you held a reference to is gone and setting
|
|
775
|
+
// data-icon on it changes nothing. A CSS transform needs no
|
|
776
|
+
// element identity at all, and animates for free.
|
|
777
|
+
billingToggle.classList.toggle('is-open', open);
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
for (const [field, btnId] of [['addresses', '#add-address-btn'], ['emails', '#add-email-btn'], ['phones', '#add-phone-btn']]) {
|
|
782
|
+
const btn = $container.find(btnId).get(0);
|
|
783
|
+
if (!btn) continue;
|
|
784
|
+
btn.addEventListener('click', () => {
|
|
785
|
+
renderRepeater(field, [...readRepeater(field), {}]);
|
|
786
|
+
updateBillingSummary();
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// The folded summary should follow what is typed above it.
|
|
791
|
+
for (const sel of ['#contact-company', '#contact-sort-code', '#contact-iban']) {
|
|
792
|
+
const el = $container.find(sel).get(0);
|
|
793
|
+
if (el) el.addEventListener('input', updateBillingSummary);
|
|
794
|
+
}
|
|
795
|
+
|
|
565
796
|
// Editor save
|
|
566
797
|
const editorSaveBtn = $container.find('#editor-save-btn').get(0);
|
|
567
798
|
if (editorSaveBtn) editorSaveBtn.addEventListener('click', saveContact);
|
|
@@ -673,13 +904,15 @@ export const contactsView = {
|
|
|
673
904
|
// Groups panel collapse toggle
|
|
674
905
|
const panelToggle = $container.find('#groups-panel-toggle').get(0);
|
|
675
906
|
const panelBody = $container.find('#groups-panel-body').get(0);
|
|
676
|
-
const chevron = $container.find('#groups-chevron').get(0);
|
|
677
907
|
if (panelToggle && panelBody) {
|
|
678
908
|
panelToggle.addEventListener('click', () => {
|
|
679
909
|
const open = panelBody.style.display !== 'none';
|
|
680
910
|
panelBody.style.display = open ? 'none' : '';
|
|
681
|
-
|
|
682
|
-
|
|
911
|
+
// Rotated, not swapped: I.scan replaces the icon span with a
|
|
912
|
+
// fresh <svg> and does not carry the id across, so the
|
|
913
|
+
// reference this held was stale after the first scan and the
|
|
914
|
+
// chevron never turned.
|
|
915
|
+
panelToggle.classList.toggle('is-open', !open);
|
|
683
916
|
});
|
|
684
917
|
}
|
|
685
918
|
|
|
@@ -40,6 +40,114 @@
|
|
|
40
40
|
"type": "text",
|
|
41
41
|
"required": false
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
"name": "company",
|
|
45
|
+
"label": "Company",
|
|
46
|
+
"type": "text",
|
|
47
|
+
"required": false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "jobTitle",
|
|
51
|
+
"label": "Job Title",
|
|
52
|
+
"type": "text",
|
|
53
|
+
"required": false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "website",
|
|
57
|
+
"label": "Website",
|
|
58
|
+
"type": "text",
|
|
59
|
+
"required": false
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "vatNumber",
|
|
63
|
+
"label": "VAT Number",
|
|
64
|
+
"type": "text",
|
|
65
|
+
"required": false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "companyNumber",
|
|
69
|
+
"label": "Company Number",
|
|
70
|
+
"type": "text",
|
|
71
|
+
"required": false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "reference",
|
|
75
|
+
"label": "Their Reference",
|
|
76
|
+
"type": "text",
|
|
77
|
+
"required": false
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "billingEmail",
|
|
81
|
+
"label": "Billing Email",
|
|
82
|
+
"type": "text",
|
|
83
|
+
"required": false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "paymentTerms",
|
|
87
|
+
"label": "Payment Terms (days)",
|
|
88
|
+
"type": "number",
|
|
89
|
+
"required": false
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "currency",
|
|
93
|
+
"label": "Preferred Currency",
|
|
94
|
+
"type": "text",
|
|
95
|
+
"required": false
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "addresses",
|
|
99
|
+
"label": "Addresses",
|
|
100
|
+
"type": "json",
|
|
101
|
+
"required": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "emails",
|
|
105
|
+
"label": "Additional Emails",
|
|
106
|
+
"type": "json",
|
|
107
|
+
"required": false
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "phones",
|
|
111
|
+
"label": "Additional Phones",
|
|
112
|
+
"type": "json",
|
|
113
|
+
"required": false
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "accountName",
|
|
117
|
+
"label": "Account Name",
|
|
118
|
+
"type": "text",
|
|
119
|
+
"required": false
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "bankName",
|
|
123
|
+
"label": "Bank",
|
|
124
|
+
"type": "text",
|
|
125
|
+
"required": false
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "sortCode",
|
|
129
|
+
"label": "Sort Code",
|
|
130
|
+
"type": "text",
|
|
131
|
+
"required": false
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "accountNumber",
|
|
135
|
+
"label": "Account Number",
|
|
136
|
+
"type": "text",
|
|
137
|
+
"required": false
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "iban",
|
|
141
|
+
"label": "IBAN",
|
|
142
|
+
"type": "text",
|
|
143
|
+
"required": false
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "bic",
|
|
147
|
+
"label": "BIC / SWIFT",
|
|
148
|
+
"type": "text",
|
|
149
|
+
"required": false
|
|
150
|
+
},
|
|
43
151
|
{
|
|
44
152
|
"name": "userId",
|
|
45
153
|
"label": "User ID",
|