CyberMax-tools commited on
Commit
6d39ecf
·
verified ·
1 Parent(s): 0f08a6d

Deploy billsprig to the hub

Browse files
billsprig/app.js CHANGED
@@ -4,7 +4,12 @@
4
  "use strict";
5
  var C = window.BillCalc, P = window.BillPDF, X = window.CMXPro;
6
  var KEY = "billsprig:v1";
7
- var HUB = "https://cybermax-tools-cybermax.static.hf.space/billsprig/index.html?utm_source=pdf-credit";
 
 
 
 
 
8
  var FREE_PDFS = 5;
9
  var $ = function (id) { return document.getElementById(id); };
10
  var app = $("app"), form = $("editor");
@@ -247,7 +252,7 @@
247
  '<section class="d-bottom"><div class="d-left">' + (payOk ? '<a class="d-pay" href="' + esc(d.payLink) + '" target="_blank" rel="noopener">Pay online</a><span class="d-paylink">' + esc(d.payLink) + "</span>" : "") +
248
  (d.notes ? '<div class="d-label">Notes</div><div class="d-notes">' + esc(d.notes) + "</div>" : "") + "</div>" +
249
  '<table class="d-totals">' + tot.map(function (r) { return "<tr><td>" + r[0] + "</td><td>" + r[1] + "</td></tr>"; }).join("") + '<tr class="t-total"><td>' + totalLabel + "</td><td>" + m(T.total) + "</td></tr></table></section>" +
250
- '<footer class="d-credit">Made with Billsprig · free invoice generator by CyberMax</footer>';
251
  fit();
252
  }
253
  function fit() {
@@ -284,7 +289,7 @@
284
  function pdfBytes() {
285
  var d = C.clone(doc); d.template = tplFor();
286
  var logo = pro() && S.logo && S.logo.data ? { bytes: b64ToBytes(S.logo.data), w: S.logo.w, h: S.logo.h } : null;
287
- return P.build(d, { credit: !pro(), creditUrl: HUB, logo: logo, accent: accentFor() });
288
  }
289
  $("btn-pdf").addEventListener("click", function () {
290
  var q = X.quota("pdf", FREE_PDFS);
@@ -499,9 +504,54 @@
499
  }
500
  applyPreset();
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  /* ---------- start ---------- */
503
  fillSelects();
504
  toForm();
 
505
  X.onChange(onPro);
506
- window.Billsprig = { state: function () { return S; }, doc: function () { return doc; }, pdf: pdfBytes };
507
  })();
 
4
  "use strict";
5
  var C = window.BillCalc, P = window.BillPDF, X = window.CMXPro;
6
  var KEY = "billsprig:v1";
7
+ /* Recipient-facing credit on free output (Pro removes it): links to the app's page on cybermaxtools.com, tagged so the
8
+ * page's view counter records it (utm_source=billsprig-share, utm_campaign=pdf|print). */
9
+ var SITE = "https://cybermaxtools.com/apps/free-invoice-generator/";
10
+ var CREDIT_LEAD = "Made with Billsprig by CyberMax", CREDIT_ASK = "make your own free: cybermaxtools.com/apps/free-invoice-generator";
11
+ var CREDIT = CREDIT_LEAD + " \u2014 " + CREDIT_ASK;
12
+ function creditUrl(ch) { return SITE + "?utm_source=billsprig-share&utm_medium=made-with&utm_campaign=" + ch; }
13
  var FREE_PDFS = 5;
14
  var $ = function (id) { return document.getElementById(id); };
15
  var app = $("app"), form = $("editor");
 
252
  '<section class="d-bottom"><div class="d-left">' + (payOk ? '<a class="d-pay" href="' + esc(d.payLink) + '" target="_blank" rel="noopener">Pay online</a><span class="d-paylink">' + esc(d.payLink) + "</span>" : "") +
253
  (d.notes ? '<div class="d-label">Notes</div><div class="d-notes">' + esc(d.notes) + "</div>" : "") + "</div>" +
254
  '<table class="d-totals">' + tot.map(function (r) { return "<tr><td>" + r[0] + "</td><td>" + r[1] + "</td></tr>"; }).join("") + '<tr class="t-total"><td>' + totalLabel + "</td><td>" + m(T.total) + "</td></tr></table></section>" +
255
+ (pro() ? "" : '<footer class="d-credit">' + CREDIT_LEAD + ' \u2014 <a href="' + creditUrl("print") + '" target="_blank" rel="noopener">' + CREDIT_ASK + "</a></footer>");
256
  fit();
257
  }
258
  function fit() {
 
289
  function pdfBytes() {
290
  var d = C.clone(doc); d.template = tplFor();
291
  var logo = pro() && S.logo && S.logo.data ? { bytes: b64ToBytes(S.logo.data), w: S.logo.w, h: S.logo.h } : null;
292
+ return P.build(d, { credit: !pro(), creditText: CREDIT, creditUrl: creditUrl("pdf"), logo: logo, accent: accentFor() });
293
  }
294
  $("btn-pdf").addEventListener("click", function () {
295
  var q = X.quota("pdf", FREE_PDFS);
 
504
  }
505
  applyPreset();
506
 
507
+ /* ---------- "Use this template": guide pages show the app's own worked examples; each table gets a button that loads
508
+ * that example into the editor above as the visitor's own copy. Also #example=<kind> (invoice, contractor, freelancer,
509
+ * receipt) opens the app pre-filled with one, so a guide or a shared link can say "start from this". Nothing leaves
510
+ * the browser. ---------- */
511
+ var KINDS = Array.prototype.map.call($("example-kind").options, function (o) { return o.value; });
512
+ function ownDraft() {
513
+ var cur = S.doc; if (!cur || !cur.to || !String(cur.to.name || "").trim()) return false;
514
+ return !/\.example\b/.test([cur.to.email, cur.from && cur.from.email, cur.payLink].join(" ")); // the built-in examples use .example addresses
515
+ }
516
+ function useTemplate(kind, quiet) {
517
+ if (KINDS.indexOf(kind) < 0) return false;
518
+ if (ownDraft() && !window.confirm("Replace your current draft with this template? Save it to history first if you need it.")) return false;
519
+ $("example-kind").value = kind;
520
+ S.doc = doc = C.example(kind);
521
+ save();
522
+ if (!quiet) {
523
+ toForm(); changed(); renderPreview();
524
+ toast("Template loaded as your own copy. Change anything; it saves in this browser.");
525
+ $("app").scrollIntoView({ behavior: "smooth", block: "start" });
526
+ }
527
+ return true;
528
+ }
529
+ function templateButtons() {
530
+ Array.prototype.forEach.call(document.querySelectorAll(".g-guide .g-tw"), function (box) {
531
+ var td = box.querySelector("tbody td"), first = td ? td.textContent.trim() : "";
532
+ var kind = KINDS.filter(function (k) { return C.example(k).items[0].desc === first; })[0];
533
+ if (!kind || (box.nextElementSibling && box.nextElementSibling.classList.contains("g-use"))) return;
534
+ var p = document.createElement("p");
535
+ p.className = "g-use";
536
+ p.innerHTML = '<button type="button" class="btn sm" data-use="' + kind + '">Use this template</button> <span>Opens it in the editor above as your own copy to edit. Nothing is uploaded.</span>';
537
+ box.parentNode.insertBefore(p, box.nextSibling);
538
+ });
539
+ }
540
+ document.addEventListener("click", function (e) {
541
+ var b = e.target.closest && e.target.closest("[data-use]");
542
+ if (b) useTemplate(b.getAttribute("data-use"));
543
+ });
544
+ (function exampleFromHash() {
545
+ var m = /[#&]example=([a-z]+)/.exec(location.hash || "");
546
+ if (!m) return;
547
+ history.replaceState(null, "", location.pathname + location.search);
548
+ if (useTemplate(m[1], true)) setTimeout(function () { toast("Template loaded as your own copy. Change anything; it saves in this browser."); }, 400);
549
+ })();
550
+
551
  /* ---------- start ---------- */
552
  fillSelects();
553
  toForm();
554
+ templateButtons();
555
  X.onChange(onPro);
556
+ window.Billsprig = { state: function () { return S; }, doc: function () { return doc; }, pdf: pdfBytes, useTemplate: useTemplate, CREDIT: CREDIT, creditUrl: creditUrl };
557
  })();
billsprig/index.html CHANGED
@@ -267,5 +267,5 @@
267
  <script src="lib/calc.js"></script>
268
  <script src="lib/pdf.js"></script>
269
  <script src="app.js"></script>
270
- <!-- cybermax-count:start --><script>(function(){try{var u="https://data.cybermaxtools.com/v?p="+encodeURIComponent(location.host+location.pathname)+"&r="+encodeURIComponent((document.referrer||"").split("/")[2]||"")+(function(){var q=new URLSearchParams(location.search),s=q.get("utm_source");return s?"&us="+encodeURIComponent(s)+"&uc="+encodeURIComponent(q.get("utm_campaign")||""):""})();navigator.sendBeacon?navigator.sendBeacon(u):(new Image).src=u}catch(e){}})()</script><!-- cybermax-count:end --></body>
271
  </html>
 
267
  <script src="lib/calc.js"></script>
268
  <script src="lib/pdf.js"></script>
269
  <script src="app.js"></script>
270
+ </body>
271
  </html>
billsprig/lib/pdf.js CHANGED
@@ -177,7 +177,7 @@
177
  function pct(v) { return String(C.round(v, 3)) + "%"; }
178
 
179
  /**
180
- * Build the PDF for a document. opts: { credit: bool, creditUrl, logo: {bytes, w, h}, accent, date: Date }
181
  * Returns a Uint8Array.
182
  */
183
  function build(doc, opts) {
@@ -342,7 +342,7 @@
342
  for (var i = 0; i < n; i++) {
343
  wr.on(i);
344
  if (opts.credit) {
345
- var credit = "Made with Billsprig · free invoice generator by CyberMax";
346
  var cw = wr.text(L, H - 28, credit, { size: 7.5, color: "#8A8F9C" });
347
  if (opts.creditUrl) wr.link(L, H - 36, cw, 11, opts.creditUrl);
348
  }
@@ -353,6 +353,9 @@
353
  return serialize(wr, { title: ty.title + (doc.number ? " " + doc.number : "") + (to.name ? " - " + to.name : ""), author: from.name || "", date: stamp }, opts.logo && opts.logo.bytes ? opts.logo : null);
354
  }
355
 
 
 
 
356
  /** Suggested file name: Invoice-INV-0042-Harbor-Pine-Bakery.pdf */
357
  function fileName(doc) {
358
  var ty = (C.TYPES[doc.type] || C.TYPES.invoice).title;
@@ -360,6 +363,6 @@
360
  return parts.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 90) + ".pdf";
361
  }
362
 
363
- var api = { build: build, fileName: fileName, encode: encode, canEncode: canEncode, width: width, wrap: wrap, STYLES: STYLES, pageSize: pageSize };
364
  if (typeof module === "object" && module.exports) module.exports = api; else root.BillPDF = api;
365
  })(typeof self !== "undefined" ? self : this);
 
