-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpersonalisation.js
More file actions
147 lines (131 loc) · 4.6 KB
/
Copy pathpersonalisation.js
File metadata and controls
147 lines (131 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { config } from "./config.js";
// localStorage key for the personalisation blob.
const LS_KEY = "decode:personalisation";
// Current Blob URL for the dynamic manifest, kept so we can revoke it on
// subsequent updates to avoid leaking object URLs.
let activeManifestUrl = null;
// Read raw personalisation overrides from localStorage. Returns {} if absent
// or unparseable.
export function getPersonalisation() {
try {
const raw = localStorage.getItem(LS_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === "object" ? parsed : {};
} catch {
return {};
}
}
// Effective config = deployment defaults from config.js overlaid by
// localStorage personalisation. Pure; no DOM side-effects.
export function getEffective() {
const p = getPersonalisation();
const appName = p.appName || config.appName;
return {
appName,
appShortName: p.appShortName || appName || config.appShortName,
themeColor: p.themeColor || config.themeColor,
backgroundColor: p.backgroundColor || config.backgroundColor,
iconDataUrl: p.iconDataUrl || null,
iconType: p.iconType || null,
};
}
// Merge partial overrides into existing personalisation. Pass null for a key
// to clear that specific override.
export function savePersonalisation(partial) {
const current = getPersonalisation();
const next = { ...current };
for (const [k, v] of Object.entries(partial)) {
if (v === null || v === undefined || v === "") delete next[k];
else next[k] = v;
}
localStorage.setItem(LS_KEY, JSON.stringify(next));
}
export function resetPersonalisation() {
localStorage.removeItem(LS_KEY);
}
function buildManifest(eff) {
const icons = eff.iconDataUrl
? [
{
src: eff.iconDataUrl,
sizes: "any",
type: eff.iconType || "image/png",
purpose: "any maskable",
},
]
: config.icons;
return {
name: eff.appName,
short_name: eff.appShortName,
start_url: "./",
scope: "./",
display: "standalone",
orientation: "portrait",
theme_color: eff.themeColor,
background_color: eff.backgroundColor,
icons,
share_target: {
action: "./",
method: "GET",
enctype: "application/x-www-form-urlencoded",
params: { text: "text" },
},
};
}
function setMeta(name, value) {
let el = document.querySelector(`meta[name="${name}"]`);
if (!el) {
el = document.createElement("meta");
el.setAttribute("name", name);
document.head.appendChild(el);
}
el.setAttribute("content", value);
}
function setLink(rel, href, type) {
let el = document.querySelector(`link[rel="${rel}"]`);
if (!el) {
el = document.createElement("link");
el.setAttribute("rel", rel);
document.head.appendChild(el);
}
el.setAttribute("href", href);
if (type) el.setAttribute("type", type);
}
// Apply the current effective config to the DOM. Safe to call repeatedly.
export function applyPersonalisation() {
const eff = getEffective();
// Title (used by iOS A2HS in absence of apple-mobile-web-app-title)
document.title = eff.appName;
setMeta("apple-mobile-web-app-title", eff.appName);
// Theme color: meta tag tints browser chrome (URL bar, installed PWA title
// bar), and --theme drives in-page accents like focus borders. The CSS var
// is only set when the user has actually personalised it, so the default
// accent in styles.css survives a Reset.
setMeta("theme-color", eff.themeColor);
document.documentElement.style.setProperty("--bg", eff.backgroundColor);
const userTheme = getPersonalisation().themeColor;
if (userTheme) {
document.documentElement.style.setProperty("--theme", userTheme);
} else {
document.documentElement.style.removeProperty("--theme");
}
// App-name text in the header, if present
const h = document.getElementById("appName");
if (h) h.textContent = eff.appName;
// Icons: apple-touch-icon for iOS, manifest icons for Android/desktop.
const iconHref = eff.iconDataUrl || config.icons[0]?.src || "./icons/icon.svg";
const iconType = eff.iconDataUrl
? eff.iconType
: config.icons[0]?.type || "image/svg+xml";
setLink("apple-touch-icon", iconHref);
setLink("icon", iconHref, iconType);
// Manifest: rewrite to a Blob URL so Android picks up personalised values
// when the user taps "Add to Home Screen".
const manifest = buildManifest(eff);
const blob = new Blob([JSON.stringify(manifest)], { type: "application/manifest+json" });
const url = URL.createObjectURL(blob);
if (activeManifestUrl) URL.revokeObjectURL(activeManifestUrl);
activeManifestUrl = url;
setLink("manifest", url);
}