From 3eefa5d6443d65568099dd90fb162c796f9eeba7 Mon Sep 17 00:00:00 2001 From: Ali Orhun Akkirman Date: Sun, 27 Sep 2026 17:46:12 +0300 Subject: [PATCH] Add installable PWA support --- README.md | 12 ++++++++ index.html | 6 ++++ public/icons/icon-192.png | Bin 0 -> 1500 bytes public/icons/icon-512.png | Bin 0 -> 4335 bytes public/manifest.webmanifest | 25 +++++++++++++++++ scripts/build.mjs | 51 ++++++++++++++++++++++++++++++++-- scripts/verify-offline.mjs | 47 ++++++++++++++++++++++++++++---- src/app.js | 20 +++++++++++++- src/sw.js | 53 ++++++++++++++++++++++++++++++++++++ 9 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 public/icons/icon-192.png create mode 100644 public/icons/icon-512.png create mode 100644 public/manifest.webmanifest create mode 100644 src/sw.js diff --git a/README.md b/README.md index 9e998c0..bcb0ca4 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,18 @@ Bu kontrol, build çıktısında uzak script/style/module bağlantısı kalmadı --- +## PWA ve çevrimdışı kullanım + +GitHub Pages sürümü installable PWA olarak yayınlanır. Manifest, uygulama ikonları ve service worker build sırasında `dist/` içine dahil edilir. + +- `start_url` ve `scope` relative (`./`) tutulduğu için proje `/mathnote/` gibi GitHub Pages alt yollarında çalışır. +- İlk başarılı ziyarette uygulama shell'i, JavaScript/CSS, KaTeX fontları, manifest ve ikonlar precache edilir. +- Yeni build'de dosya içeriğinden yeni bir cache sürümü üretilir; service worker aktive olduğunda eski MathNote cache'leri temizlenir. +- Service worker yalnızca HTTP/HTTPS üzerinde kaydolur. Bu nedenle `dist/index.html` dosyasını doğrudan `file://` ile açma desteği korunur. +- Destekleyen tarayıcılar MathNote'u masaüstüne veya ana ekrana bağımsız uygulama olarak kurabilir. + +--- + ## GitHub Pages Repo, GitHub Pages üzerinde kaynak `index.html` dosyasını doğrudan yayınlamak yerine GitHub Actions ile build edilir. `.github/workflows/pages.yml` workflow'u: diff --git a/index.html b/index.html index 42ef209..bdfcb8b 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,12 @@ Math Notebook + + + + + + diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..5da2f4d74dccd93db14a8c018a2ff44ef4655686 GIT binary patch literal 1500 zcma)+`&ZI;7{@=LcuAdZnpslVvRTa~Wjkq2NoN;lLn|+M$1==W8k#dP5VJD%WZEgD zc`d62D7wtM=+{|bd7)NJ^Ma_Eg_BYyH<9n~59plz@I23X&Ut=#-p_g7uiOCqL5o%E zRsjGkd=L2?)mLaGOpNsT=;&!<08mxFK3;+6hv&wAzFKn<0nNjQ#SV|oZhXQ^{ZYdY z{*r3)Y%|&V2r(evcB37N%8WW!!+J{ z2U|;(kvK!f1>N;Crm+&&%bQ~DKxXky!M$JYaz)&z`*IH64lwjIa9A1S};Zsbov9(r!A3nS})*0 zuW8kU>sDsuy>}KUpk#c93zy3g2k}^x(8bz`dELX-TFOz-Cwe2@WG}!6)~@#yALBJq z{`~4{%l6E2u8hTHak;XHrKXU;CMJS8wJ>vAppZ#lHk?p~#Ia{{Mv|;SN-B&Hz;z0> z+WkTmV;=)26b4HkIBp_yM6%m}po=)& zy~J)9iDQo^j?R1(&Gv&nvbx@m1o#(7DyD}keOh?e6k0e4PM56-#@XEsgU{dFlMW4z zx+TNBVE<%OdQF(_he&EW<(DayAKT^HD9n}?6iwmIEKb^%5dCsDMP_53p3p@us-^k% z0%0JgdIO=Yr;HQznB3{aDdA+6Acp=<)udgy|Kh=3PO)W0=3`244>hq?7i$UGc&jo= z+mE!fzm$=K?m8m)cQDw+zrBvET`x+@v5>cphg77r zqUz>)D^hW+jvdL`ylW$HwdaxL?hm$5;9a-sYsxWEZTYlkT2LoB422bhPHWGm;@&s3 z;y4}CH%*8GepLy3R3Rft5Ap70aDsh>v71K~g2o*0O<)V7-i}8POQ|%^7t%;E)&L#v zSiG~^S`;z@#|}PdYW#$wT0t*`TLcpYx}^e(*^G;rf>9+y+K+Do1xtA}!bBM>ah*Gj zt0627@#ohBDjO+}4g>wW@@ktOX@27Dt_f4hr3p1Ug6y3FCcQtm1YbD>4pX|@D0=4QqTh#MC#zyhva_OLBFd%{e%`J`8>`7W#rqQRVcpwMNE8k7OHq9$NUWIy$Uu0R5@YJ=)7SN&P#_sNq}2*Gn5e!D`ZA=S z=yCwr>+~EUd73e|>LEgybe2e6=udsl4ok+P#H%O&nlt^3frt12Uqjx~bJ zSNu9;Tvxi-xfZxW=xuz_xH&lX1>_9b2zq7c&aLLUXWO%Upqu<051;@f1mpZ-?XY|L zZ1$Q0c?nHB+8_?C~=?o8*Q@ycOR##?30o63tH&$Y{&%~$$7{)+mfYD@X e^uOR|C&#=&U>en@D)aQz2EP08K3s3&#s2`Moxn^0 literal 0 HcmV?d00001 diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..64001182ea66f9aef2e55c2b11504681e7cad6a7 GIT binary patch literal 4335 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 BTA2U< literal 0 HcmV?d00001 diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..1eed752 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,25 @@ +{ + "name": "MathNote – Interactive Mathematics Notebook", + "short_name": "MathNote", + "description": "Offline-capable interactive mathematics notebook", + "start_url": "./", + "scope": "./", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#1971c2", + "orientation": "any", + "icons": [ + { + "src": "./icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "./icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/scripts/build.mjs b/scripts/build.mjs index e1e0c94..be525e4 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -1,8 +1,12 @@ import { build } from 'esbuild'; -import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { relative } from 'node:path'; const outdir = new URL('../dist/', import.meta.url); const templateUrl = new URL('../index.html', import.meta.url); +const publicDir = new URL('../public/', import.meta.url); +const serviceWorkerTemplateUrl = new URL('../src/sw.js', import.meta.url); await rm(outdir, { recursive: true, force: true }); await mkdir(outdir, { recursive: true }); @@ -25,6 +29,8 @@ await build({ } }); +await cp(publicDir, outdir, { recursive: true }); + let html = await readFile(templateUrl, 'utf8'); html = html.replace( '', @@ -32,7 +38,7 @@ html = html.replace( ); html = html.replace( '', - '