1(function () { 2 var ROUTE_REFRESH_PARAM = '__route_refresh'; 3 var ROUTE_REPAIR_PARAM = '__route_repair'; 4 var SITE_TITLE_CACHE_KEY = 'wizardry_blog_site_title_v1'; 5 var APPEND_SITE_TITLE_CACHE_KEY = 'wizardry_blog_append_site_title_to_page_title_v1'; 6 var routeFailureTimer = 0; 7 var pageInitialContentReady = false; 8 var cachedBasePageTitle = ''; 9 var nostrPublishDialogPromise = null; 10 11 function normalizeSiteTitle(value) { 12 var text = String(value || '').replace(/\s+/g, ' ').trim(); 13 return text || 'Site'; 14 } 15 16 function normalizeAppendSiteTitleEnabled(value) { 17 if (value === true || value === 1 || value === '1') { 18 return true; 19 } 20 if (typeof value === 'string') { 21 var text = value.trim().toLowerCase(); 22 return text === 'true' || text === 'yes' || text === 'on'; 23 } 24 return false; 25 } 26 27 function readCachedSiteTitle() { 28 try { 29 return normalizeSiteTitle(localStorage.getItem(SITE_TITLE_CACHE_KEY) || ''); 30 } catch (_err) { 31 return 'Site'; 32 } 33 } 34 35 function readCachedAppendSiteTitleEnabled() { 36 try { 37 return normalizeAppendSiteTitleEnabled(localStorage.getItem(APPEND_SITE_TITLE_CACHE_KEY) || ''); 38 } catch (_err) { 39 return false; 40 } 41 } 42 43 function titleConfig(overrides) { 44 var opts = overrides && typeof overrides === 'object' ? overrides : {}; 45 var siteTitle = Object.prototype.hasOwnProperty.call(opts, 'siteTitle') 46 ? normalizeSiteTitle(opts.siteTitle) 47 : readCachedSiteTitle(); 48 var appendSiteTitle = Object.prototype.hasOwnProperty.call(opts, 'appendSiteTitle') 49 ? normalizeAppendSiteTitleEnabled(opts.appendSiteTitle) 50 : readCachedAppendSiteTitleEnabled(); 51 return { siteTitle: siteTitle, appendSiteTitle: appendSiteTitle }; 52 } 53 54 function renderPageTitle(baseTitle, config) { 55 var base = String(baseTitle || '').trim(); 56 var siteTitle = normalizeSiteTitle(config && config.siteTitle); 57 if (!base || !config || config.appendSiteTitle !== true || !siteTitle) { 58 return base; 59 } 60 if (base.toLowerCase() === siteTitle.toLowerCase()) { 61 return base; 62 } 63 var suffix = ' - ' + siteTitle; 64 return base.slice(-suffix.length) === suffix ? base : base + suffix; 65 } 66 67 function applyPageTitle(baseTitle, overrides) { 68 if (typeof baseTitle !== 'undefined' && baseTitle !== null) { 69 cachedBasePageTitle = String(baseTitle).trim(); 70 } else if (!cachedBasePageTitle) { 71 cachedBasePageTitle = String(document.title || '').trim(); 72 } 73 var nextTitle = renderPageTitle(cachedBasePageTitle, titleConfig(overrides)); 74 if (document.title !== nextTitle) { 75 document.title = nextTitle; 76 } 77 return nextTitle; 78 } 79 80 function removeLegacyTitleBlock(rootNode) { 81 var root = rootNode || null; 82 if (!root) { 83 return; 84 } 85 var prev = root.previousElementSibling; 86 if (!prev || prev.tagName !== 'HEADER') { 87 return; 88 } 89 if (!prev.querySelector || !prev.querySelector('.title')) { 90 return; 91 } 92 if (prev.parentNode) { 93 prev.parentNode.removeChild(prev); 94 } 95 } 96 97 function ensureReadOnlyTitleShell(titleNode) { 98 if (!titleNode) { 99 return { text: null, actions: null }; 100 } 101 var existingActions = titleNode.querySelector('.list-page-title-actions'); 102 var actionsId = String(existingActions && existingActions.id || '').trim(); 103 var actionsClassName = String(existingActions && existingActions.className || 'list-page-title-actions').trim() || 'list-page-title-actions'; 104 var text = titleNode.querySelector('.list-page-title-text'); 105 var actions = actionsId ? document.getElementById(actionsId) : existingActions; 106 if (!text) { 107 text = document.createElement('span'); 108 text.className = 'list-page-title-text'; 109 var child = titleNode.firstChild; 110 while (child) { 111 var next = child.nextSibling; 112 if (child !== actions) { 113 text.appendChild(child); 114 } 115 child = next; 116 } 117 if (actions && actions.parentNode === titleNode) { 118 titleNode.insertBefore(text, actions); 119 } else { 120 titleNode.appendChild(text); 121 } 122 } 123 if (!actions) { 124 actions = document.createElement('span'); 125 if (actionsId) { 126 actions.id = actionsId; 127 } 128 actions.className = actionsClassName; 129 titleNode.appendChild(actions); 130 } else { 131 if (actionsId && actions.id !== actionsId) { 132 actions.id = actionsId; 133 } 134 if (actions.className !== actionsClassName) { 135 actions.className = actionsClassName; 136 } 137 if (actions.parentNode !== titleNode) { 138 titleNode.appendChild(actions); 139 } 140 } 141 return { text: text, actions: actions }; 142 } 143 144 function titleActionsHostHtml(titleNode) { 145 var existingActions = titleNode && titleNode.querySelector 146 ? titleNode.querySelector('.list-page-title-actions') 147 : null; 148 var actionsId = String(existingActions && existingActions.id || '').trim(); 149 var actionsClassName = String(existingActions && existingActions.className || 'list-page-title-actions').trim() || 'list-page-title-actions'; 150 return '<span' + 151 (actionsId ? ' id="' + escapeHtml(actionsId) + '"' : '') + 152 ' class="' + escapeHtml(actionsClassName) + '"></span>'; 153 } 154 155 function ensureReadOnlyDescriptionShell(descriptionNode) { 156 if (!descriptionNode) { 157 return null; 158 } 159 var text = descriptionNode.querySelector('.list-page-description-text'); 160 if (!text) { 161 text = document.createElement('span'); 162 text.className = 'list-page-description-text'; 163 while (descriptionNode.firstChild) { 164 text.appendChild(descriptionNode.firstChild); 165 } 166 descriptionNode.appendChild(text); 167 } 168 return text; 169 } 170 171 function renderReadOnlyTitleShell(titleNode, textValue) { 172 var shell = ensureReadOnlyTitleShell(titleNode); 173 if (shell && shell.text) { 174 var nextText = String(textValue || ''); 175 if (shell.text.textContent !== nextText) { 176 shell.text.textContent = nextText; 177 } 178 } 179 return shell; 180 } 181 182 function renderReadOnlyDescriptionShell(descriptionNode, options) { 183 var opts = options && typeof options === 'object' ? options : {}; 184 var text = ensureReadOnlyDescriptionShell(descriptionNode); 185 if (!text) { 186 return null; 187 } 188 if (Object.prototype.hasOwnProperty.call(opts, 'html')) { 189 var nextHtml = String(opts.html || ''); 190 if (text.innerHTML !== nextHtml) { 191 text.innerHTML = nextHtml; 192 } 193 } else if (Object.prototype.hasOwnProperty.call(opts, 'text')) { 194 var nextText = String(opts.text || ''); 195 if (text.textContent !== nextText) { 196 text.textContent = nextText; 197 } 198 } 199 if (descriptionNode && Object.prototype.hasOwnProperty.call(opts, 'hidden')) { 200 var nextHidden = !!opts.hidden; 201 if (descriptionNode.hidden !== nextHidden) { 202 descriptionNode.hidden = nextHidden; 203 } 204 } 205 return text; 206 } 207 208 function htmlAttr(name, value) { 209 var attrName = String(name || '').trim(); 210 if (!attrName) { 211 return ''; 212 } 213 return ' ' + attrName + '="' + escapeHtml(value) + '"'; 214 } 215 216 function renderEditablePageHeadTitle(targetNode, options) { 217 if (!targetNode) { 218 return false;
219 } 220 var opts = options && typeof options === 'object' ? options : {}; 221 var activeField = String(opts.activeField || '').trim().toLowerCase(); 222 var value = String(opts.value || opts.fallbackText || ''); 223 var actionsHtml = titleActionsHostHtml(targetNode); 224 var editLabel = String(opts.editLabel || 'Edit...'); 225 var inputId = String(opts.inputId || '').trim(); 226 var inputAttrName = String(opts.inputAttrName || '').trim(); 227 var editAttrName = String(opts.editAttrName || '').trim(); 228 var saveAttrName = String(opts.saveAttrName || '').trim(); 229 var showSaveButton = opts.showSaveButton === true; 230 if (activeField === 'title') { 231 targetNode.innerHTML = '<span class="list-page-title-edit-wrap"><input id="' + escapeHtml(inputId) + '" class="list-head-inline-input" type="text" value="' + escapeHtml(value) + '"' + htmlAttr(inputAttrName, 'title') + '></span>' + 232 (showSaveButton ? ' <button type="button" class="list-inline-edit-link"' + htmlAttr(saveAttrName, 'title') + '>Save</button>' : '') + 233 actionsHtml; 234 return true; 235 } 236 targetNode.innerHTML = '<span class="list-page-title-text">' + escapeHtml(value) + '</span>' + 237 (opts.editMode ? ' <button type="button" class="list-inline-edit-link"' + htmlAttr(editAttrName, 'title') + '>' + escapeHtml(editLabel) + '</button>' : '') + 238 actionsHtml; 239 return true; 240 } 241 242 function renderEditablePageHeadDescription(targetNode, options) { 243 if (!targetNode) { 244 return false; 245 } 246 var opts = options && typeof options === 'object' ? options : {}; 247 var activeField = String(opts.activeField || '').trim().toLowerCase(); 248 var text = String(opts.text || ''); 249 var textHtml = String(opts.textHtml || ''); 250 var editLabel = String(opts.editLabel || 'Edit...'); 251 var emptyHtml = String(opts.emptyHtml || '<span class="list-page-description-empty">No description.</span>'); 252 var inputId = String(opts.inputId || '').trim(); 253 var inputAttrName = String(opts.inputAttrName || '').trim(); 254 var editAttrName = String(opts.editAttrName || '').trim(); 255 var saveAttrName = String(opts.saveAttrName || '').trim(); 256 var editingExtraHtml = String(opts.editingExtraHtml || ''); 257 if (opts.suppressEmpty === true) { 258 targetNode.hidden = true; 259 targetNode.innerHTML = ''; 260 return true; 261 } 262 targetNode.hidden = false; 263 if (activeField === 'description') { 264 targetNode.innerHTML = '<span class="list-page-description-edit-wrap"><textarea id="' + escapeHtml(inputId) + '" class="list-head-description-input" rows="4"' + htmlAttr(inputAttrName, 'description') + '>' + escapeHtml(text) + '</textarea></span> <button type="button" class="list-inline-edit-link"' + htmlAttr(saveAttrName, 'description') + '>Save</button>' + editingExtraHtml; 265 return true; 266 } 267 if (opts.editMode) { 268 targetNode.innerHTML = (text.trim() 269 ? '<span class="list-page-description-text">' + textHtml + '</span>' 270 : emptyHtml) + 271 ' <button type="button" class="list-inline-edit-link"' + htmlAttr(editAttrName, 'description') + '>' + escapeHtml(editLabel) + '</button>'; 272 return true; 273 } 274 if (text.trim()) { 275 targetNode.innerHTML = '<span class="list-page-description-text">' + textHtml + '</span>'; 276 return true; 277 } 278 if (opts.hideWhenEmpty === true) { 279 targetNode.hidden = true; 280 targetNode.innerHTML = ''; 281 return true; 282 } 283 targetNode.innerHTML = emptyHtml; 284 return true; 285 } 286 287 function managedPageHeadDescriptionState(options) { 288 var opts = options && typeof options === 'object' ? options : {}; 289 var format = String(opts.format || '').trim().toLowerCase() === 'text' ? 'text' : 'html'; 290 var text = String(opts.text || ''); 291 var hidden = Object.prototype.hasOwnProperty.call(opts, 'hidden') 292 ? !!opts.hidden 293 : (!text.trim() && opts.hideWhenEmpty === true); 294 if (format === 'text') { 295 return { 296 descriptionFallback: { 297 text: text, 298 hidden: hidden 299 } 300 }; 301 } 302 return { 303 descriptionFallback: { 304 html: String(opts.html || ''), 305 hidden: hidden 306 } 307 }; 308 } 309 310 function managedPageHeadTitleState(options) { 311 var opts = options && typeof options === 'object' ? options : {}; 312 var renderState = opts.renderState && typeof opts.renderState === 'object' ? opts.renderState : null; 313 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : null; 314 var fallbackTitleText = String(opts.fallbackTitleText || '').trim() || 'Untitled'; 315 var explicitTitleText = Object.prototype.hasOwnProperty.call(opts, 'titleText') 316 ? String(opts.titleText || '').trim() 317 : '';
318 var renderTitle = String(renderState && renderState.title || '').trim(); 319 var titleText = explicitTitleText || renderTitle || fallbackTitleText; 320 var pageTitle = null; 321 if (Object.prototype.hasOwnProperty.call(opts, 'pageTitle')) { 322 pageTitle = opts.pageTitle === null || typeof opts.pageTitle === 'undefined' 323 ? null 324 : String(opts.pageTitle || '').trim(); 325 } else if (renderTitle) { 326 pageTitle = renderTitle; 327 } else if (opts.useFallbackForPageTitle === true) { 328 pageTitle = titleText; 329 } 330 var syncStatus = payload && Object.prototype.hasOwnProperty.call(payload, 'sync_status') 331 ? payload.sync_status 332 : null; 333 var syncMessageVariant = String(opts.syncMessageVariant || '').trim(); 334 return { 335 pageTitle: pageTitle, 336 titleText: titleText, 337 syncStatus: syncStatus, 338 titleSyncOptions: syncMessageVariant ? { messageVariant: syncMessageVariant } : null 339 }; 340 } 341 342 function managedPageHeadFieldState(options) { 343 var opts = options && typeof options === 'object' ? options : {}; 344 var renderState = opts.renderState && typeof opts.renderState === 'object' ? opts.renderState : null; 345 var titleState = managedPageHeadTitleState({ 346 renderState: renderState, 347 payload: opts.payload, 348 fallbackTitleText: opts.fallbackTitleText, 349 syncMessageVariant: opts.syncMessageVariant 350 }); 351 var titleText = titleState.titleText; 352 var activeField = String(opts.activeField || '').trim().toLowerCase(); 353 var isAdmin = opts.isAdmin === true; 354 var titleInputId = String(opts.titleInputId || '').trim(); 355 var descriptionInputId = String(opts.descriptionInputId || '').trim(); 356 var inputAttrName = String(opts.inputAttrName || '').trim(); 357 var editAttrName = String(opts.editAttrName || '').trim(); 358 var saveAttrName = String(opts.saveAttrName || '').trim(); 359 var descriptionText = String(opts.descriptionText || ''); 360 var descriptionHtml = String(opts.descriptionHtml || ''); 361 var hasMainContent = opts.hasMainContent === true; 362 var focusOptions = null; 363 if (isAdmin && activeField && opts.enableFocus === true) { 364 focusOptions = { 365 activeField: activeField, 366 titleInputId: titleInputId, 367 descriptionInputId: descriptionInputId 368 }; 369 if (Object.prototype.hasOwnProperty.call(opts, 'focusSelectOnFocus')) { 370 focusOptions.selectOnFocus = opts.focusSelectOnFocus; 371 } 372 if (typeof opts.focusAfter === 'function') { 373 focusOptions.afterFocus = opts.focusAfter; 374 } 375 } 376 return { 377 pageTitle: titleState.pageTitle, 378 titleText: titleState.titleText, 379 syncStatus: titleState.syncStatus, 380 titleSyncOptions: titleState.titleSyncOptions, 381 titleEditOptions: { 382 activeField: activeField, 383 editMode: opts.editMode === true, 384 value: titleText, 385 fallbackText: String(opts.fallbackTitleText || '').trim() || 'Untitled', 386 inputId: titleInputId, 387 inputAttrName: inputAttrName, 388 editAttrName: editAttrName, 389 saveAttrName: saveAttrName, 390 showSaveButton: opts.showTitleSaveButton === true 391 }, 392 descriptionEditOptions: { 393 activeField: activeField, 394 editMode: opts.editMode === true, 395 text: descriptionText, 396 textHtml: descriptionHtml, 397 suppressEmpty: !descriptionText.trim() && !hasMainContent && activeField !== 'description', 398 hideWhenEmpty: opts.descriptionHideWhenEmpty === true, 399 inputId: descriptionInputId, 400 inputAttrName: inputAttrName, 401 editAttrName: editAttrName, 402 saveAttrName: saveAttrName, 403 editingExtraHtml: String(opts.descriptionEditingExtraHtml || '') 404 }, 405 focusOptions: focusOptions 406 }; 407 } 408 409 function renderManagedPageHeadForPage(options) { 410 var opts = options && typeof options === 'object' ? options : {}; 411 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : null; 412 var titleNode = opts.titleNode || null; 413 var descriptionNode = opts.descriptionNode || null; 414 var usingFieldState = opts.fieldStateOptions && typeof opts.fieldStateOptions === 'object'; 415 var headStateOptions = usingFieldState 416 ? Object.assign({}, opts.fieldStateOptions) 417 : Object.assign({}, opts.titleStateOptions && typeof opts.titleStateOptions === 'object' 418 ? opts.titleStateOptions 419 : {}); 420 if (!Object.prototype.hasOwnProperty.call(headStateOptions, 'payload')) { 421 headStateOptions.payload = payload; 422 } 423 var headState = usingFieldState 424 ? managedPageHeadFieldState(headStateOptions) 425 : managedPageHeadTitleState(headStateOptions); 426 var navbarTitleOptions = null; 427 if (opts.navbarTitleOptions && typeof opts.navbarTitleOptions === 'object') { 428 navbarTitleOptions = managedNavbarTitlePageOptions(Object.assign({}, opts.navbarTitleOptions, { 429 payload: Object.prototype.hasOwnProperty.call(opts.navbarTitleOptions, 'payload') 430 ? opts.navbarTitleOptions.payload 431 : payload, 432 titleNode: Object.prototype.hasOwnProperty.call(opts.navbarTitleOptions, 'titleNode') 433 ? opts.navbarTitleOptions.titleNode 434 : titleNode 435 })); 436 } 437 var renderOptions = { 438 isAdmin: opts.isAdmin === true, 439 pageTitle: headState.pageTitle, 440 titleNode: titleNode, 441 titleText: headState.titleText, 442 titleSyncOptions: headState.titleSyncOptions, 443 syncStatus: headState.syncStatus, 444 navbarTitleOptions: navbarTitleOptions 445 }; 446 if (descriptionNode && opts.descriptionStateOptions && typeof opts.descriptionStateOptions === 'object') { 447 renderOptions.descriptionNode = descriptionNode; 448 renderOptions.descriptionPayload = payload; 449 renderOptions.descriptionFallback = managedPageHeadDescriptionState(opts.descriptionStateOptions).descriptionFallback; 450 if (Object.prototype.hasOwnProperty.call(opts, 'allowPrerenderDescription')) { 451 renderOptions.allowPrerenderDescription = opts.allowPrerenderDescription; 452 } 453 } 454 if (usingFieldState) { 455 renderOptions.titleEditOptions = headState.titleEditOptions; 456 renderOptions.descriptionEditOptions = headState.descriptionEditOptions; 457 renderOptions.focusOptions = headState.focusOptions; 458 } 459 renderManagedPageHead(renderOptions); 460 return { 461 headState: headState, 462 navbarTitleOptions: navbarTitleOptions, 463 renderOptions: renderOptions 464 }; 465 } 466 467 function renderManagedPageHead(options) { 468 var opts = options && typeof options === 'object' ? options : {}; 469 var admin = opts.isAdmin === true; 470 if (admin) { 471 if (opts.titleNode) { 472 renderEditablePageHeadTitle(opts.titleNode, opts.titleEditOptions); 473 } 474 if (opts.descriptionNode) { 475 renderEditablePageHeadDescription(opts.descriptionNode, opts.descriptionEditOptions); 476 } 477 if (opts.focusOptions && typeof opts.focusOptions === 'object') { 478 focusEditablePageHeadField(opts.focusOptions); 479 } 480 if (opts.navbarTitleOptions && typeof opts.navbarTitleOptions === 'object') { 481 renderManagedNavbarTitleState(opts.navbarTitleOptions); 482 } 483 return true; 484 } 485 if (Object.prototype.hasOwnProperty.call(opts, 'pageTitle') && opts.pageTitle !== null && typeof opts.pageTitle !== 'undefined') { 486 var pageTitle = String(opts.pageTitle || '').trim(); 487 if (pageTitle) { 488 applyPageTitle(pageTitle); 489 } 490 } 491 if (opts.titleNode) { 492 var titleShell = renderReadOnlyTitleShell(opts.titleNode, opts.titleText); 493 opts.titleNode.hidden = false;
494 if (titleShell && titleShell.actions) { 495 renderSyncStatusPill(titleShell.actions, opts.syncStatus, opts.titleSyncOptions); 496 } 497 } 498 if (opts.descriptionNode && Object.prototype.hasOwnProperty.call(opts, 'descriptionFallback')) { 499 var descriptionFragment = null; 500 if (opts.allowPrerenderDescription !== false) { 501 descriptionFragment = readPrerenderHtmlFragment(opts.descriptionPayload, 'description'); 502 } 503 if (descriptionFragment) { 504 renderReadOnlyDescriptionShell(opts.descriptionNode, { 505 html: descriptionFragment.html, 506 hidden: descriptionFragment.hidden 507 }); 508 } else { 509 renderReadOnlyDescriptionShell(opts.descriptionNode, opts.descriptionFallback); 510 } 511 } 512 if (opts.navbarTitleOptions && typeof opts.navbarTitleOptions === 'object') { 513 renderManagedNavbarTitleState(opts.navbarTitleOptions); 514 } 515 return true; 516 } 517 518 function renderEditableNavbarTitleRow(targetNode, options) { 519 if (!targetNode) { 520 return false; 521 } 522 var opts = options && typeof options === 'object' ? options : {}; 523 if (opts.hidden === true) { 524 targetNode.hidden = true; 525 targetNode.innerHTML = ''; 526 return true; 527 } 528 var currentText = String(opts.currentText || ''); 529 var editing = opts.editing === true; 530 var labelText = String(opts.labelText || 'Navbar title'); 531 var inputValue = String(opts.inputValue || currentText); 532 var inputAriaLabel = String(opts.inputAriaLabel || labelText); 533 var editButtonClassName = String(opts.editButtonClassName || 'list-inline-edit-link').trim() || 'list-inline-edit-link'; 534 var saveButtonClassName = String(opts.saveButtonClassName || editButtonClassName).trim() || editButtonClassName; 535 var saveDisabled = opts.saveDisabled === true; 536 var html = '<div class="list-page-nav-title-row">'; 537 html += '<span class="list-page-nav-title-label">' + escapeHtml(labelText) + '</span>'; 538 if (editing) { 539 html += '<span class="list-page-nav-title-edit-wrap">'; 540 html += '<input type="text" class="list-page-nav-title-input" value="' + escapeHtml(inputValue) + '" data-page-nav-title-input="true" aria-label="' + escapeHtml(inputAriaLabel) + '">'; 541 html += '<button type="button" class="' + escapeHtml(saveButtonClassName) + '" data-page-nav-title-action="save"' + (saveDisabled ? ' disabled aria-disabled="true"' : '') + '>OK</button>'; 542 html += '</span>'; 543 } else { 544 html += '<span class="list-page-nav-title-value">' + escapeHtml(currentText) + '</span>'; 545 html += '<button type="button" class="' + escapeHtml(editButtonClassName) + '" data-page-nav-title-action="edit">Edit...</button>'; 546 } 547 html += '</div>'; 548 targetNode.hidden = false; 549 targetNode.innerHTML = html; 550 return true; 551 } 552 553 function focusEditablePageHeadField(options) { 554 var opts = options && typeof options === 'object' ? options : {}; 555 var activeField = String(opts.activeField || '').trim().toLowerCase(); 556 if (!activeField) { 557 return false; 558 } 559 requestAnimationFrame(function () { 560 var inputId = activeField === 'title' 561 ? String(opts.titleInputId || '').trim() 562 : String(opts.descriptionInputId || '').trim(); 563 var input = inputId ? document.getElementById(inputId) : null; 564 if (input && typeof input.focus === 'function') { 565 input.focus(); 566 if (opts.selectOnFocus !== false && typeof input.select === 'function') { 567 input.select(); 568 } 569 } 570 if (typeof opts.afterFocus === 'function') { 571 opts.afterFocus(input || null); 572 } 573 }); 574 return true; 575 } 576 577 function focusEditableNavbarTitleInput(options) { 578 var opts = options && typeof options === 'object' ? options : {}; 579 var targetNode = opts.targetNode || null; 580 if (!targetNode || !targetNode.querySelector) { 581 return false; 582 } 583 requestAnimationFrame(function () { 584 var input = targetNode.querySelector('[data-page-nav-title-input="true"]'); 585 if (input && typeof input.focus === 'function') { 586 input.focus(); 587 if (opts.selectOnFocus !== false && typeof input.select === 'function') { 588 input.select(); 589 } 590 } 591 }); 592 return true; 593 } 594 595 function renderManagedNavbarTitleRow(options) { 596 var opts = options && typeof options === 'object' ? options : {}; 597 var titleNode = opts.titleNode || null; 598 var resolvedTarget = titleNode && titleNode.querySelector 599 ? titleNode.querySelector('[data-page-nav-title-host="true"]') 600 : null; 601 if (!resolvedTarget) { 602 return false;
603 } 604 var shouldShow = opts.isAdmin === true && opts.editMode === true; 605 if (titleNode && titleNode.classList) { 606 titleNode.classList.toggle('has-nav-title-row', shouldShow); 607 } 608 if (!shouldShow) { 609 return renderEditableNavbarTitleRow(resolvedTarget, { hidden: true }); 610 } 611 var editing = opts.editing === true; 612 var rendered = renderEditableNavbarTitleRow(resolvedTarget, { 613 currentText: opts.currentText, 614 editing: editing, 615 inputValue: opts.inputValue, 616 labelText: opts.labelText, 617 inputAriaLabel: opts.inputAriaLabel, 618 editButtonClassName: opts.editButtonClassName, 619 saveButtonClassName: opts.saveButtonClassName, 620 saveDisabled: opts.saveDisabled 621 }); 622 if (rendered && editing) { 623 focusEditableNavbarTitleInput({ 624 targetNode: resolvedTarget, 625 selectOnFocus: opts.selectOnFocus 626 }); 627 } 628 return rendered; 629 } 630 631 function resolveRootAttributeFallbackText(rootNode, attributeNames, fallbackText) { 632 var root = rootNode || null; 633 var names = Array.isArray(attributeNames) ? attributeNames : []; 634 if (root && typeof root.getAttribute === 'function') { 635 for (var i = 0; i < names.length; i += 1) { 636 var attrName = String(names[i] || '').trim(); 637 if (!attrName) { 638 continue; 639 } 640 var attrValue = String(root.getAttribute(attrName) || '').trim(); 641 if (attrValue) { 642 return attrValue; 643 } 644 } 645 } 646 return String(fallbackText || '').trim(); 647 } 648 649 function resolveManagedNavbarTitle(options) { 650 var opts = options && typeof options === 'object' ? options : {}; 651 var configured = String(opts.configuredText || '').trim(); 652 if (configured) { 653 return configured; 654 } 655 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : null; 656 var payloadTitle = String(payload && payload.nav_title || '').trim(); 657 if (payloadTitle) { 658 return payloadTitle; 659 } 660 var renderState = opts.renderState && typeof opts.renderState === 'object' ? opts.renderState : null; 661 var renderTitle = String(renderState && renderState.title || '').trim(); 662 if (renderTitle) { 663 return renderTitle; 664 } 665 var fallbackText = resolveRootAttributeFallbackText( 666 opts.rootNode || null, 667 Array.isArray(opts.fallbackAttributeNames) ? opts.fallbackAttributeNames : ['data-page-title'], 668 opts.fallbackText 669 ); 670 if (fallbackText) { 671 return fallbackText; 672 } 673 return 'Untitled'; 674 } 675 676 function renderManagedNavbarTitleState(options) { 677 var opts = options && typeof options === 'object' ? options : {}; 678 var current = resolveManagedNavbarTitle({ 679 configuredText: opts.configuredText, 680 payload: opts.payload, 681 renderState: opts.renderState, 682 rootNode: opts.rootNode, 683 fallbackAttributeNames: opts.fallbackAttributeNames, 684 fallbackText: opts.fallbackText 685 }); 686 renderManagedNavbarTitleRow({ 687 isAdmin: opts.isAdmin, 688 editMode: opts.editMode, 689 titleNode: opts.titleNode, 690 currentText: current, 691 editing: opts.editing, 692 inputValue: Object.prototype.hasOwnProperty.call(opts, 'inputValue') ? opts.inputValue : current, 693 labelText: opts.labelText, 694 inputAriaLabel: opts.inputAriaLabel, 695 editButtonClassName: opts.editButtonClassName, 696 saveButtonClassName: opts.saveButtonClassName, 697 saveDisabled: opts.saveDisabled, 698 selectOnFocus: opts.selectOnFocus 699 }); 700 return current; 701 } 702 703 function managedNavbarTitleOptions(options) { 704 var opts = options && typeof options === 'object' ? options : {}; 705 var result = { 706 rootNode: opts.rootNode || null, 707 fallbackAttributeNames: Array.isArray(opts.fallbackAttributeNames) 708 ? opts.fallbackAttributeNames.slice() 709 : ['data-page-title'], 710 fallbackText: String(opts.fallbackText || '').trim() || 'Untitled' 711 }; 712 [ 713 'configuredText', 714 'payload', 715 'renderState', 716 'isAdmin', 717 'editMode', 718 'titleNode', 719 'editing', 720 'inputValue', 721 'labelText', 722 'inputAriaLabel', 723 'editButtonClassName', 724 'saveButtonClassName', 725 'saveDisabled', 726 'selectOnFocus', 727 'currentText' 728 ].forEach(function (key) { 729 if (Object.prototype.hasOwnProperty.call(opts, key)) { 730 result[key] = opts[key]; 731 } 732 }); 733 return result; 734 } 735 736 function managedNavbarTitlePageOptions(options) { 737 var opts = options && typeof options === 'object' ? options : {}; 738 return Object.assign( 739 managedNavbarTitleOptions({
740 configuredText: opts.configuredText, 741 payload: opts.payload, 742 renderState: opts.renderState, 743 rootNode: opts.rootNode, 744 fallbackAttributeNames: opts.fallbackAttributeNames, 745 fallbackText: opts.fallbackText, 746 isAdmin: opts.isAdmin, 747 editMode: opts.editMode, 748 titleNode: opts.titleNode, 749 editing: opts.editing, 750 inputValue: opts.inputValue, 751 labelText: opts.labelText, 752 inputAriaLabel: opts.inputAriaLabel, 753 editButtonClassName: opts.editButtonClassName, 754 saveButtonClassName: opts.saveButtonClassName, 755 saveDisabled: opts.saveDisabled, 756 selectOnFocus: opts.selectOnFocus 757 }), 758 { 759 state: opts.state, 760 payload: opts.payload, 761 rootNode: opts.rootNode || null, 762 slug: opts.slug, 763 isAdmin: opts.isAdmin, 764 renderHead: opts.renderHead 765 } 766 ); 767 } 768 769 function renderManagedValidationSummary(options) { 770 var opts = options && typeof options === 'object' ? options : {};
771 var containerNode = opts.containerNode || null; 772 if (!containerNode) { 773 return false; 774 } 775 var visible = opts.visible === true; 776 var errors = Array.isArray(opts.errors) ? opts.errors : []; 777 var warnings = Array.isArray(opts.warnings) ? opts.warnings : []; 778 if (!visible || (!errors.length && !warnings.length)) { 779 containerNode.hidden = true; 780 containerNode.innerHTML = ''; 781 return false; 782 } 783 var html = ''; 784 if (errors.length) { 785 html += '<div class="list-validation-block is-error"><strong>Validation errors</strong><ul>'; 786 errors.forEach(function (msg) { 787 html += '<li>' + escapeHtml(msg) + '</li>'; 788 }); 789 html += '</ul></div>'; 790 } 791 if (warnings.length) { 792 html += '<div class="list-validation-block is-warn"><strong>Validation warnings</strong><ul>'; 793 warnings.forEach(function (msg) { 794 html += '<li>' + escapeHtml(msg) + '</li>'; 795 }); 796 html += '</ul></div>'; 797 } 798 containerNode.hidden = false; 799 containerNode.innerHTML = html; 800 return true; 801 } 802 803 function filterManagedValidationMessages(messages, options) { 804 var list = Array.isArray(messages) ? messages : []; 805 var opts = options && typeof options === 'object' ? options : {}; 806 if (opts.suppressTransientStateErrors !== true) { 807 return list.slice(); 808 } 809 return list.filter(function (msg) { 810 var text = String(msg || '').trim().toLowerCase(); 811 if (!text) { 812 return false; 813 } 814 return !(/could not validate .*state/.test(text) || /validation .*temporarily unavailable/.test(text)); 815 }); 816 } 817 818 function renderManagedPayloadValidationSummary(options) { 819 var opts = options && typeof options === 'object' ? options : {}; 820 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : null; 821 var validation = payload && payload.validation && typeof payload.validation === 'object' 822 ? payload.validation 823 : {}; 824 return renderManagedValidationSummary({ 825 containerNode: opts.containerNode || null, 826 visible: opts.visible === true, 827 errors: filterManagedValidationMessages(validation.errors, { 828 suppressTransientStateErrors: opts.suppressTransientStateErrors === true 829 }), 830 warnings: opts.includeWarnings === false 831 ? [] 832 : filterManagedValidationMessages(validation.warnings, { 833 suppressTransientStateErrors: opts.suppressTransientStateErrors === true 834 }) 835 }); 836 } 837 838 function renderManagedSaveStatus(node, status) { 839 if (!node) { 840 return false; 841 } 842 var nextStatus = String(status || '').trim(); 843 node.classList.toggle('is-error', nextStatus === 'error'); 844 if (nextStatus === 'saving') { 845 node.innerHTML = '<span class="save-spinner" aria-hidden="true"></span>Saving...'; 846 return true; 847 } 848 if (nextStatus === 'error') { 849 node.textContent = 'Save failed'; 850 return true; 851 } 852 node.textContent = 'Saved'; 853 return true; 854 } 855 856 function managedSaveStatusHtml(options) { 857 var opts = options && typeof options === 'object' ? options : {}; 858 if (opts.visible !== true) { 859 return ''; 860 } 861 var statusId = String(opts.statusId || '').trim(); 862 var status = String(opts.status || '').trim(); 863 var html = '<span'; 864 if (statusId) { 865 html += ' id="' + escapeHtml(statusId) + '"'; 866 } 867 html += ' class="list-admin-save-status" aria-live="polite">'; 868 if (status === 'saving') { 869 html += '<span class="save-spinner" aria-hidden="true"></span>Saving...'; 870 } else if (status === 'error') { 871 html += 'Save failed'; 872 } else { 873 html += 'Saved'; 874 } 875 html += '</span>'; 876 return html; 877 } 878 879 function renderManagedAdminActionBarHtml(options) { 880 var opts = options && typeof options === 'object' ? options : {}; 881 function actionAttr(name, value) { 882 var attrName = String(name || '').trim(); 883 if (!attrName) { 884 return ''; 885 } 886 return ' ' + attrName + '="' + escapeHtml(String(value || '')) + '"'; 887 } 888 var html = '<span class="list-page-admin-bar">'; 889 html += managedSaveStatusHtml({ 890 visible: opts.saveIndicatorVisible === true, 891 statusId: opts.saveStatusId, 892 status: opts.saveStatus 893 }); 894 if (opts.showRevert === true) { 895 html += '<button type="button"' 896 + actionAttr(opts.revertActionAttrName || 'data-action', String(opts.revertAction || 'revert')) 897 + htmlAttr('title', String(opts.revertTitle || '')) 898 + (opts.canRevert === true ? '' : ' disabled aria-disabled="true"') 899 + '>Revert</button>'; 900 } 901 html += syncStatusPillHtml(opts.syncStatus, opts.syncOptions); 902 if (opts.showPublish === true) { 903 html += '<button type="button" class="' + escapeHtml(String(opts.primaryButtonClassName || 'list-admin-primary-btn')) + '"' 904 + actionAttr(opts.publishActionAttrName || 'data-action', String(opts.publishAction || 'publish')) 905 + '>' + escapeHtml(String(opts.publishLabel || 'Publish to Nostr...')) + '</button>'; 906 } 907 html += '<button type="button" class="' + escapeHtml(String(opts.primaryButtonClassName || 'list-admin-primary-btn')) + '"' 908 + actionAttr(opts.editActionAttrName || 'data-action', String(opts.editAction || 'toggle-edit')) 909 + '>' + escapeHtml(opts.editMode ? String(opts.doneLabel || 'Done') : String(opts.editLabel || 'Edit')) + '</button>'; 910 html += '</span>'; 911 return html; 912 } 913 914 function renderManagedAdminActionBar(options) { 915 var opts = options && typeof options === 'object' ? options : {}; 916 var adminNode = opts.adminNode || null; 917 var actionsHostNode = opts.actionsHostNode || null; 918 if (actionsHostNode && opts.clearActionsHost !== false) { 919 actionsHostNode.innerHTML = ''; 920 } 921 if (opts.isAdmin !== true) { 922 if (typeof opts.onHide === 'function') { 923 opts.onHide(); 924 } 925 if (adminNode) { 926 adminNode.hidden = true; 927 adminNode.innerHTML = ''; 928 } 929 return false;
930 } 931 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : {}; 932 var hasCanonical = !!payload.canonical_exists; 933 var hasDraftChanges = !!payload.draft_differs; 934 var canRevert = Object.prototype.hasOwnProperty.call(opts, 'canRevert') 935 ? opts.canRevert === true 936 : (hasCanonical && hasDraftChanges); 937 var html = renderManagedAdminActionBarHtml({ 938 saveIndicatorVisible: opts.saveIndicatorVisible === true, 939 saveStatusId: opts.saveStatusId, 940 saveStatus: opts.saveStatus, 941 showRevert: Object.prototype.hasOwnProperty.call(opts, 'showRevert') 942 ? opts.showRevert === true 943 : opts.editMode === true, 944 revertActionAttrName: opts.revertActionAttrName, 945 revertAction: opts.revertAction, 946 revertTitle: String( 947 opts.revertTitle || ( 948 canRevert 949 ? 'Revert draft to Nostr version' 950 : (hasCanonical ? 'No local changes to revert' : 'No Nostr version found') 951 ) 952 ), 953 canRevert: canRevert, 954 syncStatus: payload.sync_status, 955 syncOptions: opts.syncOptions, 956 showPublish: Object.prototype.hasOwnProperty.call(opts, 'showPublish') 957 ? opts.showPublish === true 958 : (opts.editMode === true || hasDraftChanges), 959 publishActionAttrName: opts.publishActionAttrName, 960 publishAction: opts.publishAction, 961 publishLabel: opts.publishLabel, 962 editActionAttrName: opts.editActionAttrName, 963 editAction: opts.editAction, 964 editMode: opts.editMode === true, 965 editLabel: opts.editLabel, 966 doneLabel: opts.doneLabel, 967 primaryButtonClassName: opts.primaryButtonClassName 968 }); 969 if (actionsHostNode) { 970 actionsHostNode.innerHTML = html; 971 } 972 if (adminNode) { 973 adminNode.hidden = true; 974 adminNode.innerHTML = ''; 975 } 976 return true; 977 } 978 979 function startManagedNavbarTitleEdit(options) { 980 var opts = options && typeof options === 'object' ? options : {}; 981 var state = opts.state && typeof opts.state === 'object' ? opts.state : null; 982 if (!state) { 983 return false; 984 } 985 state.navTitleEditing = true; 986 state.navTitleInput = resolveManagedNavbarTitle(opts); 987 if (typeof opts.renderHead === 'function') { 988 opts.renderHead(); 989 } 990 return true; 991 } 992 993 function saveManagedNavbarTitle(options) { 994 var opts = options && typeof options === 'object' ? options : {}; 995 var state = opts.state && typeof opts.state === 'object' ? opts.state : null; 996 var isAdmin = typeof opts.isAdmin === 'function' ? opts.isAdmin() : opts.isAdmin === true; 997 if (!state || !isAdmin || state.navTitleBusy) { 998 return Promise.resolve(false); 999 } 1000 var rootNode = opts.rootNode || null; 1001 var input = rootNode && rootNode.querySelector ? rootNode.querySelector('[data-page-nav-title-input="true"]') : null; 1002 if (input instanceof HTMLInputElement) { 1003 state.navTitleInput = String(input.value || ''); 1004 } 1005 var nextTitle = String(state.navTitleInput || '').trim(); 1006 var slug = String(opts.slug || '').trim(); 1007 state.navTitleBusy = true; 1008 var auth = authPayload(); 1009 return postFormJson('/cgi/blog-update-nostr-page-nav-title', { 1010 page_slug: slug, 1011 nav_title: nextTitle, 1012 session_token: auth.session_token, 1013 csrf_token: auth.csrf_token 1014 }).then(function (data) { 1015 var updated = String((data && data.nav_title) || nextTitle || resolveManagedNavbarTitle(opts)).trim(); 1016 state.navTitle = updated; 1017 state.navTitleEditing = false; 1018 state.navTitleInput = ''; 1019 if (opts.payload && typeof opts.payload === 'object') { 1020 opts.payload.nav_title = updated; 1021 } else if (state.payload && typeof state.payload === 'object') { 1022 state.payload.nav_title = updated; 1023 } 1024 try { 1025 window.dispatchEvent(new CustomEvent('wizardry-navbar-refresh-request')); 1026 } catch (_err) { 1027 // Ignore navbar refresh dispatch failures. 1028 } 1029 if (typeof opts.renderHead === 'function') { 1030 opts.renderHead(); 1031 } 1032 return true; 1033 }).catch(function (err) { 1034 window.alert(err && err.message ? err.message : 'Could not save navbar title'); 1035 return false;
1036 }).finally(function () { 1037 state.navTitleBusy = false; 1038 if (typeof opts.renderHead === 'function') { 1039 opts.renderHead(); 1040 } 1041 }); 1042 } 1043 1044 function saveManagedNavbarTitleForPage(options) { 1045 return saveManagedNavbarTitle(managedNavbarTitlePageOptions(options)); 1046 } 1047 1048 function startManagedNavbarTitleEditForPage(options) { 1049 return startManagedNavbarTitleEdit(managedNavbarTitlePageOptions(options)); 1050 } 1051 1052 function handleManagedNavbarTitleAction(event, target, options) { 1053 var targetNode = target instanceof Element ? target : null; 1054 var opts = options && typeof options === 'object' ? options : {}; 1055 if (!targetNode) { 1056 return false; 1057 } 1058 var actionNode = targetNode.closest('[data-page-nav-title-action]'); 1059 if (!(actionNode instanceof HTMLElement)) { 1060 return false; 1061 } 1062 var isAdmin = typeof opts.isAdmin === 'function' ? opts.isAdmin() : opts.isAdmin === true; 1063 if (!isAdmin || opts.editMode !== true) { 1064 return false; 1065 } 1066 if (event && typeof event.preventDefault === 'function') { 1067 event.preventDefault(); 1068 } 1069 var action = String(actionNode.getAttribute('data-page-nav-title-action') || ''); 1070 if (action === 'edit') { 1071 if (typeof opts.beginEdit === 'function') { 1072 opts.beginEdit(); 1073 } 1074 return true; 1075 } 1076 if (action === 'save') { 1077 if (typeof opts.save === 'function') { 1078 opts.save(); 1079 } 1080 return true; 1081 } 1082 return false; 1083 } 1084 1085 function resetManagedNavbarTitleState(state, options) { 1086 var targetState = state && typeof state === 'object' ? state : null; 1087 var opts = options && typeof options === 'object' ? options : {}; 1088 if (!targetState) { 1089 return false; 1090 } 1091 if (Object.prototype.hasOwnProperty.call(opts, 'payload')) { 1092 targetState.navTitle = String(opts.payload && opts.payload.nav_title || '').trim(); 1093 } else if (Object.prototype.hasOwnProperty.call(opts, 'navTitle')) { 1094 targetState.navTitle = String(opts.navTitle || '').trim(); 1095 } else if (!Object.prototype.hasOwnProperty.call(targetState, 'navTitle')) { 1096 targetState.navTitle = ''; 1097 } 1098 targetState.navTitleEditing = false; 1099 targetState.navTitleInput = ''; 1100 targetState.navTitleBusy = false; 1101 return true; 1102 } 1103 1104 function renderResolvedReadOnlyContent(options) { 1105 var opts = options && typeof options === 'object' ? options : {}; 1106 var targetNode = opts.targetNode || null; 1107 var resolved = null; 1108 var matchedStaticPrerender = false;
1109 if (opts.allowPrerender !== false) { 1110 var fragment = readPrerenderHtmlFragment(opts.payload, 'content'); 1111 if (fragment) { 1112 resolved = { 1113 adopted: true, 1114 html: fragment.html, 1115 hidden: fragment.hidden 1116 }; 1117 matchedStaticPrerender = hasMatchingStaticPrerender(targetNode, targetNode, opts.payload); 1118 } 1119 } 1120 if (!resolved && typeof opts.renderFallback === 'function') { 1121 var fallback = opts.renderFallback(); 1122 resolved = { 1123 adopted: false, 1124 html: typeof fallback === 'string' ? fallback : '', 1125 hidden: false 1126 }; 1127 } 1128 if (!resolved || !targetNode) { 1129 return resolved; 1130 } 1131 if (resolved.adopted) { 1132 if (!matchedStaticPrerender) { 1133 mountPrerenderHtmlFragment(targetNode, opts.payload, 'content', opts.mountOptions); 1134 } else { 1135 targetNode.hidden = !!resolved.hidden; 1136 } 1137 if (typeof opts.afterAdopt === 'function') { 1138 opts.afterAdopt(resolved); 1139 } 1140 } else { 1141 var mountOptions = opts.mountOptions && typeof opts.mountOptions === 'object' ? opts.mountOptions : {}; 1142 if (typeof mountOptions.applyHtml === 'function') { 1143 mountOptions.applyHtml(resolved.html); 1144 } else { 1145 targetNode.innerHTML = resolved.html; 1146 } 1147 targetNode.hidden = !!resolved.hidden; 1148 } 1149 if (typeof opts.afterRender === 'function') { 1150 opts.afterRender(resolved); 1151 } 1152 return resolved; 1153 } 1154 1155 function escapeHtml(value) { 1156 return String(value || '') 1157 .replace(/&/g, '&') 1158 .replace(/</g, '<') 1159 .replace(/>/g, '>') 1160 .replace(/"/g, '"') 1161 .replace(/'/g, '''); 1162 } 1163 1164 function syncStatusDefaults(status, options) { 1165 var opts = options && typeof options === 'object' ? options : {}; 1166 var variant = String(opts.messageVariant || 'server_copy').trim().toLowerCase(); 1167 switch (status) { 1168 case 'local_newer_than_nostr': 1169 return { 1170 label: 'Server newer', 1171 message: variant === 'local_source' 1172 ? 'Local source is newer than latest published Nostr state.' 1173 : 'Server copy is newer than the latest published Nostr state. Visitors see the server copy.', 1174 className: 'status-local-newer-than-nostr' 1175 }; 1176 case 'nostr_newer_than_local': 1177 return { 1178 label: 'Nostr newer', 1179 message: variant === 'local_source' 1180 ? 'Published Nostr state is newer than local source.' 1181 : 'Published Nostr state is newer than the server copy. Visitors still see the server copy until the site source changes.', 1182 className: 'status-nostr-newer-than-local' 1183 }; 1184 case 'in_sync': 1185 return { 1186 label: 'Synced', 1187 message: variant === 'local_source' 1188 ? 'Local source and published state are in sync.' 1189 : 'Server copy and published Nostr state are in sync.', 1190 className: 'status-in-sync' 1191 }; 1192 case 'unpublished_local_changes': 1193 return { 1194 label: 'Server only', 1195 message: variant === 'local_source' 1196 ? 'No published Nostr state yet for this page.' 1197 : 'This page exists only on the server so far. Visitors see the server copy.', 1198 className: 'status-unpublished-local-changes' 1199 }; 1200 default: 1201 return { 1202 label: 'Sync unknown', 1203 message: 'Cannot determine local-vs-Nostr sync status yet.', 1204 className: 'status-unknown' 1205 }; 1206 } 1207 } 1208 1209 function syncStatusPillHtml(syncStatus, options) { 1210 var sync = syncStatus && typeof syncStatus === 'object' ? syncStatus : {}; 1211 var status = String(sync.status || 'unknown').trim(); 1212 var config = syncStatusDefaults(status, options); 1213 var message = String(sync.message || config.message).trim(); 1214 return '<span class="page-sync-status-pill ' + config.className + '" title="' + escapeHtml(message) + '">' + escapeHtml(config.label) + '</span>'; 1215 } 1216
1217 function renderSyncStatusPill(actionsHostOrId, syncStatus, options) { 1218 var host = typeof actionsHostOrId === 'string' 1219 ? document.getElementById(String(actionsHostOrId || '').trim()) 1220 : actionsHostOrId; 1221 if (!host) { 1222 return false; 1223 } 1224 var html = syncStatusPillHtml(syncStatus, options); 1225 if (host.innerHTML !== html) { 1226 host.innerHTML = html; 1227 } 1228 return true; 1229 } 1230 1231 function stripLegacyNostrSyncPills(value) { 1232 return String(value || '').replace(/<\s*(?:nostr-sync-pill|gazeta-sync-pill)\b[^>]*>(?:\s*<\/\s*(?:nostr-sync-pill|gazeta-sync-pill)\s*>)?/gi, '').trim(); 1233 } 1234 1235 function readNostrPageBootstrap(slug, isExpectedPayload) { 1236 try { 1237 var allPayloads = window.__wizardryNostrPageBootstrap; 1238 if (!allPayloads || typeof allPayloads !== 'object') { 1239 return null; 1240 } 1241 var payload = allPayloads[String(slug || '')]; 1242 if (!payload || typeof payload !== 'object') { 1243 return null; 1244 } 1245 if (typeof isExpectedPayload === 'function' && !isExpectedPayload(payload)) { 1246 return null; 1247 } 1248 return payload; 1249 } catch (_err) { 1250 return null; 1251 } 1252 } 1253 1254 function prerenderFragment(payload, key) { 1255 return String(payload && payload.prerender && payload.prerender[key] || ''); 1256 } 1257 1258 function readPrerenderHtmlFragment(payload, name, options) { 1259 var opts = options && typeof options === 'object' ? options : {}; 1260 var fragmentName = String(name || '').trim(); 1261 if (!fragmentName) { 1262 return null; 1263 } 1264 var html = prerenderFragment(payload, fragmentName + '_html'); 1265 if (!html && opts.allowEmpty !== true) { 1266 return null; 1267 } 1268 return { 1269 html: html, 1270 hidden: /\bhidden\b/i.test(prerenderFragment(payload, fragmentName + '_attrs')) 1271 }; 1272 } 1273 1274 function mountPrerenderHtmlFragment(targetNode, payload, name, options) { 1275 var fragment = readPrerenderHtmlFragment(payload, name, options); 1276 if (!targetNode || !fragment) { 1277 return false; 1278 } 1279 var opts = options && typeof options === 'object' ? options : {}; 1280 if (typeof opts.applyHtml === 'function') { 1281 opts.applyHtml(fragment.html); 1282 } else { 1283 targetNode.innerHTML = fragment.html; 1284 } 1285 targetNode.hidden = fragment.hidden; 1286 return true; 1287 } 1288 1289 function resolveManagedPagePreviewHtml(options) { 1290 var opts = options && typeof options === 'object' ? options : {}; 1291 var payload = opts.payload && typeof opts.payload === 'object' ? opts.payload : null; 1292 if (!payload) { 1293 return ''; 1294 } 1295 if (typeof opts.normalizeState !== 'function') { 1296 return ''; 1297 } 1298 var normalizeState = opts.normalizeState; 1299 var payloadState = normalizeState((payload && payload.state) || null); 1300 var renderState = normalizeState(opts.renderState || null); 1301 if (JSON.stringify(payloadState) !== JSON.stringify(renderState)) { 1302 return ''; 1303 } 1304 var fragment = readPrerenderHtmlFragment(payload, 'content'); 1305 if (!fragment || fragment.hidden) { 1306 return ''; 1307 } 1308 return String(fragment.html || ''); 1309 } 1310 1311 function hasPaintedPrerenderShell(rootNode, contentNode) { 1312 if (!rootNode || rootNode.getAttribute('data-prerender-painted') !== 'true') { 1313 return false; 1314 } 1315 if (!contentNode) { 1316 return true; 1317 } 1318 return contentNode.getAttribute('data-prerender-painted') === 'true'; 1319 } 1320 1321 function hasMatchingStaticPrerender(rootNode, contentNode, payload) { 1322 var signature = String(payload && payload.prerender_signature || '').trim(); 1323 if (!signature || !rootNode || !contentNode) { 1324 return false; 1325 } 1326 if (!hasPaintedPrerenderShell(rootNode, contentNode)) { 1327 return false; 1328 } 1329 return rootNode.getAttribute('data-prerender-signature') === signature || 1330 contentNode.getAttribute('data-prerender-signature') === signature; 1331 } 1332 1333 function pageBootstrapCacheKey(slug) { 1334 return 'nostr_page_bootstrap_v1:' + String(slug || '').trim(); 1335 } 1336 1337 function readBootstrapCache(options) { 1338 var opts = options && typeof options === 'object' ? options : {}; 1339 var storageKey = String(opts.storageKey || '').trim(); 1340 if (!storageKey) { 1341 return null; 1342 } 1343 var clearOnDeny = opts.clearOnDeny !== false; 1344 try { 1345 if (typeof opts.allowRead === 'function' && opts.allowRead() === false) { 1346 if (clearOnDeny) { 1347 localStorage.removeItem(storageKey); 1348 } 1349 return null; 1350 } 1351 var raw = localStorage.getItem(storageKey); 1352 if (!raw) { 1353 return null; 1354 } 1355 var parsed = JSON.parse(raw); 1356 if (!parsed || typeof parsed !== 'object') { 1357 return null; 1358 }
1359 var savedAt = Number(parsed.saved_at || 0); 1360 var maxAgeMs = Number(opts.maxAgeMs || 0); 1361 if (!isFinite(savedAt) || savedAt <= 0 || (maxAgeMs > 0 && (Date.now() - savedAt) > maxAgeMs)) { 1362 localStorage.removeItem(storageKey); 1363 return null; 1364 } 1365 var expectedAuthSignature = typeof opts.authSignature === 'function' 1366 ? String(opts.authSignature() || '') 1367 : String(opts.authSignature || ''); 1368 if (String(parsed.auth_signature || '') !== expectedAuthSignature) { 1369 return null; 1370 } 1371 if (!parsed.payload || typeof parsed.payload !== 'object') { 1372 return null; 1373 } 1374 if (typeof opts.isExpectedPayload === 'function' && !opts.isExpectedPayload(parsed.payload)) { 1375 localStorage.removeItem(storageKey); 1376 return null; 1377 } 1378 return parsed.payload; 1379 } catch (_err) { 1380 return null; 1381 } 1382 } 1383 1384 function writeBootstrapCache(payload, options) { 1385 var opts = options && typeof options === 'object' ? options : {}; 1386 var storageKey = String(opts.storageKey || '').trim(); 1387 if (!storageKey || !payload || typeof payload !== 'object') { 1388 return; 1389 } 1390 var clearOnDeny = opts.clearOnDeny !== false; 1391 try { 1392 if (typeof opts.allowWrite === 'function' && opts.allowWrite(payload) === false) { 1393 if (clearOnDeny) { 1394 localStorage.removeItem(storageKey); 1395 } 1396 return; 1397 } 1398 var authSignature = typeof opts.authSignature === 'function' 1399 ? String(opts.authSignature() || '') 1400 : String(opts.authSignature || ''); 1401 localStorage.setItem(storageKey, JSON.stringify({ 1402 auth_signature: authSignature, 1403 payload: payload, 1404 saved_at: Date.now() 1405 })); 1406 } catch (_err) { 1407 // Ignore storage failures. 1408 } 1409 } 1410 1411 function sessionAuthSignature() { 1412 var auth = authPayload(); 1413 return String(auth.session_token || '') + '|' + String(auth.csrf_token || ''); 1414 } 1415 1416 function writePageBootstrapCache(payload, options) { 1417 var opts = options && typeof options === 'object' ? options : {}; 1418 return writeBootstrapCache(payload, { 1419 storageKey: pageBootstrapCacheKey(opts.slug), 1420 authSignature: opts.authSignature || sessionAuthSignature, 1421 allowWrite: opts.allowWrite, 1422 clearOnDeny: opts.clearOnDeny 1423 }); 1424 } 1425 1426 function commitManagedPagePayload(targetPayload, sourcePayload, options) { 1427 var opts = options && typeof options === 'object' ? options : {}; 1428 var target = mergeManagedPagePayload(targetPayload, sourcePayload, { 1429 includeState: opts.includeState, 1430 validationFallback: opts.validationFallback, 1431 passthroughFields: opts.passthroughFields 1432 }); 1433 if (!target) { 1434 return target; 1435 } 1436 if (Object.prototype.hasOwnProperty.call(opts, 'draftExists')) { 1437 target.draft_exists = !!opts.draftExists; 1438 } 1439 if (Object.prototype.hasOwnProperty.call(opts, 'draftDiffers')) { 1440 target.draft_differs = !!opts.draftDiffers; 1441 } 1442 if (Object.prototype.hasOwnProperty.call(opts, 'statePayload')) { 1443 target.state = opts.statePayload; 1444 } 1445 if (opts.persist !== false && opts.slug) { 1446 writePageBootstrapCache(target, { 1447 slug: opts.slug, 1448 authSignature: opts.authSignature || sessionAuthSignature, 1449 clearOnDeny: opts.clearOnDeny, 1450 allowWrite: managedBootstrapAllowWriteForPersistMode(opts.persistMode) 1451 }); 1452 } 1453 return target; 1454 } 1455 1456 function reconcileManagedPageRefreshRender(options) { 1457 var opts = options && typeof options === 'object' ? options : {}; 1458 var state = opts.state && typeof opts.state === 'object' ? opts.state : null; 1459 if (!state) { 1460 return false; 1461 } 1462 var previousSignature = String(opts.previousSignature || ''); 1463 var nextRenderSignature = String(opts.nextRenderSignature || ''); 1464 var shouldRepaint = !state.initialContentPainted || previousSignature !== nextRenderSignature; 1465 if (!shouldRepaint || (typeof opts.shouldPreserveAdoptedPrerender === 'function' && opts.shouldPreserveAdoptedPrerender() === true)) { 1466 if (typeof opts.renderPreserved === 'function') { 1467 opts.renderPreserved(); 1468 }
1469 state.renderSignature = nextRenderSignature; 1470 return false; 1471 } 1472 if (typeof opts.beforeRenderAll === 'function') { 1473 opts.beforeRenderAll(); 1474 } 1475 if (typeof opts.renderAll === 'function') { 1476 opts.renderAll(); 1477 } 1478 state.renderSignature = nextRenderSignature; 1479 return true; 1480 } 1481 1482 function bindSessionAuthRefresh(options) { 1483 var opts = options && typeof options === 'object' ? options : {}; 1484 var onChange = typeof opts.onChange === 'function' ? opts.onChange : null; 1485 if (!onChange) { 1486 return false; 1487 } 1488 var onVisible = typeof opts.onVisible === 'function' ? opts.onVisible : null; 1489 var onHidden = typeof opts.onHidden === 'function' ? opts.onHidden : null; 1490 var storageKeys = Array.isArray(opts.storageKeys) ? opts.storageKeys.map(function (key) { 1491 return String(key || ''); 1492 }).filter(Boolean) : ['session_token', 'csrf_token']; 1493 window.addEventListener('blog-auth-changed', onChange); 1494 window.addEventListener('storage', function (event) { 1495 if (!event || !event.key) { 1496 return; 1497 } 1498 if (storageKeys.indexOf(String(event.key || '')) !== -1) { 1499 onChange(event); 1500 } 1501 }); 1502 window.addEventListener('focus', onChange); 1503 document.addEventListener('visibilitychange', function () { 1504 if (document.visibilityState === 'visible') { 1505 if (onVisible) { 1506 onVisible(); 1507 return; 1508 } 1509 onChange(); 1510 return; 1511 } 1512 if (onHidden) { 1513 onHidden(); 1514 } 1515 }); 1516 var pollState = opts.pollState && typeof opts.pollState === 'object' ? opts.pollState : null; 1517 var pollDurationMs = Math.max(0, Number(opts.pollDurationMs || 0) || 0); 1518 if (pollState && pollDurationMs > 0 && !pollState.timer) { 1519 pollState.startedAt = Date.now(); 1520 pollState.timer = window.setInterval(function () { 1521 if (Date.now() - Number(pollState.startedAt || 0) > pollDurationMs) { 1522 window.clearInterval(pollState.timer); 1523 pollState.timer = 0; 1524 return; 1525 } 1526 onChange(); 1527 if (typeof opts.stopWhen === 'function' && opts.stopWhen()) { 1528 window.clearInterval(pollState.timer); 1529 pollState.timer = 0; 1530 } 1531 }, Math.max(100, Number(opts.pollIntervalMs || 500) || 500)); 1532 } 1533 return true; 1534 } 1535 1536 function authPayload() { 1537 try { 1538 return { 1539 session_token: String(localStorage.getItem('session_token') || '').trim(), 1540 csrf_token: String(localStorage.getItem('csrf_token') || '').trim() 1541 }; 1542 } catch (_err) { 1543 return { session_token: '', csrf_token: '' }; 1544 } 1545 } 1546 1547 function hasLikelyAuthenticatedSession() { 1548 var auth = authPayload(); 1549 return !!(auth && auth.session_token && auth.csrf_token); 1550 } 1551 1552 function hasLikelyAdminSession() { 1553 if (!hasLikelyAuthenticatedSession()) { 1554 return false; 1555 } 1556 try { 1557 return String(localStorage.getItem('last_auth_is_admin') || '')
1557=== '1'; 1558 } catch (_err) { 1559 return false; 1560 } 1561 } 1562 1563 function viewerAuthSignature() { 1564 return sessionAuthSignature() + '|' + (hasLikelyAdminSession() ? 'admin' : 'visitor'); 1565 } 1566 1567 function managedBootstrapAllowWriteForPersistMode(persistMode) { 1568 var mode = String(persistMode || 'always').trim().toLowerCase(); 1569 return function (nextPayload) { 1570 if (mode === 'authenticated_admin') { 1571 return !!(nextPayload && nextPayload.is_admin && hasLikelyAuthenticatedSession()); 1572 } 1573 return true; 1574 }; 1575 } 1576 1577 function eventHasVerifiedAdminState(event) { 1578 return !!(event && event.detail && event.detail.authenticated === true && typeof event.detail.is_admin !== 'undefined'); 1579 } 1580 1581 function authEventIsLikelyAdmin(event) { 1582 if (eventHasVerifiedAdminState(event)) { 1583 return event.detail.is_admin === true; 1584 } 1585 return hasLikelyAdminSession(); 1586 } 1587 1588 function canUseVisitorPrerenderReadOnly(options) { 1589 var opts = options && typeof options === 'object' ? options : {}; 1590 if (opts.isAdmin === true && opts.allowAdmin !== true) { 1591 return false; 1592 } 1593 if (opts.allowAuthenticated !== true && hasLikelyAuthenticatedSession()) { 1594 return false; 1595 } 1596 if (typeof opts.extraCheck === 'function') { 1597 return opts.extraCheck() !== false; 1598 } 1599 if (Object.prototype.hasOwnProperty.call(opts, 'extraAllowed')) { 1600 return opts.extraAllowed !== false; 1601 } 1602 return true; 1603 } 1604 1605 function canUsePayloadPrerenderReadOnly(payload, options) { 1606 if (!payload || typeof payload !== 'object') { 1607 return false; 1608 } 1609 var opts = options && typeof options === 'object' ? options : {}; 1610 var allowAuthenticated = false; 1611 var allowAdmin = false; 1612 if (typeof opts.allowAuthenticated === 'function') { 1613 allowAuthenticated = opts.allowAuthenticated(payload) === true; 1614 } else if (typeof opts.allowAuthenticatedField === 'string' && opts.allowAuthenticatedField) { 1615 allowAuthenticated = payload[opts.allowAuthenticatedField] === true; 1616 } else if (Object.prototype.hasOwnProperty.call(opts, 'allowAuthenticated')) { 1617 allowAuthenticated = opts.allowAuthenticated === true; 1618 } 1619 if (typeof opts.allowAdmin === 'function') { 1620 allowAdmin = opts.allowAdmin(payload) === true; 1621 } else if (typeof opts.allowAdminField === 'string' && opts.allowAdminField) { 1622 allowAdmin = payload[opts.allowAdminField] === true; 1623 } else if (Object.prototype.hasOwnProperty.call(opts, 'allowAdmin')) { 1624 allowAdmin = opts.allowAdmin === true; 1625 } 1626 return canUseVisitorPrerenderReadOnly({ 1627 isAdmin: !!payload.is_admin, 1628 allowAuthenticated: allowAuthenticated, 1629 allowAdmin: allowAdmin, 1630 extraCheck: opts.extraCheck, 1631 extraAllowed: opts.extraAllowed 1632 }); 1633 } 1634 1635 function ensureNostrPublishDialog() { 1636 if (window.blogNostrPublishDialog && typeof window.blogNostrPublishDialog.open === 'function') { 1637 return Promise.resolve(true); 1638 } 1639 if (nostrPublishDialogPromise) { 1640 return nostrPublishDialogPromise; 1641 } 1642 nostrPublishDialogPromise = new Promise(function (resolve) { 1643 var script = document.createElement('script'); 1644 script.src = '/static/nostr-publish-dialog.js'; 1645 script.async = true; 1646 script.onload = function () { 1647 resolve(!!(window.blogNostrPublishDialog && typeof window.blogNostrPublishDialog.open === 'function')); 1648 }; 1649 script.onerror = function () { 1650 nostrPublishDialogPromise = null; 1651 resolve(false); 1652 }; 1653 document.head.appendChild(script); 1654 }); 1655 return nostrPublishDialogPromise; 1656 } 1657 1658 var managedMarkdownParserPromise = null; 1659 var MANAGED_MARKDOWN_PARSER_SRC = 'https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js'; 1660 1661 function markedParserReady() { 1662 return !!(window.marked && typeof window.marked.parse === 'function' && typeof window.marked.parseInline === 'function'); 1663 } 1664 1665 function ensureManagedMarkdownParser() { 1666 if (markedParserReady()) { 1667 return Promise.resolve(true); 1668 } 1669 if (managedMarkdownParserPromise) { 1670 return managedMarkdownParserPromise; 1671 } 1672 managedMarkdownParserPromise = new Promise(function (resolve) { 1673 var script = document.querySelector('script[src="' + MANAGED_MARKDOWN_PARSER_SRC + '"]'); 1674 function finish(ok) { 1675 if (!ok) { 1676 managedMarkdownParserPromise = null; 1677 } 1678 resolve(ok); 1679 } 1680 function handleLoad() { 1681 finish(markedParserReady()); 1682 } 1683 function handleError() { 1684 finish(false); 1685 } 1686 if (!script) { 1687 script = document.createElement('script'); 1688 script.src = MANAGED_MARKDOWN_PARSER_SRC; 1689 script.async = true; 1690 script.setAttribute('data-managed-markdown-parser', 'true'); 1691 } 1692 script.addEventListener('load', handleLoad, { once: true }); 1693 script.addEventListener('error', handleError, { once: true }); 1694 if (!script.parentNode) { 1695 document.head.appendChild(script); 1696 } 1697 }); 1698 return managedMarkdownParserPromise; 1699 } 1700 1701 function scheduleMarkedUpgrade(upgradeState, onReady, options) { 1702 var state = upgradeState && typeof upgradeState === 'object' ? upgradeState : null; 1703 var opts = options && typeof options === 'object' ? options : {}; 1704 if (!state || typeof onReady !== 'function' || markedParserReady()) { 1705 return false;
1706 } 1707 if (state.timer) { 1708 return false; 1709 } 1710 if (opts.loadParser) { 1711 ensureManagedMarkdownParser(); 1712 } 1713 state.attempts = 0; 1714 function pollForMarked() { 1715 if (markedParserReady()) { 1716 state.timer = 0; 1717 onReady(); 1718 return; 1719 } 1720 state.attempts += 1; 1721 if (state.attempts >= 50) { 1722 state.timer = 0; 1723 return; 1724 } 1725 state.timer = window.setTimeout(pollForMarked, 100); 1726 } 1727 state.timer = window.setTimeout(pollForMarked, 100); 1728 return true; 1729 } 1730 1731 function renderManagedMarkdownInline(md, options) { 1732 var value = String(md || ''); 1733 var opts = options && typeof options === 'object' ? options : {}; 1734 if (!value) { 1735 return ''; 1736 } 1737 if (window.marked && typeof window.marked.parseInline === 'function') { 1738 return window.marked.parseInline(value); 1739 } 1740 if (opts.upgradeState && typeof opts.onUpgrade === 'function') { 1741 scheduleMarkedUpgrade(opts.upgradeState, opts.onUpgrade, opts); 1742 } 1743 if (typeof opts.renderFallback === 'function') { 1744 return opts.renderFallback(value); 1745 } 1746 return escapeHtml(value); 1747 } 1748 1749 function safeManagedMarkdownHref(raw) { 1750 var href = String(raw || '').trim().replace(/^<|>$/g, ''); 1751 if (!href) { 1752 return ''; 1753 } 1754 if (/^(https?:|mailto:|\/|#)/i.test(href)) { 1755 return href; 1756 } 1757 if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(href)) { 1758 return ''; 1759 } 1760 return href; 1761 } 1762 1763 function renderManagedMarkdownInlineLinkFallback(md) { 1764 var value = String(md || ''); 1765 var linkPattern = /\[([^\]]+)\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g; 1766 var html = ''; 1767 var lastIndex = 0; 1768 var match; 1769 while ((match = linkPattern.exec(value)) !== null) { 1770 html += escapeHtml(value.slice(lastIndex, match.index)); 1771 var href = safeManagedMarkdownHref(match[2] || ''); 1772 var label = escapeHtml(match[1] || ''); 1773 if (href) { 1774 html += '<a href="' + escapeHtml(href) + '">' + label + '</a>'; 1775 } else { 1776 html += label; 1777 } 1778 lastIndex = linkPattern.lastIndex; 1779 } 1780 html += escapeHtml(value.slice(lastIndex)); 1781 return html; 1782 } 1783 1784 function renderManagedMarkdownBlock(md, options) { 1785 var value = String(md || ''); 1786 var opts = options && typeof options === 'object' ? options : {}; 1787 if (!value) { 1788 return ''; 1789 } 1790 if (window.marked && typeof window.marked.parse === 'function') { 1791 return window.marked.parse(value); 1792 } 1793 if (opts.upgradeState && typeof opts.onUpgrade === 'function') { 1794 scheduleMarkedUpgrade(opts.upgradeState, opts.onUpgrade, opts); 1795 } 1796 if (typeof opts.renderFallback === 'function') { 1797 return opts.renderFallback(value); 1798 } 1799 var inlineFallback = typeof opts.renderInlineFallback === 'function' 1800 ? opts.renderInlineFallback 1801 : escapeHtml; 1802 return '<p>' + inlineFallback(value).replace(/\n/g, '<br>') + '</p>'; 1803 } 1804 1805 function normalizeSlug(value) { 1806 return String(value || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, ''); 1807 } 1808 1809 function slugFromPath(pathname) { 1810 var path = String(pathname || '').trim(); 1811 if (!path) { 1812 return 'index'; 1813 } 1814 path = path.split('?')[0].split('#')[0].replace(/\/+$/, ''); 1815 if (!path || path === '/') { 1816 return 'index'; 1817 } 1818 if (path.indexOf('/pages/') === 0) { 1819 path = path.slice('/pages/'.length); 1820 } else if (path.charAt(0) === '/') { 1821 path = path.slice(1); 1822 } 1823 path = path.replace(/\.html?$/i, ''); 1824 return path.indexOf('/') >= 0 ? '' : normalizeSlug(path); 1825 } 1826 1827 function routeRootSlugFromDom() { 1828 var blogRoot = document.getElementById('blog-page-root'); 1829 if (blogRoot) { 1830 return normalizeSlug(blogRoot.getAttribute('data-page-slug') || blogRoot.getAttribute('data-blog-slug') || ''); 1831 } 1832 var nip23Root = document.getElementById('nip23-page-root'); 1833 if (nip23Root) { 1834 return normalizeSlug(nip23Root.getAttribute('data-page-slug') || ''); 1835 } 1836 var listRoot = document.getElementById('list-page-root') || document.getElementById('icon-gallery-root'); 1837 if (listRoot) { 1838 return normalizeSlug(listRoot.getAttribute('data-list-slug') || ''); 1839 } 1840 var rankingRoot = document.getElementById('public-ranking-root'); 1841 if (rankingRoot) { 1842 return normalizeSlug(rankingRoot.getAttribute('data-ranking-slug') || ''); 1843 } 1844 var contactRoot = document.getElementById('contact-page-root'); 1845 if (contactRoot) { 1846 return normalizeSlug(contactRoot.getAttribute('data-page-slug') || ''); 1847 } 1848 return ''; 1849 } 1850 1851 function slugsEquivalent(expected, root) { 1852 var a = normalizeSlug(expected); 1853 var b = normalizeSlug(root); 1854 return !!(a && b && (a === b || (a === 'index' && b === 'blog') || (a === 'blog' && b === 'index'))); 1855 } 1856 1857 function forceHydrationReveal() { 1858 try { 1859 var gate = window.__wizardryHydration; 1860 if (gate && typeof gate.forceReveal === 'function') { 1861 gate.forceReveal(); 1862 return; 1863 } 1864 } catch (_gateErr) { 1865 // Ignore hydration gate issues. 1866 } 1867 if (document.documentElement) { 1868 document.documentElement.classList.remove('app-hydrating'); 1869 } 1870 } 1871 1872 function markHydrationPageReady() { 1873 try { 1874 var gate = window.__wizardryHydration; 1875 if (gate && typeof gate.markPageReady === 'function') { 1876 gate.markPageReady(); 1877 } 1878 } catch (_gateErr) { 1879 // Ignore hydration gate issues. 1880 } 1881 } 1882 1883 function resolveManagedPageReadySlug(slug) { 1884 var explicitSlug = String(slug || '').trim(); 1885 if (explicitSlug) { 1886 return explicitSlug; 1887 } 1888 return routeRootSlugFromDom() || slugFromPath(window.location.pathname || '/') || ''; 1889 } 1890 1891 function hasPaintedManagedContent(contentNode) { 1892 return !!( 1893 contentNode && 1894 contentNode.getAttribute && 1895 contentNode.getAttribute('data-prerender-painted') === 'true' 1896 ); 1897 } 1898 1899 function notifyInitialContentPainted(slug, options) { 1900 var opts = options && typeof options === 'object' ? options : {}; 1901 var state = opts.state && typeof opts.state === 'object' ? opts.state : null; 1902 if (state && state.initialContentPainted) { 1903 return false;
1904 } 1905 if (state) { 1906 state.initialContentPainted = true; 1907 } 1908 var rootNode = opts.rootNode || null; 1909 var loadingClass = String(opts.loadingClass || '').trim(); 1910 if (rootNode && rootNode.classList && loadingClass) { 1911 rootNode.classList.remove(loadingClass); 1912 } 1913 pageInitialContentReady = true; 1914 try { 1915 window.dispatchEvent(new CustomEvent('blog-page-initial-content-ready', { 1916 detail: { slug: resolveManagedPageReadySlug(slug) } 1917 })); 1918 } catch (_err) { 1919 // Ignore event dispatch failures. 1920 } 1921 if (opts.markPageReady === true) { 1922 markHydrationPageReady(); 1923 } 1924 return true; 1925 } 1926 1927 function notifyCurrentManagedPageReady(options) { 1928 var opts = options && typeof options === 'object' ? options : {}; 1929 return notifyInitialContentPainted( 1930 Object.prototype.hasOwnProperty.call(opts, 'slug') ? opts.slug : '', 1931 opts 1932 ); 1933 } 1934 1935 function renderLoadFallback(err, options) { 1936 var opts = options && typeof options === 'object' ? options : {}; 1937 var consoleLabel = String(opts.consoleLabel || 'Page').trim() || 'Page'; 1938 if (err && window.console && typeof window.console.warn === 'function') { 1939 window.console.warn(consoleLabel + ' refresh failed:', err); 1940 } 1941 var state = opts.state && typeof opts.state === 'object' ? opts.state : null;
1942 var contentNode = opts.contentNode || null; 1943 if ((state && state.initialContentPainted) || !contentNode) { 1944 return false; 1945 } 1946 if (hasPaintedManagedContent(contentNode)) { 1947 return notifyInitialContentPainted(opts.slug || '', opts); 1948 } 1949 var fallbackText = String(opts.fallbackText || 'Page content is still loading.'); 1950 contentNode.innerHTML = '<p class="placeholder">' + fallbackText 1951 .replace(/&/g, '&') 1952 .replace(/</g, '<') 1953 .replace(/>/g, '>') 1954 .replace(/"/g, '"') 1955 .replace(/'/g, ''') + '</p>'; 1956 return notifyInitialContentPainted(opts.slug || '', opts); 1957 } 1958 1959 function loadPublicPageFragment(options) { 1960 var opts = options && typeof options === 'object' ? options : {}; 1961 var endpoint = String(opts.endpoint || '').trim(); 1962 var contentNode = opts.contentNode || null; 1963 if (!endpoint || !contentNode) { 1964 return Promise.resolve(false); 1965 } 1966 1967 var requestOptions = Object.assign({ 1968 cache: 'no-store', 1969 credentials: 'same-origin' 1970 }, opts.requestOptions || {}); 1971 var didNotify = false; 1972 1973 function notifyReady() { 1974 if (didNotify) { 1975 return false; 1976 } 1977 didNotify = true; 1978 return notifyInitialContentPainted(opts.slug || '', opts); 1979 } 1980 1981 return fetch(endpoint, requestOptions) 1982 .then(function (res) { return res.text(); }) 1983 .then(function (html) { 1984 var rendered = String(html || '').trim(); 1985 if (!rendered) { 1986 throw new Error(String(opts.emptyMessage || 'Public page fragment returned no content.')); 1987 } 1988 if (typeof opts.renderFragment === 'function') { 1989 opts.renderFragment(rendered); 1990 } else { 1991 contentNode.innerHTML = rendered; 1992 } 1993 if (typeof opts.afterRender === 'function') { 1994 opts.afterRender(rendered); 1995 } 1996 notifyReady(); 1997 return true; 1998 }) 1999 .catch(function (err) { 2000 if (typeof opts.afterError === 'function') { 2001 opts.afterError(err); 2002 } 2003 if (renderLoadFallback(err, opts)) { 2004 didNotify = true; 2005 } 2006 return false; 2007 }); 2008 } 2009 2010 function renderManagedNostrBootstrapPayload(payload, options) { 2011 var opts = options && typeof options === 'object' ? options : {}; 2012 var isExpectedPayload = typeof opts.isExpectedPayload === 'function' ? opts.isExpectedPayload : null; 2013 if (!payload || (isExpectedPayload && !isExpectedPayload(payload))) { 2014 return false; 2015 } 2016 var authenticated = window.__wizardryHasLikelyAuthenticatedSession(); 2017 var likelyAdmin = hasLikelyAdminSession(); 2018 var optimisticPayload = payload;
2019 if (opts.promoteAuthenticated !== false && !optimisticPayload.is_admin && likelyAdmin) { 2020 optimisticPayload = Object.assign({}, optimisticPayload, { is_admin: true }); 2021 } 2022 if (typeof opts.applyState !== 'function') { 2023 return false; 2024 } 2025 opts.applyState(optimisticPayload, payload, authenticated); 2026 var canAdoptStaticPrerender = typeof opts.canAdoptStaticPrerender === 'function' 2027 ? opts.canAdoptStaticPrerender(optimisticPayload, payload, authenticated) 2028 : true; 2029 var matchesStaticPrerender = canAdoptStaticPrerender 2030 && typeof opts.hasMatchingStaticPrerender === 'function' 2031 && opts.hasMatchingStaticPrerender(optimisticPayload, payload, authenticated); 2032 if (matchesStaticPrerender) { 2033 if (typeof opts.renderAdoptedPrerender === 'function') { 2034 opts.renderAdoptedPrerender(optimisticPayload, payload, authenticated); 2035 } 2036 if (typeof opts.afterRender === 'function') { 2037 opts.afterRender(optimisticPayload, payload, authenticated); 2038 } 2039 if (typeof opts.markInitialContentPainted === 'function') { 2040 opts.markInitialContentPainted(); 2041 } 2042 return true; 2043 } 2044 if (typeof opts.renderAll !== 'function') { 2045 return false; 2046 } 2047 opts.renderAll(optimisticPayload, payload, authenticated); 2048 if (typeof opts.afterRender === 'function') { 2049 opts.afterRender(optimisticPayload, payload, authenticated); 2050 } 2051 if (typeof opts.markInitialContentPainted === 'function') { 2052 opts.markInitialContentPainted(); 2053 } 2054 return true; 2055 } 2056 2057 function renderManagedNostrPageBootstrapPayload(payload, options) { 2058 var opts = options && typeof options === 'object' ? options : {}; 2059 var rootNode = opts.rootNode || null;
2060 var contentNode = opts.contentNode || null; 2061 var bootstrapNotify = opts.markInitialContentPainted; 2062 if (!bootstrapNotify && opts.notifyInitialContentPainted && typeof opts.notifyInitialContentPainted === 'object') { 2063 bootstrapNotify = function () { 2064 notifyInitialContentPainted( 2065 Object.prototype.hasOwnProperty.call(opts.notifyInitialContentPainted, 'slug') 2066 ? opts.notifyInitialContentPainted.slug 2067 : '', 2068 opts.notifyInitialContentPainted 2069 ); 2070 }; 2071 } 2072 if (!bootstrapNotify && opts.notifyInitialContentPainted !== false) { 2073 var shouldSynthesizeNotify = Object.prototype.hasOwnProperty.call(opts, 'state') 2074 || Object.prototype.hasOwnProperty.call(opts, 'rootNode') 2075 || Object.prototype.hasOwnProperty.call(opts, 'loadingClass') 2076 || Object.prototype.hasOwnProperty.call(opts, 'markPageReady') 2077 || Object.prototype.hasOwnProperty.call(opts, 'slug'); 2078 if (shouldSynthesizeNotify) { 2079 var notifyOptions = {}; 2080 if (Object.prototype.hasOwnProperty.call(opts, 'state')) { 2081 notifyOptions.state = opts.state; 2082 } 2083 if (Object.prototype.hasOwnProperty.call(opts, 'rootNode')) { 2084 notifyOptions.rootNode = opts.rootNode; 2085 } 2086 if (Object.prototype.hasOwnProperty.call(opts, 'loadingClass')) { 2087 notifyOptions.loadingClass = opts.loadingClass; 2088 } 2089 if (Object.prototype.hasOwnProperty.call(opts, 'markPageReady')) { 2090 notifyOptions.markPageReady = opts.markPageReady; 2091 } 2092 if (Object.prototype.hasOwnProperty.call(opts, 'slug')) { 2093 notifyOptions.slug = opts.slug; 2094 } 2095 bootstrapNotify = function () { 2096 notifyInitialContentPainted( 2097 Object.prototype.hasOwnProperty.call(notifyOptions, 'slug') ? notifyOptions.slug : '', 2098 notifyOptions 2099 ); 2100 }; 2101 } 2102 } 2103 return renderManagedNostrBootstrapPayload(payload, { 2104 isExpectedPayload: opts.isExpectedPayload, 2105 applyState: opts.applyState, 2106 canAdoptStaticPrerender: opts.canAdoptStaticPrerender, 2107 promoteAuthenticated: opts.promoteAuthenticated, 2108 hasMatchingStaticPrerender: typeof opts.hasMatchingStaticPrerender === 'function' 2109 ? opts.hasMatchingStaticPrerender 2110 : function (optimisticPayload) { 2111 return hasMatchingStaticPrerender(rootNode, contentNode, optimisticPayload); 2112 }, 2113 renderAdoptedPrerender: typeof opts.renderAdoptedPrerender === 'function' 2114 ? opts.renderAdoptedPrerender 2115 : function () { 2116 if (typeof opts.renderHead === 'function') { 2117 opts.renderHead(); 2118 } 2119 if (typeof opts.renderAdmin === 'function') { 2120 opts.renderAdmin(); 2121 } 2122 if (typeof opts.renderValidation === 'function') { 2123 opts.renderValidation(); 2124 } 2125 }, 2126 renderAll: opts.renderAll, 2127 afterRender: opts.afterRender, 2128 markInitialContentPainted: bootstrapNotify 2129 }); 2130 } 2131 2132 function renderManagedNostrBootstrapSources(options) { 2133 var opts = options && typeof options === 'object' ? options : {}; 2134 var renderPayload = typeof opts.renderPayload === 'function' ? opts.renderPayload : null; 2135 if (!renderPayload) { 2136 return false; 2137 } 2138 var cachedPayload = readBootstrapCache({ 2139 storageKey: pageBootstrapCacheKey(opts.slug), 2140 maxAgeMs: opts.maxAgeMs, 2141 authSignature: opts.authSignature || sessionAuthSignature, 2142 isExpectedPayload: opts.isExpectedPayload, 2143 allowRead: opts.allowRead, 2144 clearOnDeny: opts.clearOnDeny 2145 }); 2146 if (renderPayload(cachedPayload)) { 2147 return true; 2148 } 2149 var prerenderedPayload = readNostrPageBootstrap(opts.slug, opts.isExpectedPayload); 2150 if (!prerenderedPayload || !renderPayload(prerenderedPayload)) { 2151 return false; 2152 } 2153 writePageBootstrapCache(prerenderedPayload, { 2154 slug: opts.slug, 2155 authSignature: opts.authSignature || sessionAuthSignature, 2156 allowWrite: opts.allowWrite || managedBootstrapAllowWriteForPersistMode(opts.persistMode), 2157 clearOnDeny: opts.clearOnDeny 2158 }); 2159 return true; 2160 } 2161 2162 function refreshManagedNostrPage(options) { 2163 var opts = options && typeof options === 'object' ? options : {}; 2164 var endpoint = String(opts.endpoint || '/cgi/blog-get-nostr-page').trim() || '/cgi/blog-get-nostr-page'; 2165 var slug = String(opts.slug || '').trim(); 2166 var requestSlug = Object.prototype.hasOwnProperty.call(opts, 'pageSlug') 2167 ? String(opts.pageSlug || slug).trim() 2168 : slug; 2169 var retryAttempts = Math.max(1, Math.floor(Number(opts.retryAttempts || 1) || 1
2169)); 2170 var auth = authPayload(); 2171 var params = new URLSearchParams({ 2172 page_slug: requestSlug, 2173 session_token: auth.session_token, 2174 csrf_token: auth.csrf_token 2175 }); 2176 var extraParams = opts.extraParams && typeof opts.extraParams === 'object' ? opts.extraParams : null; 2177 if (extraParams) { 2178 Object.keys(extraParams).forEach(function (key) { 2179 var name = String(key || '').trim(); 2180 if (!name) { 2181 return; 2182 } 2183 var value = extraParams[key]; 2184 if (value === undefined || value === null) { 2185 return; 2186 } 2187 params.set(name, String(value)); 2188 }); 2189 } 2190 2191 function resolveRetryDelayMs(attempt, err) { 2192 if (attempt >= retryAttempts) { 2193 return -1; 2194 } 2195 var configured = opts.retryDelayMs; 2196 if (typeof configured === 'function') { 2197 configured = configured(attempt, err); 2198 } 2199 var delayMs = Number(configured); 2200 if (!isFinite(delayMs) || delayMs < 0) { 2201 delayMs = 0; 2202 } 2203 return delayMs; 2204 } 2205 2206 function fetchManagedPageOnce() { 2207 return fetch(endpoint, { 2208 method: 'POST', 2209 cache: 'no-store', 2210 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 2211 body: params.toString() 2212 }).then(function (res) { 2213 return res.text().then(function (text) { 2214 var data; 2215 try { 2216 data = JSON.parse(text); 2217 } catch (_err) { 2218 throw new Error('Invalid JSON response'); 2219 } 2220 if (!res.ok || !data || data.success === false) { 2221 if (typeof opts.handleFailurePayload === 'function' && opts.handleFailurePayload(data || null, res) === true) { 2222 return null; 2223 } 2224 var err = new Error((data && data.error) || ('Request failed (' + res.status + ')')); 2225 err.code = data && data.code ? String(data.code) : ''; 2226 err.payload = data || null; 2227 throw err; 2228 } 2229 return data; 2230 }); 2231 }).then(function (payload) { 2232 if (!payload) { 2233 return null; 2234 } 2235 if (typeof opts.isExpectedPayload === 'function' && !opts.isExpectedPayload(payload)) { 2236 throw new Error(String(opts.unexpectedPayloadMessage || 'Unexpected managed page payload')); 2237 } 2238 if (opts.persist !== false) { 2239 writePageBootstrapCache(payload, { 2240 slug: slug || requestSlug, 2241 authSignature: opts.authSignature || sessionAuthSignature, 2242 allowWrite: opts.allowWrite || managedBootstrapAllowWriteForPersistMode(opts.persistMode), 2243 clearOnDeny: opts.clearOnDeny 2244 }); 2245 } 2246 if (typeof opts.renderPayload === 'function') { 2247 opts.renderPayload(payload); 2248 } 2249 if (typeof opts.afterRender === 'function') { 2250 opts.afterRender(payload); 2251 } 2252 return payload; 2253 }); 2254 } 2255 2256 function fetchManagedPageAttempt(attempt) { 2257 return fetchManagedPageOnce().catch(function (err) { 2258 var delayMs = resolveRetryDelayMs(attempt, err); 2259 if (delayMs >= 0) { 2260 return new Promise(function (resolve) { 2261 window.setTimeout(resolve, delayMs); 2262 }).then(function () { 2263 return fetchManagedPageAttempt(attempt + 1); 2264 }); 2265 } 2266 throw err; 2267 }); 2268 } 2269 2270 return fetchManagedPageAttempt(1).catch(function (err) { 2271 if (typeof opts.handleError === 'function') { 2272 opts.handleError(err); 2273 return null; 2274 } 2275 if (opts.loadFallback && typeof opts.loadFallback === 'object') { 2276 var fallbackOptions = Object.assign({}, opts.loadFallback); 2277 if (!Object.prototype.hasOwnProperty.call(fallbackOptions, 'slug')) { 2278 fallbackOptions.slug = slug || requestSlug; 2279 } 2280 renderLoadFallback(err, fallbackOptions); 2281 return null; 2282 } 2283 throw err; 2284 }).finally(function () { 2285 if (typeof opts.onFinally === 'function') { 2286 opts.onFinally(); 2287 return; 2288 } 2289 if (opts.notifyInitialContentPainted && typeof opts.notifyInitialContentPainted === 'object') { 2290 var notifyOptions = Object.assign({}, opts.notifyInitialContentPainted); 2291 notifyInitialContentPainted( 2292 Object.prototype.hasOwnProperty.call(notifyOptions, 'slug') ? notifyOptions.slug : (slug || requestSlug), 2293 notifyOptions 2294 ); 2295 } 2296 }); 2297 } 2298 2299 function refreshManagedNostrPageView(options) { 2300 var opts = options && typeof options === 'object' ? options : {}; 2301 var refreshOptions = Object.assign({}, opts); 2302 delete refreshOptions.consoleLabel; 2303 delete refreshOptions.state; 2304 delete refreshOptions.contentNode; 2305 delete refreshOptions.fallbackText; 2306 delete refreshOptions.rootNode; 2307 delete refreshOptions.loadingClass; 2308 delete refreshOptions.markPageReady; 2309 2310 if (opts.loadFallback !== false) { 2311 var loadFallback = opts.loadFallback && typeof opts.loadFallback === 'object' 2312 ? Object.assign({}, opts.loadFallback) 2313 : {}; 2314 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'consoleLabel') && Object.prototype.hasOwnProperty.call(opts, 'consoleLabel')) {
2315 loadFallback.consoleLabel = opts.consoleLabel; 2316 } 2317 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'state') && Object.prototype.hasOwnProperty.call(opts, 'state')) { 2318 loadFallback.state = opts.state; 2319 } 2320 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'contentNode') && Object.prototype.hasOwnProperty.call(opts, 'contentNode')) { 2321 loadFallback.contentNode = opts.contentNode; 2322 } 2323 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'fallbackText') && Object.prototype.hasOwnProperty.call(opts, 'fallbackText')) { 2324 loadFallback.fallbackText = opts.fallbackText; 2325 } 2326 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'rootNode') && Object.prototype.hasOwnProperty.call(opts, 'rootNode')) { 2327 loadFallback.rootNode = opts.rootNode; 2328 } 2329 if (!Object.prototype.hasOwnProperty.call(loadFallback, 'loadingClass') && Object.prototype.hasOwnProperty.call(opts, 'loadingClass')) { 2330 loadFallback.loadingClass = opts.loadingClass; 2331 } 2332 refreshOptions.loadFallback = loadFallback; 2333 } else { 2334 delete refreshOptions.loadFallback; 2335 } 2336 2337 if (opts.notifyInitialContentPainted !== false) { 2338 var notifyOptions = opts.notifyInitialContentPainted && typeof opts.notifyInitialContentPainted === 'object' 2339 ? Object.assign({}, opts.notifyInitialContentPainted) 2340 : {}; 2341 if (!Object.prototype.hasOwnProperty.call(notifyOptions, 'state') && Object.prototype.hasOwnProperty.call(opts, 'state')) { 2342 notifyOptions.state = opts.state; 2343 } 2344 if (!Object.prototype.hasOwnProperty.call(notifyOptions, 'rootNode') && Object.prototype.hasOwnProperty.call(opts, 'rootNode')) { 2345 notifyOptions.rootNode = opts.rootNode; 2346 } 2347 if (!Object.prototype.hasOwnProperty.call(notifyOptions, 'loadingClass') && Object.prototype.hasOwnProperty.call(opts, 'loadingClass')) { 2348 notifyOptions.loadingClass = opts.loadingClass; 2349 } 2350 if (!Object.prototype.hasOwnProperty.call(notifyOptions, 'markPageReady') && Object.prototype.hasOwnProperty.call(opts, 'markPageReady')) { 2351 notifyOptions.markPageReady = opts.markPageReady; 2352 } 2353 refreshOptions.notifyInitialContentPainted = notifyOptions; 2354 } else { 2355 delete refreshOptions.notifyInitialContentPainted; 2356 } 2357 2358 if ( 2359 hasPaintedManagedContent(opts.contentNode) && 2360 refreshOptions.notifyInitialContentPainted && 2361 typeof refreshOptions.notifyInitialContentPainted === 'object' 2362 ) { 2363 notifyInitialContentPainted( 2364 Object.prototype.hasOwnProperty.call(refreshOptions.notifyInitialContentPainted, 'slug') 2365 ? refreshOptions.notifyInitialContentPainted.slug 2366 : opts.slug, 2367 refreshOptions.notifyInitialContentPainted 2368 ); 2369 } 2370 2371 return refreshManagedNostrPage(refreshOptions); 2372 } 2373 2374 function mergeManagedPagePayload(targetPayload, sourcePayload, options) { 2375 var target = targetPayload && typeof targetPayload === 'object' ? targetPayload : null; 2376 var source = sourcePayload && typeof sourcePayload === 'object' ? sourcePayload : null; 2377 var opts = options && typeof options === 'object' ? options : {}; 2378 if (!target || !source) { 2379 return target; 2380 } 2381 if (opts.includeState !== false && source.state && typeof source.state === 'object') { 2382 target.state = source.state; 2383 } 2384 if (Object.prototype.hasOwnProperty.call(source, 'sync_status')) { 2385 target.sync_status = source.sync_status; 2386 } 2387 var hasValidationFallback = Object.prototype.hasOwnProperty.call(opts, 'validationFallback'); 2388 if (Object.prototype.hasOwnProperty.call(source, 'validation')) { 2389 target.validation = source.validation || (hasValidationFallback ? opts.validationFallback : target.validation); 2390 } else if (hasValidationFallback) { 2391 target.validation = opts.validationFallback; 2392 } 2393 ['canonical_exists', 'draft_exists', 'draft_differs'].forEach(function (fieldName) { 2394 if (Object.prototype.hasOwnProperty.call(source, fieldName)) { 2395 target[fieldName] = !!source[fieldName]; 2396 } 2397 }); 2398 ['prerender', 'prerender_signature'].forEach(function (fieldName) { 2399 if (Object.prototype.hasOwnProperty.call(source, fieldName)) { 2400 target[fieldName] = source[fieldName]; 2401 } 2402 }); 2403 var passthroughFields = Array.isArray(opts.passthroughFields) ? opts.passthroughFields : []; 2404 passthroughFields.forEach(function (fieldName) { 2405 if (!fieldName || !Object.prototype.hasOwnProperty.call(source, fieldName)) { 2406 return; 2407 } 2408 target[fieldName] = source[fieldName]; 2409 }); 2410 return target; 2411 } 2412 2413 function postFormJson(url, payload, options) { 2414 var opts = options && typeof options === 'object' ? options : {}; 2415 var body = new URLSearchParams(payload || {}); 2416 return fetch(String(url || ''), { 2417 method: 'POST', 2418 cache: 'no-store', 2419 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 2420 body: body.toString() 2421 }).then(function (res) { 2422 return res.text().then(function (text) { 2423 var data; 2424 try { 2425 data = JSON.parse(text); 2426 } catch (_err) { 2427 throw new Error(String(opts.invalidJsonMessage || 'Invalid JSON response')); 2428 } 2429 if (!res.ok || !data || data.success === false) { 2430 var err = new Error((data && data.error) || ('Request failed (' + res.status + ')')); 2431 err.code = data && data.code ? String(data.code) : ''; 2432 err.payload = data || null; 2433 throw err; 2434 } 2435 return data; 2436 }); 2437 }); 2438 } 2439 2440 function showRouteLoadFailure() { 2441 var mainContainer = document.querySelector('main'); 2442 var nav = document.querySelector('nav.site-nav'); 2443 var body = document.body; 2444 var parent = mainContainer || (nav && nav.parentNode) || body; 2445 if (!parent) { 2446 return; 2447 } 2448 var existing = document.getElementById('route-load-failure'); 2449 if (existing && existing.parentNode) { 2450 existing.parentNode.removeChild(existing); 2451 } 2452 var panel = document.createElement('section'); 2453 panel.id = 'route-load-failure'; 2454 panel.className = 'route-load-failure'; 2455 panel.innerHTML = '<h1>Page is still loading.</h1><p>Try refreshing in a moment.</p>'; 2456 if (mainContainer) { 2457 mainContainer.insertBefore(panel, mainContainer.firstChild || null); 2458 } else if (nav && nav.parentNode) { 2459 nav.insertAdjacentElement('afterend', panel); 2460 } else { 2461 body.insertBefore(panel, body.firstChild || null); 2462 } 2463 forceHydrationReveal(); 2464 } 2465 2466 function cancelRouteLoadFailure() { 2467 if (routeFailureTimer) { 2468 window.clearTimeout(routeFailureTimer); 2469 routeFailureTimer = 0; 2470 } 2471 var existing = document.getElementById('route-load-failure'); 2472 if (existing && existing.parentNode) { 2473 existing.parentNode.removeChild(existing); 2474 } 2475 } 2476 2477 function scheduleRouteLoadFailure() { 2478 cancelRouteLoadFailure(); 2479 routeFailureTimer = window.setTimeout(function () { 2480 routeFailureTimer = 0; 2481 if (!pageInitialContentReady) { 2482 showRouteLoadFailure(); 2483 } 2484 }, 2200); 2485 } 2486
2487 function clearManagedRouteParams(url, options) { 2488 var opts = options && typeof options === 'object' ? options : {}; 2489 if (!url || typeof url !== 'object' || !url.searchParams) { 2490 return false; 2491 } 2492 if (opts.removeRefresh !== false) { 2493 url.searchParams.delete(ROUTE_REFRESH_PARAM); 2494 } 2495 if (opts.removeRepair !== false) { 2496 url.searchParams.delete(ROUTE_REPAIR_PARAM); 2497 } 2498 if (!window.history || typeof window.history.replaceState !== 'function') { 2499 return false; 2500 } 2501 var next = url.pathname; 2502 var query = url.searchParams.toString(); 2503 if (query) { 2504 next += '?' + query; 2505 } 2506 if (url.hash) { 2507 next += url.hash; 2508 } 2509 window.history.replaceState(null, '', next); 2510 return true; 2511 } 2512 2513 function themeFromConfig() { 2514 var theme = 'lapidarist'; 2515 try { 2516 var bootstrap = window.__wizardrySiteBootstrap || {}; 2517 var config = bootstrap && bootstrap.config && typeof bootstrap.config === 'object' ? bootstrap.config : {}; 2518 var hasConfiguredTheme = Object.prototype.hasOwnProperty.call(config, 'theme'); 2519 var cachedTheme = hasConfiguredTheme ? '' : (localStorage.getItem('wizardry_blog_theme_v1') || ''); 2520 var nextTheme = String(hasConfiguredTheme ? config.theme : cachedTheme).trim(); 2521 if (nextTheme) { 2522 theme = nextTheme; 2523 } 2524 } catch (_err) { 2525 // Ignore storage failures. 2526 } 2527 return theme; 2528 } 2529 2530 function themeStylesheetVersion() { 2531 return String(window.__wizardryThemeStylesheetVersion || '20260622-lapidarist-restore1'); 2532 } 2533 2534 function reconcileThemeLink() { 2535 var theme = themeFromConfig(); 2536 var href = '/static/themes/' + encodeURIComponent(theme) + '.css?v=' + encodeURIComponent(themeStylesheetVersion()); 2537 var link = document.getElementById('theme-stylesheet'); 2538 if (!link) { 2539 link = document.createElement('link'); 2540 link.id = 'theme-stylesheet'; 2541 link.rel = 'stylesheet'; 2542 (document.head || document.documentElement).appendChild(link); 2543 } 2544 var currentHref = String(link.getAttribute('data-theme-href') || link.getAttribute('href') || '').trim(); 2545 if (currentHref !== href) { 2546 var preloader = document.createElement('link'); 2547 preloader.rel = 'stylesheet'; 2548 preloader.href = href; 2549 preloader.media = 'not all'; 2550 preloader.setAttribute('data-theme-preload', 'true'); 2551 var committed = false; 2552 function commit() { 2553 if (committed) { 2554 return; 2555 } 2556 committed = true; 2557 link.setAttribute('href', href); 2558 if (preloader.parentNode) { 2559 preloader.parentNode.removeChild(preloader); 2560 } 2561 } 2562 preloader.addEventListener('load', commit, { once: true }); 2563 preloader.addEventListener('error', commit, { once: true }); 2564 (link.parentNode || document.head || document.documentElement).appendChild(preloader); 2565 window.setTimeout(commit, 1500); 2566 } 2567 link.setAttribute('data-theme-href', href); 2568 } 2569 2570 window.__wizardryNotifyInitialContentPainted = notifyInitialContentPainted; 2571 window.__wizardryNotifyCurrentManagedPageReady = notifyCurrentManagedPageReady; 2572 window.__wizardryLoadPublicPageFragment = loadPublicPageFragment; 2573 window.__wizardryRenderManagedNostrPageBootstrapPayload = renderManagedNostrPageBootstrapPayload; 2574 window.__wizardryRemoveLegacyTitleBlock = removeLegacyTitleBlock; 2575 window.__wizardryRenderManagedPageHeadForPage = renderManagedPageHeadForPage; 2576 window.__wizardryRenderManagedPageHead = renderManagedPageHead; 2577 window.__wizardryHandleManagedNavbarTitleAction = handleManagedNavbarTitleAction; 2578 window.__wizardryRenderManagedPayloadValidationSummary = renderManagedPayloadValidationSummary; 2579 window.__wizardryRenderManagedSaveStatus = renderManagedSaveStatus; 2580 window.__wizardryRenderManagedAdminActionBar = renderManagedAdminActionBar; 2581 window.__wizardryStartManagedNavbarTitleEditForPage = startManagedNavbarTitleEditForPage; 2582 window.__wizardrySaveManagedNavbarTitleForPage = saveManagedNavbarTitleForPage; 2583 window.__wizardryResolveRootAttributeFallbackText = resolveRootAttributeFallbackText; 2584 window.__wizardryResetManagedNavbarTitleState = resetManagedNavbarTitleState; 2585 window.__wizardryRenderResolvedReadOnlyContent = renderResolvedReadOnlyContent; 2586 window.__wizardryStripLegacyNostrSyncPills = stripLegacyNostrSyncPills; 2587 window.__wizardryAuthPayload = authPayload; 2588 window.__wizardrySessionAuthSignature = sessionAuthSignature; 2589 window.__wizardryViewerAuthSignature = viewerAuthSignature; 2590 window.__wizardryBindSessionAuthRefresh = bindSessionAuthRefresh; 2591 window.__wizardryHasLikelyAuthenticatedSession = hasLikelyAuthenticatedSession; 2592 window.__wizardryAuthEventIsLikelyAdmin = authEventIsLikelyAdmin; 2593 window.__wizardryCanUseVisitorPrerenderReadOnly = canUseVisitorPrerenderReadOnly; 2594 window.__wizardryCanUsePayloadPrerenderReadOnly = canUsePayloadPrerenderReadOnly; 2595 window.__wizardryEnsureNostrPublishDialog = ensureNostrPublishDialog; 2596 window.__wizardryEnsureManagedMarkdownParser = ensureManagedMarkdownParser; 2597 window.__wizardryRenderManagedMarkdownInline = renderManagedMarkdownInline; 2598 window.__wizardryRenderManagedMarkdownInlineLinkFallback = renderManagedMarkdownInlineLinkFallback; 2599 window.__wizardryRenderManagedMarkdownBlock = renderManagedMarkdownBlock; 2600 window.__wizardryManagedSlugFromPath = slugFromPath; 2601 window.__wizardryRenderManagedNostrBootstrapSources = renderManagedNostrBootstrapSources; 2602 window.__wizardryRefreshManagedNostrPage = refreshManagedNostrPage; 2603 window.__wizardryRefreshManagedNostrPageView = refreshManagedNostrPageView; 2604 window.__wizardryCommitManagedPagePayload = commitManagedPagePayload; 2605 window.__wizardryReconcileManagedPageRefreshRender = reconcileManagedPageRefreshRender; 2606 window.__wizardryPostFormJson = postFormJson; 2607 2608 window.__wizardrySetPageTitleConfig = function (options) { 2609 var opts = options && typeof options === 'object' ? options : {}; 2610 var overrides = {}; 2611 if (Object.prototype.hasOwnProperty.call(opts, 'siteTitle')) { 2612 var normalizedSiteTitle = normalizeSiteTitle(opts.siteTitle); 2613 overrides.siteTitle = normalizedSiteTitle;
2614 try { 2615 localStorage.setItem(SITE_TITLE_CACHE_KEY, normalizedSiteTitle); 2616 } catch (_err) { 2617 // Ignore storage failures. 2618 } 2619 } 2620 if (Object.prototype.hasOwnProperty.call(opts, 'appendSiteTitle')) { 2621 var enabled = normalizeAppendSiteTitleEnabled(opts.appendSiteTitle); 2622 overrides.appendSiteTitle = enabled; 2623 try { 2624 localStorage.setItem(APPEND_SITE_TITLE_CACHE_KEY, enabled ? '1' : '0'); 2625 } catch (_err) { 2626 // Ignore storage failures. 2627 } 2628 } 2629 return applyPageTitle( 2630 Object.prototype.hasOwnProperty.call(opts, 'baseTitle') ? opts.baseTitle : undefined, 2631 overrides 2632 ); 2633 }; 2634 2635 reconcileThemeLink(); 2636 applyPageTitle(document.title); 2637 2638 var canonical = document.createElement('link'); 2639 canonical.rel = 'canonical'; 2640 canonical.href = window.location.origin + window.location.pathname; 2641 document.head.appendChild(canonical); 2642 2643 var ogUrl = document.createElement('meta'); 2644 ogUrl.setAttribute('property', 'og:url'); 2645 ogUrl.setAttribute('content', canonical.href); 2646 document.head.appendChild(ogUrl); 2647 2648 document.addEventListener('DOMContentLoaded', function () { 2649 var firstHeading = document.querySelector('h1[id]') || document.querySelector('h1'); 2650 if (firstHeading && !firstHeading.id) { 2651 firstHeading.id = 'main-content'; 2652 } 2653 2654 try { 2655 var currentUrl = new URL(window.location.href); 2656 var expectedSlug = slugFromPath(currentUrl.pathname || '/'); 2657 var rootSlug = routeRootSlugFromDom(); 2658 var hasRefreshParam = currentUrl.searchParams.get(ROUTE_REFRESH_PARAM) === '1'; 2659 var hasRepairParam = currentUrl.searchParams.get(ROUTE_REPAIR_PARAM) === '1'; 2660 if (expectedSlug && rootSlug && !slugsEquivalent(expectedSlug, rootSlug)) { 2661 if (!hasRefreshParam) { 2662 currentUrl.searchParams.set(ROUTE_REFRESH_PARAM, '1'); 2663 window.location.replace(currentUrl.toString()); 2664 return; 2665 } 2666 scheduleRouteLoadFailure(); 2667 return; 2668 } 2669 if ((hasRefreshParam || hasRepairParam) && window.history && typeof window.history.replaceState === 'function') { 2670 clearManagedRouteParams(currentUrl); 2671 } 2672 } catch (_routeErr) { 2673 // Ignore URL parsing/runtime failures. 2674 } 2675 }); 2676 2677 window.addEventListener('blog-page-initial-content-ready', function () { 2678 cancelRouteLoadFailure(); 2679 }); 2680})();
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.