177
  function pct(v) { return String(C.round(v, 3)) + "%"; }
178
 
179
  /**
180
+ * Build the PDF for a document. opts: { credit: bool, creditText, creditUrl, logo: {bytes, w, h}, accent, date: Date }
181
  * Returns a Uint8Array.
182
  */
183
  function build(doc, opts) {
 
342
  for (var i = 0; i < n; i++) {
343
  wr.on(i);
344
  if (opts.credit) {
345
+ var credit = opts.creditText || CREDIT;
346
  var cw = wr.text(L, H - 28, credit, { size: 7.5, color: "#8A8F9C" });
347
  if (opts.creditUrl) wr.link(L, H - 36, cw, 11, opts.creditUrl);
348
  }
 
353
  return serialize(wr, { title: ty.title + (doc.number ? " " + doc.number : "") + (to.name ? " - " + to.name : ""), author: from.name || "", date: stamp }, opts.logo && opts.logo.bytes ? opts.logo : null);
354
  }
355
 
356
+ /** Default footer credit on free PDFs (app.js passes the same text plus a tagged creditUrl). */
357
+ var CREDIT = "Made with Billsprig by CyberMax \u2014 make your own free: cybermaxtools.com/apps/free-invoice-generator";
358
+
359
  /** Suggested file name: Invoice-INV-0042-Harbor-Pine-Bakery.pdf */
360
  function fileName(doc) {
361
  var ty = (C.TYPES[doc.type] || C.TYPES.invoice).title;
 
363
  return parts.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 90) + ".pdf";
364
  }
