PageSourceSearch

https://www.srilankatravelguides.com/assets/page_management_runtime.js?v=20260129_1

js srilankatravelguides.com collected 2026-10-04 20:15:50 UTC 9,241 bytes, 303 lines download raw bytes

1/*
2  TGSL Page Management Runtime
3
4  - Reads /api/settings/page-management
5  - For non-staff users: hides links to disabled pages and blocks access
6    via redirect when a disabled route is opened.
7
8  Notes:
9  - The app uses HashRouter, but also mirrors hash routes into clean paths.
10  - This script is intentionally framework-agnostic (DOM + URL based).
11*/
12
13(function () {
14  "use strict";
15
16  const API_BASE = "/api";
17
18  const DEFAULT_CFG = {
19    home: true,
20    destinations: true,
21    blog: true,
22    vehicles: true,
23    hotels: true,
24    guides: true,
25    drivers: true,
26    planner: true,
27    about: true,
28    contact: true,
29    faq: true,
30    privacy: true,
31    terms: true,
32    register: true,
33  };
34
35  function normalizeCfg(cfg) {
36    const out = Object.assign({}, DEFAULT_CFG);
37    if (!cfg || typeof cfg !== "object") return out;
38    Object.keys(DEFAULT_CFG).forEach((k) => {
39      if (Object.prototype.hasOwnProperty.call(cfg, k)) {
40        out[k] = !!cfg[k];
41      }
42    });
43    return out;
44  }
45
46  function hasLoginHintCookie() {
47    try {
48      return document.cookie.indexOf("tgsl_logged_in=1") !== -1;
49    } catch (e) {
50      return false;
51    }
52  }
53
54  async function fetchJson(path, options) {
55    const res = await fetch(API_BASE + path, Object.assign({
56      method: "GET",
57      credentials: "include",
58      headers: { "Content-Type": "application/json" },
59    }, options || {}));
60
61    const text = await res.text();
62    let data = null;
63    try { data = JSON.parse(text); } catch (e) { data = null; }
64
65    if (!res.ok) {
66      const msg = (data && data.error) ? data.error : (text || ("HTTP " + res.status));
67      throw new Error(msg);
68    }
69    return data;
70  }
71
72  function getRoutePath() {
73    try {
74      const h = String(window.location.hash || "");
75      if (h.startsWith("#/")) {
76        const p = h.slice(1); // "/guides?x=1"
77        return p.split("?")[0] || "/";
78      }
79      // Fallback: clean path
80      return String(window.location.pathname || "/") || "/";
81    } catch (e) {
82      return "/";
83    }
84  }
85
86  function pickFallbackRoute(cfg) {
87    const priority = [
88      ["home", "/"],
89      ["destinations", "/destinations"],
90      ["vehicles", "/vehicles"],
91      ["hotels", "/hotels"],
92      ["guides", "/guides"],
93      ["drivers", "/drivers"],
94      ["blog", "/blog"],
95      ["planner", "/planner"],
96      ["about", "/about"],
97      ["contact", "/contact"],
98      ["faq", "/faq"],
99      ["privacy", "/privacy"],
100      ["terms", "/terms"],
101      // If everything is disabled, keep the user on login (always available)
102      ["_always", "/login"],
103    ];
104
105    for (const [k, path] of priority) {
106      if (k === "_always") return path;
107      if (cfg && cfg[k]) return path;
108    }
109    return "/login";
110  }
111
112  function disabledKeyForPath(path, cfg) {
113    const p = String(path || "/");
114    if (p === "/" || p === "") {
115      return cfg.home ? null : "home";
116    }
117
118    // Listing section pages + detail pages
119    if (p.startsWith("/destinations") || p.startsWith("/destination/")) return cfg.destinations ? null : "destinations";
120    if (p.startsWith("/blog") || p.startsWith("/blog/")) return cfg.blog ? null : "blog";
121    if (p.startsWith("/vehicles") || p.startsWith("/vehicle/")) return cfg.vehicles ? null : "vehicles";
122    if (p.startsWith("/hotels") || p.startsWith("/hotel/")) return cfg.hotels ? null : "hotels";
123    if (p.startsWith("/guides") || p.startsWith("/guide/")) return cfg.guides ? null : "guides";
124    if (p.startsWith("/drivers") || p.startsWith("/driver/")) return cfg.drivers ? null : "drivers";
125
126    // Static pages
127    if (p.startsWith("/planner")) return cfg.planner ? null : "planner";
128    if (p.startsWith("/about")) return cfg.about ? null : "about";
129    if (p.startsWith("/contact")) return cfg.contact ? null : "contact";
130    if (p.startsWith("/faq")) return cfg.faq ? null : "faq";
131    if (p.startsWith("/privacy")) return cfg.privacy ? null : "privacy";
132    if (p.startsWith("/terms")) return cfg.terms ? null : "terms";
133    if (p.startsWith("/register")) return cfg.register ? null : "register";
134
135    return null;
136  }
137
138  function redirectTo(path) {
139    try {
140      const p = String(path || "/");
141      const target = window.location.origin + "/#" + (p.startsWith("/") ? p : ("/" + p));
142      // Prevent redirect loops
143      if (window.location.href === target) return;
144      window.location.replace(target);
145    } catch (e) {
146      try {
147        window.location.hash = "#" + path;
148      } catch (e2) {}
149    }
150  }
151
152  function hrefAttr(el) {
153    if (!el) return "";
154    try {
155      return String(el.getAttribute("href") || "");
156    } catch (e) {
157      return "";
158    }
159  }
160
161  function markHidden(el) {
162    try {
163      if (!el || el.nodeType !== 1) return;
164      if (el.getAttribute("data-tgsl-page-hidden") === "1") return;
165      el.setAttribute("data-tgsl-page-hidden", "1");
166      el.style.display = "none";
167    } catch (e) {}
168  }
169
170  function rewriteHref(el, newPath) {
171    try {
172      if (!el || el.nodeType !== 1) return;
173      const p = String(newPath || "/");
174      el.setAttribute("href", p);
175    } catch (e) {}
176  }
177
178  function applyDomVisibility(cfg) {
179    const fallback = pickFallbackRoute(cfg);
180
181    // Exact home links: rewrite to fallback when home is disabled.
182    if (!cfg.home) {
183      const homeLinks = document.querySelectorAll('a[href="/"], a[href="#/"], a[href="/#/"]');
184      homeLinks.forEach((a) => rewriteHref(a, fallback));
185    }
186
187    // Helper: hide all anchors matching any of the provided patterns.
188    function hideByPatterns(patterns, exactOnly) {
189      const anchors = document.querySelectorAll("a[href]");
190      anchors.forEach((a) => {
191        const h = hrefAttr(a);
192        if (!h) return;
193
194        for (const p of patterns) {
195          if (exactOnly) {
196            if (h === p) { markHidden(a); return; }
197          } else {
198            if (h.indexOf(p) !== -1) { markHidden(a); return; }
199          }
200        }
201      });
202    }
203
204    // Section pages + their detail pages
205    if (!cfg.destinations) hideByPatterns(["/destinations", "#/destinations", "/destination/", "#/destination/"], false);
206    if (!cfg.blog) hideByPatterns(["/blog", "#/blog"], false);
207    if (!cfg.vehicles) hideByPatterns(["/vehicles", "#/vehicles", "/vehicle/", "#/vehicle/"], false);
208    if (!cfg.hotels) hideByPatterns(["/hotels", "#/hotels", "/hotel/", "#/hotel/"], false);
209    if (!cfg.guides) hideByPatterns(["/guides", "#/guides", "/guide/", "#/guide/"], false);
210    if (!cfg.drivers) hideByPatterns(["/drivers", "#/drivers", "/driver/", "#/driver/"], false);
211
212    // Static pages
213    if (!cfg.planner) hideByPatterns(["/planner", "#/planner"], false);
214    if (!cfg.about) hideByPatterns(["/about", "#/about"], false);
215    if (!cfg.contact) hideByPatterns(["/contact", "#/contact"], false);
216    if (!cfg.faq) hideByPatterns(["/faq", "#/faq"], false);
217    if (!cfg.privacy) hideByPatterns(["/privacy", "#/privacy"], false);
218    if (!cfg.terms) hideByPatterns(["/terms", "#/terms"], false);
219    if (!cfg.register) hideByPatterns(["/register", "#/register"], false);
220  }
221
222  function enforceCurrentRoute(cfg) {
223    const p = getRoutePath();
224    const disabledKey = disabledKeyForPath(p, cfg);
225    if (!disabledKey) return;
226    const fallback = pickFallbackRoute(cfg);
227    redirectTo(fallback);
228  }
229
230  let state = {
231    cfg: normalizeCfg(null),
232    isStaff: false,
233    started: false,
234  };
235
236  function applyAll() {
237    if (state.isStaff) return;
238    enforceCurrentRoute(state.cfg);
239    applyDomVisibility(state.cfg);
240  }
241
242  function startObservers() {
243    if (state.started) return;
244    state.started = true;
245
246    // Apply immediately (best-effort)
247    applyAll();
248
249    // Re-apply on route changes
250    window.addEventListener("hashchange", applyAll);
251    window.addEventListener("popstate", applyAll);
252
253    // Re-apply when React updates DOM
254    try {
255      const obs = new MutationObserver(() => {
256        applyDomVisibility(state.cfg);
257      });
258      obs.observe(document.documentElement, { childList: true, subtree: true });
259    } catch (e) {}
260  }
261
262  async function init() {
263    // 1) Load page management config (public endpoint)
264    try {
265      const data = await fetchJson("/settings/page-management");
266      state.cfg = normalizeCfg((data && data.pageManagement) ? data.pageManagement : data);
267    } catch (e) {
268      state.cfg = normalizeCfg(null);
269    }
270
271    // 2) Determine if current user is staff (admin/editor)
272    if (hasLoginHintCookie()) {
273      try {
274        const me = await fetchJson("/auth/me");
275        const u = (me && me.user) ? me.user : me;
276        state.isStaff = !!(u && (u.isAdmin || u.role === "admin" || u.role === "editor"));
277      } catch (e) {
278        state.isStaff = false;
279      }
280    }
281
282    // Expose for debugging
283    try {
284      window.__tgsl_page_management = { cfg: state.cfg, isStaff: state.isStaff };
285    } catch (e) {}
286
287    startObservers();
288  }
289
290  // Allow live updates (admin panel can dispatch this event)
291  window.addEventListener("tgsl-page-management-updated", function (ev) {
292    try {
293      if (state.isStaff) return;
294      const next = ev && ev.detail && ev.detail.pageManagement;
295      if (!next) return;
296      state.cfg = normalizeCfg(next);
297      applyAll();
298    } catch (e) {}
299  });
300
301  // Boot
302  init();
303})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.