formlab-mcp 0.6.55 → 0.6.56
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/package.json +1 -1
- package/server.json +2 -2
- package/vendor/cost.js +39 -11
- package/vendor/equipment-calibration.js +42 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.56",
|
|
4
4
|
"mcpName": "io.github.juliu1980/formlab-mcp",
|
|
5
5
|
"description": "Read-only Model Context Protocol server for FormLab \u2014 lets Claude (and other MCP clients) read and analyze your FormLab data, from a local export file OR your live cloud workspace.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.juliu1980/formlab-mcp",
|
|
4
4
|
"description": "Read-only MCP for FormLab — let Claude query your formulation lab. Free reads a local export; Pro connects to your live cloud workspace with a dedicated read-only token.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.56",
|
|
6
6
|
"websiteUrl": "https://formvix.com/mcp",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/juliu1980/FormLab",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "formlab-mcp",
|
|
16
|
-
"version": "0.6.
|
|
16
|
+
"version": "0.6.56",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/vendor/cost.js
CHANGED
|
@@ -138,28 +138,39 @@ function getAllUnits() {
|
|
|
138
138
|
const custom = Object.keys((typeof db !== 'undefined' && db?.customUnits) || {});
|
|
139
139
|
return [...new Set([...builtin, ...custom])];
|
|
140
140
|
}
|
|
141
|
+
// The units people reach for first, in that order (22x). Every list puts these ahead of the
|
|
142
|
+
// rest of their group (the picker groups by Mass / Volume / …), so a list opens on g, kg, mg —
|
|
143
|
+
// not on µg, ug, mcg, the size order the conversion tables are written in. Everything else,
|
|
144
|
+
// custom units included, keeps its place after them.
|
|
145
|
+
const _UNITS_COMMON_FIRST = ['wt%', 'vol%', 'g', 'kg', 'mg', 'µg', 'lb', 'oz', 't', 'tonne', 'ton',
|
|
146
|
+
'mL', 'L', 'µL', 'gal', 'qt', 'pt', 'fl_oz', 'm³', 'mm', 'cm', 'm', 'in', 'ft', 'units', 'each'];
|
|
147
|
+
function unitsCommonFirst(units) {
|
|
148
|
+
const rank = new Map(_UNITS_COMMON_FIRST.map((u, i) => [u, i]));
|
|
149
|
+
return units.map((u, i) => [u, rank.has(u) ? rank.get(u) : 1000 + i]).sort((a, b) => a[1] - b[1]).map(x => x[0]);
|
|
150
|
+
}
|
|
141
151
|
function ensureUnitDatalists() {
|
|
142
152
|
if (typeof document === 'undefined' || !document.body) return;
|
|
143
153
|
const q = (s) => String(s).replace(/"/g, '"');
|
|
144
154
|
const make = (id, units) => {
|
|
145
155
|
let dl = document.getElementById(id);
|
|
146
156
|
if (!dl) { dl = document.createElement('datalist'); dl.id = id; document.body.appendChild(dl); }
|
|
147
|
-
dl.innerHTML = units.map((u) => `<option value="${q(u)}"></option>`).join('');
|
|
157
|
+
dl.innerHTML = unitsCommonFirst(units).map((u) => `<option value="${q(u)}"></option>`).join('');
|
|
148
158
|
};
|
|
149
159
|
make('fl-units-all', getAllUnits()); // composition / batch quantity
|
|
150
160
|
make('fl-units-cost', getCostUnits()); // ingredient set-price (mass/vol/length/each)
|
|
151
|
-
make('fl-units-qty', getQuantityUnits()); //
|
|
161
|
+
make('fl-units-qty', getQuantityUnits()); // an amount made: batch target, sample amount (mass/vol/count)
|
|
162
|
+
make('fl-units-vol', Object.keys(getVolUnits())); // a measured volume (22w)
|
|
152
163
|
}
|
|
153
164
|
|
|
154
|
-
// Units for a
|
|
155
|
-
//
|
|
156
|
-
//
|
|
165
|
+
// Units for an amount made — a batch's target quantity and a sample's amount (22w: one list
|
|
166
|
+
// for both; the batch had borrowed the composition list, wt% / ppm / lengths / mol included,
|
|
167
|
+
// while samples lacked tonne, bbl, m³…): every mass and volume unit (your custom ones
|
|
168
|
+
// included), plus a count. NOT fractions (wt% / ppm), which only make sense in a recipe ratio,
|
|
169
|
+
// and not lengths.
|
|
157
170
|
function getQuantityUnits() {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const custom = Object.keys((typeof db !== 'undefined' && db?.customUnits) || {});
|
|
162
|
-
return [...new Set([...mass, ...vol, ...count, ...custom])];
|
|
171
|
+
const customCount = Object.entries((typeof db !== 'undefined' && db?.customUnits) || {})
|
|
172
|
+
.filter(([_, v]) => v?.dimension === 'count').map(([k]) => k);
|
|
173
|
+
return [...new Set([...Object.keys(getMassUnits()), ...Object.keys(getVolUnits()), 'units', 'each', ...customCount])];
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
// ============================================================
|
|
@@ -309,9 +320,25 @@ function getQuantityUnits() {
|
|
|
309
320
|
}
|
|
310
321
|
|
|
311
322
|
document.addEventListener('focusin', function (e) { if (isCombo(e.target) && !refocusing) open(e.target); });
|
|
323
|
+
// The ▾ drawn at the right end (css input.fl-unit-combo) acts like a ▾ button, as
|
|
324
|
+
// js/combo.js's fields do (22v): a hand over it, and a click there closes an OPEN list.
|
|
325
|
+
// "Open" is judged at mousedown: a click into an unfocused field opens it on focusin, and
|
|
326
|
+
// that same click must not close it again.
|
|
327
|
+
var ARROW_W = 28, openAtDown = false;
|
|
328
|
+
function onArrow(el, e) { var r = el.getBoundingClientRect(); return e.clientX >= r.right - ARROW_W; }
|
|
329
|
+
document.addEventListener('mousemove', function (e) {
|
|
330
|
+
if (!isCombo(e.target)) return;
|
|
331
|
+
var c = onArrow(e.target, e) ? 'pointer' : '';
|
|
332
|
+
if (e.target.style.cursor !== c) e.target.style.cursor = c;
|
|
333
|
+
});
|
|
334
|
+
document.addEventListener('mouseout', function (e) { if (isCombo(e.target)) e.target.style.cursor = ''; });
|
|
312
335
|
// A click on the field reopens the list after a pick (the field kept focus,
|
|
313
336
|
// so no focusin fires). Capture phase: dialogs stop click propagation.
|
|
314
|
-
document.addEventListener('click', function (e) {
|
|
337
|
+
document.addEventListener('click', function (e) {
|
|
338
|
+
if (!isCombo(e.target)) return;
|
|
339
|
+
if (openAtDown && onArrow(e.target, e)) { openAtDown = false; hide(); return; }
|
|
340
|
+
if (!isOpen()) open(e.target);
|
|
341
|
+
}, true);
|
|
315
342
|
// Typing directly in the field filters too (only matters when there's no
|
|
316
343
|
// search box — i.e. short lists; otherwise focus is in the search box).
|
|
317
344
|
document.addEventListener('input', function (e) {
|
|
@@ -322,6 +349,7 @@ function getQuantityUnits() {
|
|
|
322
349
|
document.addEventListener('keydown', function (e) { if (isCombo(e.target)) navKeydown(e); });
|
|
323
350
|
// mousedown (capture) so a click on a suggestion wins over the input's blur.
|
|
324
351
|
document.addEventListener('mousedown', function (e) {
|
|
352
|
+
if (isCombo(e.target)) openAtDown = isOpen() && activeInput === e.target;
|
|
325
353
|
if (!isOpen()) return;
|
|
326
354
|
var item = e.target.closest && e.target.closest('.fl-unit-combo-item');
|
|
327
355
|
if (item) { e.preventDefault(); choose(item.getAttribute('data-val')); return; }
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
// ============================================================
|
|
17
17
|
|
|
18
18
|
const EQ_CAL_SOON_DAYS = 30;
|
|
19
|
+
// The longest interval taken as real (20 years). Anything above, at or below zero, or not a
|
|
20
|
+
// number counts as "no interval": a typo like 1e9 months used to throw in the date maths
|
|
21
|
+
// and take the Equipment list and the dashboard down with it.
|
|
22
|
+
const EQ_CAL_MAX_MONTHS = 240;
|
|
19
23
|
const EQ_CAL_RESULTS = [
|
|
20
24
|
{ value: 'pass', label: 'Pass', hint: 'within tolerance as found' },
|
|
21
25
|
{ value: 'adjusted', label: 'Adjusted, then pass', hint: 'out of tolerance as found, adjusted and verified' },
|
|
@@ -30,19 +34,31 @@ function _eqCalToday() {
|
|
|
30
34
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
31
35
|
}
|
|
32
36
|
function eqCalOn() { return typeof hasFeature !== 'function' || hasFeature('equipment.calibration'); }
|
|
37
|
+
// Whole months, 1..EQ_CAL_MAX_MONTHS, else 0.
|
|
38
|
+
function eqCalInterval(e) {
|
|
39
|
+
const n = Math.round(Number(e && e.calIntervalMonths));
|
|
40
|
+
return (n >= 1 && n <= EQ_CAL_MAX_MONTHS) ? n : 0;
|
|
41
|
+
}
|
|
42
|
+
const _EQ_CAL_ISO = /^\d{4}-\d{2}-\d{2}$/;
|
|
43
|
+
// Ids go into onclick="fn('…')": escJsAttr (utils.js) also covers the ' that esc() leaves.
|
|
44
|
+
function _eqCalJs(v) { return typeof escJsAttr === 'function' ? escJsAttr(v) : String(v == null ? '' : v).replace(/[^\w-]/g, ''); }
|
|
33
45
|
|
|
34
|
-
// Newest first; entries without a date last.
|
|
46
|
+
// Newest first; entries without a date last. Two on the same day (a fail, then the
|
|
47
|
+
// adjusted re-calibration) are ordered by when they were logged, the later first.
|
|
35
48
|
function eqCalHistory(e) {
|
|
36
|
-
const list = (e && Array.isArray(e.calibrations)) ? e.calibrations.filter(
|
|
37
|
-
return list.slice().sort((a, b) => String(b.date || '').localeCompare(String(a.date || ''))
|
|
49
|
+
const list = (e && Array.isArray(e.calibrations)) ? e.calibrations.filter(c => c && typeof c === 'object') : [];
|
|
50
|
+
return list.slice().sort((a, b) => String(b.date || '').localeCompare(String(a.date || ''))
|
|
51
|
+
|| String(b.loggedAt || '').localeCompare(String(a.loggedAt || '')));
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
// Calendar months, clamped to the month's last day (Jan 31 + 1 mo = Feb 28/29), as
|
|
41
55
|
// flDateAddIso (utils.js) — kept here so the MCP server's vendored copy needs nothing else.
|
|
42
56
|
function _eqCalAddMonths(iso, months) {
|
|
43
|
-
|
|
57
|
+
months = Math.round(Number(months));
|
|
58
|
+
if (!_EQ_CAL_ISO.test(String(iso || '')) || !(months > 0) || months > EQ_CAL_MAX_MONTHS) return '';
|
|
44
59
|
const [y, m, d] = String(iso).split('-').map(Number);
|
|
45
60
|
const first = new Date(Date.UTC(y, m - 1 + months, 1));
|
|
61
|
+
if (!isFinite(first.getTime())) return '';
|
|
46
62
|
const last = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate();
|
|
47
63
|
return new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth(), Math.min(d, last))).toISOString().slice(0, 10);
|
|
48
64
|
}
|
|
@@ -61,15 +77,18 @@ function _eqCalDays(fromIso, toIso) {
|
|
|
61
77
|
// due: the next due date (the latest entry's own nextDue, else its date + the interval)
|
|
62
78
|
function eqCalStatus(e, todayIso) {
|
|
63
79
|
const today = todayIso || _eqCalToday();
|
|
64
|
-
const interval =
|
|
80
|
+
const interval = eqCalInterval(e);
|
|
65
81
|
const hist = eqCalHistory(e);
|
|
66
82
|
const last = hist[0] || null;
|
|
67
|
-
|
|
83
|
+
// Results are written lower-case; an imported "FAIL" is still a fail.
|
|
84
|
+
const result = last ? String(last.result || '').trim().toLowerCase() : '';
|
|
85
|
+
const base = { state: 'none', due: '', last: last ? (last.date || '') : '', lastResult: result, interval, daysToDue: null, count: hist.length };
|
|
68
86
|
if (!e || e.status === 'Retired') return base;
|
|
69
87
|
if (!last) return interval ? { ...base, state: 'never' } : base;
|
|
70
88
|
// A failed instrument has no next due date: it needs a passing calibration first.
|
|
71
|
-
if (
|
|
72
|
-
|
|
89
|
+
if (result === 'fail') return { ...base, state: 'failed' };
|
|
90
|
+
// A next due that is not a date (an import's "soon") falls back to the interval.
|
|
91
|
+
const due = (_EQ_CAL_ISO.test(String(last.nextDue || '')) ? last.nextDue : '') || _eqCalAddMonths(last.date, interval) || '';
|
|
73
92
|
const days = due ? _eqCalDays(today, due) : null;
|
|
74
93
|
if (!due) return { ...base, state: 'ok' };
|
|
75
94
|
const state = days < 0 ? 'overdue' : days <= EQ_CAL_SOON_DAYS ? 'soon' : 'ok';
|
|
@@ -118,12 +137,12 @@ function eqCalCardHtml(e) {
|
|
|
118
137
|
</div></div>`;
|
|
119
138
|
}
|
|
120
139
|
const st = eqCalStatus(e);
|
|
121
|
-
const interval =
|
|
140
|
+
const interval = eqCalInterval(e);
|
|
122
141
|
const files = Array.isArray(e.fileAttachments) ? e.fileAttachments : [];
|
|
123
142
|
const certName = (id) => { const f = files.find(a => a && a.id === id); return f ? (f.name || f.title || f.url || 'Certificate') : ''; };
|
|
124
143
|
const editBtns = on ? `<div style="display:flex;gap:6px">
|
|
125
|
-
<button class="btn btn-secondary btn-sm" onclick="openEqCalIntervalModal('${e.id}')">${interval ? 'Every ' + interval + ' mo' : 'Set interval'}</button>
|
|
126
|
-
<button class="btn btn-primary btn-sm" onclick="openEqCalLogModal('${e.id}')">+ Log calibration</button>
|
|
144
|
+
<button class="btn btn-secondary btn-sm" onclick="openEqCalIntervalModal('${_eqCalJs(e.id)}')">${interval ? 'Every ' + interval + ' mo' : 'Set interval'}</button>
|
|
145
|
+
<button class="btn btn-primary btn-sm" onclick="openEqCalLogModal('${_eqCalJs(e.id)}')">+ Log calibration</button>
|
|
127
146
|
</div>` : `<button class="btn btn-secondary btn-sm" onclick="proUpsell('equipment.calibration')"><span class="fl-pro" aria-hidden="true"></span> Pro</button>`;
|
|
128
147
|
const summary = st.state === 'none'
|
|
129
148
|
? `<div style="color:var(--text-muted);font-size:13px">Not tracked yet. Set how often it is calibrated, or log its last calibration.</div>`
|
|
@@ -138,9 +157,9 @@ function eqCalCardHtml(e) {
|
|
|
138
157
|
<td>${esc(c.by || '—')}</td>
|
|
139
158
|
<td>${c.result ? `<span class="badge" style="${_EQ_CAL_TONE[c.result === 'fail' ? 'failed' : c.result === 'adjusted' ? 'soon' : 'ok']};font-size:11px">${esc(_EQ_CAL_RESULT_LABEL[c.result] || c.result)}</span>` : '—'}</td>
|
|
140
159
|
<td style="font-variant-numeric:tabular-nums">${esc(c.nextDue || '—')}</td>
|
|
141
|
-
<td>${c.certificateId && certName(c.certificateId) ? `<a style="color:var(--primary);cursor:pointer" onclick="attachOpenInTab('${
|
|
160
|
+
<td>${c.certificateId && certName(c.certificateId) ? `<a style="color:var(--primary);cursor:pointer" onclick="attachOpenInTab('${_eqCalJs(c.certificateId)}')">${esc(certName(c.certificateId))}</a>` : (c.certificateRef ? esc(c.certificateRef) : '<span style="color:var(--dk-dim, #cbd5e1)">—</span>')}</td>
|
|
142
161
|
<td style="color:var(--text-muted);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(c.notes || '')}">${esc(c.notes || '')}</td>
|
|
143
|
-
<td style="text-align:right;white-space:nowrap">${on ? `<button class="btn btn-secondary btn-sm" onclick="openEqCalLogModal('${e.id}','${
|
|
162
|
+
<td style="text-align:right;white-space:nowrap">${on ? `<button class="btn btn-secondary btn-sm" onclick="openEqCalLogModal('${_eqCalJs(e.id)}','${_eqCalJs(c.id)}')" title="Edit this calibration">${typeof ICON !== 'undefined' && ICON.edit ? ICON.edit : 'Edit'}</button>` : ''}</td>
|
|
144
163
|
</tr>`).join('');
|
|
145
164
|
const table = hist.length ? `<div class="table-wrap" style="margin-top:14px"><table class="eq-cal-table">
|
|
146
165
|
<thead><tr><th>Date</th><th>Performed by</th><th>Result</th><th>Next due</th><th>Certificate</th><th>Notes</th><th></th></tr></thead>
|
|
@@ -158,12 +177,13 @@ function openEqCalIntervalModal(eqId) {
|
|
|
158
177
|
if (!e) return;
|
|
159
178
|
showModal('Calibration interval', `
|
|
160
179
|
<div class="form-group"><label>Calibrate every</label>
|
|
161
|
-
<div style="display:flex;align-items:center;gap:8px"><input id="eq-cal-interval" type="number" min="0" step="1" style="width:110px" value="${esc(e
|
|
180
|
+
<div style="display:flex;align-items:center;gap:8px"><input id="eq-cal-interval" type="number" min="0" step="1" style="width:110px" value="${esc(eqCalInterval(e) || '')}" placeholder="12" max="${EQ_CAL_MAX_MONTHS}"> <span>months</span></div>
|
|
162
181
|
<div style="font-size:12px;color:var(--text-muted);margin-top:6px">Sets the next due date from each calibration's date. Leave empty to stop tracking a due date (the history stays).</div>
|
|
163
182
|
</div>`, () => {
|
|
164
183
|
const raw = (document.getElementById('eq-cal-interval') || {}).value;
|
|
165
184
|
const n = raw === '' ? null : Math.round(Number(raw));
|
|
166
185
|
if (n != null && !(n >= 0)) { toast('Enter a number of months.'); return false; }
|
|
186
|
+
if (n > EQ_CAL_MAX_MONTHS) { toast(`Up to ${EQ_CAL_MAX_MONTHS} months (20 years).`); return false; }
|
|
167
187
|
updateEquipment(eqId, { calIntervalMonths: n || null });
|
|
168
188
|
if (typeof renderEquipmentDetail === 'function') renderEquipmentDetail(eqId);
|
|
169
189
|
return true;
|
|
@@ -179,13 +199,13 @@ function openEqCalLogModal(eqId, calId) {
|
|
|
179
199
|
const e = typeof getEquipment === 'function' ? getEquipment(eqId) : (db.equipment || []).find(x => x.id === eqId);
|
|
180
200
|
if (!e) return;
|
|
181
201
|
const c = calId ? (e.calibrations || []).find(x => x && x.id === calId) : null;
|
|
182
|
-
const interval =
|
|
202
|
+
const interval = eqCalInterval(e);
|
|
183
203
|
const date = c ? (c.date || '') : _eqCalToday();
|
|
184
204
|
const files = Array.isArray(e.fileAttachments) ? e.fileAttachments : [];
|
|
185
205
|
const certOpts = `<option value="">— None —</option>` + files.map(a => `<option value="${esc(a.id)}"${c && c.certificateId === a.id ? ' selected' : ''}>${esc(a.name || a.title || a.url || a.id)}</option>`).join('');
|
|
186
206
|
const hint = (t) => `<div style="font-size:11.5px;color:var(--text-muted);margin-top:4px;line-height:1.4">${t}</div>`;
|
|
187
207
|
const body = `<div class="form-grid">
|
|
188
|
-
<div class="form-group"><label>Date calibrated *</label><input id="eq-cal-date" type="date" value="${esc(date)}" onchange="_eqCalAutoDue(${interval})"></div>
|
|
208
|
+
<div class="form-group"><label>Date calibrated *</label><input id="eq-cal-date" type="date" value="${esc(date)}" max="${_eqCalToday()}" onchange="_eqCalAutoDue(${interval})"></div>
|
|
189
209
|
<div class="form-group"><label>Performed by</label><input id="eq-cal-by" value="${esc(c ? (c.by || '') : _eqCalMe())}" placeholder="Name, or the service provider" data-fl-person="equipment.calibrations[].by testResults.performedBy equipment.owner"></div>
|
|
190
210
|
<div class="form-group span-2"><label>Result *</label>
|
|
191
211
|
<select id="eq-cal-result">${EQ_CAL_RESULTS.map(r => `<option value="${r.value}"${(c ? c.result : 'pass') === r.value ? ' selected' : ''}>${esc(r.label)} — ${esc(r.hint)}</option>`).join('')}</select>
|
|
@@ -211,9 +231,13 @@ function openEqCalLogModal(eqId, calId) {
|
|
|
211
231
|
loggedBy: c ? (c.loggedBy || '') : _eqCalMe(),
|
|
212
232
|
};
|
|
213
233
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(rec.date)) { toast('Enter the date it was calibrated.'); return false; }
|
|
234
|
+
if (rec.date > _eqCalToday()) { toast('The calibration date is in the future. Log it once it is done.'); return false; }
|
|
214
235
|
if (rec.nextDue && rec.nextDue < rec.date) { toast('Next due is before the calibration date.'); return false; }
|
|
215
236
|
Object.keys(rec).forEach(k => rec[k] === undefined && delete rec[k]);
|
|
216
|
-
|
|
237
|
+
// The record as it is NOW: a calibration synced in from another device while this
|
|
238
|
+
// dialog was open must not be dropped by a list read when it opened.
|
|
239
|
+
const cur = (typeof getEquipment === 'function' ? getEquipment(eqId) : null) || e;
|
|
240
|
+
const list = (cur.calibrations || []).filter(x => x && x.id !== rec.id);
|
|
217
241
|
list.push(rec);
|
|
218
242
|
const patch = { calibrations: list };
|
|
219
243
|
updateEquipment(eqId, patch);
|
|
@@ -225,7 +249,7 @@ function openEqCalLogModal(eqId, calId) {
|
|
|
225
249
|
// Delete sits in the dialog's footer, left of the buttons.
|
|
226
250
|
setTimeout(() => {
|
|
227
251
|
const foot = document.querySelector('#modal-backdrop .modal-footer');
|
|
228
|
-
if (foot && !foot.querySelector('.eq-cal-del')) foot.insertAdjacentHTML('afterbegin', `<button class="btn btn-danger btn-sm eq-cal-del" style="margin-right:auto" onclick="deleteEqCal('${eqId}','${
|
|
252
|
+
if (foot && !foot.querySelector('.eq-cal-del')) foot.insertAdjacentHTML('afterbegin', `<button class="btn btn-danger btn-sm eq-cal-del" style="margin-right:auto" onclick="deleteEqCal('${_eqCalJs(eqId)}','${_eqCalJs(c.id)}')">Delete</button>`);
|
|
229
253
|
}, 0);
|
|
230
254
|
}
|
|
231
255
|
}
|