365
 
366
+ var api = { build: build, fileName: fileName, encode: encode, canEncode: canEncode, width: width, wrap: wrap, STYLES: STYLES, pageSize: pageSize, CREDIT: CREDIT };
367
  if (typeof module === "object" && module.exports) module.exports = api; else root.BillPDF = api;
368
  })(typeof self !== "undefined" ? self : this);
billsprig/style.css CHANGED
@@ -171,6 +171,8 @@ input:focus,select:focus,textarea:focus{border-color:var(--accent);outline:none;
171
  .d-totals tr.t-total td:last-child{color:var(--acc);font-size:17px}
172
  .d-credit{position:absolute;left:61px;bottom:30px;font-size:10px;color:#8A8F9C}
173
  html.cmx-pro .d-credit{display:none}
 
 
174
  /* modern */
175
  .tpl-modern{padding-top:0}
176
  .tpl-modern .d-band{display:flex;justify-content:space-between;align-items:center;background:var(--acc);color:#fff;margin:0 -61px 32px;padding:26px 61px;min-height:128px}
@@ -292,3 +294,7 @@ footer.more{border-top:1px solid var(--line);margin-top:30px;padding:22px 0 40px
292
  #doc .d-credit{position:fixed;bottom:8mm}
293
  .d-items tr{break-inside:avoid}
294
  }
 
 
 
 
 
171
  .d-totals tr.t-total td:last-child{color:var(--acc);font-size:17px}
172
  .d-credit{position:absolute;left:61px;bottom:30px;font-size:10px;color:#8A8F9C}
173
  html.cmx-pro .d-credit{display:none}
174
+ .d-credit a{color:inherit;text-decoration:none}
175
+ .d-credit a:hover{text-decoration:underline}
176
  /* modern */
177
  .tpl-modern{padding-top:0}
178
  .tpl-modern .d-band{display:flex;justify-content:space-between;align-items:center;background:var(--acc);color:#fff;margin:0 -61px 32px;padding:26px 61px;min-height:128px}
 
294
  #doc .d-credit{position:fixed;bottom:8mm}
295
  .d-items tr{break-inside:avoid}
296
  }
297
+
298
+ /* "Use this template" under a guide's worked example */
299
+ .g-use{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:4px 0 14px;font-size:13.5px;opacity:.95}
300
+ .g-use span{opacity:.8}
billsprig/sw.js CHANGED
@@ -1,5 +1,5 @@
1
  /* Billsprig offline cache. © 2026 CyberMax. All rights reserved. */
2
- var CACHE = "billsprig-v2";
3
  var FILES = ["./", "index.html", "style.css", "pro.js", "lib/calc.js", "lib/pdf.js", "app.js", "manifest.webmanifest", "icon.svg", "icon-192.png", "icon-512.png"];
4
  self.addEventListener("install", function (e) {
5
  e.waitUntil(caches.open(CACHE).then(function (c) { return c.addAll(FILES); }).then(function () { return self.skipWaiting(); }));
 
1
  /* Billsprig offline cache. © 2026 CyberMax. All rights reserved. */
2
+ var CACHE = "billsprig-v3";
3
  var FILES = ["./", "index.html", "style.css", "pro.js", "lib/calc.js", "lib/pdf.js", "app.js", "manifest.webmanifest", "icon.svg", "icon-192.png", "icon-512.png"];
4
  self.addEventListener("install", function (e) {
5
  e.waitUntil(caches.open(CACHE).then(function (c) { return c.addAll(FILES); }).then(function () { return self.skipWaiting(); }));