mnfst 0.5.32 → 0.5.33

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/dist/manifest.js CHANGED
@@ -117,10 +117,13 @@
117
117
  return new Promise((resolve, reject) => {
118
118
  const url = getPluginUrl(pluginName, version);
119
119
 
120
- // Check if already loaded
120
+ // Skip if script with same src already in DOM (e.g. prerendered HTML or second loader run)
121
121
  const existing = document.querySelector(`script[src="${url}"]`);
122
- if (existing && existing.complete) {
123
- return resolve();
122
+ if (existing) {
123
+ if (existing.complete) return resolve();
124
+ existing.addEventListener('load', () => resolve());
125
+ existing.addEventListener('error', () => reject(new Error(`Failed to load ${pluginName} from ${url}`)));
126
+ return;
124
127
  }
125
128
 
126
129
  const script = document.createElement('script');
@@ -297,6 +300,11 @@
297
300
  detectAppwriteFromManifest();
298
301
 
299
302
  if (config && config.plugins.length > 0) {
303
+ if (window.__manifestLoaderStarted) {
304
+ return;
305
+ }
306
+ window.__manifestLoaderStarted = true;
307
+
300
308
  const MANIFEST_DEPENDENT_PLUGINS = [
301
309
  'data', 'localization', 'components',
302
310
  'appwrite-auth', 'appwrite-data', 'appwrite-presence'
@@ -597,6 +597,39 @@ function shouldElementBeVisible(element, normalizedPath) {
597
597
  return true;
598
598
  }
599
599
 
600
+ // Resolve :attr and x-bind:attr whose value is $x.path so injected meta/link have real content (SPA + prerender).
601
+ function resolveDataHeadBindings(element) {
602
+ const x = typeof window !== 'undefined' && window.$x;
603
+ if (!x) return;
604
+ const toResolve = [];
605
+ for (let i = 0; i < element.attributes.length; i++) {
606
+ const attr = element.attributes[i];
607
+ const name = attr.name;
608
+ let bindingAttr = null;
609
+ if (name.startsWith(':')) bindingAttr = name.slice(1);
610
+ else if (name.startsWith('x-bind:')) bindingAttr = name.slice(7);
611
+ if (!bindingAttr) continue;
612
+ const expr = (attr.value || '').trim();
613
+ if (!expr.startsWith('$x.')) continue;
614
+ const path = expr.slice(3).trim();
615
+ if (!path) continue;
616
+ toResolve.push({ bindingName: attr.name, attrName: bindingAttr, path });
617
+ }
618
+ for (const { bindingName, attrName, path } of toResolve) {
619
+ let value;
620
+ try {
621
+ value = path.split('.').reduce(function (obj, key) {
622
+ return obj != null && typeof obj === 'object' ? obj[key] : undefined;
623
+ }, x);
624
+ } catch (e) {
625
+ continue;
626
+ }
627
+ if (value === undefined) continue;
628
+ element.setAttribute(attrName, String(value));
629
+ element.removeAttribute(bindingName);
630
+ }
631
+ }
632
+
600
633
  // Generate unique identifier for head content
601
634
  function generateHeadId(element) {
602
635
  const position = element.getAttribute('data-order');
@@ -632,10 +665,14 @@ function processElementHeadContent(element, normalizedPath) {
632
665
  const isVisible = shouldElementBeVisible(element, normalizedPath);
633
666
 
634
667
  if (isVisible) {
635
- // Check if we've already injected this content
668
+ // Skip if already injected (in-memory) or already present in DOM (e.g. prerendered)
636
669
  if (injectedHeadContent.has(headId)) {
637
670
  return;
638
671
  }
672
+ if (document.head.querySelector(`[data-route-head="${headId}"]`)) {
673
+ injectedHeadContent.add(headId);
674
+ return;
675
+ }
639
676
 
640
677
  // Add new head content
641
678
  Array.from(headTemplate.content.children).forEach(child => {
@@ -646,8 +683,9 @@ function processElementHeadContent(element, normalizedPath) {
646
683
  script.setAttribute('data-route-head', headId);
647
684
  document.head.appendChild(script);
648
685
  } else {
649
- // For other elements, clone and add
686
+ // For other elements, clone and add (resolve $x bindings so meta/link have real values in SPA)
650
687
  const clonedChild = child.cloneNode(true);
688
+ resolveDataHeadBindings(clonedChild);
651
689
  clonedChild.setAttribute('data-route-head', headId);
652
690
  document.head.appendChild(clonedChild);
653
691
  }
@@ -831,6 +869,29 @@ window.ManifestRoutingHead = {
831
869
 
832
870
  // Router anchors
833
871
 
872
+ // Parse pipeline syntax: 'scope | targets' (shared for directive and route-change handler)
873
+ function parseAnchorsExpression(expr) {
874
+ if (!expr || expr.trim() === '') {
875
+ return { scope: '', targets: 'h1, h2, h3, h4, h5, h6' };
876
+ }
877
+ if (expr.includes('|')) {
878
+ const parts = expr.split('|').map(p => p.trim());
879
+ return {
880
+ scope: parts[0] || '',
881
+ targets: parts[1] || 'h1, h2, h3, h4, h5, h6'
882
+ };
883
+ }
884
+ return { scope: '', targets: expr };
885
+ }
886
+
887
+ function isVisible(el) {
888
+ if (!el || !el.getBoundingClientRect) return false;
889
+ const rect = el.getBoundingClientRect();
890
+ if (rect.width === 0 && rect.height === 0) return false;
891
+ const style = window.getComputedStyle(el);
892
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
893
+ }
894
+
834
895
  // Anchors functionality
835
896
  function initializeAnchors() {
836
897
 
@@ -839,24 +900,9 @@ function initializeAnchors() {
839
900
 
840
901
 
841
902
  try {
842
- // Parse pipeline syntax: 'scope | targets'
843
- const parseExpression = (expr) => {
844
- if (!expr || expr.trim() === '') {
845
- return { scope: '', targets: 'h1, h2, h3, h4, h5, h6' };
846
- }
847
-
848
- if (expr.includes('|')) {
849
- const parts = expr.split('|').map(p => p.trim());
850
- return {
851
- scope: parts[0] || '',
852
- targets: parts[1] || 'h1, h2, h3, h4, h5, h6'
853
- };
854
- } else {
855
- return { scope: '', targets: expr };
856
- }
857
- };
903
+ const parseExpression = parseAnchorsExpression;
858
904
 
859
- // Extract anchors function
905
+ // Extract anchors function (only from visible scope containers to avoid prior-route content)
860
906
  const extractAnchors = (expr) => {
861
907
  const parsed = parseExpression(expr);
862
908
 
@@ -864,7 +910,8 @@ function initializeAnchors() {
864
910
  if (!parsed.scope) {
865
911
  containers = [document.body];
866
912
  } else {
867
- containers = Array.from(document.querySelectorAll(parsed.scope));
913
+ const all = Array.from(document.querySelectorAll(parsed.scope));
914
+ containers = all.filter(isVisible);
868
915
  }
869
916
 
870
917
  let elements = [];
@@ -1127,21 +1174,44 @@ document.addEventListener('alpine:init', () => {
1127
1174
  }
1128
1175
  });
1129
1176
 
1130
- // Refresh anchors when route changes
1177
+ // Refresh anchors when route changes — wait for scope DOM to update (e.g. x-markdown) to avoid showing prior page's anchors
1131
1178
  window.addEventListener('manifest:route-change', () => {
1132
- // Immediately clear the store to hide the h5 element
1133
1179
  Alpine.store('anchors', { count: 0 });
1134
1180
 
1135
- // Wait longer for content to load after route change
1136
- setTimeout(() => {
1137
- const anchorElements = document.querySelectorAll('[x-anchors]');
1138
- anchorElements.forEach(el => {
1139
- const expression = el.getAttribute('x-anchors');
1140
- if (expression && el._x_anchorRefresh) {
1141
- el._x_anchorRefresh();
1142
- }
1181
+ const runWhenScopeReady = (el) => {
1182
+ const expression = el.getAttribute('x-anchors');
1183
+ if (!expression || !el._x_anchorRefresh) return;
1184
+ const { scope } = parseAnchorsExpression(expression);
1185
+ if (!scope) {
1186
+ setTimeout(() => el._x_anchorRefresh(), 400);
1187
+ return;
1188
+ }
1189
+ const containers = Array.from(document.querySelectorAll(scope)).filter(isVisible);
1190
+ const container = containers[0];
1191
+ if (!container) {
1192
+ el._x_anchorRefresh();
1193
+ return;
1194
+ }
1195
+ let done = false;
1196
+ const finish = () => {
1197
+ if (done) return;
1198
+ done = true;
1199
+ observer?.disconnect();
1200
+ clearTimeout(fallback);
1201
+ el._x_anchorRefresh();
1202
+ };
1203
+ let t = 0;
1204
+ const observer = new MutationObserver(() => {
1205
+ clearTimeout(t);
1206
+ t = setTimeout(finish, 50);
1143
1207
  });
1144
- }, 200);
1208
+ observer.observe(container, { childList: true, subtree: true });
1209
+ const fallback = setTimeout(finish, 800);
1210
+ };
1211
+
1212
+ requestAnimationFrame(() => {
1213
+ document.querySelectorAll('[x-anchors]').forEach(runWhenScopeReady);
1214
+ });
1145
1215
  });
1146
1216
 
1147
1217
  // Refresh anchors when hash changes (for active state updates)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.32",
3
+ "version": "0.5.33",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter"