Greasy Fork is available in English.

Awesome LinuxDo Reader Lite Core Library

Core runtime modules for Awesome LinuxDo Reader Lite.

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/590254/1899420/Awesome%20LinuxDo%20Reader%20Lite%20Core%20Library.js

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Awesome LinuxDo Reader Lite Core Library
// @name:zh-CN   Awesome LinuxDo Reader Lite 核心库
// @namespace    https://github.com/sunbigfly/awesome-linuxdo-reader
// @version      1.3.1
// @description  Core runtime modules for Awesome LinuxDo Reader Lite.
// @description:zh-CN 应用、数据、Discourse、Shell、主题、流与 userscript 运行核心
// @author       sunbigfly
// @license      MIT
// @homepageURL  https://github.com/sunbigfly/awesome-linuxdo-reader
// @supportURL   https://github.com/sunbigfly/awesome-linuxdo-reader/issues
// @match        https://linux.do/*
// @grant        none
// ==/UserScript==

/* Awesome LinuxDo Reader Lite 1.3.1 - main-lite-core
 * 应用、数据、Discourse、Shell、主题、流与 userscript 运行核心
 * 项目 TypeScript 源码保持可读;固定版本第三方依赖压缩打包。
 * 不要直接编辑此文件;修改 lite/src 后重新构建。
 */
(function () {
	'use strict';

	const root = globalThis;
	const runtimeKey = "__AWESOME_LINUXDO_READER_LITE_MODULE_RUNTIME__";
	let runtime = root[runtimeKey];
	if (runtime === undefined) {
		const factories = new Map();
		const sourceHashes = new Map();
		const modules = new Map();
		const libraries = new Set();
		let started = false;
		const externalModuleIds = Object.freeze({"@xsai/generate-text":"vendor/xsai-generate-text.js"});

		const resolve = (parentId, request) => {
			const externalId = externalModuleIds[request];
			if (externalId) return externalId;
			if (!request.startsWith('.')) {
				throw new Error(`[main-lite] unsupported external module: ${request}`);
			}
			const parts = parentId.split('/');
			parts.pop();
			for (const part of request.split('/')) {
				if (!part || part === '.') continue;
				if (part === '..') {
					if (!parts.length) {
						throw new Error(`[main-lite] module escapes root: ${parentId} -> ${request}`);
					}
					parts.pop();
				} else {
					parts.push(part);
				}
			}
			const resolved = parts.join('/');
			return /\.(?:js|json)$/.test(resolved) ? resolved : `${resolved}.js`;
		};

		const requireModule = (id) => {
			const cached = modules.get(id);
			if (cached) return cached.exports;
			const factory = factories.get(id);
			if (!factory) throw new Error(`[main-lite] missing module: ${id}`);
			const module = { exports: {} };
			modules.set(id, module);
			try {
				factory(module, module.exports, (request) => (
					requireModule(resolve(id, request))
				));
			} catch (error) {
				modules.delete(id);
				throw error;
			}
			return module.exports;
		};

		runtime = Object.freeze({
			schemaVersion: 1,
			sourceVersion: "1.3.1",
			register(id, factory, sourceHash) {
				const currentHash = sourceHashes.get(id);
				if (currentHash !== undefined) {
					if (currentHash !== sourceHash) {
						throw new Error(`[main-lite] conflicting module: ${id}`);
					}
					return;
				}
				factories.set(id, factory);
				sourceHashes.set(id, sourceHash);
			},
			markLibrary(name) {
				libraries.add(name);
			},
			start(entryId, expectedLibraries) {
				for (const name of expectedLibraries) {
					if (!libraries.has(name)) {
						throw new Error(`[main-lite] missing library: ${name}`);
					}
				}
				if (started) return requireModule(entryId);
				started = true;
				try {
					return requireModule(entryId);
				} catch (error) {
					started = false;
					throw error;
				}
			},
		});
		Object.defineProperty(root, runtimeKey, {
			configurable: true,
			enumerable: false,
			writable: false,
			value: runtime,
		});
	}
	if (runtime.schemaVersion !== 1 || runtime.sourceVersion !== "1.3.1") {
		throw new Error('[main-lite] Library 版本不匹配');
	}

	var __defProp = Object.defineProperty;
	var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
	var __getOwnPropNames = Object.getOwnPropertyNames;
	var __hasOwnProp = Object.prototype.hasOwnProperty;
	var __export = (target, all) => {
	  for (var name in all)
	    __defProp(target, name, { get: all[name], enumerable: !0 });
	};
	var __copyProps = (to, from, except, desc) => {
	  if (from && typeof from == "object" || typeof from == "function")
	    for (let key of __getOwnPropNames(from))
	      !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
	  return to;
	};
	var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
	var __create = Object.create;
	var __getProtoOf = Object.getPrototypeOf;
	var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
	  // If the importer is in node compatibility mode or this is not an ESM
	  // file that has been converted to a CommonJS file using a Babel-
	  // compatible transform (i.e. "__esModule" has not been set), then set
	  // "default" to the CommonJS "module.exports" for node compatibility.
	  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
	  mod
	));

/* Source: lite/src/app/reader-application-stages.ts */
runtime.register("src/app/reader-application-stages.js", function(module, exports, require) {
	var reader_application_stages_exports = {};
	__export(reader_application_stages_exports, {
	  createPreferencesStorageSyncStage: () => createPreferencesStorageSyncStage
	});
	module.exports = __toCommonJS(reader_application_stages_exports);
	function createPreferencesStorageSyncStage(options) {
	  const key = String(options.key).trim();
	  if (!key) throw new Error("preferences storage key 不能为空");
	  const windowPort = options.window ?? window;
	  return Object.freeze({
	    name: "preferences-storage-sync",
	    required: !1,
	    setup: (scope) => {
	      scope.listen(windowPort, "storage", (rawEvent) => {
	        const event = rawEvent;
	        if (!(event.key !== key && event.key !== null || event.key === key && event.oldValue === event.newValue))
	          try {
	            options.repository.reloadExternal();
	          } catch (cause) {
	            options.onError?.(cause);
	          }
	      });
	    }
	  });
	}
}, "9ba835be1c1dd532abf442a2488ce815108b4324cf870d46ed435f814888efac");

/* Source: lite/src/app/reader-application.ts */
runtime.register("src/app/reader-application.js", function(module, exports, require) {
	var reader_application_exports = {};
	__export(reader_application_exports, {
	  BrowserDiscourseHostPort: () => BrowserDiscourseHostPort,
	  ReaderApplication: () => ReaderApplication,
	  browserBodyReady: () => browserBodyReady,
	  detectDiscourseHost: () => detectDiscourseHost
	});
	module.exports = __toCommonJS(reader_application_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function abortError(reason) {
	  return reason instanceof Error ? reason : new DOMException("Reader application 已销毁", "AbortError");
	}
	function stageName(value) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error("application stage name 不能为空");
	  return normalized;
	}
	class ReaderApplication {
	  changes = new import_signal.Signal();
	  diagnostics = new import_signal.Signal();
	  #options;
	  #scope = new import_lifecycle.LifecycleScope();
	  #controller = new AbortController();
	  #state = "idle";
	  #startPromise = null;
	  constructor(options) {
	    const names = options.stages.map((stage) => stageName(stage.name));
	    if (new Set(names).size !== names.length)
	      throw new Error("application stage name 不能重复");
	    this.#options = options, this.#scope.add(() => {
	      this.#controller.signal.aborted || this.#controller.abort(abortError(null));
	    });
	  }
	  get state() {
	    return this.#state;
	  }
	  get scope() {
	    return this.#scope;
	  }
	  start() {
	    return this.#startPromise ? this.#startPromise : this.#state === "destroyed" ? Promise.resolve("destroyed") : (this.#startPromise = this.#run(), this.#startPromise);
	  }
	  destroy() {
	    this.#state !== "destroyed" && (this.#setState("destroyed"), this.#scope.destroy());
	  }
	  async #run() {
	    try {
	      this.#setState("waiting-body"), await this.#options.bodyReady(this.#controller.signal), this.#throwIfDestroyed();
	      let preferences = this.#options.preferences.load().value;
	      const preferenceChanges = new import_signal.Signal();
	      this.#scope.add(() => preferenceChanges.clear());
	      const publishPreferences = (value) => {
	        if (preferences !== value) {
	          preferences = value;
	          for (const cause of preferenceChanges.emit(preferences))
	            this.diagnostics.emit(Object.freeze({
	              stage: "preferences-live",
	              required: !1,
	              cause
	            }));
	        }
	      }, stopPreferenceChanges = this.#options.preferences.changes?.subscribe((snapshot) => {
	        publishPreferences(snapshot.value);
	      }, this.#scope);
	      this.#setState("waiting-host");
	      const host = await this.#options.host.waitForHost(this.#controller.signal);
	      if (this.#throwIfDestroyed(), !host)
	        return stopPreferenceChanges?.(), this.#setState("skipped"), this.#state;
	      this.#setState("starting");
	      const context = Object.freeze({
	        preferences,
	        readPreferences: () => preferences,
	        ...this.#options.preferences.update === void 0 ? {} : {
	          updatePreferences: (patch) => {
	            this.#throwIfDestroyed();
	            const snapshot = this.#options.preferences.update(patch);
	            return publishPreferences(snapshot.value), snapshot.value;
	          }
	        },
	        preferenceChanges,
	        host
	      });
	      for (const stage of this.#options.stages) {
	        this.#throwIfDestroyed();
	        const child = this.#scope.child();
	        try {
	          const cleanup = await stage.setup(child, context);
	          typeof cleanup == "function" && child.add(cleanup), this.#throwIfDestroyed();
	        } catch (cause) {
	          try {
	            child.destroy();
	          } catch (cleanupCause) {
	            this.diagnostics.emit(Object.freeze({
	              stage: `${stage.name}:cleanup`,
	              required: stage.required,
	              cause: cleanupCause
	            }));
	          }
	          if (this.diagnostics.emit(Object.freeze({
	            stage: stage.name,
	            required: stage.required,
	            cause
	          })), stage.required) throw cause;
	        }
	      }
	      return this.#throwIfDestroyed(), this.#setState("running"), this.#state;
	    } catch {
	      if (this.#state === "destroyed" || this.#controller.signal.aborted)
	        return "destroyed";
	      this.#setState("failed");
	      try {
	        this.#scope.destroy();
	      } catch (cleanupCause) {
	        this.diagnostics.emit(Object.freeze({
	          stage: "application:cleanup",
	          required: !0,
	          cause: cleanupCause
	        }));
	      }
	      return this.#state;
	    }
	  }
	  #throwIfDestroyed() {
	    if (this.#state === "destroyed" || this.#controller.signal.aborted)
	      throw abortError(this.#controller.signal.reason);
	  }
	  #setState(state) {
	    this.#state !== state && (this.#state = state, this.changes.emit(state));
	  }
	}
	function detectDiscourseHost(moduleLookup, documentPort) {
	  try {
	    if (moduleLookup("discourse/lib/url"))
	      return Object.freeze({ detection: "native-module" });
	  } catch {
	  }
	  return documentPort.querySelector(
	    'meta[name="generator"][content*="Discourse" i],meta[name="discourse_theme_id"],#data-preloaded,#ember-app .d-header,#ember-app .topic-list,#ember-app .topic-post'
	  ) ? Object.freeze({ detection: "dom-marker" }) : null;
	}
	class BrowserDiscourseHostPort {
	  #moduleLookup;
	  #document;
	  #window;
	  #timeoutMs;
	  #createObserver;
	  constructor(options) {
	    this.#moduleLookup = options.moduleLookup, this.#document = options.document ?? document, this.#window = options.window ?? window;
	    const timeoutMs = options.timeoutMs ?? 15e3;
	    if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 12e4)
	      throw new RangeError("Discourse host timeout 必须是 1..120000 的安全整数");
	    this.#timeoutMs = timeoutMs, this.#createObserver = options.createObserver ?? ((callback) => new MutationObserver(callback));
	  }
	  waitForHost(signal) {
	    const immediate = detectDiscourseHost(this.#moduleLookup, this.#document);
	    return immediate ? Promise.resolve(immediate) : signal.aborted ? Promise.reject(abortError(signal.reason)) : this.#document.readyState === "complete" ? Promise.resolve(null) : new Promise((resolve, reject) => {
	      let settled = !1, timer = 0;
	      const observer = this.#createObserver?.(() => check()), cleanup = () => {
	        timer && this.#window.clearTimeout(timer), observer?.disconnect(), this.#window.removeEventListener("load", onLoad), signal.removeEventListener("abort", onAbort);
	      }, finish = (value) => {
	        settled || (settled = !0, cleanup(), resolve(value));
	      }, check = () => {
	        const detected = detectDiscourseHost(this.#moduleLookup, this.#document);
	        return detected && finish(detected), detected;
	      }, onLoad = () => {
	        finish(check());
	      }, onAbort = () => {
	        settled || (settled = !0, cleanup(), reject(abortError(signal.reason)));
	      };
	      observer?.observe(this.#document.documentElement, {
	        childList: !0,
	        subtree: !0
	      }), this.#window.addEventListener("load", onLoad), signal.addEventListener("abort", onAbort, { once: !0 }), timer = this.#window.setTimeout(() => {
	        finish(detectDiscourseHost(this.#moduleLookup, this.#document));
	      }, this.#timeoutMs);
	    });
	  }
	}
	function browserBodyReady(documentPort, signal) {
	  return documentPort.body ? Promise.resolve() : signal.aborted ? Promise.reject(abortError(signal.reason)) : new Promise((resolve, reject) => {
	    const cleanup = () => {
	      documentPort.removeEventListener("DOMContentLoaded", onReady), signal.removeEventListener("abort", onAbort);
	    }, onReady = () => {
	      cleanup(), resolve();
	    }, onAbort = () => {
	      cleanup(), reject(abortError(signal.reason));
	    };
	    documentPort.addEventListener("DOMContentLoaded", onReady, { once: !0 }), signal.addEventListener("abort", onAbort, { once: !0 });
	  });
	}
}, "06e20cfe8a3ff2bb72a0055cda065487e96fe86b16de928d7c7f9b70a9f770ce");

/* Source: lite/src/app/reader-browser-runtime.ts */
runtime.register("src/app/reader-browser-runtime.js", function(module, exports, require) {
	var reader_browser_runtime_exports = {};
	__export(reader_browser_runtime_exports, {
	  ReaderBrowserRuntime: () => ReaderBrowserRuntime,
	  createReaderBrowserRuntimeStage: () => createReaderBrowserRuntimeStage
	});
	module.exports = __toCommonJS(reader_browser_runtime_exports);
	var import_native_host_api = require("../discourse/native-host-api.js"), import_native_request_descriptors = require("../discourse/native-request-descriptors.js"), import_reader_cache_management_surface = require("../cache/reader-cache-management-surface.js"), import_browser_asset_cache = require("../cache/browser-asset-cache.js"), import_discourse_application_cache_invalidation = require("../cache/discourse-application-cache-invalidation.js"), import_reader_settings_config_manager = require("../state/reader-settings-config-manager.js"), import_native_composer = require("../discourse/native-composer.js"), import_reader_control_tooltip = require("../components/reader-control-tooltip.js"), import_reader_icon = require("../components/reader-icon.js"), import_reader_native_composer_window = require("../discourse/reader-native-composer-window.js"), import_native_post_model_factory = require("../discourse/native-post-model-factory.js"), import_native_presence = require("../discourse/native-presence.js"), import_identifiers = require("../discourse/identifiers.js"), import_reader_history_navigation_controller = require("../history/reader-history-navigation-controller.js"), import_reader_history_model = require("../history/reader-history-model.js"), import_reader_history_repository = require("../history/reader-history-repository.js"), import_reader_history_navigation_view = require("../history/reader-history-navigation-view.js"), import_reader_history_panel_view = require("../history/reader-history-panel-view.js"), import_discourse_bookmark_adapter = require("../bookmark/discourse-bookmark-adapter.js"), import_reader_bookmark_controller = require("../bookmark/reader-bookmark-controller.js"), import_reader_bookmark_panel_view = require("../bookmark/reader-bookmark-panel-view.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_reader_topic_live_navigation_controller = require("../live/reader-topic-live-navigation-controller.js"), import_reader_resource_monitor = require("../monitor/reader-resource-monitor.js"), import_browser_request_observation = require("../network/browser-request-observation.js"), import_reader_topic_live_navigation_view = require("../live/reader-topic-live-navigation-view.js"), import_reader_rate_limit_notice = require("../shell/reader-rate-limit-notice.js"), import_reader_escape_surface = require("../shell/reader-escape-surface.js"), import_reader_image_download_service = require("../media/reader-image-download-service.js"), import_reader_image_resource_service = require("../media/reader-image-resource-service.js"), import_reader_media_prefetch_service = require("../media/reader-media-prefetch-service.js"), import_reader_topic_image_index = require("../media/reader-topic-image-index.js"), import_reader_topic_image_interaction = require("../media/reader-topic-image-interaction.js"), import_reader_lightbox_feature = require("../media/reader-lightbox-feature.js"), import_reader_compact_image_viewer = require("../media/reader-compact-image-viewer.js"), import_reader_topic_media_feature = require("../media/reader-topic-media-feature.js"), import_reader_media_controller = require("../media/reader-media-controller.js"), import_reader_cooked_content_feature = require("../media/reader-cooked-content-feature.js"), import_reader_poll_feature = require("../media/reader-poll-feature.js"), import_browser_shared_request_permit = require("../network/browser-shared-request-permit.js"), import_coordinated_request_client = require("../network/coordinated-request-client.js"), import_discourse_native_read_transport = require("../network/discourse-native-read-transport.js"), import_public_resource_request_adapter = require("../network/public-resource-request-adapter.js"), import_discourse_native_user_port = require("../user/discourse-native-user-port.js"), import_reader_user_domain_session = require("../user/reader-user-domain-session.js"), import_reader_credit_account_adapter = require("../user/reader-credit-account-adapter.js"), import_reader_connect_trust_adapter = require("../user/reader-connect-trust-adapter.js"), import_reader_user_endorsement_adapter = require("../user/reader-user-endorsement-adapter.js"), import_reader_user_card_view = require("../user/reader-user-card-view.js"), import_reader_settings_user_view = require("../user/reader-settings-user-view.js"), import_discourse_notification_adapter = require("../notification/discourse-notification-adapter.js"), import_reader_notification_controller = require("../notification/reader-notification-controller.js"), import_reader_notification_panel_view = require("../notification/reader-notification-panel-view.js"), import_action_request_adapter = require("../post/action-request-adapter.js"), import_discourse_action_transport = require("../post/discourse-action-transport.js"), import_post_action_controller = require("../post/post-action-controller.js"), import_discourse_action_descriptors = require("../post/discourse-action-descriptors.js"), import_post_action_feature_commands = require("../post/post-action-feature-commands.js"), import_user_action_feature_commands = require("../post/user-action-feature-commands.js"), import_reader_post_action_feature = require("../post/reader-post-action-feature.js"), import_reader_bookmark_action_coordinator = require("../post/reader-bookmark-action-coordinator.js"), import_reader_share_action_coordinator = require("../post/reader-share-action-coordinator.js"), import_reader_topic_notification_coordinator = require("../post/reader-topic-notification-coordinator.js"), import_reader_topic_shared_issue_coordinator = require("../post/reader-topic-shared-issue-coordinator.js"), import_topic_action_feature_commands = require("../post/topic-action-feature-commands.js"), import_reader_topic_action_rail = require("../post/reader-topic-action-rail.js"), import_reader_post_management_action_coordinator = require("../post/reader-post-management-action-coordinator.js"), import_reader_selection_quote_feature = require("../post/reader-selection-quote-feature.js"), import_reader_workspace_coordinator = require("../shell/reader-workspace-coordinator.js"), import_reader_feedback_surface = require("../shell/reader-feedback-surface.js"), import_reader_action_surface_coordinator = require("../shell/reader-action-surface-coordinator.js"), import_reader_exclusive_panel_coordinator = require("../shell/reader-exclusive-panel-coordinator.js"), import_reader_shell_recovery_view = require("../shell/reader-shell-recovery-view.js"), import_reader_report_form_surface = require("../shell/reader-report-form-surface.js"), import_reader_assignment_form_surface = require("../shell/reader-assignment-form-surface.js"), import_reader_choice_form_surface = require("../shell/reader-choice-form-surface.js"), import_reader_topic_edit_form_surface = require("../shell/reader-topic-edit-form-surface.js"), import_reader_settings_controller = require("../settings/reader-settings-controller.js"), import_reader_settings_view = require("../settings/reader-settings-view.js"), import_reader_theme_settings_control = require("../settings/reader-theme-settings-control.js"), import_reader_window_settings_form = require("../settings/reader-window-settings-form.js"), import_reader_shortcut_settings_form = require("../settings/reader-shortcut-settings-form.js"), import_reader_custom_site_settings_form = require("../settings/reader-custom-site-settings-form.js"), import_reader_webdav_settings_form = require("../settings/reader-webdav-settings-form.js"), import_browser_discourse_site_probe = require("../site/browser-discourse-site-probe.js"), import_reader_webdav_coordinator = require("../sync/reader-webdav-coordinator.js"), import_reader_webdav_category_ports = require("../sync/reader-webdav-category-ports.js"), import_reader_performance_settings_form = require("../settings/reader-performance-settings-form.js"), import_reader_reading_settings_form = require("../settings/reader-reading-settings-form.js"), import_reader_translation_settings_form = require("../settings/reader-translation-settings-form.js"), import_reader_appearance_settings_form = require("../settings/reader-appearance-settings-form.js"), import_reader_font_settings_form = require("../settings/reader-font-settings-form.js"), import_reader_motion_settings_form = require("../settings/reader-motion-settings-form.js"), import_reader_layout_settings_form = require("../settings/reader-layout-settings-form.js"), import_reader_interaction_settings_form = require("../settings/reader-interaction-settings-form.js"), import_reader_image_settings_form = require("../settings/reader-image-settings-form.js"), import_reader_select_surface = require("../shell/reader-select-surface.js"), import_reader_image_preferences = require("../media/reader-image-preferences.js"), import_reader_reply_tree_preferences = require("../topic/reader-reply-tree-preferences.js"), import_reader_about_settings_content = require("../settings/reader-about-settings-content.js"), import_reader_open_queue_session = require("../queue/reader-open-queue-session.js"), import_reader_topic_download_manager = require("../queue/reader-topic-download-manager.js"), import_reader_topic_offline_document = require("../archive/reader-topic-offline-document.js"), import_reader_topic_offline_artifact_repository = require("../archive/reader-topic-offline-artifact-repository.js"), import_reader_cooked_content_feature2 = require("../media/reader-cooked-content-feature.js"), import_reader_katex_controller = require("../media/reader-katex-controller.js"), import_reader_shortcut_controller = require("../shell/reader-shortcut-controller.js"), import_reader_appearance_style_controller = require("../appearance/reader-appearance-style-controller.js"), import_reader_theme_controller = require("../appearance/reader-theme-controller.js"), import_reader_font_style_controller = require("../font/reader-font-style-controller.js"), import_reader_loading_animation_view = require("../motion/reader-loading-animation-view.js"), import_reader_layout_style_controller = require("../layout/reader-layout-style-controller.js"), import_reader_topic_factory = require("../topic/reader-topic-factory.js"), import_reader_topic_navigation_controller = require("../topic/reader-topic-navigation-controller.js"), import_reader_native_topic_route = require("../topic/reader-native-topic-route.js"), import_reader_topic_flow_controller = require("../topic/reader-topic-flow-controller.js"), import_reader_topic_navigation_preferences = require("../topic/reader-topic-navigation-preferences.js"), import_reader_topic_scroll_adapter = require("../topic/reader-topic-scroll-adapter.js"), import_reader_topic_local_archive_feature = require("../topic/reader-topic-local-archive-feature.js"), import_reader_topic_timeline_controller = require("../topic/reader-topic-timeline-controller.js"), import_reader_topic_timeline_view = require("../topic/reader-topic-timeline-view.js"), import_reader_topic_header = require("../topic/reader-topic-header.js"), import_reader_topic_edit_controller = require("../topic/reader-topic-edit-controller.js"), import_reader_topic_comments_header = require("../topic/reader-topic-comments-header.js"), import_reader_topic_only_op_controller = require("../topic/reader-topic-only-op-controller.js"), import_reader_topic_special_content_feature = require("../topic/reader-topic-special-content-feature.js"), import_reader_topic_context_controller = require("../topic/reader-topic-context-controller.js"), import_reader_topic_context_surface = require("../topic/reader-topic-context-surface.js"), import_reader_topic_context_state = require("../topic/reader-topic-context-state.js"), import_topic_session = require("../topic/topic-session.js"), import_reader_translation_feature = require("../translation/reader-translation-feature.js"), import_translation_request_adapter = require("../translation/translation-request-adapter.js"), import_reader_data_runtime = require("./reader-data-runtime.js"), import_reader_performance_policy = require("./reader-performance-policy.js");
	const readerSurfaceOnlyCloseEvents = /* @__PURE__ */ new WeakSet(), hostTopicUserCardSelector = "html.ldp-reader-workspace :is(.topic-list-item,.latest-topic-list-item) :is(.posters,.topic-poster) [data-user-card]";
	function readerReportOptions(document, flagTypes, availableNames, appliesTo) {
	  return Object.freeze(flagTypes.filter(
	    (flag) => flag.enabled && availableNames.has(flag.nameKey) && (!flag.appliesTo.length || flag.appliesTo.includes(appliesTo))
	  ).map((flag) => {
	    const template = document.createElement("template");
	    template.innerHTML = flag.description;
	    const description = String(
	      template.content.textContent ?? ""
	    ).replace(/\s+/g, " ").trim();
	    return Object.freeze({
	      id: flag.id,
	      label: flag.label,
	      description: description || "提交给社区管理人员审核。",
	      requireMessage: flag.requireMessage
	    });
	  }));
	}
	function readerNativeModelValue(value, key) {
	  if (!value || typeof value != "object" && typeof value != "function")
	    return;
	  const model = value;
	  return typeof model.get == "function" ? model.get(key) : model[key];
	}
	function readerPollViewer(currentUser, fallbackUsername) {
	  const id = Number(readerNativeModelValue(currentUser, "id")), username = String(
	    readerNativeModelValue(currentUser, "username") ?? fallbackUsername
	  ).trim(), rawGroups = readerNativeModelValue(currentUser, "groups"), groups = Array.isArray(rawGroups) ? rawGroups.map((group) => String(
	    readerNativeModelValue(group, "name") ?? group
	  ).trim()).filter(Boolean) : [];
	  return Object.freeze({
	    id: Number.isSafeInteger(id) && id > 0 ? id : null,
	    username: username || null,
	    staff: ["staff", "admin", "moderator"].some((key) => readerNativeModelValue(currentUser, key) === !0),
	    groups: Object.freeze(groups)
	  });
	}
	function readerShellElement(root, selector, owner) {
	  const value = root.querySelector(selector);
	  if (!value) throw new Error(`${owner} 缺少 Shell 命名控件:${selector}`);
	  return value;
	}
	function readerTopicHeaderElements(root) {
	  const query = (selector) => readerShellElement(root, selector, "主题 Header");
	  return Object.freeze({
	    titleJump: query(".ldp-title-jump"),
	    metaHost: query(".ldp-meta"),
	    metaStats: query(".ldp-meta-stats"),
	    metaOwner: query(".ldp-meta-owner"),
	    metaOwnerValue: query(".ldp-meta-owner-value"),
	    onlyOpToggle: query(".ldp-only-op-toggle"),
	    onlyOpProgress: query(".ldp-only-op-progress"),
	    onlyOpProgressValue: query(".ldp-only-op-progress-value"),
	    topicIdentityHost: query(".ldp-title-topic-row")
	  });
	}
	function readerTopicTimelineElements(root) {
	  const query = (selector) => readerShellElement(root, selector, "时间轴 View");
	  return Object.freeze({
	    root,
	    timeline: query(".ldp-topic-timeline"),
	    date: query(".ldp-topic-timeline-date"),
	    track: query(".ldp-topic-timeline-track"),
	    cursor: query(".ldp-topic-timeline-cursor"),
	    current: query(".ldp-topic-timeline-current"),
	    total: query(".ldp-topic-timeline-total"),
	    preview: query(".ldp-topic-timeline-preview"),
	    relative: query(".ldp-topic-timeline-relative"),
	    jump: query(".ldp-topic-timeline-jump"),
	    top: query(".ldp-topic-timeline-top"),
	    jumpForm: query(".ldp-topic-timeline-jump-form"),
	    jumpInput: query(".ldp-topic-timeline-jump-input"),
	    jumpSubmit: query(
	      ".ldp-topic-timeline-jump-submit"
	    ),
	    jumpHint: query(".ldp-topic-timeline-jump-hint")
	  });
	}
	function readerTopicLiveNavigationElements(root) {
	  const liveRoot = root.querySelector(".ldp-live-update"), jump = root.querySelector(".ldp-live-update-jump"), label = jump?.querySelector("span") ?? null, dismiss = root.querySelector(
	    ".ldp-live-update-dismiss"
	  );
	  if (!liveRoot || !jump || !label || !dismiss)
	    throw new Error("实时新回复 View 缺少 Shell 命名控件");
	  return Object.freeze({
	    root: liveRoot,
	    jump,
	    label,
	    dismiss
	  });
	}
	function readerNotificationPanelElements(root) {
	  const query = (selector) => readerShellElement(root, selector, "消息面板"), modeTabs = [...root.querySelectorAll(
	    ".ldp-notification-mode-tab"
	  )], groupPanels = [...root.querySelectorAll(
	    "[data-notification-mode-panel]"
	  )], groupTabs = [...root.querySelectorAll(
	    ".ldp-notification-tab"
	  )];
	  if (modeTabs.length !== 2 || groupPanels.length !== 2 || groupTabs.length !== 14)
	    throw new Error("消息面板必须提供 2 个模式与完整 14 个分类锚点");
	  return Object.freeze({
	    root,
	    toggle: query(".ldp-notifications-toggle"),
	    badge: query(".ldp-notification-unread-badge"),
	    popover: query(".ldp-notifications-popover"),
	    modeTabs: Object.freeze(modeTabs),
	    groupPanels: Object.freeze(groupPanels),
	    groupTabs: Object.freeze(groupTabs),
	    toolbar: query(".ldp-notification-toolbar"),
	    unreadStatus: query(".ldp-notification-unread-status"),
	    markAll: query(".ldp-notification-mark-all"),
	    newMessage: query(".ldp-notification-new-message"),
	    search: query(".ldp-notification-search"),
	    searchClear: query(".ldp-notification-search-clear"),
	    list: query(".ldp-notification-list"),
	    pagePrevious: query(".ldp-notification-page-prev"),
	    pageInfo: query(".ldp-notification-page-info"),
	    pageNext: query(".ldp-notification-page-next")
	  });
	}
	function readerBookmarkPanelElements(root) {
	  const query = (selector) => readerShellElement(root, selector, "收藏面板"), tabs = [...root.querySelectorAll(
	    ".ldp-bookmark-tab"
	  )];
	  if (tabs.length !== 3)
	    throw new Error("收藏面板必须提供回应、帖子、楼层三个分类锚点");
	  return Object.freeze({
	    root,
	    toggle: query(".ldp-bookmarks-toggle"),
	    popover: query(".ldp-bookmarks-popover"),
	    tabs: Object.freeze(tabs),
	    defaultActions: query(".ldp-bookmarks-default-actions"),
	    multiButton: query(".ldp-bookmarks-multi"),
	    bulkActions: query(".ldp-bookmarks-bulk-actions"),
	    selectScope: query(".ldp-bookmarks-select-scope"),
	    selectToggle: query(".ldp-bookmarks-select-toggle"),
	    deleteSelected: query(
	      ".ldp-bookmarks-delete-selected"
	    ),
	    deleteSelectedLabel: query(
	      ".ldp-bookmarks-delete-selected-label"
	    ),
	    multiDone: query(".ldp-bookmarks-multi-done"),
	    search: query(".ldp-bookmarks-search"),
	    searchClear: query(".ldp-bookmarks-search-clear"),
	    reactionFilters: query(".ldp-reaction-filters"),
	    list: query(".ldp-bookmarks-list"),
	    pagePrevious: query(".ldp-bookmarks-page-prev"),
	    pageInfo: query(".ldp-bookmarks-page-info"),
	    pageNext: query(".ldp-bookmarks-page-next")
	  });
	}
	function readerShellFailureKind(cause) {
	  if (cause && typeof cause == "object") {
	    const kind = "kind" in cause ? String(cause.kind ?? "") : "";
	    if ([
	      "cloudflare",
	      "rate-limit",
	      "authentication",
	      "forbidden",
	      "not-found",
	      "conflict",
	      "validation",
	      "client",
	      "timeout",
	      "server"
	    ].includes(kind))
	      return kind;
	    if ("cloudflareMitigated" in cause && cause.cloudflareMitigated === !0)
	      return "cloudflare";
	    const status = "status" in cause ? Number(cause.status) : 0;
	    if (status === 400 || status === 422) return "validation";
	    if (status === 401) return "authentication";
	    if (status === 403) return "forbidden";
	    if (status === 404 || status === 410) return "not-found";
	    if (status === 429) return "rate-limit";
	    if (status === 408) return "timeout";
	    if (status === 409 || status === 412) return "conflict";
	    if (status === 425 || status >= 500 && status <= 599) return "server";
	    if (status >= 400 && status <= 499) return "client";
	    const name = "name" in cause ? String(cause.name ?? "") : "";
	    if (name === "TimeoutError") return "timeout";
	    if (name === "TypeError" || name === "NetworkError" || name === "OfflineError")
	      return "network";
	  }
	  return "unknown";
	}
	function readerShellRecoveryFailure(cause, challengeHref) {
	  const kind = readerShellFailureKind(cause), copy = {
	    cloudflare: [
	      "Cloudflare 验证尚未完成",
	      "新的 Reader 请求已暂停,后台不会继续打开新验证页;请手动完成唯一验证后重新加载。"
	    ],
	    "rate-limit": [
	      "请求收到 429",
	      "当前逻辑请求已按 Retry-After 有界等待;固定预防窗口仍保留真实启动记录。稍后可手动重试,无需刷新页面或清缓存。"
	    ],
	    authentication: [
	      "登录状态已失效",
	      "服务器返回 401;请先在原站恢复登录,再重新加载当前帖子。"
	    ],
	    forbidden: [
	      "当前请求无权限",
	      "服务器返回 403;该请求不会被当成 429,也不会触发限流恢复。"
	    ],
	    "not-found": [
	      "帖子或楼层不存在",
	      "服务器返回 404/410;内容可能已删除、移动或当前账号不可见。"
	    ],
	    conflict: [
	      "请求状态发生冲突",
	      "服务器返回 409/412;保留当前页面状态,刷新对应内容后再试。"
	    ],
	    validation: [
	      "请求内容未通过校验",
	      "服务器返回 400/422;不会自动重放,请检查当前输入或页面状态。"
	    ],
	    client: [
	      "请求被服务器拒绝",
	      "这是当前请求自身的 4xx 异常;不会升级成 429 或全局验证闸门。"
	    ],
	    timeout: [
	      "请求超时",
	      "有界自动重试仍未恢复;可检查网络后手动重试。"
	    ],
	    network: [
	      "网络暂时不可用",
	      "当前 Topic 状态未被伪造或清空;网络恢复后可手动重试。"
	    ],
	    server: [
	      "服务器暂时不可用",
	      "有界自动重试仍未取得成功响应;稍后可继续手动重试。"
	    ],
	    unknown: [
	      "帖子加载失败",
	      "当前失败已保留为诊断事实;可手动重试或关闭阅读器。"
	    ]
	  };
	  return Object.freeze({
	    kind,
	    message: copy[kind][0],
	    detail: copy[kind][1],
	    ...kind === "cloudflare" && challengeHref ? { challengeHref } : {}
	  });
	}
	function readerShellOpenRetryable(cause) {
	  return ["timeout", "network", "server"].includes(
	    readerShellFailureKind(cause)
	  );
	}
	function documentTopicId(document) {
	  const segments = document.location.pathname.split("/").filter(Boolean), topicIndex = segments.indexOf("t");
	  if (topicIndex < 0) return null;
	  const tail = segments.slice(topicIndex + 1), topicOffset = /^\d+$/.test(tail[0] ?? "") ? 0 : 1, topicId = Number(tail[topicOffset]);
	  return Number.isSafeInteger(topicId) && topicId > 0 ? topicId : null;
	}
	class ReaderBrowserRuntime {
	  scope;
	  shell;
	  workspace;
	  permit;
	  data;
	  nativeAjax;
	  userNative;
	  users;
	  connectHistory;
	  creditAccount;
	  userEndorsements;
	  userActions;
	  userCardView;
	  translationRequests;
	  translationFeature;
	  resourceRequests;
	  imageResources;
	  mediaPrefetch;
	  imageDownloads;
	  blobDownloads;
	  userMediaViewer;
	  assetCaches;
	  postReactions;
	  composerIsolation;
	  applicationCacheInvalidation;
	  composer;
	  controlTooltip;
	  actionSurfaces;
	  feedback;
	  recovery;
	  rateLimitNotice;
	  reportForm;
	  assignmentForm;
	  choiceForm;
	  topicEditForm;
	  selectSurface;
	  threadContextState;
	  history;
	  historyNavigation;
	  historyNavigationView;
	  historyPanelView;
	  notificationNative;
	  notificationRequests;
	  notificationActions;
	  notificationController;
	  notificationPanelView;
	  bookmarkNative;
	  bookmarkRequests;
	  bookmarkActions;
	  bookmarkController;
	  bookmarkPanelView;
	  topicFactory;
	  #performance;
	  #openRecoveryController = null;
	  #lastFailedRequest = null;
	  #challengeHref;
	  #openRetryDelay;
	  #loadingProgress;
	  #manualChallengeController;
	  #manualChallengePromise = null;
	  #destroyed = !1;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#manualChallengeController = this.scope.abortController(
	      new DOMException("Reader runtime 已销毁", "AbortError")
	    ), this.shell = options.shell, this.workspace = options.workspace, this.#openRetryDelay = options.openRetryDelay ?? import_coordinated_request_client.abortableDelay, this.#loadingProgress = options.loadingProgress ?? null, this.#performance = options.performance ?? Object.freeze({
	      pageSize: options.topic.pageSize,
	      streamOverscanScreens: 1.5,
	      streamMaxMountedPostCount: 80,
	      nestedPrefetchScreens: 2.5,
	      requestMaxConcurrent: options.data.scheduler.maxConcurrent,
	      requestMinIntervalMs: options.permit.minIntervalMs,
	      requestRateTargetPercent: 85,
	      requestShortBudget: options.permit.shortBudget,
	      requestLongBudget: options.permit.longBudget
	    }), this.assetCaches = options.assetCacheStorage ? new import_browser_asset_cache.ReaderBrowserAssetCacheRepository(options.assetCacheStorage) : null;
	    let challengeHref = "";
	    try {
	      challengeHref = (0, import_browser_shared_request_permit.browserCloudflareChallengeHref)(
	        options.topic.origin ?? options.data.rateLimit.baseUrl ?? options.document.baseURI,
	        options.document.location?.href ?? options.document.baseURI
	      );
	    } catch {
	    }
	    this.#challengeHref = challengeHref;
	    const reportTopicFeature = (topicId, feature, cause) => {
	      try {
	        options.onTopicFeatureError?.(Object.freeze({
	          topicId,
	          feature,
	          cause
	        }));
	      } catch {
	      }
	    };
	    try {
	      this.selectSurface = new import_reader_select_surface.ReaderSelectSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        parentScope: this.scope
	      }), this.actionSurfaces = new import_reader_action_surface_coordinator.ReaderActionSurfaceCoordinator({
	        parentScope: this.scope
	      }), this.controlTooltip = new import_reader_control_tooltip.ReaderControlTooltip({
	        document: options.document,
	        surfaceHost: this.shell.view.surfaceHost,
	        ...options.share ? { copyText: (value) => options.share.copyText(value) } : {},
	        parentScope: this.scope
	      });
	      const openNative = this.shell.view.root.querySelector("a.ldp-open"), browserWindow = options.document.defaultView;
	      if (openNative && browserWindow) {
	        const openNativeTopic = (event) => {
	          const pointer = event;
	          event.type === "click" && pointer.button !== 0 || event.type === "auxclick" && pointer.button !== 1 || !openNative.href || openNative.hidden || !(0, import_reader_native_topic_route.openReaderNativeTopicTab)(browserWindow, openNative.href) || (event.preventDefault(), event.stopPropagation());
	        };
	        this.scope.listen(openNative, "click", openNativeTopic), this.scope.listen(openNative, "auxclick", openNativeTopic);
	      }
	      if (this.feedback = new import_reader_feedback_surface.ReaderFeedbackSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        coordinator: this.actionSurfaces,
	        ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	        parentScope: this.scope
	      }), this.recovery = new import_reader_shell_recovery_view.ReaderShellRecoveryView({
	        document: options.document,
	        host: this.shell.view.topicHost,
	        onRetry: async () => {
	          const request = this.#lastFailedRequest;
	          if (!request) return !1;
	          await this.data.client.resetRateLimits();
	          const result = await this.openTarget({
	            ...request,
	            forceRefresh: !0
	          });
	          return result.topic.status === "opened" || result.topic.status === "reused";
	        },
	        onClose: async () => {
	          await this.close();
	        },
	        parentScope: this.scope
	      }), this.reportForm = new import_reader_report_form_surface.ReaderReportFormSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        coordinator: this.actionSurfaces,
	        ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	        parentScope: this.scope
	      }), this.assignmentForm = new import_reader_assignment_form_surface.ReaderAssignmentFormSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        coordinator: this.actionSurfaces,
	        ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	        parentScope: this.scope
	      }), this.choiceForm = new import_reader_choice_form_surface.ReaderChoiceFormSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        coordinator: this.actionSurfaces,
	        ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	        parentScope: this.scope
	      }), this.topicEditForm = new import_reader_topic_edit_form_surface.ReaderTopicEditFormSurface({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	        parentScope: this.scope
	      }), this.threadContextState = new import_reader_topic_context_state.ReaderTopicContextStateRepository({
	        storage: options.threadContextStorage ?? (0, import_reader_topic_context_state.readerTopicContextWebStorage)(options.storage),
	        authScope: options.topic.authScope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "thread-context",
	          cause
	        )
	      }), this.threadContextState.load(), options.lightbox && options.openTopicImage)
	        throw new Error(
	          "标准 Lightbox 与自定义 openTopicImage 只能配置一个"
	        );
	      const challengeOrigin = String(
	        options.topic.origin ?? options.data.rateLimit.baseUrl ?? options.document.location?.origin ?? ""
	      ), challengeWindow = options.document.defaultView;
	      this.nativeAjax = new import_discourse_native_read_transport.BrowserDiscourseNativeAjaxPort(
	        options.host,
	        options.topic.origin === void 0 ? {} : { origin: options.topic.origin }
	      ), this.permit = new import_browser_shared_request_permit.BrowserSharedRequestPermit({
	        ...options.permit,
	        storage: options.storage,
	        sourceId: options.sourceId,
	        locks: options.locks ?? null,
	        storageEvents: options.storageEvents ?? null,
	        ...options.broadcastChannelFactory === void 0 ? {} : { broadcastChannelFactory: options.broadcastChannelFactory },
	        .../^https?:\/\//i.test(challengeOrigin) && typeof challengeWindow?.open == "function" ? {
	          challenge: {
	            origin: challengeOrigin,
	            redirectHref: options.document.location?.href ?? options.document.baseURI,
	            verify: async (signal) => {
	              const response = await this.nativeAjax.request({
	                path: "/session/current.json",
	                method: "GET",
	                signal,
	                noStore: !0
	              });
	              return response.status >= 100 && response.cloudflareMitigated !== !0;
	            },
	            screen: challengeWindow.screen,
	            open: (url, name, features) => challengeWindow.open(
	              url,
	              name,
	              features
	            )
	          }
	        } : {},
	        parentScope: this.scope
	      });
	      const rateLimitNotice = this.shell.view.root.querySelector(
	        ".ldp-rate-limit-notice"
	      ), rateLimitDetail = rateLimitNotice?.querySelector(
	        ".ldp-rate-limit-detail"
	      ), rateLimitChallenge = rateLimitNotice?.querySelector(
	        ".ldp-rate-limit-challenge"
	      );
	      if (!rateLimitNotice || !rateLimitDetail || !rateLimitChallenge)
	        throw new Error("Reader Shell 缺少 429 状态投影锚点");
	      this.rateLimitNotice = new import_reader_rate_limit_notice.ReaderRateLimitNotice({
	        document: options.document,
	        elements: {
	          root: rateLimitNotice,
	          detail: rateLimitDetail,
	          challenge: rateLimitChallenge
	        },
	        challengeHref: this.#challengeHref,
	        snapshot: () => this.permit.snapshot(),
	        parentScope: this.scope
	      }), this.permit.reconcileCloudflareChallenge().then(() => this.rateLimitNotice.refresh()).catch(() => {
	      }), this.scope.listen(this.shell.view.root, "click", (event) => {
	        const target = event.target, anchor = typeof target?.closest == "function" ? target.closest(
	          "a.ldp-rate-limit-challenge,a.ldp-error-challenge"
	        ) : null;
	        !anchor?.href || !this.#challengeHref || (event.preventDefault(), event.stopPropagation(), this.#openManualCloudflareChallenge(anchor.href));
	      }), this.data = new import_reader_data_runtime.ReaderDataRuntime({
	        ...options.data,
	        permit: this.permit,
	        storage: options.storage,
	        sourceId: options.sourceId,
	        locks: options.locks ?? null,
	        indexedDb: options.indexedDb ?? null,
	        ...options.broadcastChannelFactory === void 0 ? {} : { broadcastChannelFactory: options.broadcastChannelFactory },
	        parentScope: this.scope
	      }), this.composerIsolation = new import_native_composer.DiscourseComposerHostIsolation({
	        host: options.host,
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "post-action",
	          cause
	        )
	      }), this.applicationCacheInvalidation = new import_discourse_application_cache_invalidation.DiscourseApplicationCacheInvalidationCoordinator({
	        host: options.host,
	        composerEvents: this.composerIsolation,
	        cache: this.data.responses,
	        currentTopicId: () => this.shell.activeTopicId === null ? documentTopicId(options.document) : Number(this.shell.activeTopicId),
	        onPostChanged: (post) => {
	          this.shell.activeValue?.services.live.ingestPostDelta(post);
	        },
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "post-action",
	          cause
	        )
	      });
	      const nativeActions = new import_discourse_action_transport.BrowserDiscourseNativeActionPort(
	        options.host,
	        this.nativeAjax,
	        this.composerIsolation
	      ), nativeReads = new import_discourse_native_read_transport.BrowserDiscourseNativeReadTransport(
	        this.nativeAjax
	      ), actionDescriptors = new import_discourse_action_descriptors.DiscourseActionDescriptors();
	      this.userNative = new import_discourse_native_user_port.BrowserDiscourseNativeUserPort(options.host, {
	        readTransport: nativeReads,
	        ...options.topic.origin === void 0 ? {} : { basePath: options.topic.origin },
	        categoryExperts: options.document.location?.hostname === "linux.do"
	      }), this.userEndorsements = new import_reader_user_endorsement_adapter.ReaderUserEndorsementAdapter({
	        gateway: this.data.gateway,
	        transport: nativeReads,
	        authScope: options.topic.authScope
	      });
	      const connect = options.connect ? new import_reader_connect_trust_adapter.ReaderConnectTrustAdapter({
	        gateway: this.data.gateway,
	        http: options.connect.http,
	        authScope: options.topic.authScope,
	        document: options.document
	      }) : null;
	      this.connectHistory = connect ? new import_reader_connect_trust_adapter.ReaderConnectTrustHistoryAdapter({
	        gateway: this.data.gateway,
	        ajax: this.nativeAjax,
	        storage: options.storage,
	        authScope: options.topic.authScope
	      }) : null, this.connectHistory && this.scope.add(this.data.readCoordination.subscribeConfirmations(
	        (confirmation) => {
	          this.connectHistory?.recordReadConfirmation(confirmation);
	        }
	      )), this.creditAccount = options.credit ? new import_reader_credit_account_adapter.ReaderCreditAccountAdapter({
	        gateway: this.data.gateway,
	        http: options.credit.http,
	        authScope: options.topic.authScope,
	        ...options.credit.storage ? { storage: options.credit.storage } : {}
	      }) : null;
	      const credit = this.creditAccount;
	      this.users = new import_reader_user_domain_session.ReaderUserDomainSession({
	        gateway: this.data.gateway,
	        native: this.userNative,
	        authScope: options.topic.authScope,
	        ...options.searchForms === void 0 ? {} : { searchForms: options.searchForms },
	        ...connect ? { connect } : {},
	        ...credit ? { credit } : {},
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "user",
	          cause
	        )
	      });
	      const userAbort = this.scope.abortController(
	        new Error("Reader 用户 action scope 已销毁")
	      ), userMutation = new import_action_request_adapter.ActionRequestAdapter({
	        gateway: this.data.gateway,
	        nativeActions,
	        authScope: options.topic.authScope,
	        signal: userAbort.signal
	      });
	      this.userActions = new import_post_action_controller.PostActionController({
	        mutation: userMutation,
	        cache: this.data.responses,
	        scope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "user",
	          cause
	        )
	      });
	      const userCommands = new import_user_action_feature_commands.UserActionFeatureCommands({
	        state: this.users
	      }), setUserNotificationLevel = async (username, level, expiringAt) => {
	        const binding = this.userNative.actionBinding(username);
	        await this.userActions.dispatch(
	          userCommands.notificationLevel(
	            username,
	            level,
	            actionDescriptors.userNotificationLevel({
	              username,
	              user: binding.user,
	              level,
	              expiringAt: expiringAt ?? null,
	              actingUser: binding.actingUser
	            })
	          )
	        );
	      };
	      if (this.composer = new import_native_composer.DiscourseComposerCoordinator({
	        host: options.host,
	        document: options.document,
	        isolation: this.composerIsolation,
	        parentScope: this.scope,
	        onError: (cause) => {
	          try {
	            options.lightbox?.onError?.(cause);
	          } catch {
	          }
	        }
	      }), this.composer.installSubmitGuard({
	        document: options.document,
	        parentScope: this.scope
	      }), this.userCardView = new import_reader_user_card_view.ReaderUserCardView({
	        document: options.document,
	        root: this.shell.view.surfaceHost,
	        hoverDelegates: Object.freeze([Object.freeze({
	          root: options.document,
	          selector: hostTopicUserCardSelector,
	          capture: !0
	        })]),
	        session: this.users,
	        userHref: (username) => this.userNative.requestIdentity(username),
	        avatarSource: (template, size) => this.userNative.avatarSource(template, size),
	        toggleFollow: async (username, followed) => {
	          await this.userActions.dispatch(userCommands.follow(
	            username,
	            followed,
	            actionDescriptors.userFollowToggle({ username, followed }),
	            (0, import_native_host_api.discourseNativeCurrentUsername)(options.host)
	          ));
	        },
	        openMessage: async (username) => {
	          await this.composer.openPrivateMessage(username);
	        },
	        setNotificationLevel: async (username, level, expiringAt) => {
	          await setUserNotificationLevel(username, level, expiringAt), this.feedback.show(
	            level === "normal" ? `已恢复 @${username} 的常规通知` : level === "mute" ? `已将 @${username} 设为免打扰` : `已忽略 @${username}`
	          );
	        },
	        ignoreUser: (username) => this.choiceForm.open({
	          title: `忽略 @${username}`,
	          intro: "Discourse 要求为“忽略”设置截止时间;到期后会自动恢复为常规。",
	          fieldLabel: "忽略期限",
	          mode: "select",
	          options: Object.freeze([
	            Object.freeze({ value: "1", label: "1 天" }),
	            Object.freeze({ value: "7", label: "1 周" }),
	            Object.freeze({ value: "30", label: "1 个月", selected: !0 }),
	            Object.freeze({ value: "120", label: "4 个月" }),
	            Object.freeze({ value: "365", label: "1 年" })
	          ]),
	          submitLabel: "确认忽略",
	          emptySelectionError: "请选择有效的忽略期限",
	          submit: async ([value]) => {
	            const days = Number(value);
	            if (![1, 7, 30, 120, 365].includes(days))
	              throw new Error("请选择有效的忽略期限");
	            return await setUserNotificationLevel(
	              username,
	              "ignore",
	              new Date(Date.now() + days * 864e5).toISOString()
	            ), `已忽略 @${username}`;
	          }
	        }),
	        endorseUser: async (profile) => {
	          const username = profile.identity.username, catalog = await this.userEndorsements.load(
	            username,
	            userAbort.signal
	          );
	          if (!catalog.categories.length)
	            throw new Error("当前没有可认可的类别");
	          const existingIds = new Set(
	            (profile.categoryExperts.endorsements ?? []).map((item) => item.categoryId)
	          );
	          return this.choiceForm.open({
	            title: `认可 @${username}`,
	            intro: catalog.remainingEndorsements === null ? "选择要认可的专家类别。" : `今天还可新增 ${catalog.remainingEndorsements} 次认可。`,
	            mode: "multiple",
	            options: Object.freeze(catalog.categories.map((category) => {
	              const existing = existingIds.has(category.id);
	              return Object.freeze({
	                value: String(category.id),
	                label: category.name,
	                selected: existing,
	                disabled: existing,
	                description: existing ? "已经认可" : "选择后将认可该用户为此类别的专家"
	              });
	            })),
	            submitLabel: "确认认可",
	            emptySelectionError: "请选择一个尚未认可的类别",
	            submit: async (values) => {
	              if (catalog.remainingEndorsements !== null && catalog.remainingEndorsements < 1)
	                throw new Error("今天的认可次数已用完");
	              const addedIds = values.map(Number).filter((categoryId) => Number.isSafeInteger(categoryId) && categoryId > 0 && !existingIds.has(categoryId));
	              if (!addedIds.length)
	                throw new Error("请选择一个尚未认可的类别");
	              return await this.userActions.dispatch(userCommands.endorse(
	                username,
	                actionDescriptors.categoryExpertEndorse({
	                  username,
	                  categoryIds: Object.freeze([
	                    .../* @__PURE__ */ new Set([...existingIds, ...addedIds])
	                  ])
	                })
	              )), "认可已提交";
	            },
	            signal: userAbort.signal
	          }).catch((cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "user",
	            cause
	          )), !0;
	        },
	        ...options.lightbox ? {
	          openMedia: (items, initialIndex, anchor, profile, returnFocus) => {
	            const viewer = this.userMediaViewer, descriptor = items[initialIndex];
	            if (!viewer || !descriptor)
	              throw new Error(
	                "用户媒体紧凑查看器尚未装配"
	              );
	            const item = Object.freeze({
	              key: `user:${descriptor.kind}:${descriptor.src}`,
	              previewSrc: descriptor.src,
	              originalSrc: descriptor.originalSrc ?? descriptor.src,
	              alt: descriptor.alt,
	              topicId: this.shell.activeTopicId ?? 1,
	              sourcePostNumber: 1,
	              imageOrder: initialIndex
	            });
	            viewer.open({
	              item,
	              kind: descriptor.kind === "avatar" ? "avatar" : "background",
	              anchor,
	              returnFocus: () => returnFocus ?? anchor,
	              outsideSafeSurface: anchor,
	              flair: descriptor.kind === "avatar" ? profile.flair : null,
	              ...this.imageDownloads ? {
	                onDownload: async () => {
	                  await this.imageDownloads.download(
	                    item,
	                    initialIndex,
	                    { original: !0 }
	                  );
	                }
	              } : {},
	              onDismiss: () => this.userCardView.close()
	            });
	          }
	        } : {},
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "user",
	          cause
	        )
	      }), this.#applyPerformanceInfrastructure(), this.translationRequests = options.translation ? new import_translation_request_adapter.TranslationRequestAdapter({
	        ...options.translation,
	        gateway: this.data.gateway
	      }) : null, this.scope.add(() => this.translationRequests?.destroy()), options.translationView === !1)
	        this.translationFeature = null;
	      else if (this.translationRequests) {
	        const {
	          buttonHost,
	          initialMode,
	          renderIcon: translationRenderIcon,
	          onError: translationOnError,
	          ...translationViewOptions
	        } = options.translationView ?? {}, resolvedButtonHost = buttonHost ?? this.shell.view.root.querySelector(
	          ".ldp-head-btns"
	        );
	        if (!resolvedButtonHost)
	          throw new Error("翻译 View 缺少稳定 Shell headerActions");
	        this.translationFeature = new import_reader_translation_feature.ReaderTranslationFeature({
	          ...translationViewOptions,
	          document: options.document,
	          translator: this.translationRequests,
	          buttonHost: resolvedButtonHost,
	          surfaces: () => {
	            const discussion = this.shell.view.modal.querySelector(
	              ":scope > .ldp-descendant-replies-layer"
	            );
	            return Object.freeze([
	              this.shell.view.body,
	              ...discussion ? [discussion] : []
	            ]);
	          },
	          ...translationRenderIcon ? { renderIcon: translationRenderIcon } : options.renderIcon ? {
	            renderIcon: (document) => options.renderIcon("languages", document)
	          } : {},
	          initialMode: initialMode ?? "original",
	          parentScope: this.scope,
	          notify: (message) => this.feedback.show(message),
	          onError: (cause) => {
	            try {
	              translationOnError?.(cause);
	            } catch {
	            }
	            reportTopicFeature(
	              this.shell.activeTopicId ?? 0,
	              "translation",
	              cause
	            );
	          }
	        });
	      } else {
	        if (options.translationView)
	          throw new Error(
	            "翻译 View 需要先配置唯一 TranslationRequestAdapter"
	          );
	        this.translationFeature = null;
	      }
	      if (options.resources) {
	        const {
	          objectUrls,
	          maxObjectUrls,
	          downloadMount,
	          downloadUrlRevokeAfterMs,
	          ...resourceOptions
	        } = options.resources;
	        this.resourceRequests = new import_public_resource_request_adapter.PublicResourceRequestAdapter({
	          ...resourceOptions,
	          gateway: this.data.gateway
	        }), this.imageResources = new import_reader_image_resource_service.ReaderImageResourceService({
	          resources: this.resourceRequests,
	          objectUrls,
	          ...maxObjectUrls === void 0 ? {} : { maxObjectUrls },
	          parentScope: this.scope
	        }), this.mediaPrefetch = new import_reader_media_prefetch_service.ReaderMediaPrefetchService({
	          document: options.document,
	          baseUrl: resourceOptions.baseUrl,
	          resources: this.resourceRequests,
	          concurrency: 2
	        }), this.blobDownloads = new import_reader_image_download_service.BrowserBlobDownloadPort({
	          document: options.document,
	          mount: downloadMount ?? options.document.body ?? options.document.documentElement,
	          objectUrls,
	          ...downloadUrlRevokeAfterMs === void 0 ? {} : { revokeAfterMs: downloadUrlRevokeAfterMs },
	          parentScope: this.scope
	        }), this.imageDownloads = new import_reader_image_download_service.ReaderImageDownloadService({
	          resources: this.imageResources,
	          downloads: this.blobDownloads
	        });
	      } else
	        this.resourceRequests = null, this.imageResources = null, this.mediaPrefetch = null, this.imageDownloads = null, this.blobDownloads = null;
	      this.userMediaViewer = options.lightbox ? new import_reader_compact_image_viewer.ReaderCompactImageViewer({
	        document: options.document,
	        mount: typeof options.lightbox.mount == "function" ? options.lightbox.mount() : options.lightbox.mount,
	        ...this.imageResources || options.lightbox.originalSources ? {
	          originalSources: this.imageResources ?? options.lightbox.originalSources
	        } : {},
	        ...options.lightbox.frameScheduler ? { frameScheduler: options.lightbox.frameScheduler } : {},
	        notify: (message) => this.feedback.show(message),
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "user",
	          cause
	        )
	      }) : null;
	      const {
	        onReady: topicReady,
	        createDomOptions,
	        ...topicFactoryOptions
	      } = options.topicFactory, topicBaseUrl = options.topic.origin ?? options.data.rateLimit.baseUrl ?? options.document.baseURI, nativeRelativeTime = (0, import_native_host_api.discourseNativeRelativeTimeFormatter)(options.host), nativeExactTime = (0, import_native_host_api.discourseNativeExactTimeFormatter)(options.host), nativeTopicPresentation = (0, import_native_host_api.discourseNativeTopicPresentation)(options.host), nativeTopicEditCatalog = (0, import_native_host_api.discourseNativeTopicEditCatalog)(options.host), nativePresence = new import_native_presence.BrowserDiscoursePresencePort(options.host), nativeTopicLinks = (0, import_native_host_api.discourseNativeTopicLinks)(options.host, topicBaseUrl), nativeFlagCatalog = (0, import_native_host_api.discourseNativeFlagCatalog)(options.host), nativeEmojiMenu = (0, import_native_host_api.discourseNativeEmojiMenu)(options.host), nativeAdminMenu = (0, import_native_host_api.discourseNativePostAdminMenu)(options.host), nativePostModels = new import_native_post_model_factory.DiscourseNativePostModelFactory(options.host), nativeBookmarkForm = new import_native_host_api.BrowserDiscourseNativeBookmarkForm(options.host);
	      this.postReactions = new import_reader_post_action_feature.DiscoursePostReactionCatalog(
	        nativePostModels
	      );
	      const postReactions = this.postReactions, topicFeatures = /* @__PURE__ */ new WeakMap(), topicDoms = /* @__PURE__ */ new WeakMap(), topicContextSurfaces = /* @__PURE__ */ new WeakMap(), topicHeaders = /* @__PURE__ */ new WeakMap(), topicHeaderViews = /* @__PURE__ */ new WeakMap(), topicOnlyOpControllers = /* @__PURE__ */ new WeakMap(), topicNavigations = /* @__PURE__ */ new WeakMap(), topicFlows = /* @__PURE__ */ new WeakMap(), topicTimelines = /* @__PURE__ */ new WeakMap(), topicTimelineViews = /* @__PURE__ */ new WeakMap(), topicLiveNavigations = /* @__PURE__ */ new WeakMap(), topicLiveNavigationViews = /* @__PURE__ */ new WeakMap();
	      if (options.notifications === !1)
	        this.notificationNative = null, this.notificationRequests = null, this.notificationActions = null, this.notificationController = null, this.notificationPanelView = null;
	      else {
	        const notificationOptions = options.notifications ?? {}, notificationAbort = this.scope.abortController(
	          new Error("Reader 通知 application scope 已销毁")
	        );
	        this.notificationNative = new import_native_host_api.BrowserDiscourseNotificationNativeState(options.host), this.notificationRequests = new import_discourse_notification_adapter.DiscourseNotificationRequestAdapter({
	          gateway: this.data.gateway,
	          ajax: this.nativeAjax,
	          native: this.notificationNative,
	          authScope: options.topic.authScope,
	          signal: notificationAbort.signal,
	          ...options.topic.basePath === void 0 ? {} : { basePath: options.topic.basePath },
	          replyExpansionCache: {
	            kind: "discourse-topic-posts",
	            tags: ["notifications"],
	            ...options.topic.caches.posts
	          }
	        });
	        const notificationMutation = new import_action_request_adapter.ActionRequestAdapter({
	          gateway: this.data.gateway,
	          nativeActions,
	          authScope: options.topic.authScope,
	          signal: notificationAbort.signal
	        });
	        this.notificationActions = new import_post_action_controller.PostActionController({
	          mutation: notificationMutation,
	          cache: this.data.responses,
	          scope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "notification",
	            cause
	          )
	        }), this.notificationController = new import_reader_notification_controller.ReaderNotificationController({
	          requests: this.notificationRequests,
	          native: this.notificationNative,
	          actions: this.notificationActions,
	          cache: this.data.responses,
	          target: {
	            openTarget: async (request) => {
	              const result = await this.openTarget(request);
	              return (result.topic.status === "opened" || result.topic.status === "reused") && result.navigation?.status === "revealed";
	            }
	          },
	          ...notificationOptions.maxCachedPages === void 0 ? {} : {
	            maxCachedPages: notificationOptions.maxCachedPages
	          },
	          ...notificationOptions.liveRefreshDelayMs === void 0 ? {} : {
	            liveRefreshDelayMs: notificationOptions.liveRefreshDelayMs
	          },
	          backgroundWarmDelayMs: notificationOptions.backgroundWarmDelayMs ?? 1800,
	          ...notificationOptions.schedule === void 0 ? {} : { schedule: notificationOptions.schedule },
	          ...notificationOptions.cancel === void 0 ? {} : { cancel: notificationOptions.cancel },
	          ...(notificationOptions.searchForms ?? options.searchForms) === void 0 ? {} : {
	            searchForms: notificationOptions.searchForms ?? options.searchForms
	          },
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "notification",
	            cause
	          )
	        }), this.notificationPanelView = new import_reader_notification_panel_view.ReaderNotificationPanelView({
	          ...notificationOptions,
	          ...notificationOptions.renderIcon ? {} : options.renderIcon ? { renderIcon: options.renderIcon } : {},
	          document: options.document,
	          controller: this.notificationController,
	          elements: readerNotificationPanelElements(
	            this.shell.view.root
	          ),
	          baseUrl: topicBaseUrl,
	          relativeTime: nativeRelativeTime,
	          notify: (message) => this.feedback.show(message),
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "notification",
	            cause
	          )
	        });
	      }
	      if (options.bookmarks === !1)
	        this.bookmarkNative = null, this.bookmarkRequests = null, this.bookmarkActions = null, this.bookmarkController = null, this.bookmarkPanelView = null;
	      else {
	        const bookmarkOptions = options.bookmarks ?? {}, bookmarkAbort = this.scope.abortController(
	          new Error("Reader 收藏 application scope 已销毁")
	        );
	        this.bookmarkNative = new import_native_host_api.BrowserDiscourseBookmarkNativeState(options.host), this.bookmarkRequests = new import_discourse_bookmark_adapter.DiscourseBookmarkRequestAdapter({
	          gateway: this.data.gateway,
	          ajax: this.nativeAjax,
	          native: this.bookmarkNative,
	          authScope: options.topic.authScope,
	          signal: bookmarkAbort.signal,
	          cache: {
	            kind: "discourse-bookmark-collection",
	            tags: ["bookmarks", "reactions-given"],
	            freshForMs: 30 * 6e4,
	            retainForMs: options.topic.caches.posts.retainForMs,
	            persist: !0
	          }
	        });
	        const bookmarkMutation = new import_action_request_adapter.ActionRequestAdapter({
	          gateway: this.data.gateway,
	          nativeActions,
	          authScope: options.topic.authScope,
	          signal: bookmarkAbort.signal
	        });
	        this.bookmarkActions = new import_post_action_controller.PostActionController({
	          mutation: bookmarkMutation,
	          cache: this.data.responses,
	          scope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "bookmark",
	            cause
	          )
	        }), this.bookmarkController = new import_reader_bookmark_controller.ReaderBookmarkController({
	          requests: this.bookmarkRequests,
	          native: this.bookmarkNative,
	          actions: this.bookmarkActions,
	          cache: this.data.responses,
	          target: {
	            openTarget: async (request) => {
	              const result = await this.openTarget(request);
	              return (result.topic.status === "opened" || result.topic.status === "reused") && result.navigation?.status === "revealed";
	            }
	          },
	          ...bookmarkOptions.tabOrder === void 0 ? {} : { tabOrder: bookmarkOptions.tabOrder },
	          ...bookmarkOptions.pageSize === void 0 ? {} : { pageSize: bookmarkOptions.pageSize },
	          ...bookmarkOptions.liveRefreshDelayMs === void 0 ? {} : {
	            liveRefreshDelayMs: bookmarkOptions.liveRefreshDelayMs
	          },
	          ...bookmarkOptions.changeTabOrder === void 0 ? {} : {
	            changeTabOrder: bookmarkOptions.changeTabOrder
	          },
	          ...(bookmarkOptions.searchForms ?? options.searchForms) === void 0 ? {} : {
	            searchForms: bookmarkOptions.searchForms ?? options.searchForms
	          },
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "bookmark",
	            cause
	          )
	        }), this.bookmarkPanelView = new import_reader_bookmark_panel_view.ReaderBookmarkPanelView({
	          ...bookmarkOptions,
	          ...bookmarkOptions.renderIcon ? {} : options.renderIcon ? { renderIcon: options.renderIcon } : {},
	          document: options.document,
	          controller: this.bookmarkController,
	          elements: readerBookmarkPanelElements(
	            this.shell.view.root
	          ),
	          baseUrl: topicBaseUrl,
	          relativeTime: nativeRelativeTime,
	          reactionIconSource: bookmarkOptions.reactionIconSource ?? ((reaction) => (0, import_native_host_api.discourseNativeEmojiUrl)(
	            options.host,
	            reaction
	          )),
	          confirmDelete: bookmarkOptions.confirmDelete ?? ((request) => this.feedback.confirm({
	            title: request.title,
	            message: request.message,
	            note: "该操作会同步到 Discourse 收藏。",
	            confirmLabel: request.confirmLabel,
	            tone: "danger",
	            details: [{
	              label: "收藏数量",
	              value: String(request.count)
	            }]
	          })),
	          notify: (message) => this.feedback.show(message),
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "bookmark",
	            cause
	          )
	        });
	      }
	      const coreTopicFactory = (0, import_reader_topic_factory.createReaderTopicFactory)({
	        ...topicFactoryOptions,
	        document: options.document,
	        onPhase: (phase, context) => {
	          options.loadingProgress?.update({
	            topicId: Number(context.topicId),
	            phase
	          });
	        },
	        createDomOptions: (bundle, context, root) => {
	          const currentUsername = (0, import_native_host_api.discourseNativeCurrentUsername)(options.host), topicImages = new import_reader_topic_image_index.ReaderTopicImageIndex({
	            document: options.document,
	            baseUrl: topicBaseUrl,
	            topicId: context.topicId,
	            session: bundle.services.session,
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "image-index",
	              cause
	            )
	          }), {
	            onError: mediaOnError,
	            visibility: mediaVisibility,
	            renderRetryIcon,
	            onLayoutChanged: mediaImageLayoutChanged,
	            onContentLayoutChanged: mediaContentLayoutChanged,
	            ...mediaOptions
	          } = options.media ?? {}, notifyTopicLayoutChanged = () => {
	            topicDoms.get(context.scope)?.notifyContentLayoutChanged();
	          }, topicMedia = new import_reader_topic_media_feature.ReaderTopicMediaFeature({
	            ...mediaOptions,
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            ...renderRetryIcon ? { renderRetryIcon } : options.renderIcon ? {
	              renderRetryIcon: (document) => options.renderIcon(
	                "rotate-ccw",
	                document
	              )
	            } : {},
	            document: options.document,
	            baseUrl: topicBaseUrl,
	            visibility: mediaVisibility ?? (() => options.document.visibilityState),
	            onLayoutChanged: (image) => {
	              mediaImageLayoutChanged?.(image), notifyTopicLayoutChanged();
	            },
	            onContentLayoutChanged: (root2) => {
	              mediaContentLayoutChanged?.(root2), notifyTopicLayoutChanged();
	            },
	            parentScope: context.scope,
	            onError: (cause) => {
	              try {
	                mediaOnError?.(cause);
	              } catch {
	              }
	              reportTopicFeature(
	                context.topicId,
	                "post-media",
	                cause
	              );
	            }
	          }), topicCookedContent = new import_reader_cooked_content_feature.ReaderCookedContentFeature({
	            document: options.document,
	            mount: this.shell.view.modal,
	            baseUrl: topicBaseUrl,
	            ...options.share ? { clipboard: options.share } : {},
	            ...this.blobDownloads ? { downloads: this.blobDownloads } : {},
	            notify: (message) => this.feedback.show(message),
	            onLayoutChanged: notifyTopicLayoutChanged,
	            onPrepared: (root2) => {
	              this.translationFeature?.syncPost(root2);
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "cooked-content",
	              cause
	            )
	          }), domOptions = createDomOptions(
	            bundle,
	            context,
	            root,
	            Object.freeze({
	              composer: this.composer,
	              presentation: nativeTopicPresentation,
	              relativeTime: nativeRelativeTime,
	              exactTime: nativeExactTime,
	              currentUsername
	            })
	          ), replyTreePresentation = domOptions.replyTreePresentation ?? new import_reader_reply_tree_preferences.ReaderReplyTreePresentation(
	            bundle.replies.topology,
	            domOptions.replyTreePreferences?.read(),
	            {
	              canonicalCoverageComplete: () => bundle.replies.coverage().complete
	            }
	          ), topicPresentationChanges = new import_signal.Signal(), topicPostCommands = new import_post_action_feature_commands.PostActionFeatureCommands(
	            bundle.services.postActions
	          ), topicPoll = new import_reader_poll_feature.ReaderTopicPollFeature({
	            document: options.document,
	            actions: bundle.services.actions,
	            commands: topicPostCommands,
	            descriptors: actionDescriptors,
	            readPost: (postId) => bundle.services.session.postById(postId),
	            viewer: () => readerPollViewer(
	              nativePostModels.currentUser(),
	              currentUsername
	            ),
	            topicArchived: () => bundle.services.session.topic?.archived === !0,
	            notify: (message) => this.feedback.show(message),
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "poll",
	              cause
	            )
	          }), topicBookmarkActions = new import_reader_bookmark_action_coordinator.ReaderBookmarkActionCoordinator({
	            topicId: context.topicId,
	            session: bundle.services.session,
	            actions: bundle.services.actions,
	            postCommands: topicPostCommands,
	            descriptors: actionDescriptors,
	            forms: nativeBookmarkForm,
	            models: nativePostModels
	          }), topicShareActions = options.share ? new import_reader_share_action_coordinator.ReaderShareActionCoordinator({
	            topicId: context.topicId,
	            topic: () => {
	              const topic = bundle.services.session.topic;
	              if (!topic)
	                throw new Error(
	                  "分享链接时 canonical Topic 尚未就绪"
	                );
	              return topic;
	            },
	            links: nativeTopicLinks,
	            surface: options.share,
	            fallbackTitle: () => options.document.title || "LINUX DO"
	          }) : null, topicNotificationActions = new import_reader_topic_notification_coordinator.ReaderTopicNotificationCoordinator({
	            topicId: context.topicId,
	            session: bundle.services.session,
	            actions: bundle.services.actions,
	            descriptors: actionDescriptors,
	            models: nativePostModels
	          }), topicSharedIssueActions = new import_reader_topic_shared_issue_coordinator.ReaderTopicSharedIssueCoordinator({
	            topicId: context.topicId,
	            session: bundle.services.session,
	            actions: bundle.services.actions,
	            descriptors: actionDescriptors,
	            settings: nativePostModels,
	            currentUsername
	          }), assignmentAbort = context.scope.abortController(
	            new Error("Reader Topic 指定表单生命周期已结束")
	          ), topicManagementActions = new import_reader_post_management_action_coordinator.ReaderPostManagementActionCoordinator({
	            topicId: context.topicId,
	            session: bundle.services.session,
	            actions: bundle.services.actions,
	            postCommands: topicPostCommands,
	            descriptors: actionDescriptors,
	            models: nativePostModels,
	            composer: this.composer,
	            assignments: this.assignmentForm,
	            assignmentSignal: assignmentAbort.signal,
	            feedback: this.feedback,
	            adminMenu: nativeAdminMenu,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "post-action",
	              cause
	            )
	          }), topicPostActions = new import_reader_post_action_feature.ReaderPostActionFeature({
	            document: options.document,
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            surfaceHost: this.shell.view.surfaceHost,
	            topic: () => {
	              const topic = bundle.services.session.topic;
	              if (!topic)
	                throw new Error(
	                  "楼层动作渲染时 canonical Topic 尚未就绪"
	                );
	              return topic;
	            },
	            actions: bundle.services.actions,
	            commands: topicPostCommands,
	            descriptors: actionDescriptors,
	            models: nativePostModels,
	            reactions: postReactions,
	            capabilityInput: (post) => {
	              const topic = bundle.services.session.topic, currentUser = nativePostModels.currentUser();
	              return Object.freeze({
	                post,
	                ...topic ? { topic } : {},
	                ...currentUser ? { currentUser } : {},
	                currentUsername: (0, import_native_host_api.discourseNativeCurrentUsername)(
	                  options.host
	                ),
	                plugins: Object.freeze({
	                  boosts: (0, import_native_host_api.discourseNativeBoostsAvailable)(
	                    options.host
	                  ) || Object.hasOwn(post, "can_boost") || Array.isArray(post.boosts)
	                })
	              });
	            },
	            topicActionRail: !!options.topicActionRail,
	            refreshMissingCapabilities: async (post) => {
	              const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(
	                post.post_number
	              );
	              postNumber !== null && await bundle.services.session.loadTarget(postNumber, {
	                scope: "around",
	                forceRefresh: !0,
	                advanceCursor: !1
	              });
	            },
	            presentation: nativeTopicPresentation,
	            currentUsername,
	            ...options.boostCopy ? {
	              readBoostCopySettings: options.boostCopy.readSettings
	            } : {},
	            emojiMenu: nativeEmojiMenu,
	            confirmBoostDelete: ({ username }) => this.feedback.confirm({
	              title: "删除 Boost",
	              message: username ? `确认删除 @${username} 的这条 Boost?` : "确认删除这条 Boost?",
	              note: "该操作会同步到 Discourse。",
	              confirmLabel: "删除",
	              tone: "danger"
	            }),
	            reportBoost: async ({
	              postId,
	              boostId,
	              username
	            }) => {
	              const access = await bundle.services.boostReportAccess.load(
	                boostId
	              );
	              if (!access.canFlag)
	                throw new Error(
	                  access.alreadyFlagged ? "你已经举报过这个 Boost" : "当前账号不能举报这个 Boost"
	                );
	              const available = new Set(
	                access.availableFlagNames
	              ), reportOptions = readerReportOptions(
	                options.document,
	                nativeFlagCatalog.flagTypes(),
	                available,
	                "DiscourseBoosts::Boost"
	              ), reportedUsername = access.username || username;
	              return this.reportForm.open({
	                title: "举报 Boost",
	                intro: reportedUsername ? `举报 @${reportedUsername} 的 Boost 会直接提交给社区,不会离开阅读器。` : "举报会直接提交给社区,不会离开阅读器。",
	                options: reportOptions,
	                messageMaxLength: nativeFlagCatalog.messageMaxLength(),
	                submit: async ({
	                  optionId,
	                  message
	                }) => (await bundle.services.actions.dispatch(
	                  topicPostCommands.boostReport(
	                    postId,
	                    actionDescriptors.boostReport({
	                      boostId,
	                      flagTypeId: optionId,
	                      ...message ? { message } : {}
	                    })
	                  )
	                ), "举报已提交")
	              });
	            },
	            reportPost: async (post) => {
	              const topic = bundle.services.session.topic;
	              if (!topic)
	                throw new Error(
	                  "楼层举报时 canonical Topic 尚未就绪"
	                );
	              const flagTypes = nativeFlagCatalog.flagTypes(), native = nativePostModels.reportContext(
	                topic,
	                post,
	                flagTypes.map((flag) => flag.nameKey)
	              ), actionByName = new Map(
	                native.actions.map((entry) => [
	                  entry.nameKey,
	                  entry.action
	                ])
	              ), reportOptions = readerReportOptions(
	                options.document,
	                flagTypes,
	                new Set(actionByName.keys()),
	                "Post"
	              ), flagById = new Map(
	                flagTypes.map((flag) => [
	                  flag.id,
	                  flag
	                ])
	              ), postId = Number(post.id), postNumber = Number(post.post_number), topicStarter = postNumber === 1;
	              return this.reportForm.open({
	                title: topicStarter ? "举报主题" : "举报楼层",
	                intro: topicStarter ? "举报主题会直接提交给社区,不会离开阅读器。" : postNumber > 0 ? `举报 #${postNumber} 楼会直接提交给社区,不会离开阅读器。` : "举报会直接提交给社区,不会离开阅读器。",
	                options: reportOptions,
	                messageMaxLength: nativeFlagCatalog.messageMaxLength(),
	                submit: async ({
	                  optionId,
	                  message
	                }) => {
	                  const flag = flagById.get(optionId), postAction = flag ? actionByName.get(flag.nameKey) : null;
	                  if (!flag || !postAction)
	                    throw new Error(
	                      "当前举报类型已不可用,请重新打开表单"
	                    );
	                  return await bundle.services.actions.dispatch(
	                    topicPostCommands.report(
	                      postId,
	                      actionDescriptors.postReport({
	                        postId,
	                        post: native.post,
	                        postAction,
	                        flagTypeId: optionId,
	                        ...message ? { message } : {}
	                      })
	                    )
	                  ), "举报已提交";
	                }
	              });
	            },
	            bookmarks: topicBookmarkActions,
	            ...topicShareActions ? { shares: topicShareActions } : {},
	            topicNotifications: topicNotificationActions,
	            sharedIssue: topicSharedIssueActions,
	            management: topicManagementActions,
	            notify: (message) => this.feedback.show(message),
	            composer: this.composer,
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "post-action",
	              cause
	            )
	          }), topicActionRail = options.topicActionRail ? new import_reader_topic_action_rail.ReaderTopicActionRail({
	            document: options.document,
	            mount: this.shell.view.modal,
	            shellRoot: this.shell.view.root,
	            identity: domOptions.identity,
	            actions: topicPostActions,
	            preferences: options.topicActionRail,
	            jumpToTop: async () => {
	              const timeline = topicTimelines.get(context.scope);
	              if (!timeline)
	                throw new Error(
	                  "主帖操作列跳转时 Topic timeline 尚未就绪"
	                );
	              const result = await timeline.jumpTo(1, {
	                alignment: "start",
	                highlight: !0
	              });
	              if (result.status !== "revealed")
	                throw new Error(
	                  `主帖操作列回顶失败:${result.status}`
	                );
	            },
	            ...options.downloadCurrentTopic ? { downloadCurrentTopic: options.downloadCurrentTopic } : {},
	            ...options.openTopicDownloadManager ? {
	              openTopicDownloadManager: options.openTopicDownloadManager
	            } : {},
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "post-action",
	              cause
	            )
	          }) : null;
	          topicActionRail && (0, import_reader_topic_action_rail.bindReaderTopicActionRailStarter)({
	            readStarter: () => bundle.services.session.postByNumber(1),
	            loadStarter: () => bundle.services.session.loadTarget(1, {
	              scope: "single",
	              advanceCursor: !1
	            }),
	            waitUntilReady: () => bundle.services.session.init(),
	            subscribe: (listener, scope) => bundle.services.session.changes.subscribe(
	              listener,
	              scope
	            ),
	            update: (starter) => topicActionRail.update(starter),
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "post-action",
	              cause
	            )
	          });
	          const topicSpecialContent = new import_reader_topic_special_content_feature.ReaderTopicSpecialContentFeature({
	            document: options.document,
	            session: bundle.services.session,
	            presentationChanges: topicPresentationChanges,
	            presentation: nativeTopicPresentation,
	            relativeTime: nativeRelativeTime,
	            actions: bundle.services.actions,
	            commands: topicPostCommands,
	            descriptors: actionDescriptors,
	            models: nativePostModels,
	            loadPostVotingComments: (postId, afterCommentId) => bundle.services.requests.loadPostVotingComments(postId, {
	              afterCommentId,
	              refresh: !0
	            }),
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            navigate: async (postNumber) => {
	              const navigation = topicNavigations.get(
	                context.scope
	              );
	              if (!navigation)
	                throw new Error(
	                  "特殊正文跳转时 Topic navigation 尚未就绪"
	                );
	              const result = await navigation.navigate({
	                postNumber,
	                source: "solved-answer",
	                alignment: "center",
	                highlight: !0
	              });
	              if (result.status !== "revealed")
	                throw new Error(
	                  `特殊正文楼层 #${postNumber} 跳转失败:${result.status}`
	                );
	            },
	            onBodyLayerChanged: (view) => {
	              topicCookedContent.refresh(view), topicMedia.refresh(view), this.translationFeature?.syncPost(
	                view.slots.root
	              ), notifyTopicLayoutChanged();
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "special-content",
	              cause
	            )
	          }), topicContext = new import_reader_topic_context_controller.ReaderTopicContextController({
	            session: bundle.services.session,
	            replies: bundle.replies,
	            loadCrossTopicQuotedPost: async (targetTopicId, targetPostNumber) => {
	              const targetOptions = { scope: "single" };
	              let lastError;
	              for (const candidate of bundle.services.requests.targetCandidates(
	                targetPostNumber,
	                targetOptions,
	                targetTopicId
	              ))
	                try {
	                  const payload = await bundle.services.requests.loadTargetCandidate(
	                    candidate,
	                    targetPostNumber,
	                    targetOptions,
	                    targetTopicId
	                  ), post = (0, import_topic_session.discoursePostsFromPayload)(payload).find((value) => Number(value.post_number) === targetPostNumber);
	                  if (post) return post;
	                } catch (error) {
	                  lastError = error;
	                  const status = Number(
	                    error?.status
	                  );
	                  if (error instanceof DOMException && error.name === "AbortError") throw error;
	                  if ((0, import_native_request_descriptors.discourseNativeTargetFailureIsDefinitive)({
	                    endpoint: candidate.endpoint,
	                    scope: targetOptions.scope,
	                    status
	                  })) return null;
	                  if ([401, 403, 429].includes(status)) throw error;
	                }
	              if (lastError) throw lastError;
	              return null;
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "thread-context",
	              cause
	            )
	          }), topicScroll = new import_reader_topic_scroll_adapter.ReaderTopicScrollAdapter({
	            ...options.navigation,
	            scrollRoot: this.shell.view.body,
	            viewportChangeTarget: this.shell.view.root,
	            parentScope: context.scope
	          }), topicContextFeature = new import_reader_topic_context_surface.ReaderTopicContextFeature({
	            document: options.document,
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            controller: topicContext,
	            replies: bundle.replies,
	            presentationChanges: topicPresentationChanges,
	            presentation: replyTreePresentation,
	            avatarSource: (template, size) => nativeTopicPresentation.avatarSource(template, size),
	            scrollRoot: this.shell.view.body,
	            quoteHintHost: this.shell.view.surfaceHost,
	            notify: (message) => this.feedback.show(message),
	            navigate: () => topicNavigations.get(context.scope) ?? null,
	            target: {
	              open: async (request) => {
	                await this.openTarget(request);
	              }
	            },
	            onQuoteBodyChanged: (view) => {
	              topicMedia.refresh(view), this.translationFeature?.syncPost(
	                view.slots.root
	              );
	            },
	            onRevealNextReplyLevel: (postNumber) => topicDoms.get(context.scope)?.revealNextReplyLevel(postNumber) ?? !1,
	            revealQuoteTarget: (target, mode) => {
	              typeof target.getBoundingClientRect == "function" && topicScroll.alignPost(target, {
	                source: mode === "match" ? "quote-match" : "quote",
	                alignment: mode === "match" ? "nearest" : "start",
	                highlight: mode === "floor"
	              });
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "thread-context",
	              cause
	            )
	          }), topicCommentsHeader = new import_reader_topic_comments_header.ReaderTopicCommentsHeader({
	            document: options.document,
	            topicId: context.topicId,
	            session: bundle.services.session,
	            presence: nativePresence,
	            presentation: nativeTopicPresentation,
	            currentUsername,
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "topic-header",
	              cause
	            )
	          }), topicLocalArchive = new import_reader_topic_local_archive_feature.ReaderTopicLocalArchiveFeature({
	            document: options.document,
	            topicRoot: root,
	            session: bundle.services.session,
	            parentScope: context.scope
	          });
	          if (context.scope.add(
	            bundle.services.snapshots.setPersistenceDelayReader(
	              (minimumIdleMs) => topicScroll.remainingUserIdleMs(minimumIdleMs)
	            )
	          ), domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_topic_media_feature.ReaderTopicMediaFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderTopicMediaFeature"
	            );
	          if (domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_cooked_content_feature.ReaderCookedContentFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderCookedContentFeature"
	            );
	          if (domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_topic_special_content_feature.ReaderTopicSpecialContentFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderTopicSpecialContentFeature"
	            );
	          if (domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_topic_context_surface.ReaderTopicContextFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderTopicContextFeature"
	            );
	          if (domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_post_action_feature.ReaderPostActionFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderPostActionFeature"
	            );
	          if (domOptions.postFeatures?.some(
	            (feature) => feature instanceof import_reader_poll_feature.ReaderTopicPollFeature
	          ))
	            throw new Error(
	              "ReaderBrowserRuntime 已拥有唯一 ReaderTopicPollFeature"
	            );
	          const postFeatures = Object.freeze([
	            topicLocalArchive,
	            topicCookedContent,
	            topicPoll,
	            topicSpecialContent,
	            topicContextFeature,
	            topicPostActions,
	            topicCommentsHeader,
	            ...domOptions.postFeatures ?? [],
	            topicMedia
	          ]);
	          return topicFeatures.set(context.scope, Object.freeze({
	            topicImages,
	            topicMedia,
	            topicCookedContent,
	            topicSpecialContent,
	            topicContext,
	            topicContextFeature,
	            highlightDiscussionTarget: (target) => topicScroll.highlight.highlight(target),
	            topicActionRail,
	            topicPostActions,
	            presentation: Object.freeze({
	              identity: domOptions.identity,
	              renderPost: domOptions.render,
	              postFeatures
	            })
	          })), Object.freeze({
	            ...domOptions,
	            replyTreePresentation,
	            presentationChanges: topicPresentationChanges,
	            postFeatures,
	            scroll: topicScroll,
	            readDirectReplyPrefetchScreens: () => this.#performance.nestedPrefetchScreens,
	            readDirectReplyPrefetchIdleMs: () => Math.max(
	              120,
	              this.#performance.requestMinIntervalMs + 30
	            ),
	            readDirectReplyPrefetchConcurrency: () => Math.max(
	              1,
	              Math.min(
	                2,
	                this.#performance.requestMaxConcurrent - 1
	              )
	            ),
	            ...options.topicFlowScheduler === void 0 ? {} : {
	              directReplyPrefetchScheduler: {
	                schedule: (callback, delayMs) => options.topicFlowScheduler.schedule(
	                  callback,
	                  "near-window",
	                  delayMs
	                ),
	                cancel: (handle) => options.topicFlowScheduler.cancel(
	                  handle
	                )
	              }
	            }
	          });
	        },
	        createBundle: (context) => this.data.createTopicBundle(
	          context,
	          {
	            ...options.topic,
	            pageSize: this.#performance.pageSize,
	            host: options.host,
	            nativeAjax: this.nativeAjax,
	            nativeActions,
	            composerEvents: this.composerIsolation,
	            ...options.loadingProgress ? {
	              onLoadingSource: (source, counts) => {
	                options.loadingProgress.update({
	                  topicId: Number(context.topicId),
	                  phase: source,
	                  cachedCount: counts.cachedCount,
	                  missingCount: counts.missingCount
	                });
	              }
	            } : {}
	          }
	        ),
	        onAssembled: (value, context) => {
	          topicDoms.set(context.scope, value.dom);
	          const features = topicFeatures.get(context.scope);
	          if (!features)
	            throw new Error(
	              "Topic context surface 装配时 presentation 尚未就绪"
	            );
	          const translationGeneration = this.translationFeature?.activateTopic(context.topicId);
	          context.scope.add(() => this.translationFeature?.deactivateTopic(
	            context.topicId,
	            translationGeneration
	          ));
	          let translationWindowPostNumbers = /* @__PURE__ */ new Set();
	          const updateTranslationWindow = () => {
	            const posts = [...translationWindowPostNumbers].map((postNumber) => value.services.session.postByNumber(postNumber)).filter((post) => post !== void 0);
	            this.translationFeature?.updatePreloadWindow(
	              context.topicId,
	              posts,
	              translationGeneration
	            );
	          };
	          value.dom.windowChanges.subscribe((commit) => {
	            translationWindowPostNumbers = /* @__PURE__ */ new Set([
	              ...commit.tree.mountedRoots,
	              ...commit.tree.mountedReplies
	            ]), updateTranslationWindow(), this.translationFeature?.syncMountedPosts();
	          }, context.scope), value.services.session.changes.subscribe((commit) => {
	            let changedWindow = !1;
	            for (const postNumber of commit.changedPostNumbers) {
	              const post = value.services.session.postByNumber(postNumber), parentPostNumber = (0, import_identifiers.tryDiscoursePostNumber)(
	                post?.reply_to_post_number
	              );
	              !translationWindowPostNumbers.has(postNumber) && (parentPostNumber === null || !translationWindowPostNumbers.has(parentPostNumber)) || (translationWindowPostNumbers.add(postNumber), changedWindow = !0);
	            }
	            changedWindow && updateTranslationWindow();
	          }, context.scope);
	          let contextSurface = null;
	          const navigation = new import_reader_topic_navigation_controller.ReaderTopicNavigationController({
	            session: value.services.session,
	            dom: value.dom,
	            hidden: {
	              isHidden: (postNumber) => value.dom.isPostHidden(postNumber),
	              async revealPost(postNumber) {
	                const surface = contextSurface;
	                return surface ? surface.revealDiscussionPost(postNumber) : null;
	              }
	            },
	            listenUserScrollIntent: (listener) => value.dom.listenUserScrollIntent(listener),
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "navigation",
	              cause
	            )
	          });
	          topicNavigations.set(context.scope, navigation);
	          const topicFlow = new import_reader_topic_flow_controller.ReaderTopicFlowController({
	            dom: value.dom,
	            readPerformance: () => this.#performance,
	            sessionChanges: value.services.session.changes,
	            readLoadDone: () => value.services.session.loadDone,
	            ...options.topicFlowScheduler === void 0 ? {} : {
	              scheduler: options.topicFlowScheduler
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "navigation",
	              cause
	            )
	          });
	          topicFlows.set(context.scope, topicFlow), contextSurface = new import_reader_topic_context_surface.ReaderTopicContextSurface({
	            document: options.document,
	            controller: features.topicContext,
	            replies: value.replies,
	            discussionHost: this.shell.view.modal,
	            workspace: this.workspace.workspace,
	            identity: features.presentation.identity,
	            renderPost: features.presentation.renderPost,
	            postFeatures: features.presentation.postFeatures,
	            postProjector: value.dom.postProjector,
	            readDiscussionMaterializedPostLimit: () => Math.max(
	              12,
	              Math.floor(
	                this.#performance.streamMaxMountedPostCount / 2
	              )
	            ),
	            highlight: features.highlightDiscussionTarget,
	            stateRepository: this.threadContextState,
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "thread-context",
	              cause
	            )
	          }), topicContextSurfaces.set(context.scope, contextSurface), features.topicContextFeature.connectQuoteSource({
	            captureAnchor: () => {
	              const viewport = value.dom.captureViewportAnchor();
	              return viewport ? Object.freeze({
	                viewport: Object.freeze({
	                  ...viewport,
	                  postNumber: (0, import_identifiers.discoursePostNumber)(
	                    viewport.postNumber
	                  )
	                }),
	                replyWindow: contextSurface.captureDiscussionState(),
	                quoteHighlight: null
	              }) : null;
	            },
	            restore: async (source) => this.#restoreQuoteSource(source)
	          }), this.translationFeature?.syncMountedPosts();
	          const header = new import_reader_topic_header.ReaderTopicHeaderController({
	            session: value.services.session,
	            presentation: nativeTopicPresentation,
	            parentScope: context.scope,
	            onError: (cause) => {
	              reportTopicFeature(
	                context.topicId,
	                "topic-header",
	                cause
	              ), this.feedback.show(
	                cause instanceof Error ? cause.message : "打开帖子编辑器失败"
	              );
	            }
	          });
	          topicHeaders.set(context.scope, header);
	          const onlyOp = new import_reader_topic_only_op_controller.ReaderTopicOnlyOpController({
	            session: value.services.session,
	            presentationChanges: value.dom.presentationChanges,
	            presentation: value.dom.replyTreePresentation,
	            onProjectionChanged: (resetScroll) => {
	              value.dom.refreshRootProjection(resetScroll);
	            },
	            onEnabledChanged: (enabled) => {
	              topicFlow.setProjectionPriority(enabled);
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "navigation",
	              cause
	            )
	          });
	          topicOnlyOpControllers.set(context.scope, onlyOp);
	          const topicVoteSession = value.services.session, topicVoteCommands = new import_topic_action_feature_commands.TopicActionFeatureCommands({
	            topicId: context.topicId,
	            session: topicVoteSession
	          }), headerView = new import_reader_topic_header.ReaderTopicHeaderView({
	            controller: header,
	            hostDocument: options.document,
	            elements: readerTopicHeaderElements(
	              this.shell.view.root
	            ),
	            onlyOp,
	            ...options.renderIcon ? { renderIcon: options.renderIcon } : {},
	            onJumpFirst: async () => {
	              const result = await navigation.navigate({
	                postNumber: 1,
	                source: "link",
	                alignment: "start",
	                highlight: !0
	              });
	              if (result.status !== "revealed")
	                throw new Error(
	                  `主题标题跳转失败:${result.status}`
	                );
	            },
	            onToggleTopicVote: async (voted) => {
	              if (!(0, import_native_host_api.discourseNativeCurrentUsername)(options.host))
	                throw new Error("登录后才能为主题投票。");
	              await value.services.actions.dispatch(topicVoteCommands.vote(
	                voted,
	                actionDescriptors.topicVoteToggle({
	                  topicId: context.topicId,
	                  voted
	                })
	              ));
	            },
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "topic-header",
	              cause
	            )
	          });
	          topicHeaderViews.set(context.scope, headerView);
	          const topicEditTrigger = this.shell.view.root.querySelector(
	            ".ldp-topic-edit-trigger"
	          );
	          if (!topicEditTrigger)
	            throw new Error("Reader Header 缺少 Topic 编辑入口");
	          new import_reader_topic_edit_controller.ReaderTopicEditController({
	            topicId: context.topicId,
	            session: value.services.session,
	            trigger: topicEditTrigger,
	            form: this.topicEditForm,
	            catalog: nativeTopicEditCatalog,
	            actions: value.services.actions,
	            descriptors: actionDescriptors,
	            models: nativePostModels,
	            notify: (message) => this.feedback.show(message),
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "topic-header",
	              cause
	            )
	          });
	          const readTotalPostCount = () => {
	            const topic = value.services.session.topic ?? value.topic, candidates = [
	              Number(topic.highest_post_number),
	              Number(topic.posts_count),
	              ...value.services.session.cachedPosts().map((post) => (0, import_identifiers.tryDiscoursePostNumber)(post.post_number) ?? 0)
	            ];
	            return Math.max(
	              1,
	              ...candidates.filter((candidate) => Number.isSafeInteger(candidate) && candidate > 0)
	            );
	          }, timelinePresentation = value.dom.replyTreePresentation, timeline = new import_reader_topic_timeline_controller.ReaderTopicTimelineController({
	            navigation,
	            readTotalPostCount,
	            readNavigablePostNumbers: () => timelinePresentation.roots(),
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "navigation",
	              cause
	            )
	          });
	          topicTimelines.set(context.scope, timeline);
	          const timelineViewOptions = options.timelineView;
	          if (timelineViewOptions) {
	            const {
	              readPreferences,
	              preferences,
	              formatRelative,
	              notify,
	              ...viewOptions
	            } = timelineViewOptions, timestamp = (value2) => typeof value2 == "string" && value2.trim() ? value2 : null, currentTopic = () => value.services.session.topic ?? value.topic, timelineView = new import_reader_topic_timeline_view.ReaderTopicTimelineView({
	              ...viewOptions,
	              controller: timeline,
	              elements: readerTopicTimelineElements(
	                this.shell.view.root
	              ),
	              preferences: readPreferences?.() ?? preferences,
	              readCreatedAt: (postNumber) => {
	                const post = value.services.session.postByNumber(postNumber);
	                return timestamp(post?.created_at) ?? (postNumber === 1 ? timestamp(currentTopic().created_at) : null);
	              },
	              readLatestReplyAt: () => timestamp(currentTopic().last_posted_at),
	              formatRelative: formatRelative ?? nativeRelativeTime,
	              notify: notify ?? ((message) => this.feedback.show(message)),
	              parentScope: context.scope,
	              onError: (cause) => reportTopicFeature(
	                context.topicId,
	                "navigation",
	                cause
	              )
	            });
	            topicTimelineViews.set(context.scope, timelineView);
	          }
	          const liveNavigation = new import_reader_topic_live_navigation_controller.ReaderTopicLiveNavigationController({
	            live: value.services.live,
	            navigation,
	            parentScope: context.scope,
	            onError: (cause) => reportTopicFeature(
	              context.topicId,
	              "navigation",
	              cause
	            )
	          });
	          topicLiveNavigations.set(context.scope, liveNavigation), topicLiveNavigationViews.set(
	            context.scope,
	            new import_reader_topic_live_navigation_view.ReaderTopicLiveNavigationView({
	              navigation: liveNavigation,
	              elements: readerTopicLiveNavigationElements(
	                this.shell.view.root
	              ),
	              notify: (message) => this.feedback.show(message),
	              parentScope: context.scope
	            })
	          ), value.dom.visibleRootChanges.subscribe((change) => {
	            const visibleRootPostNumber = timelinePresentation.rootOf(change.postNumber) ?? change.postNumber;
	            timeline.syncVisiblePost(visibleRootPostNumber, change), liveNavigation.syncViewport(change);
	          }, context.scope), value.services.session.changes.subscribe(() => {
	            timeline.refresh(), topicTimelineViews.get(context.scope)?.refresh();
	          }, context.scope), value.services.composerEvents.changes.subscribe((commit) => {
	            commit.postNumber !== null && navigation.navigate({
	              postNumber: commit.postNumber,
	              source: "composer",
	              alignment: "nearest",
	              focus: !0,
	              highlight: !0
	            }).catch(() => {
	            });
	          }, context.scope);
	        }
	      });
	      this.topicFactory = async (context) => {
	        const result = await coreTopicFactory(context), openNative2 = this.shell.view.root.querySelector(
	          ".ldp-open"
	        );
	        if (!openNative2)
	          throw new Error("Reader Shell 缺少原生主题入口");
	        const nativeHref = (0, import_reader_native_topic_route.readerNativeTopicHref)(
	          nativeTopicLinks.topicHref(context.topicId),
	          topicBaseUrl
	        );
	        openNative2.href = nativeHref, openNative2.hidden = !nativeHref, context.scope.add(() => {
	          openNative2.href === nativeHref && (openNative2.removeAttribute("href"), openNative2.hidden = !0);
	        });
	        const features = topicFeatures.get(context.scope);
	        if (!features) throw new Error("Topic presentation features 未完成装配");
	        const topicHeader = topicHeaders.get(context.scope);
	        if (!topicHeader) throw new Error("Topic header 未完成装配");
	        const topicHeaderView = topicHeaderViews.get(context.scope);
	        if (!topicHeaderView) throw new Error("Topic header View 未完成装配");
	        const topicOnlyOp = topicOnlyOpControllers.get(context.scope);
	        if (!topicOnlyOp) throw new Error("只看楼主控制器未完成装配");
	        const topicNavigation = topicNavigations.get(context.scope);
	        if (!topicNavigation) throw new Error("Topic navigation 未完成装配");
	        const topicFlow = topicFlows.get(context.scope);
	        if (!topicFlow) throw new Error("Topic flow 未完成装配");
	        const topicContextSurface = topicContextSurfaces.get(context.scope);
	        if (!topicContextSurface)
	          throw new Error("Topic context surface 未完成装配");
	        const topicTimeline = topicTimelines.get(context.scope);
	        if (!topicTimeline) throw new Error("Topic timeline 未完成装配");
	        const topicTimelineView = topicTimelineViews.get(context.scope) ?? null, topicLiveNavigation = topicLiveNavigations.get(context.scope);
	        if (!topicLiveNavigation)
	          throw new Error("Topic live navigation 未完成装配");
	        const topicLiveNavigationView = topicLiveNavigationViews.get(context.scope);
	        if (!topicLiveNavigationView)
	          throw new Error("Topic live navigation View 未完成装配");
	        let value = null;
	        const lightboxOptions = options.lightbox, lightboxPostCommands = new import_post_action_feature_commands.PostActionFeatureCommands(
	          result.value.services.postActions
	        ), topicLightbox = lightboxOptions ? new import_reader_lightbox_feature.ReaderLightboxFeature({
	          document: options.document,
	          mount: typeof lightboxOptions.mount == "function" ? lightboxOptions.mount() : lightboxOptions.mount,
	          topic: () => result.value.services.session.topic ?? result.value.topic,
	          session: result.value.services.session,
	          replies: result.value.replies,
	          composer: this.composer,
	          identity: features.presentation.identity,
	          renderPost: features.presentation.renderPost,
	          postFeatures: features.presentation.postFeatures,
	          reactionSurfaces: features.topicPostActions,
	          postProjector: result.value.dom.postProjector,
	          minimumCommentLength: () => nativePostModels.minimumPostLength(),
	          submitComment: async ({ topic, targetPost, raw }) => (await this.composer.openReply({
	            topic,
	            post: targetPost,
	            initialRaw: raw,
	            replaceRaw: !0
	          }), result.value.services.actions.dispatch(
	            lightboxPostCommands.reply(
	              actionDescriptors.replyCreate({
	                postId: Number(targetPost.id),
	                replyToPostNumber: Number(
	                  targetPost.post_number
	                )
	              })
	            )
	          )),
	          topicImages: features.topicImages,
	          ...lightboxOptions.originalSources ? { originalSources: lightboxOptions.originalSources } : {},
	          ...this.imageResources ? { imageResources: this.imageResources } : {},
	          ...this.imageDownloads ? { imageDownloads: this.imageDownloads } : {},
	          ...lightboxOptions.confirmOriginalDownload ? {
	            confirmOriginalDownload: lightboxOptions.confirmOriginalDownload
	          } : {
	            confirmOriginalDownload: (missing, total) => this.feedback.confirm({
	              title: total === 1 ? "下载当前图片" : "批量下载图片",
	              message: total === 1 ? "当前原图尚未缓存,是否按阅读器限速获取原图?" : `${missing} 张原图尚未缓存,是否按阅读器限速逐张获取?`,
	              note: "取消将使用每张图片当前最高缓存或预览质量。",
	              confirmLabel: "获取原图",
	              cancelLabel: "使用预览质量",
	              tone: "primary"
	            })
	          },
	          notify: (message) => this.feedback.show(message),
	          ...lightboxOptions.originalByDefault === void 0 ? {} : {
	            originalByDefault: lightboxOptions.originalByDefault
	          },
	          ...lightboxOptions.commentsExpandedByDefault === void 0 ? {} : {
	            commentsExpandedByDefault: lightboxOptions.commentsExpandedByDefault
	          },
	          ...lightboxOptions.descriptionExpandedByDefault === void 0 ? {} : {
	            descriptionExpandedByDefault: lightboxOptions.descriptionExpandedByDefault
	          },
	          ...lightboxOptions.readDefaults ? { readDefaults: lightboxOptions.readDefaults } : {},
	          ...lightboxOptions.preferences ? { preferences: lightboxOptions.preferences } : {},
	          ...lightboxOptions.commentsEnabled === void 0 ? {} : { commentsEnabled: lightboxOptions.commentsEnabled },
	          ...lightboxOptions.frameScheduler ? { frameScheduler: lightboxOptions.frameScheduler } : {},
	          onJumpToPost: async (item) => {
	            if (!value)
	              throw new Error("Topic context 尚未完成装配");
	            await value.topicNavigation.navigate({
	              postNumber: item.sourcePostNumber,
	              source: "lightbox",
	              alignment: "center",
	              highlight: !0
	            }), await lightboxOptions.onJumpToPost?.(item, value);
	          },
	          onClose: () => {
	            value && lightboxOptions.onClose?.(value);
	          },
	          parentScope: context.scope,
	          onError: (cause) => {
	            try {
	              lightboxOptions.onError?.(cause);
	            } catch {
	            }
	            reportTopicFeature(
	              context.topicId,
	              "image-interaction",
	              cause
	            );
	          }
	        }) : null, openTopicImage = options.openTopicImage, topicImageInteraction = topicLightbox || openTopicImage ? new import_reader_topic_image_interaction.ReaderTopicImageInteraction({
	          topicHost: result.value.root,
	          additionalHosts: [
	            topicContextSurface.discussionContentHost
	          ],
	          images: features.topicImages,
	          open: (request) => {
	            if (topicLightbox) {
	              topicLightbox.open({
	                items: request.items,
	                initialIndex: request.initialIndex,
	                returnFocus: request.returnFocus,
	                ...request.commentsEnabled === void 0 ? {} : { commentsEnabled: request.commentsEnabled },
	                ...request.includeTopicImages === void 0 ? {} : { includeTopicImages: request.includeTopicImages }
	              });
	              return;
	            }
	            if (!value) throw new Error("Topic context 尚未完成装配");
	            return openTopicImage(request, value);
	          },
	          currentTopicId: context.topicId,
	          loadQuotedPost: (targetTopicId, targetPostNumber) => features.topicContext.loadQuotedPost(
	            targetTopicId,
	            targetPostNumber
	          ),
	          parentScope: context.scope,
	          onError: (cause) => reportTopicFeature(
	            context.topicId,
	            "image-interaction",
	            cause
	          )
	        }) : null, topicSelectionQuote = new import_reader_selection_quote_feature.ReaderSelectionQuoteFeature({
	          document: options.document,
	          root: this.shell.view.surfaceHost,
	          contentRoot: this.shell.view.modal,
	          topicId: context.topicId,
	          topic: () => result.value.services.session.topic ?? result.value.topic,
	          postById: (postId) => result.value.services.session.postById(postId),
	          postByNumber: (postNumber) => result.value.services.session.postByNumber(postNumber),
	          images: features.topicImages,
	          composer: this.composer,
	          ...options.share ? { clipboard: options.share } : {},
	          feedback: this.feedback,
	          parentScope: context.scope,
	          onError: (cause) => reportTopicFeature(
	            context.topicId,
	            "selection-quote",
	            cause
	          )
	        });
	        value = Object.freeze({
	          ...result.value,
	          topicImages: features.topicImages,
	          topicMedia: features.topicMedia,
	          topicCookedContent: features.topicCookedContent,
	          topicHeader,
	          topicHeaderView,
	          topicOnlyOp,
	          topicSpecialContent: features.topicSpecialContent,
	          topicContext: features.topicContext,
	          topicContextFeature: features.topicContextFeature,
	          topicContextSurface,
	          topicLiveNavigation,
	          topicLiveNavigationView,
	          topicNavigation,
	          topicFlow,
	          topicSelectionQuote,
	          topicActionRail: features.topicActionRail,
	          topicTimeline,
	          topicTimelineView,
	          topicImageInteraction,
	          topicLightbox
	        });
	        const readyCleanup = topicReady?.(value, context);
	        return typeof readyCleanup == "function" && context.scope.add(readyCleanup), Object.freeze({
	          ...result,
	          value,
	          prepareClose: async (reason) => {
	            await result.prepareClose?.(reason), this.#rememberHistoryTopic(value);
	          }
	        });
	      }, this.history = new import_reader_history_repository.ReaderHistoryRepository({
	        storage: options.storage,
	        authScope: options.topic.authScope,
	        ...options.history?.key === void 0 ? {} : { key: options.history.key },
	        ...options.history?.maxAgeMs === void 0 ? {} : { maxAgeMs: options.history.maxAgeMs },
	        ...options.history?.now === void 0 ? {} : { now: options.history.now }
	      }), this.history.load(), this.historyNavigation = new import_reader_history_navigation_controller.ReaderHistoryNavigationController({
	        history: this.history,
	        readSortMode: options.history?.readSortMode ?? (() => "recent-viewed"),
	        port: {
	          activeTopicId: () => this.shell.activeTopicId,
	          captureAnchor: () => this.#captureAndRememberHistoryAnchor(),
	          openTopic: async (topicId) => {
	            const result = await this.shell.open(
	              topicId,
	              this.topicFactory
	            );
	            return (result.status === "opened" || result.status === "reused") && this.#rememberHistoryTopic(result.value), this.#historyOpenResult(result);
	          },
	          restoreAnchor: async (topicId, anchor, restoreOptions) => {
	            const value = this.shell.activeValue;
	            if (this.shell.activeTopicId !== topicId || value === null)
	              throw new Error(
	                `历史目标 Topic ${topicId} 未处于 active 状态`
	              );
	            const result = await value.topicNavigation.navigate({
	              postNumber: anchor.viewport.postNumber,
	              source: "history",
	              alignment: "nearest",
	              highlight: restoreOptions?.highlight !== !1
	            });
	            if (result.status !== "revealed")
	              throw new Error(
	                `历史楼层 #${anchor.viewport.postNumber} 恢复失败:${result.status}`
	              );
	            const navigationRevision = value.topicNavigation.revision;
	            if (anchor.replyWindow ? await value.topicContextSurface.restoreDiscussionState(anchor.replyWindow) : value.topicContext.closeDiscussion(), !!value.topicNavigation.isCurrent(navigationRevision)) {
	              if (!await value.topicContextFeature.restoreQuoteHighlightState(anchor.quoteHighlight))
	                throw new Error(
	                  `历史引用高亮 #${anchor.quoteHighlight?.postNumber ?? 0} 恢复失败`
	                );
	              if (!(anchor.quoteHighlight === null && !value.topicNavigation.isCurrent(navigationRevision))) {
	                if (!value.dom.restoreViewportAnchor(anchor.viewport))
	                  throw new Error(
	                    `历史楼层 #${anchor.viewport.postNumber} 缺少 canonical 根布局`
	                  );
	                value.topicTimeline.syncVisiblePost(
	                  anchor.viewport.postNumber
	                ), this.#rememberHistoryTopic(
	                  value,
	                  anchor.viewport.postNumber
	                );
	              }
	            }
	          }
	        },
	        parentScope: this.scope,
	        onError: (cause) => reportTopicFeature(
	          this.shell.activeTopicId ?? 0,
	          "history",
	          cause
	        )
	      });
	      const historyViewOptions = options.history?.navigationView;
	      if (historyViewOptions) {
	        const root = this.shell.view.root, backEdge = root.querySelector(
	          ".ldp-reader-history-edge-back"
	        ), forwardEdge = root.querySelector(
	          ".ldp-reader-history-edge-forward"
	        ), backButton = root.querySelector(
	          ".ldp-reader-history-back"
	        ), forwardButton = root.querySelector(
	          ".ldp-reader-history-forward"
	        );
	        if (!backEdge || !forwardEdge || !backButton || !forwardButton)
	          throw new Error(
	            "历史导航 View 缺少 Shell 命名边缘或按钮"
	          );
	        this.historyNavigationView = new import_reader_history_navigation_view.ReaderHistoryNavigationView({
	          navigation: this.historyNavigation,
	          elements: {
	            root,
	            modal: this.shell.view.modal,
	            backEdge,
	            forwardEdge,
	            backButton,
	            forwardButton
	          },
	          preferences: historyViewOptions.preferences,
	          window: historyViewOptions.window === void 0 ? options.document.defaultView : historyViewOptions.window,
	          topicTitle: (topicId) => this.history.entry(topicId)?.title ?? null,
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "history",
	            cause
	          )
	        });
	      } else
	        this.historyNavigationView = null;
	      const historyPanelOptions = options.history?.panelView;
	      if (historyPanelOptions) {
	        const root = this.shell.view.root, toggle = root.querySelector(
	          ".ldp-history-toggle"
	        ), popover = root.querySelector(
	          ".ldp-history-popover"
	        ), sortToggle = root.querySelector(
	          ".ldp-history-sort-toggle"
	        ), multiButton = root.querySelector(
	          ".ldp-history-multi"
	        ), clearButton = root.querySelector(
	          ".ldp-history-clear"
	        ), defaultActions = root.querySelector(
	          ".ldp-history-default-actions"
	        ), bulkActions = root.querySelector(
	          ".ldp-history-bulk-actions"
	        ), selectScope = root.querySelector(
	          ".ldp-history-select-scope"
	        ), selectToggle = root.querySelector(
	          ".ldp-history-select-toggle"
	        ), deleteSelected = root.querySelector(
	          ".ldp-history-delete-selected"
	        ), deleteSelectedLabel = root.querySelector(
	          ".ldp-history-delete-selected-label"
	        ), multiDone = root.querySelector(
	          ".ldp-history-multi-done"
	        ), search = root.querySelector(
	          ".ldp-history-search"
	        ), searchClear = root.querySelector(
	          ".ldp-history-search-clear"
	        ), list = root.querySelector(
	          ".ldp-history-list"
	        ), pagePrevious = root.querySelector(
	          ".ldp-history-page-prev"
	        ), pageInfo = root.querySelector(
	          ".ldp-history-page-info"
	        ), pageNext = root.querySelector(
	          ".ldp-history-page-next"
	        );
	        if (!toggle || !popover || !sortToggle || !multiButton || !clearButton || !defaultActions || !bulkActions || !selectScope || !selectToggle || !deleteSelected || !deleteSelectedLabel || !multiDone || !search || !searchClear || !list || !pagePrevious || !pageInfo || !pageNext)
	          throw new Error(
	            "历史列表 View 缺少 Shell 命名面板控件"
	          );
	        this.historyPanelView = new import_reader_history_panel_view.ReaderHistoryPanelView({
	          ...historyPanelOptions,
	          ...(historyPanelOptions.searchForms ?? options.searchForms) === void 0 ? {} : {
	            searchForms: historyPanelOptions.searchForms ?? options.searchForms
	          },
	          confirmDelete: historyPanelOptions.confirmDelete ?? ((request) => this.feedback.confirm(request)),
	          notify: historyPanelOptions.notify ?? ((message) => this.feedback.show(message)),
	          document: options.document,
	          history: this.history,
	          elements: {
	            root,
	            toggle,
	            popover,
	            sortToggle,
	            multiButton,
	            clearButton,
	            defaultActions,
	            bulkActions,
	            selectScope,
	            selectToggle,
	            deleteSelected,
	            deleteSelectedLabel,
	            multiDone,
	            search,
	            searchClear,
	            list,
	            pagePrevious,
	            pageInfo,
	            pageNext
	          },
	          openEntry: async (entry) => {
	            await this.openTarget({
	              topicId: entry.topicId,
	              postNumber: entry.postNumber,
	              source: "restore"
	            });
	          },
	          parentScope: this.scope,
	          onError: (cause) => reportTopicFeature(
	            this.shell.activeTopicId ?? 0,
	            "history",
	            cause
	          )
	        });
	      } else
	        this.historyPanelView = null;
	      this.shell.changes.subscribe((state) => {
	        (state === "switching" || state === "closed") && this.#closeApplicationSurfaces();
	      }, this.scope);
	    } catch (error) {
	      throw this.scope.destroy(), error;
	    }
	  }
	  get performance() {
	    return this.#performance;
	  }
	  applyPerformance(snapshot) {
	    if (this.#destroyed || this.scope.destroyed) return;
	    this.#performance = snapshot, this.#applyPerformanceInfrastructure();
	    const active = this.shell.activeValue;
	    active?.services.session.applyPageSize(snapshot.pageSize), active?.topicFlow.refreshPerformance();
	  }
	  async open(topicId) {
	    return (await this.openTarget({
	      topicId,
	      source: "link"
	    })).topic;
	  }
	  async openTarget(request) {
	    if (this.#destroyed || this.scope.destroyed)
	      return Object.freeze({
	        topic: Object.freeze({
	          status: "failed",
	          topicId: (0, import_identifiers.discourseTopicId)(request.topicId),
	          cause: new Error("ReaderBrowserRuntime 已销毁")
	        }),
	        navigation: null
	      });
	    const normalizedTopicId = (0, import_identifiers.discourseTopicId)(request.topicId);
	    this.#openRecoveryController?.abort(
	      new DOMException("新的打开事务已开始", "AbortError")
	    );
	    const recoveryController = new AbortController();
	    this.#openRecoveryController = recoveryController;
	    const transactionIsCurrent = () => !this.#destroyed && !this.scope.destroyed && !recoveryController.signal.aborted && this.#openRecoveryController === recoveryController, superseded = () => Object.freeze({
	      topic: Object.freeze({
	        status: "superseded",
	        topicId: normalizedTopicId
	      }),
	      navigation: null
	    }), releaseLoading = this.#loadingProgress?.begin(
	      Number(normalizedTopicId),
	      request.postNumber
	    );
	    try {
	      this.recovery.clear();
	      const previousTopicId = this.shell.activeTopicId !== null && this.shell.activeTopicId !== normalizedTopicId ? this.shell.activeTopicId : null, previousAnchor = previousTopicId === null ? null : this.historyNavigation.captureCurrent();
	      let result;
	      for (let attempt = 0; ; attempt += 1) {
	        if (result = await this.shell.open(
	          normalizedTopicId,
	          this.topicFactory
	        ), !transactionIsCurrent()) return superseded();
	        if (result.status !== "failed" || !readerShellOpenRetryable(result.cause) || attempt >= 2)
	          break;
	        this.feedback.show(
	          `帖子加载暂时失败,${attempt + 1} 秒后自动重试一次`
	        );
	        try {
	          await this.#openRetryDelay(
	            (attempt + 1) * 1e3,
	            recoveryController.signal
	          );
	        } catch {
	          return superseded();
	        }
	      }
	      if (result.status === "opened" || result.status === "reused") {
	        this.#lastFailedRequest = null, this.#rememberHistoryTopic(result.value), this.historyNavigation.snapshot.activeTopicId !== result.topicId && this.historyNavigation.activate(result.topicId);
	        let navigation = null;
	        try {
	          if (request.postNumber !== void 0) {
	            for (let attempt = 0; ; attempt += 1) {
	              let navigationCause = null;
	              try {
	                navigation = await result.value.topicNavigation.navigate({
	                  postNumber: request.postNumber,
	                  source: request.source,
	                  ...request.alignment === void 0 ? {} : { alignment: request.alignment },
	                  ...request.focus === void 0 ? {} : { focus: request.focus },
	                  ...request.highlight === void 0 ? {} : { highlight: request.highlight },
	                  ...request.forceRefresh === void 0 ? {} : { forceRefresh: request.forceRefresh }
	                });
	              } catch (cause) {
	                navigationCause = cause;
	              }
	              if (!transactionIsCurrent()) return superseded();
	              if (!(navigationCause !== null ? readerShellOpenRetryable(navigationCause) : navigation?.status === "unresolved-tree") || attempt >= 2) {
	                if (navigationCause !== null)
	                  return this.#lastFailedRequest = Object.freeze({ ...request }), this.feedback.show(
	                    "帖子已打开,但目标楼层定位失败;当前 Topic 已保留,可稍后再次跳转"
	                  ), Object.freeze({ topic: result, navigation: null });
	                navigation?.status === "unresolved-tree" && this.feedback.show(
	                  "帖子已打开,但目标楼层的回复树暂未完成挂载;当前 Topic 已保留,可稍后再次跳转"
	                );
	                break;
	              }
	              this.feedback.show(
	                `目标楼层定位暂时失败,${attempt + 1} 秒后自动重试一次`
	              );
	              try {
	                await this.#openRetryDelay(
	                  (attempt + 1) * 1e3,
	                  recoveryController.signal
	                );
	              } catch {
	                return superseded();
	              }
	            }
	            if (!transactionIsCurrent()) return superseded();
	            if (navigation?.status !== "revealed")
	              return Object.freeze({ topic: result, navigation });
	            const targetPost = result.value.services.session.postByNumber(
	              request.postNumber
	            ), treeParent = result.value.replies.topology.parentOf(
	              request.postNumber
	            ), canonicalParent = treeParent === void 0 ? (0, import_identifiers.tryDiscoursePostNumber)(
	              targetPost?.reply_to_post_number
	            ) : treeParent;
	            if (canonicalParent != null && canonicalParent > 1 && !navigation.element?.closest(
	              ".ldp-descendant-replies-layer"
	            ) && (await result.value.topicContext.openDiscussion(
	              request.postNumber
	            ), !transactionIsCurrent()))
	              return superseded();
	            request.quoteHighlight && (navigation.element && result.value.topicContextFeature.applyRevealedQuoteHighlight(
	              request.quoteHighlight,
	              navigation.element
	            ) || this.feedback.show(
	              `目的地内容已修改;已定位到楼层 #${request.postNumber}`
	            ));
	          }
	          return Object.freeze({ topic: result, navigation });
	        } finally {
	          transactionIsCurrent() && (this.#rememberHistoryTopic(result.value), this.historyNavigation.snapshot.activeTopicId !== result.topicId && this.historyNavigation.activate(result.topicId));
	        }
	      }
	      if (result.status === "failed") {
	        if (!transactionIsCurrent()) return superseded();
	        this.#lastFailedRequest = Object.freeze({ ...request });
	        let previousRestored = previousTopicId !== null && this.shell.activeTopicId === previousTopicId;
	        if (previousTopicId !== null && !previousRestored && this.shell.activeTopicId === null) {
	          const previous = await this.shell.open(
	            previousTopicId,
	            this.topicFactory
	          );
	          if (!transactionIsCurrent()) return superseded();
	          if (previousRestored = previous.status === "opened" || previous.status === "reused", previousRestored && previousAnchor)
	            try {
	              await this.historyNavigation.restore(
	                previousTopicId,
	                previousAnchor
	              );
	            } catch {
	              this.feedback.show(
	                "原帖子已恢复,但之前的阅读位置未能完整还原"
	              );
	            }
	        }
	        previousRestored ? this.feedback.show("切换帖子失败,已保留当前帖子") : this.recovery.show(
	          readerShellRecoveryFailure(
	            result.cause,
	            this.#challengeHref
	          )
	        );
	      }
	      return Object.freeze({ topic: result, navigation: null });
	    } finally {
	      this.#openRecoveryController === recoveryController && (this.#openRecoveryController = null), releaseLoading?.();
	    }
	  }
	  async close() {
	    return this.#openRecoveryController?.abort(
	      new DOMException("Reader 已关闭", "AbortError")
	    ), this.#openRecoveryController = null, this.#lastFailedRequest = null, this.recovery.clear(), this.historyNavigation.captureCurrent(), this.#closeApplicationSurfaces(), this.shell.closeTopic();
	  }
	  async #restoreQuoteSource(source) {
	    if (this.#destroyed || this.scope.destroyed) return !1;
	    const topicId = (0, import_identifiers.discourseTopicId)(source.topicId), anchor = source.anchor === null ? null : (0, import_reader_history_model.normalizeReaderHistoryAnchorState)(source.anchor), active = this.shell.activeValue;
	    if (this.shell.activeTopicId === topicId && active) {
	      if (anchor) {
	        await this.historyNavigation.restore(topicId, anchor, {
	          highlight: !1
	        });
	        const restored2 = this.shell.activeValue;
	        return restored2 && this.#highlightQuoteSource(restored2, source), !0;
	      }
	      return (await active.topicNavigation.navigate({
	        postNumber: source.postNumber,
	        source: "quote",
	        alignment: "nearest",
	        highlight: !0
	      })).status === "revealed";
	    }
	    const opened = await this.openTarget({
	      topicId,
	      postNumber: anchor?.viewport.postNumber ?? source.postNumber,
	      source: "quote",
	      alignment: "nearest",
	      highlight: anchor === null
	    });
	    if (opened.topic.status !== "opened" && opened.topic.status !== "reused")
	      return !1;
	    if (!anchor) return opened.navigation?.status === "revealed";
	    await this.historyNavigation.restore(topicId, anchor, {
	      highlight: !1
	    });
	    const restored = this.shell.activeValue;
	    return restored && this.#highlightQuoteSource(restored, source), !0;
	  }
	  #highlightQuoteSource(value, source) {
	    source.anchor?.replyWindow && value.topicContextSurface.highlightDiscussionPost(source.postNumber) || value.dom.highlightPost(source.postNumber);
	  }
	  readerSurfaceOpen() {
	    if (this.shell.view.root.hidden) return !1;
	    if (this.actionSurfaces.active || this.userCardView.isOpen || this.userMediaViewer?.activeRoot?.isConnected) return !0;
	    const document = this.shell.view.root.ownerDocument;
	    return (0, import_reader_native_composer_window.visibleDiscourseNativeFloatingSurface)(document) ? !0 : (0, import_reader_escape_surface.readerFrontmostEscapeSurface)(document) !== null;
	  }
	  readerExitBlocked() {
	    return this.composer.isOpen() || this.readerSurfaceOpen();
	  }
	  readerShortcutContextBlocked() {
	    if (this.composer.isOpen()) return !0;
	    const document = this.shell.view.root.ownerDocument;
	    return !!(0, import_reader_escape_surface.readerSurfaceQuery)(document, [
	      ".ldp-settings-popover:not([hidden])",
	      ".ldp-reader-action-layer:not([hidden])",
	      ".ldp-lightbox",
	      ".ldp-code-preview-layer"
	    ].join(","));
	  }
	  handleCloseReaderShortcut(event) {
	    if ((0, import_reader_shortcut_controller.readerShortcutBindingFromEvent)(event) !== "Escape") {
	      if (this.readerShortcutContextBlocked())
	        return event.type !== "keydown";
	      if (this.readerExitBlocked())
	        return this.#dispatchSurfaceCloseEscape(), !0;
	    }
	    return this.closeExpandedReply() || this.close();
	  }
	  closeExpandedReply() {
	    const postNumber = this.shell.activeValue?.topicContextFeature.collapseExpandedDefaultPost() ?? null;
	    return postNumber ? (this.feedback.show(`已收起楼层 #${postNumber}`), !0) : !1;
	  }
	  #closeApplicationSurfaces() {
	    this.userMediaViewer?.close(), this.userCardView.close(), this.actionSurfaces.closeActive();
	  }
	  #dispatchSurfaceCloseEscape() {
	    const document = this.shell.view.root.ownerDocument, window = document.defaultView;
	    if (!window) return;
	    let event;
	    typeof window.KeyboardEvent == "function" ? event = new window.KeyboardEvent("keydown", {
	      key: "Escape",
	      code: "Escape",
	      bubbles: !0,
	      cancelable: !0
	    }) : (event = new window.Event("keydown", {
	      bubbles: !0,
	      cancelable: !0
	    }), Object.defineProperties(event, {
	      key: { value: "Escape", configurable: !0 },
	      code: { value: "Escape", configurable: !0 }
	    })), readerSurfaceOnlyCloseEvents.add(event), document.dispatchEvent(event);
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.#openRecoveryController?.abort(
	      new DOMException("Reader runtime 已销毁", "AbortError")
	    ), this.#openRecoveryController = null, this.#lastFailedRequest = null, this.historyNavigation.captureCurrent(), this.scope.destroy());
	  }
	  #openManualCloudflareChallenge(href) {
	    if (this.#manualChallengePromise || this.scope.destroyed) return;
	    const promise = this.permit.resolveCloudflareChallenge({
	      href,
	      signal: this.#manualChallengeController.signal,
	      focus: !0
	    }).then(async (passed) => (this.scope.destroyed || (passed && (await this.data.client.resetRateLimits(), await this.rateLimitNotice.refresh()), this.feedback.show(
	      passed ? "Cloudflare 验证已通过,请求已回到原有有序管线" : "验证浮窗未完成;请允许弹出窗口后重试"
	    )), passed)).catch((error) => {
	      throw this.#manualChallengeController.signal.aborted || this.feedback.show("Cloudflare 验证未完成,请稍后重试"), error;
	    }).finally(() => {
	      this.#manualChallengePromise === promise && (this.#manualChallengePromise = null);
	    });
	    this.#manualChallengePromise = promise, promise.catch(() => {
	    });
	  }
	  #applyPerformanceInfrastructure() {
	    const snapshot = this.#performance;
	    this.permit.applyRuntimePolicy({
	      shortBudget: snapshot.requestShortBudget,
	      longBudget: snapshot.requestLongBudget,
	      minIntervalMs: snapshot.requestMinIntervalMs,
	      maxConcurrent: snapshot.requestMaxConcurrent
	    }), this.data.applyRequestRuntimePolicy({
	      maxConcurrent: snapshot.requestMaxConcurrent
	    });
	  }
	  #captureAndRememberHistoryAnchor() {
	    const anchor = this.#captureHistoryAnchor(), value = this.shell.activeValue;
	    return anchor && value && this.#rememberHistoryTopic(
	      value,
	      anchor.viewport.postNumber
	    ), anchor;
	  }
	  #captureHistoryAnchor() {
	    const value = this.shell.activeValue;
	    if (!value) return null;
	    const viewport = value.dom.captureViewportAnchor();
	    return viewport ? (0, import_reader_history_model.normalizeReaderHistoryAnchorState)({
	      viewport,
	      replyWindow: value.topicContextSurface.captureDiscussionState(),
	      quoteHighlight: value.topicContextFeature.captureQuoteHighlightState()
	    }) : null;
	  }
	  #rememberHistoryTopic(value, postNumber = value.topicTimeline.snapshot.currentPostNumber) {
	    const topic = value.services.session.topic ?? value.topic, posts = value.services.session.cachedPosts(), firstPost = posts.find(
	      (post) => (0, import_identifiers.tryDiscoursePostNumber)(post.post_number) === 1
	    ), readPostNumbers = posts.filter(
	      (post) => post.read === !0
	    ).map((post) => post.post_number);
	    readPostNumbers.push(
	      ...value.services.read.snapshot().confirmed
	    );
	    try {
	      this.history.remember({
	        topicId: value.services.session.topicId,
	        title: topic.title,
	        postsCount: Math.max(
	          Number(topic.posts_count) || 0,
	          Number(topic.highest_post_number) || 0
	        ),
	        avatarTemplate: topic.details?.created_by?.avatar_template ?? firstPost?.avatar_template,
	        ownerUsername: topic.details?.created_by?.username ?? firstPost?.username,
	        postNumber,
	        readPostNumbers
	      });
	    } catch {
	    }
	  }
	  #historyOpenResult(result) {
	    return result.status === "failed" ? Object.freeze({
	      status: "failed",
	      topicId: result.topicId,
	      cause: result.cause
	    }) : Object.freeze({
	      status: result.status,
	      topicId: result.topicId
	    });
	  }
	}
	function createReaderBrowserRuntimeStage(options) {
	  const appearanceByShell = /* @__PURE__ */ new WeakMap(), themeByShell = /* @__PURE__ */ new WeakMap(), fontByShell = /* @__PURE__ */ new WeakMap(), appearancePreferences = options.appearance || void 0, themeOptions = options.theme || void 0, fontPreferences = options.font || void 0, motionPreferences = options.motion || void 0, boostCopyPreferences = options.boostCopy || void 0;
	  if (motionPreferences && options.selectNavigationPreferences)
	    throw new Error(
	      "动画设置与独立导航偏好选择器只能配置一个"
	    );
	  const selectNavigationPreferences = motionPreferences ? (preferences) => (0, import_reader_motion_settings_form.readerMotionNavigationPreferences)(
	    motionPreferences.read(preferences)
	  ) : options.selectNavigationPreferences, shellOptions = appearancePreferences || themeOptions || fontPreferences ? Object.freeze({
	    ...options.shell,
	    createWorkspaceOptions: (shell, context) => {
	      const workspaceOptions = options.shell.createWorkspaceOptions(shell, context), theme = themeOptions ? new import_reader_theme_controller.ReaderThemeController({
	        root: shell.view.root,
	        preferences: themeOptions.preferences,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        system: themeOptions.system,
	        parentScope: shell.scope
	      }) : null;
	      theme && themeByShell.set(shell, theme);
	      const appearance = appearancePreferences ? new import_reader_appearance_style_controller.ReaderAppearanceStyleController({
	        root: shell.view.root,
	        preferences: appearancePreferences,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        environment: {
	          read: () => Object.freeze({
	            ...workspaceOptions.readAppearance(),
	            ...theme ? { theme: theme.snapshot.resolved } : {}
	          }),
	          subscribe: (listener, scope) => {
	            const publish = () => listener(
	              Object.freeze({
	                ...workspaceOptions.readAppearance(),
	                ...theme ? {
	                  theme: theme.snapshot.resolved
	                } : {}
	              })
	            );
	            return workspaceOptions.appearanceChanges?.subscribe(
	              publish,
	              scope
	            ), theme?.changes.subscribe(publish, scope), () => {
	            };
	          }
	        },
	        parentScope: shell.scope
	      }) : null;
	      appearance && appearanceByShell.set(shell, appearance);
	      const font = fontPreferences ? new import_reader_font_style_controller.ReaderFontStyleController({
	        root: shell.view.root,
	        pageRoot: workspaceOptions.elements.pageRoot,
	        resizeTarget: shell.view.modal,
	        preferences: fontPreferences,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        readReaderWidth: () => shell.view.modal.clientWidth || 1080,
	        readSiteFontFamily: () => {
	          const target = options.runtime.document.body ?? workspaceOptions.elements.pageRoot;
	          return options.runtime.document.defaultView?.getComputedStyle(target).fontFamily || "inherit";
	        },
	        readExternalFontRendering: () => {
	          const pageRoot = workspaceOptions.elements.pageRoot;
	          return pageRoot.hasAttribute("fr-init-once") ? (options.runtime.document.defaultView?.getComputedStyle(pageRoot).getPropertyValue("--fr-render-text").trim() ?? "") !== "" : !1;
	        },
	        userAgent: options.runtime.document.defaultView?.navigator.userAgent ?? "",
	        platform: options.runtime.document.defaultView?.navigator.platform ?? "",
	        ...workspaceOptions.createMutationObserver ? {
	          createMutationObserver: workspaceOptions.createMutationObserver
	        } : {},
	        ...workspaceOptions.createResizeObserver ? {
	          createResizeObserver: workspaceOptions.createResizeObserver
	        } : {},
	        parentScope: shell.scope
	      }) : null;
	      return font && fontByShell.set(shell, font), Object.freeze({
	        ...workspaceOptions,
	        ...appearance ? {
	          readAppearance: () => appearance.snapshot.embedded,
	          appearanceChanges: appearance.embeddedChanges
	        } : {}
	      });
	    }
	  }) : options.shell;
	  return (0, import_reader_workspace_coordinator.createReaderShellWorkspaceStage)({
	    ...shellOptions,
	    onReady(shell, workspace, context) {
	      const theme = themeByShell.get(shell) ?? null, appearance = appearanceByShell.get(shell) ?? null, font = fontByShell.get(shell) ?? null, rawNavigation = options.runtime.navigation;
	      if (boostCopyPreferences && options.runtime.boostCopy)
	        throw new Error(
	          "Boost 复制偏好投影与自定义 runtime 读取器只能配置一个"
	        );
	      const boostCopy = boostCopyPreferences ? Object.freeze({
	        readSettings: () => boostCopyPreferences.read(
	          context.readPreferences()
	        )
	      }) : options.runtime.boostCopy, topicActionRailPreferences = options.topicActionRail;
	      if (topicActionRailPreferences && options.runtime.topicActionRail !== void 0)
	        throw new Error(
	          "主帖操作列偏好投影与自定义 runtime 端口只能配置一个"
	        );
	      if (topicActionRailPreferences && !context.updatePreferences)
	        throw new Error(
	          "主帖操作列拖动与收纳需要 application 唯一偏好写端口"
	        );
	      const topicActionRail = topicActionRailPreferences === !1 ? !1 : topicActionRailPreferences ? Object.freeze({
	        read: () => topicActionRailPreferences.read(
	          context.readPreferences()
	        ),
	        subscribe: (listener, scope) => context.preferenceChanges.subscribe(
	          (preferences) => listener(
	            topicActionRailPreferences.read(preferences)
	          ),
	          scope
	        ),
	        update: (patch) => {
	          const current = topicActionRailPreferences.read(
	            context.readPreferences()
	          );
	          context.updatePreferences(
	            topicActionRailPreferences.createPatch(
	              Object.freeze({
	                ...current,
	                ...patch
	              })
	            )
	          );
	        }
	      }) : options.runtime.topicActionRail;
	      if (options.selectPerformancePreferences && options.runtime.performance)
	        throw new Error(
	          "性能偏好投影与自定义 runtime performance 只能配置一个"
	        );
	      const performancePolicy = options.selectPerformancePreferences ? new import_reader_performance_policy.ReaderPerformancePolicy({
	        preferences: options.selectPerformancePreferences(
	          context.readPreferences()
	        ),
	        shortBudgetCeiling: options.performanceBudgetCeilings?.short ?? options.runtime.permit.shortBudget,
	        longBudgetCeiling: options.performanceBudgetCeilings?.long ?? options.runtime.permit.longBudget,
	        capabilities: (0, import_reader_performance_policy.readBrowserPerformanceCapabilities)(
	          options.runtime.document.defaultView?.navigator
	        )
	      }) : null;
	      if (selectNavigationPreferences && (rawNavigation?.readOverscan || rawNavigation?.readMaxMountedPostCount || rawNavigation?.readLifetimeMs))
	        throw new Error(
	          "导航偏好投影与自定义虚拟窗口/高亮读取器只能配置一个"
	        );
	      const navigationPreferences = selectNavigationPreferences ? new import_reader_topic_navigation_preferences.ReaderTopicNavigationPreferenceProjection({
	        root: shell.view.root,
	        preferences: selectNavigationPreferences(
	          context.readPreferences()
	        ),
	        ...performancePolicy === null ? {} : {
	          readPerformance: () => performancePolicy.value
	        },
	        parentScope: shell.scope
	      }) : null, navigation = navigationPreferences ? Object.freeze({
	        ...rawNavigation,
	        readOverscan: () => navigationPreferences.readOverscan(),
	        readMaxMountedPostCount: () => navigationPreferences.readMaxMountedPostCount(),
	        readLifetimeMs: () => navigationPreferences.readHighlightLifetimeMs()
	      }) : rawNavigation, rawHistory = options.runtime.history;
	      if (options.selectHistoryNavigationPreferences && rawHistory?.navigationView)
	        throw new Error(
	          "历史导航偏好投影与自定义 navigationView 只能配置一个"
	        );
	      const navigationHistory = options.selectHistoryNavigationPreferences ? Object.freeze({
	        ...rawHistory,
	        navigationView: Object.freeze({
	          preferences: options.selectHistoryNavigationPreferences(
	            context.readPreferences()
	          )
	        })
	      }) : rawHistory;
	      if (options.selectHistoryPanelPreferences && !navigationHistory?.panelView)
	        throw new Error(
	          "历史列表偏好投影需要先配置唯一 panelView 端口"
	        );
	      const history = options.selectHistoryPanelPreferences ? Object.freeze({
	        ...navigationHistory,
	        readSortMode: () => options.selectHistoryPanelPreferences(
	          context.readPreferences()
	        ).sortMode,
	        panelView: Object.freeze({
	          ...navigationHistory.panelView,
	          preferences: options.selectHistoryPanelPreferences(
	            context.readPreferences()
	          )
	        })
	      }) : navigationHistory, rawBookmarks = options.runtime.bookmarks;
	      if (options.selectBookmarkPreferences && rawBookmarks === !1)
	        throw new Error(
	          "收藏偏好投影与禁用收藏中心不能同时配置"
	        );
	      const bookmarks = options.selectBookmarkPreferences ? Object.freeze({
	        ...rawBookmarks ?? {},
	        tabOrder: options.selectBookmarkPreferences(
	          context.readPreferences()
	        ).tabOrder
	      }) : rawBookmarks, rawTimelineView = options.runtime.timelineView;
	      if (options.selectTimelineViewPreferences && (!rawTimelineView || rawTimelineView.readPreferences))
	        throw new Error(
	          "时间轴偏好投影需要唯一 timelineView,且不能同时注入自定义读取器"
	        );
	      let timelinePreferences = options.selectTimelineViewPreferences?.(
	        context.readPreferences()
	      ) ?? null;
	      const timelineView = options.selectTimelineViewPreferences && rawTimelineView ? Object.freeze({
	        ...rawTimelineView,
	        preferences: timelinePreferences,
	        readPreferences: () => timelinePreferences
	      }) : rawTimelineView;
	      if (options.selectTranslationMode === void 0 != (options.persistTranslationMode === void 0))
	        throw new Error(
	          "翻译偏好投影必须同时提供 selectTranslationMode 与 persistTranslationMode"
	        );
	      const rawTranslationView = options.runtime.translationView;
	      if (options.selectTranslationMode && rawTranslationView === !1)
	        throw new Error(
	          "翻译偏好投影与禁用翻译 View 不能同时配置"
	        );
	      if (options.selectTranslationMode && rawTranslationView && (rawTranslationView.initialMode !== void 0 || rawTranslationView.persistMode !== void 0))
	        throw new Error(
	          "翻译偏好投影与自定义 initialMode/persistMode 只能配置一个"
	        );
	      const translationView = options.selectTranslationMode ? Object.freeze({
	        ...rawTranslationView || {},
	        initialMode: options.selectTranslationMode(
	          context.readPreferences()
	        ),
	        persistMode: options.persistTranslationMode
	      }) : rawTranslationView, imagePreferences = options.image || null, rawLightbox = options.runtime.lightbox;
	      if (imagePreferences && rawLightbox?.preferences)
	        throw new Error(
	          "图片偏好投影与自定义 Lightbox 偏好端口只能配置一个"
	        );
	      if (imagePreferences && rawLightbox && !context.updatePreferences)
	        throw new Error(
	          "Lightbox 几何持久化需要 application 唯一偏好写端口"
	        );
	      const lightbox = imagePreferences && rawLightbox ? Object.freeze({
	        ...rawLightbox,
	        preferences: Object.freeze({
	          read: () => {
	            const current = imagePreferences.read(
	              context.readPreferences()
	            );
	            return Object.freeze({
	              originalByDefault: current.lightboxOriginalByDefault,
	              commentsExpanded: current.lightboxCommentsExpandedByDefault,
	              descriptionExpanded: current.lightboxDescriptionExpanded,
	              lightboxDescriptionHeight: current.lightboxDescriptionHeight,
	              lightboxCommentsWidthPercent: current.lightboxCommentsWidthPercent
	            });
	          },
	          update: (patch) => {
	            const current = imagePreferences.read(
	              context.readPreferences()
	            ), next = (0, import_reader_image_preferences.normalizeReaderImagePreferences)({
	              ...current,
	              ...patch.originalByDefault === void 0 ? {} : {
	                lightboxOriginalByDefault: patch.originalByDefault
	              },
	              ...patch.commentsExpanded === void 0 ? {} : {
	                lightboxCommentsExpandedByDefault: patch.commentsExpanded
	              },
	              ...patch.descriptionExpanded === void 0 ? {} : {
	                lightboxDescriptionExpanded: patch.descriptionExpanded
	              },
	              ...patch.lightboxDescriptionHeight === void 0 ? {} : {
	                lightboxDescriptionHeight: patch.lightboxDescriptionHeight
	              },
	              ...patch.lightboxCommentsWidthPercent === void 0 ? {} : {
	                lightboxCommentsWidthPercent: patch.lightboxCommentsWidthPercent
	              }
	            });
	            context.updatePreferences(
	              imagePreferences.createPatch(next)
	            );
	          }
	        })
	      }) : rawLightbox, loadingAnimation = motionPreferences ? new import_reader_loading_animation_view.ReaderLoadingAnimationView({
	        document: options.runtime.document,
	        host: shell.view.body,
	        shell,
	        preference: motionPreferences.read(
	          context.readPreferences()
	        ).loadingAnimation,
	        siteName: motionPreferences.siteName,
	        parentScope: shell.scope
	      }) : null;
	      let downloadCurrentTopic = null, openTopicDownloadManager = null;
	      const runtime = new ReaderBrowserRuntime({
	        ...options.runtime,
	        ...performancePolicy === null ? {} : { performance: performancePolicy.value },
	        ...navigation === void 0 ? {} : { navigation },
	        ...history === void 0 ? {} : { history },
	        ...bookmarks === void 0 ? {} : { bookmarks },
	        ...timelineView === void 0 ? {} : { timelineView },
	        ...translationView === void 0 ? {} : { translationView },
	        ...lightbox === void 0 ? {} : { lightbox },
	        ...boostCopy === void 0 ? {} : { boostCopy },
	        ...topicActionRail === void 0 ? {} : { topicActionRail },
	        ...options.openQueue && options.runtime.resources ? {
	          downloadCurrentTopic: () => downloadCurrentTopic?.(),
	          openTopicDownloadManager: () => openTopicDownloadManager?.()
	        } : {},
	        shell,
	        workspace,
	        ...loadingAnimation ? { loadingProgress: loadingAnimation } : {},
	        parentScope: shell.scope
	      }), reportCacheError = (cause) => {
	        try {
	          options.runtime.onTopicFeatureError?.(Object.freeze({
	            topicId: runtime.shell.activeTopicId ?? 0,
	            feature: "cache",
	            cause
	          }));
	        } catch {
	        }
	      };
	      new import_browser_request_observation.DiscourseNativeAjaxObservationAdapter({
	        observer: runtime.data.requests,
	        jqueryModule: (0, import_native_host_api.discourseNativeJqueryModule)(options.runtime.host),
	        document: options.runtime.document,
	        hostRequestBudget: runtime.permit
	      }).install(runtime.scope);
	      const refreshTopicButton = shell.view.root.querySelector(
	        ".ldp-reader-refresh"
	      ), closeReaderButton = shell.view.root.querySelector(
	        ".ldp-close"
	      ), layoutToggleButton = shell.view.root.querySelector(
	        ".ldp-layout-toggle"
	      );
	      if (!refreshTopicButton || !closeReaderButton || !layoutToggleButton)
	        throw runtime.destroy(), new Error("Reader Shell 缺少布局、刷新或关闭入口");
	      let currentTopicRefresh = null;
	      const syncHeaderTopicActions = () => {
	        refreshTopicButton.disabled = currentTopicRefresh !== null || runtime.shell.activeValue === null;
	        const presentation = workspace.workspace.snapshot.presentation, fullPage = presentation.fullPage;
	        layoutToggleButton.hidden = presentation.embedded, layoutToggleButton.setAttribute(
	          "aria-pressed",
	          String(fullPage)
	        ), layoutToggleButton.setAttribute(
	          "aria-label",
	          fullPage ? "切换为浮窗阅读器" : "切换为全屏阅读器"
	        ), layoutToggleButton.title = fullPage ? "切换为浮窗阅读器" : "切换为全屏阅读器", layoutToggleButton.replaceChildren((0, import_reader_icon.renderReaderIcon)(
	          options.runtime.document,
	          fullPage ? "minimize-2" : "maximize-2",
	          options.runtime.renderIcon
	        ));
	      }, refreshCurrentTopic = (refreshOptions = {}) => {
	        if (currentTopicRefresh) return currentTopicRefresh;
	        const topicId = runtime.shell.activeTopicId, active = runtime.shell.activeValue;
	        if (!topicId || !active)
	          return Promise.reject(new Error("当前没有可重建的主题"));
	        const transaction = (async () => {
	          const anchor = runtime.historyNavigation.captureCurrent(), onlyOpEnabled = active.topicOnlyOp.snapshot.enabled, sources = active.topicImages.snapshot().items.flatMap((item) => [
	            item.previewSrc,
	            item.originalSrc
	          ]);
	          if (runtime.shell.activeTopicId !== topicId || runtime.shell.activeValue !== active)
	            throw new Error("当前主题已切换,已取消缓存重建");
	          if (!await runtime.shell.closeTopic())
	            throw new Error("当前主题关闭事务失败");
	          const cleanupFailures = [];
	          try {
	            const report = await runtime.data.responses.invalidateWithReport({ tags: [`topic:${topicId}`] });
	            cleanupFailures.push(...report.failures.map(
	              (failure) => failure.cause
	            ));
	          } catch (cause) {
	            cleanupFailures.push(cause);
	          }
	          if (refreshOptions.clearImages !== !1 && runtime.imageResources && sources.length)
	            try {
	              const report = await runtime.imageResources.invalidateSources(sources);
	              cleanupFailures.push(...report.failures.map(
	                (failure) => failure.cause
	              ));
	            } catch (cause) {
	              cleanupFailures.push(cause);
	            }
	          const reopened = await runtime.openTarget({
	            topicId,
	            ...anchor ? { postNumber: anchor.viewport.postNumber } : {},
	            source: "restore"
	          });
	          if (reopened.topic.status === "failed") {
	            const refreshCause = reopened.topic.cause, recovered = await (async () => {
	              try {
	                return await active.services.snapshots.persistCurrentSnapshot(), await runtime.openTarget({
	                  topicId,
	                  ...anchor ? { postNumber: anchor.viewport.postNumber } : {},
	                  source: "restore"
	                });
	              } catch (recoveryCause) {
	                throw new AggregateError(
	                  [refreshCause, recoveryCause],
	                  "刷新当前帖子失败,且刷新前内容未能恢复"
	                );
	              }
	            })();
	            if (recovered.topic.status === "failed")
	              throw new AggregateError(
	                [refreshCause, recovered.topic.cause],
	                "刷新当前帖子失败,且刷新前内容未能恢复"
	              );
	            if (recovered.topic.status === "superseded")
	              throw new Error("刷新前内容恢复已被新的打开事务取代");
	            onlyOpEnabled && recovered.topic.value.topicOnlyOp.setEnabled(!0);
	            let recoveryMessage = "刷新当前帖子失败,已恢复刷新前内容;可稍后再试。";
	            if (anchor)
	              try {
	                await runtime.historyNavigation.restore(topicId, anchor);
	              } catch {
	                recoveryMessage = "刷新当前帖子失败,已恢复刷新前内容,但之前的阅读位置未能完整还原。";
	              }
	            return Object.freeze({
	              complete: !1,
	              restored: !0,
	              message: recoveryMessage
	            });
	          }
	          if (reopened.topic.status === "superseded")
	            throw new Error("当前主题重建已被新的打开事务取代");
	          return onlyOpEnabled && reopened.topic.value.topicOnlyOp.setEnabled(!0), anchor && await runtime.historyNavigation.restore(topicId, anchor), Object.freeze({
	            complete: cleanupFailures.length === 0,
	            ...cleanupFailures.length ? {
	              message: "当前主题已从原站重新获取,但部分旧缓存未能清理;可再次重试。"
	            } : {}
	          });
	        })();
	        return currentTopicRefresh = transaction, refreshTopicButton.classList.add("is-refreshing"), refreshTopicButton.setAttribute("aria-busy", "true"), syncHeaderTopicActions(), transaction.finally(() => {
	          currentTopicRefresh === transaction && (currentTopicRefresh = null, refreshTopicButton.classList.remove("is-refreshing"), refreshTopicButton.removeAttribute("aria-busy"), syncHeaderTopicActions());
	        }).catch(() => {
	        }), transaction;
	      };
	      runtime.scope.listen(closeReaderButton, "click", () => {
	        runtime.close();
	      }), runtime.scope.listen(refreshTopicButton, "click", (event) => {
	        event.preventDefault(), event.stopPropagation(), !refreshTopicButton.disabled && refreshCurrentTopic().then((result) => {
	          !result.complete && result.message && runtime.feedback.show(result.message);
	        }).catch((cause) => {
	          runtime.feedback.show(
	            cause instanceof Error ? cause.message : "刷新当前帖子失败"
	          );
	        });
	      }), runtime.scope.listen(layoutToggleButton, "click", () => {
	        const mode = workspace.workspace.snapshot.presentation.mode;
	        workspace.setMode(
	          mode === "fullpage" ? "floating" : "fullpage"
	        );
	      }), runtime.shell.changes.subscribe(
	        syncHeaderTopicActions,
	        runtime.scope
	      ), workspace.workspace.changes.subscribe(
	        syncHeaderTopicActions,
	        runtime.scope
	      ), syncHeaderTopicActions();
	      const imageProjection = imagePreferences ? new import_reader_image_preferences.ReaderImagePreferencesProjection({
	        contentRoot: shell.view.root,
	        lightboxRoot: options.runtime.document.documentElement,
	        parentScope: runtime.scope
	      }) : null;
	      if (imageProjection && imagePreferences) {
	        const readImageMode = () => (0, import_reader_image_preferences.readerImagePresentationMode)(
	          workspace.workspace.snapshot
	        ), applyImagePreferences = () => {
	          imageProjection.applyMode(
	            imagePreferences.read(
	              context.readPreferences()
	            ),
	            readImageMode()
	          );
	        };
	        applyImagePreferences(), context.preferenceChanges.subscribe((preferences) => {
	          imageProjection.applyMode(
	            imagePreferences.read(preferences),
	            readImageMode()
	          );
	        }, runtime.scope), workspace.workspace.changes.subscribe(
	          applyImagePreferences,
	          runtime.scope
	        );
	      }
	      appearance?.changes.subscribe(() => {
	        runtime.shell.activeValue?.dom.notifyScroll();
	      }, runtime.scope);
	      const readLayoutMode = () => workspace.workspace.snapshot.presentation.mode === "fullpage" ? "fullpage" : "standard", layout = options.layout ? new import_reader_layout_style_controller.ReaderLayoutStyleController({
	        root: shell.view.root,
	        preferences: options.layout,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        mode: {
	          read: readLayoutMode,
	          subscribe: (listener, scope) => workspace.workspace.changes.subscribe(
	            () => listener(readLayoutMode()),
	            scope
	          )
	        },
	        parentScope: runtime.scope
	      }) : null;
	      if (options.settings !== !1 && options.settings !== void 0 && !context.updatePreferences)
	        throw runtime.destroy(), new Error(
	          "设置 controller 需要 application 唯一偏好写端口"
	        );
	      const settings = options.settings === !1 || !context.updatePreferences ? null : new import_reader_settings_controller.ReaderSettingsController({
	        preferences: {
	          read: context.readPreferences,
	          update: context.updatePreferences
	        },
	        ...options.settings?.initialPanelId === void 0 ? {} : {
	          initialPanelId: options.settings.initialPanelId
	        }
	      });
	      settings && runtime.scope.add(() => settings.destroy());
	      const settingsViewOptions = options.settings ? options.settings.view : void 0, settingsView = settings && settingsViewOptions !== !1 ? new import_reader_settings_view.ReaderSettingsView({
	        document: options.runtime.document,
	        controller: settings,
	        feedback: runtime.feedback,
	        toggleHost: shell.view.root.querySelector(
	          ".ldp-head-btns"
	        ) ?? shell.view.root,
	        surfaceHost: shell.view.surfaceHost,
	        ...options.runtime.renderIcon ? { renderIcon: options.runtime.renderIcon } : {},
	        ...settingsViewOptions?.brandName === void 0 ? {} : { brandName: settingsViewOptions.brandName },
	        ...(settingsViewOptions?.logoUrl ?? shell.view.root.querySelector(
	          "[data-ldp-site-logo]"
	        )?.src) === void 0 ? {} : {
	          logoUrl: settingsViewOptions?.logoUrl ?? shell.view.root.querySelector(
	            "[data-ldp-site-logo]"
	          ).src
	        },
	        parentScope: runtime.scope
	      }) : null;
	      settingsView && theme && context.updatePreferences && new import_reader_theme_settings_control.ReaderThemeSettingsControl({
	        document: options.runtime.document,
	        host: settingsView.themeHost(),
	        theme,
	        persist: context.updatePreferences,
	        ...themeOptions?.hostTheme ? { hostTheme: themeOptions.hostTheme } : {},
	        feedback: runtime.feedback,
	        ...options.runtime.renderIcon ? { renderIcon: options.runtime.renderIcon } : {},
	        parentScope: runtime.scope
	      }), settingsView && new import_reader_window_settings_form.ReaderWindowSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("window"),
	        workspace,
	        parentScope: runtime.scope
	      });
	      const sitesFormOptions = options.settings ? options.settings.sitesForm : void 0;
	      if (sitesFormOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "适用站点 form 需要启用唯一 Settings View"
	        );
	      if (settingsView && sitesFormOptions) {
	        const coordinatedProbe = sitesFormOptions.probe ? new import_browser_discourse_site_probe.CoordinatedDiscourseSiteProbe({
	          gateway: runtime.data.gateway,
	          transport: sitesFormOptions.probe
	        }) : null;
	        new import_reader_custom_site_settings_form.ReaderCustomSiteSettingsForm({
	          document: options.runtime.document,
	          host: settingsView.panelHost("sites"),
	          repository: sitesFormOptions.repository,
	          probe: coordinatedProbe,
	          parentScope: runtime.scope
	        });
	      }
	      const aboutContentOptions = options.settings ? options.settings.aboutContent : void 0;
	      if (aboutContentOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "关于内容需要启用唯一 Settings View"
	        );
	      if (settingsView && aboutContentOptions) {
	        const siteLogo = shell.view.root.querySelector(
	          "[data-ldp-site-logo]"
	        )?.src;
	        new import_reader_about_settings_content.ReaderAboutSettingsContent({
	          document: options.runtime.document,
	          host: settingsView.panelHost("about"),
	          version: aboutContentOptions.version,
	          brandName: (settingsViewOptions ? settingsViewOptions.brandName : void 0) ?? "Awesome LinuxDo Reader",
	          ...siteLogo ? { logoUrl: siteLogo } : {},
	          ...aboutContentOptions.manualUrl ? { manualUrl: aboutContentOptions.manualUrl } : {},
	          parentScope: runtime.scope
	        });
	      }
	      const imageFormOptions = options.settings ? options.settings.imageForm : void 0;
	      if (imageFormOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "图片设置 form 需要启用唯一 Settings View"
	        );
	      if (settingsView && imageFormOptions && new import_reader_image_settings_form.ReaderImageSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("image"),
	        controller: settings,
	        preferences: imageFormOptions,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        parentScope: runtime.scope
	      }), settingsView) {
	        let settingsUserView = null, settingsUsername = "";
	        const mountSettingsUser = () => {
	          const currentUsername = (0, import_native_host_api.discourseNativeCurrentUsername)(
	            options.runtime.host
	          ).toLocaleLowerCase();
	          settingsUserView && currentUsername === settingsUsername || (settingsUserView?.destroy(), settingsUsername = currentUsername, settingsUserView = new import_reader_settings_user_view.ReaderSettingsUserView({
	            document: options.runtime.document,
	            host: settingsView.panelHost("user"),
	            session: runtime.users,
	            username: currentUsername,
	            avatarSource: (template, size) => runtime.userNative.avatarSource(template, size),
	            connectEnabled: !!options.runtime.connect && options.runtime.document.location?.hostname === "linux.do",
	            history: runtime.connectHistory,
	            creditEnabled: options.runtime.document.location?.hostname === "linux.do",
	            ...options.runtime.renderIcon ? { renderIcon: options.runtime.renderIcon } : {},
	            parentScope: runtime.scope,
	            onError: (cause) => {
	              try {
	                options.runtime.onTopicFeatureError?.(Object.freeze({
	                  topicId: runtime.shell.activeTopicId ?? 0,
	                  feature: "user",
	                  cause
	                }));
	              } catch {
	              }
	            }
	          }));
	        };
	        mountSettingsUser(), settingsView.changes.subscribe((snapshot) => {
	          snapshot.open && snapshot.activePanelId === "user" && mountSettingsUser();
	        }, runtime.scope);
	      }
	      const performanceFormOptions = options.settings ? options.settings.performanceForm : void 0;
	      if (performanceFormOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "性能设置 form 需要启用唯一 Settings View"
	        );
	      settingsView && performanceFormOptions && new import_reader_performance_settings_form.ReaderPerformanceSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("performance"),
	        controller: settings,
	        preferences: performanceFormOptions,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        parentScope: runtime.scope
	      });
	      const readingFormOptions = options.settings ? options.settings.readingForm : void 0;
	      if (readingFormOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "阅读与导航设置 form 需要启用唯一 Settings View"
	        );
	      settingsView && readingFormOptions && new import_reader_reading_settings_form.ReaderReadingSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("reading"),
	        controller: settings,
	        preferences: readingFormOptions,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        parentScope: runtime.scope
	      });
	      const translationFormOptions = options.settings ? options.settings.translationForm : void 0;
	      if (translationFormOptions && (!settingsView || !runtime.translationRequests))
	        throw runtime.destroy(), new Error(
	          "翻译设置 form 需要启用 Settings View 与 TranslationRequestAdapter"
	        );
	      settingsView && translationFormOptions && runtime.translationRequests && new import_reader_translation_settings_form.ReaderTranslationSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("translation"),
	        repository: translationFormOptions.repository,
	        access: runtime.translationRequests,
	        parentScope: runtime.scope
	      });
	      const interactionFormOptions = options.settings ? options.settings.interactionForm : void 0;
	      if (interactionFormOptions && !settingsView)
	        throw runtime.destroy(), new Error(
	          "帖子与回复设置 form 需要启用唯一 Settings View"
	        );
	      if (settingsView && interactionFormOptions && new import_reader_interaction_settings_form.ReaderInteractionSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("interaction"),
	        controller: settings,
	        boostCopy: interactionFormOptions.boostCopy,
	        topicActionRail: interactionFormOptions.topicActionRail,
	        replyTree: interactionFormOptions.replyTree,
	        ...interactionFormOptions.replyTreePreview === void 0 ? {} : {
	          replyTreePreview: interactionFormOptions.replyTreePreview
	        },
	        ...interactionFormOptions.boostsAvailable === void 0 ? {} : {
	          boostsAvailable: interactionFormOptions.boostsAvailable
	        },
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        parentScope: runtime.scope
	      }), settingsView && layout && new import_reader_layout_settings_form.ReaderLayoutSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("layout"),
	        controller: settings,
	        layout,
	        parentScope: runtime.scope
	      }), settingsView && appearance && new import_reader_appearance_settings_form.ReaderAppearanceSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("appearance"),
	        controller: settings,
	        appearance,
	        parentScope: runtime.scope
	      }), settingsView && font) {
	        const localFontWindow = options.runtime.document.defaultView;
	        new import_reader_font_settings_form.ReaderFontSettingsForm({
	          document: options.runtime.document,
	          host: settingsView.panelHost("font"),
	          controller: settings,
	          font,
	          ...localFontWindow?.queryLocalFonts ? {
	            queryLocalFonts: async () => (await localFontWindow.queryLocalFonts()).map((entry) => entry.family ?? "").filter(Boolean)
	          } : {},
	          parentScope: runtime.scope
	        });
	      }
	      settingsView && motionPreferences && navigationPreferences && new import_reader_motion_settings_form.ReaderMotionSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("flash"),
	        controller: settings,
	        navigation: navigationPreferences,
	        preferences: motionPreferences,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        parentScope: runtime.scope
	      });
	      const configuration = options.settings && options.settings.configuration ? options.settings.configuration : null, configurationManager = configuration ? new import_reader_settings_config_manager.ReaderSettingsConfigManager({
	        codec: new import_reader_settings_config_manager.ReaderSettingsConfigCodec(configuration.codec),
	        defaults: configuration.defaults,
	        preferences: {
	          read: context.readPreferences,
	          update: (preferences) => {
	            context.updatePreferences(preferences);
	          }
	        },
	        customSites: configuration.customSites,
	        translation: configuration.translation,
	        webDav: configuration.webDav
	      }) : null, cacheSurface = settingsView ? new import_reader_cache_management_surface.ReaderCacheManagementSurface({
	        document: options.runtime.document,
	        host: settingsView.panelHost("cache"),
	        ...configurationManager ? {
	          configuration: {
	            export: () => configurationManager.export(),
	            prepare: (payload) => configurationManager.prepare(payload),
	            apply: (prepared) => configurationManager.apply(prepared),
	            reset: () => configurationManager.reset(),
	            confirm: (request) => runtime.feedback.confirm(request),
	            saveTextFile: (content, filename) => {
	              if (!runtime.blobDownloads)
	                throw new Error(
	                  "浏览器文件下载 capability 不可用"
	                );
	              runtime.blobDownloads.save(
	                new Blob(
	                  [content],
	                  { type: "application/json" }
	                ),
	                filename
	              );
	            }
	          }
	        } : {},
	        history: runtime.history,
	        responses: runtime.data.responses,
	        ...runtime.assetCaches ? { assetCaches: runtime.assetCaches } : {},
	        applicationCaches: {
	          stats: async () => {
	            const users = runtime.users.cacheStats(), notifications = runtime.notificationController?.cacheStats() ?? { pages: 0, records: 0 }, bookmarks2 = runtime.bookmarkController?.cacheStats() ?? { bookmarks: 0, reactions: 0 }, creditBridge = await runtime.creditAccount?.cacheStats() ?? {
	              records: 0,
	              bytes: 0,
	              cachedAt: null,
	              expired: !1
	            }, imageObjects = runtime.imageResources?.diagnostics() ?? {
	              objectUrls: 0,
	              objectUrlLimit: 0
	            }, hostIdentity = (0, import_reader_topic_header.readerTopicHostIdentityCacheStats)(
	              options.runtime.document
	            );
	            return Object.freeze({
	              categories: Object.freeze({
	                topics: Object.freeze({
	                  records: (runtime.shell.activeValue ? 1 : 0) + hostIdentity.categoryEntries + hostIdentity.tagEntries,
	                  detail: runtime.shell.activeTopicId === null ? `当前会话:未打开主题;宿主身份派生:${hostIdentity.categoryEntries} 个分类键 · ${hostIdentity.tagEntries} 个标签` : `当前会话:主题 ${runtime.shell.activeTopicId}(清理时联网重建);宿主身份派生:${hostIdentity.categoryEntries} 个分类键 · ${hostIdentity.tagEntries} 个标签`
	                }),
	                users: Object.freeze({
	                  records: users.profiles + users.followLists + users.externalSnapshots + creditBridge.records,
	                  detail: `内存热缓存:${users.profiles} 个资料 · ${users.followLists} 份关注列表 · ${users.externalSnapshots} 份账户摘要;LDC bridge ${creditBridge.records} 条(${creditBridge.bytes} B)`
	                }),
	                notifications: Object.freeze({
	                  records: notifications.records,
	                  detail: `内存热缓存:${notifications.pages} 页 · ${notifications.records} 条消息`
	                }),
	                responses: Object.freeze({
	                  records: bookmarks2.bookmarks + bookmarks2.reactions,
	                  detail: `内存热缓存:${bookmarks2.bookmarks} 条收藏 · ${bookmarks2.reactions} 条回应`
	                }),
	                assets: Object.freeze({
	                  records: imageObjects.objectUrls,
	                  detail: `内存对象 URL:${imageObjects.objectUrls} / ${imageObjects.objectUrlLimit}`
	                })
	              })
	            });
	          },
	          clear: async (categories) => {
	            const selected = new Set(categories), failed = [];
	            if (selected.has("users"))
	              try {
	                runtime.users.clearCache(), await runtime.creditAccount?.clearCache();
	              } catch (cause) {
	                failed.push("users"), reportCacheError(cause);
	              }
	            if (selected.has("notifications"))
	              try {
	                runtime.notificationController?.clearCache();
	              } catch (cause) {
	                failed.push("notifications"), reportCacheError(cause);
	              }
	            if (selected.has("responses"))
	              try {
	                runtime.bookmarkController?.clearCache();
	              } catch (cause) {
	                failed.push("responses"), reportCacheError(cause);
	              }
	            if (selected.has("topics"))
	              try {
	                runtime.shell.activeValue && ((await refreshCurrentTopic({
	                  clearImages: !1
	                })).complete || failed.push("topics"));
	              } catch (cause) {
	                failed.push("topics"), reportCacheError(cause);
	              } finally {
	                (0, import_reader_topic_header.clearReaderTopicHostIdentityCache)(
	                  options.runtime.document
	                );
	              }
	            return Object.freeze({ failed: Object.freeze(failed) });
	          }
	        },
	        clearImageObjectUrls: () => runtime.imageResources?.clearObjectUrls(),
	        currentTopicAvailable: () => runtime.shell.activeTopicId !== null,
	        clearCurrentTopic: () => refreshCurrentTopic(),
	        notify: (message) => runtime.feedback.show(message),
	        onError: reportCacheError,
	        parentScope: runtime.scope
	      }) : null;
	      cacheSurface && runtime.shell.changes.subscribe(
	        () => cacheSurface.sync(),
	        runtime.scope
	      );
	      const resourceMonitor = settingsView ? new import_reader_resource_monitor.ReaderResourceMonitor({
	        document: options.runtime.document,
	        host: settingsView.panelHost("logs"),
	        readerRoot: shell.view.root,
	        requests: runtime.data.requests,
	        schedulerSnapshot: () => runtime.data.client.scheduler.snapshot(),
	        permitSnapshot: () => runtime.permit.snapshot(),
	        performancePolicySnapshot: () => runtime.performance,
	        topicSnapshot: () => {
	          const active = runtime.shell.activeValue, session = active?.services.session, coverage = session?.postStreamCoverage(), unavailableFloors = session?.unavailablePostNumbers() ?? Object.freeze([]), unavailableSet = new Set(
	            unavailableFloors
	          ), relations = active?.replies.topology.snapshot().relations ?? [], views = active?.dom.domOwner.views() ?? [], mountedViews = views.filter(
	            (view) => view.slots.root.isConnected
	          ), roots = mountedViews.map(
	            (view) => view.slots.root
	          ), imageCatalog = active?.topicImages.snapshot(), imageRetry = active?.topicMedia.images.diagnostics(), mediaRuntime = active?.topicMedia.media.diagnostics(), imageResources = runtime.imageResources?.diagnostics(), hlsSources = roots.reduce(
	            (total, root) => total + [
	              ...root.querySelectorAll(
	                "video"
	              )
	            ].filter(
	              (video) => (0, import_reader_media_controller.readerHlsSource)(
	                video,
	                options.runtime.document.baseURI
	              )
	            ).length,
	            0
	          ), nativeHlsSources = roots.reduce(
	            (total, root) => total + [
	              ...root.querySelectorAll(
	                "video"
	              )
	            ].filter((video) => {
	              if (!(0, import_reader_media_controller.readerHlsSource)(
	                video,
	                options.runtime.document.baseURI
	              )) return !1;
	              try {
	                return !!video.canPlayType(
	                  "application/vnd.apple.mpegurl"
	                ) && mediaRuntime?.nativeManagedMediaSource === !0;
	              } catch {
	                return !1;
	              }
	            }).length,
	            0
	          );
	          return Object.freeze({
	            topicId: runtime.shell.activeTopicId,
	            mountedFloors: mountedViews.length,
	            preparedFloors: active?.dom.preparedPostViewCount ?? views.length,
	            retainedFloors: session?.cachedPosts().length ?? 0,
	            nestedFloors: relations.filter(
	              (relation) => relation.parentPostNumber !== null
	            ).length,
	            media: roots.reduce(
	              (total, root) => total + root.querySelectorAll(
	                "img,video,audio,iframe"
	              ).length,
	              0
	            ),
	            initializedFromCache: session?.initializedFromCache ?? !1,
	            expectedFloors: coverage?.expectedPostCount ?? 0,
	            streamFloors: coverage?.streamPostCount ?? 0,
	            missingFloors: coverage?.missingPostCount ?? 0,
	            unavailableFloors,
	            mediaDiagnostics: Object.freeze({
	              catalogImages: imageCatalog?.items.length ?? 0,
	              catalogComplete: imageCatalog?.complete ?? !1,
	              catalogPending: imageCatalog?.pending ?? !1,
	              catalogFailedBatches: imageCatalog?.failedBatchCount ?? 0,
	              persistentCacheEnabled: runtime.imageResources !== null,
	              objectUrls: imageResources?.objectUrls ?? 0,
	              objectUrlLimit: imageResources?.objectUrlLimit ?? 0,
	              boundImages: imageRetry?.boundImages ?? 0,
	              failedImages: imageRetry?.failedImages ?? 0,
	              retryingImages: imageRetry?.retryingImages ?? 0,
	              crossOriginFailures: imageRetry?.crossOriginFailures ?? 0,
	              failedPostNumbers: imageRetry?.failedPostNumbers ?? Object.freeze([]),
	              unavailableSourcePostNumbers: Object.freeze([
	                ...new Set(
	                  (imageCatalog?.items ?? []).map(
	                    (item) => item.sourcePostNumber
	                  ).filter(
	                    (postNumber) => unavailableSet.has(
	                      postNumber
	                    )
	                  )
	                )
	              ].sort((left, right) => left - right)),
	              hlsSources,
	              nativeHlsSources,
	              activeHlsPlayers: mediaRuntime?.activeHlsPlayers ?? 0,
	              hlsLibraryAvailable: mediaRuntime?.hlsLibraryAvailable ?? !1,
	              hlsLibrarySupported: mediaRuntime?.hlsLibrarySupported ?? !1,
	              nativeManagedMediaSource: mediaRuntime?.nativeManagedMediaSource ?? !1
	            })
	          });
	        },
	        performance: options.runtime.document.defaultView?.performance ?? null,
	        parentScope: runtime.scope
	      }) : null;
	      if (settingsView && resourceMonitor) {
	        const syncResourceMonitor = () => {
	          const snapshot = settingsView.snapshot;
	          snapshot.open && snapshot.activePanelId === "logs" ? resourceMonitor.start() : resourceMonitor.stop();
	        };
	        settingsView.changes.subscribe(
	          syncResourceMonitor,
	          runtime.scope
	        ), syncResourceMonitor();
	      }
	      const queuePreferences = options.openQueue || null;
	      if (queuePreferences && !context.updatePreferences)
	        throw runtime.destroy(), new Error(
	          "阅读队列与退出策略需要 application 唯一偏好写端口"
	        );
	      queuePreferences && runtime.composer.installCloseGuard({
	        document: options.runtime.document,
	        enabled: () => queuePreferences.read(
	          context.readPreferences()
	        ).confirmNativeComposerClose,
	        notify: (message) => runtime.feedback.show(message),
	        parentScope: runtime.scope
	      });
	      const queuePrefetchForegroundBusy = () => {
	        if (options.runtime.document.visibilityState !== "visible" || runtime.shell.state === "opening" || runtime.shell.state === "switching") return !0;
	        const lastScrollAt = runtime.shell.activeValue?.dom.lastUserScrollAt() ?? 0, now = options.runtime.document.defaultView?.performance.now() ?? performance.now();
	        return lastScrollAt > 0 && now - lastScrollAt < 700;
	      }, waitForQueuePrefetchIdle = async (signal) => {
	        for (; !signal.aborted && queuePrefetchForegroundBusy(); )
	          await (0, import_coordinated_request_client.abortableDelay)(180, signal);
	        if (signal.aborted) throw signal.reason;
	      }, waitForTopicDownloadIdle = async (signal) => {
	        for (; !signal.aborted && options.runtime.document.visibilityState === "visible" && queuePrefetchForegroundBusy(); )
	          await (0, import_coordinated_request_client.abortableDelay)(180, signal);
	        if (signal.aborted) throw signal.reason;
	      }, waitForTopicDownloadRequestHeadroom = async (signal, nestedReplies = !1) => {
	        for (; !signal.aborted; ) {
	          await waitForTopicDownloadIdle(signal);
	          const snapshot = await runtime.permit.snapshot();
	          if (snapshot.challengeState === "idle" && (0, import_reader_performance_policy.readerBulkBackgroundRequestHasHeadroom)(snapshot, nestedReplies)) return;
	          const delayMs = snapshot.nextPermitDelay > 0 ? Math.max(180, Math.min(1e3, snapshot.nextPermitDelay)) : 360;
	          await (0, import_coordinated_request_client.abortableDelay)(delayMs, signal);
	        }
	        throw signal.reason;
	      }, topicDownloadRequestMustPause = (error) => runtime.data.client.requestResume(error) !== null, queueTopicPresentation = queuePreferences ? (0, import_native_host_api.discourseNativeTopicPresentation)(options.runtime.host) : null, topicOfflineArtifacts = runtime.blobDownloads ? new import_reader_topic_offline_artifact_repository.ReaderTopicOfflineArtifactRepository(runtime.data.responses) : null, topicDownloadQuoteEndpointPreferences = /* @__PURE__ */ new Map(), topicDownloadUnavailableQuoteTargets = /* @__PURE__ */ new Set(), openQueue = queuePreferences ? new import_reader_open_queue_session.ReaderOpenQueueSession({
	        document: options.runtime.document,
	        root: shell.view.modal,
	        workspaceRoot: shell.view.root,
	        storage: options.runtime.storage,
	        authScope: options.runtime.topic.authScope,
	        target: runtime,
	        currentTopicId: () => runtime.shell.activeTopicId,
	        readerOpen: () => [
	          "opening",
	          "switching",
	          "running",
	          "failed"
	        ].includes(runtime.shell.state),
	        historyEntry: (topicId) => topicId ? runtime.history.entry(topicId) : runtime.history.ordered("recent-viewed")[0] ?? null,
	        avatarSource: (template, size) => queueTopicPresentation?.avatarSource(template, size) ?? "",
	        historyAnchor: (topicId) => runtime.historyNavigation.snapshot.states[String(topicId)] ?? null,
	        restoreHistoryAnchor: async (topicId, anchor) => {
	          await runtime.historyNavigation.restore(topicId, anchor);
	        },
	        prefetch: async (topicId, postNumber, signal, report) => {
	          const scope = runtime.scope.child(), abort = scope.abortController(
	            new DOMException("阅读队列预加载已释放", "AbortError"),
	            signal
	          ), bundle = runtime.data.createTopicBundle({
	            topicId,
	            scope,
	            signal: abort.signal,
	            mount: () => () => {
	            }
	          }, {
	            ...options.runtime.topic,
	            host: options.runtime.host,
	            nativeAjax: runtime.nativeAjax
	          });
	          try {
	            await (0, import_coordinated_request_client.abortableDelay)(900, abort.signal), await waitForQueuePrefetchIdle(abort.signal), await bundle.services.session.init({ background: !0 });
	            const session = bundle.services.session, stream = [...session.streamPostIds()], targetPost = postNumber ? session.postByNumber(postNumber) : void 0, targetPostId = Number(
	              targetPost?.id
	            ), foundIndex = targetPostId ? stream.findIndex((postId) => Number(postId) === targetPostId) : -1, targetIndex = foundIndex >= 0 ? foundIndex : Math.max(
	              0,
	              Math.min(stream.length - 1, Number(postNumber ?? 1) - 1)
	            );
	            let ids = stream;
	            if (stream.length > 200 && Number(postNumber ?? 1) > 1) {
	              const before = Math.min(targetIndex, 100);
	              let start = Math.max(0, targetIndex - before), end = Math.min(stream.length, targetIndex + 200 - before);
	              start = Math.max(0, end - 200), end = Math.min(stream.length, start + 200), ids = stream.slice(start, end);
	            } else if (stream.length > 200 && targetIndex > 0) {
	              const before = Math.min(targetIndex, 80);
	              let localStart = Math.max(0, targetIndex - before), localEnd = Math.min(
	                stream.length,
	                localStart + 160
	              );
	              localStart = Math.max(0, localEnd - 160);
	              const selected = new Set(stream.slice(0, 40));
	              stream.slice(localStart, localEnd).forEach((id) => selected.add(id)), selected.size < 200 && stream.slice(localEnd, localEnd + 200 - selected.size).forEach((id) => selected.add(id)), ids = [...selected].slice(0, 200);
	            } else stream.length > 200 && (ids = stream.slice(0, 200));
	            let loadedCount = stream.reduce(
	              (count, postId) => count + +!!session.postById(Number(postId)),
	              0
	            );
	            for (let offset = 0; offset < ids.length; offset += session.pageSize) {
	              const batch = ids.slice(offset, offset + session.pageSize), loadedBefore = batch.reduce(
	                (count, postId) => count + +!!session.postById(Number(postId)),
	                0
	              );
	              await session.loadPostsByIds(
	                batch,
	                { background: !0, maxAttempts: 1 }
	              );
	              const loadedAfter = batch.reduce(
	                (count, postId) => count + +!!session.postById(Number(postId)),
	                0
	              );
	              loadedCount += loadedAfter - loadedBefore, report({
	                loadedCount,
	                totalCount: stream.length
	              });
	            }
	            const nested = await session.loadReplyBranches(
	              ids.map((postId) => Number(
	                session.postById(Number(postId))?.post_number ?? 0
	              )).filter((postNumber2) => Number.isSafeInteger(postNumber2) && postNumber2 > 0),
	              {
	                background: !0,
	                maxPages: 32,
	                maxAttempts: 2,
	                beforePage: () => waitForQueuePrefetchIdle(abort.signal)
	              }
	            );
	            report({
	              nestedLoadedCount: nested.loadedReplyCount,
	              nestedTotalCount: nested.expectedReplyCount
	            });
	            const topic = session.topic, mediaPosts = nested.postNumbers.map((childPostNumber) => session.postByNumber(childPostNumber)).filter((post) => post !== void 0), media = runtime.mediaPrefetch ? await runtime.mediaPrefetch.prefetch({
	              posts: mediaPosts,
	              signal: abort.signal,
	              waitUntilIdle: waitForQueuePrefetchIdle,
	              reactionSources: (post) => topic ? runtime.postReactions.options(topic, post).flatMap((option) => option.imageUrl ? [option.imageUrl] : []) : [],
	              onProgress: (progress) => report({
	                mediaLoadedCount: progress.loadedCount,
	                mediaTotalCount: progress.totalCount
	              })
	            }) : Object.freeze({
	              loadedCount: 0,
	              totalCount: 0,
	              failedCount: 0,
	              complete: !0
	            });
	            return await session.flush(), loadedCount = stream.reduce(
	              (count, postId) => count + +!!session.postById(Number(postId)),
	              0
	            ), Object.freeze({
	              loadedCount,
	              totalCount: stream.length,
	              nestedLoadedCount: nested.loadedReplyCount,
	              nestedTotalCount: nested.expectedReplyCount,
	              mediaLoadedCount: media.loadedCount,
	              mediaTotalCount: media.totalCount,
	              complete: loadedCount >= stream.length && nested.complete && media.complete
	            });
	          } finally {
	            await bundle.prepareClose?.("close"), scope.destroy();
	          }
	        },
	        ...runtime.blobDownloads ? {
	          topicDownloads: {
	            downloads: runtime.blobDownloads,
	            requestResume: (error) => runtime.data.client.requestResume(error),
	            mount: shell.view.modal,
	            floating: !0,
	            positionAnchor: () => runtime.shell.activeValue?.topicActionRail?.downloadHistoryButton ?? null,
	            confirmRemoval: async (context2, host) => {
	              const requestedAt = Number(
	                context2.localDownloadRequestedAt
	              ), localDownload = requestedAt > 0 ? `曾于 ${new Date(requestedAt).toLocaleString(
	                "zh-CN"
	              )} 触发浏览器下载` : "Reader 未记录曾触发本地下载", choice = await runtime.feedback.choose({
	                title: "移除 Topic 下载记录?",
	                message: context2.hasCachedHtml ? "请选择是否同时删除 Reader 缓存中的离线 HTML。" : "该记录没有可清理的 Reader 缓存 HTML。",
	                details: Object.freeze([
	                  Object.freeze({
	                    label: "Reader 缓存 HTML",
	                    value: context2.hasCachedHtml ? "已保存" : "未找到"
	                  }),
	                  Object.freeze({
	                    label: "本地下载",
	                    value: localDownload
	                  }),
	                  ...context2.filename ? [Object.freeze({
	                    label: "文件名",
	                    value: context2.filename
	                  })] : []
	                ]),
	                note: "受浏览器安全限制,Reader 无法检查本地文件是否仍存在,也无法删除下载目录中的文件;如需删除请手动处理。",
	                cancelLabel: "取消",
	                ...context2.hasCachedHtml ? { secondaryLabel: "仅移除记录" } : {},
	                confirmLabel: context2.hasCachedHtml ? "记录和缓存都删除" : "移除记录",
	                icon: "trash"
	              }, host);
	              return choice === "confirm" ? "remove-record-and-cache" : choice === "secondary" ? "remove-record" : "cancel";
	            },
	            confirmBulkRemoval: async (contexts, host) => {
	              const cachedCount = contexts.filter((context2) => context2.hasCachedHtml).length, downloadedCount = contexts.filter((context2) => context2.localDownloadRequestedAt > 0).length, choice = await runtime.feedback.choose({
	                title: `移除 ${contexts.length} 条 Topic 下载记录?`,
	                message: cachedCount ? "请选择是否同时删除这些记录在 Reader 中缓存的离线 HTML。" : "所选记录没有可清理的 Reader 缓存 HTML。",
	                details: Object.freeze([
	                  Object.freeze({
	                    label: "下载记录",
	                    value: `${contexts.length} 条`
	                  }),
	                  Object.freeze({
	                    label: "Reader 缓存 HTML",
	                    value: `${cachedCount} 份`
	                  }),
	                  Object.freeze({
	                    label: "曾触发本地下载",
	                    value: `${downloadedCount} 条`
	                  })
	                ]),
	                note: "Reader 无法检查或删除下载目录中的本地文件;如需删除,请在文件管理器中手动处理。",
	                cancelLabel: "取消",
	                ...cachedCount > 0 ? { secondaryLabel: "仅移除记录" } : {},
	                confirmLabel: cachedCount > 0 ? "记录和缓存都删除" : "移除记录",
	                icon: "trash"
	              }, host);
	              return choice === "confirm" ? "remove-record-and-cache" : choice === "secondary" ? "remove-record" : "cancel";
	            },
	            hydrateHtmlWindow: import_reader_topic_offline_document.hydrateReaderTopicOfflineDocumentWindow,
	            ...topicOfflineArtifacts ? { artifacts: topicOfflineArtifacts } : {},
	            worker: async (topicId, fallbackTitle, signal, report, selection) => {
	              const scope = runtime.scope.child(), abort = scope.abortController(
	                new DOMException("Topic 后台下载已释放", "AbortError"),
	                signal
	              );
	              let backgroundNetworkRequestCount = 0;
	              const beforeDownloadNetwork = async (networkSignal, nestedReplies = !1) => {
	                await waitForTopicDownloadRequestHeadroom(
	                  networkSignal,
	                  nestedReplies
	                ), backgroundNetworkRequestCount += 1;
	              }, bundle = runtime.data.createTopicBundle({
	                topicId,
	                scope,
	                signal: abort.signal,
	                mount: () => () => {
	                }
	              }, {
	                ...options.runtime.topic,
	                host: options.runtime.host,
	                nativeAjax: runtime.nativeAjax
	              });
	              try {
	                report({
	                  phase: "loading-topic",
	                  detail: "正在读取 Topic 与本地存档"
	                }), await waitForTopicDownloadIdle(abort.signal);
	                const topic = await bundle.services.session.init({
	                  background: !0,
	                  beforeNetwork: beforeDownloadNetwork
	                }), session = bundle.services.session, cachedAtStart = session.cachedPosts(), streamCoverageAtStart = session.postStreamCoverage(), archiveAtStart = session.localArchiveState(), localArchivePlan = (0, import_reader_topic_download_manager.readerTopicDownloadLocalArchivePlan)({
	                  topicStatus: archiveAtStart.topic?.status,
	                  cachedPostCount: cachedAtStart.length,
	                  expectedPostCount: streamCoverageAtStart.expectedPostCount,
	                  streamPostCount: streamCoverageAtStart.streamPostCount,
	                  missingStreamPostCount: streamCoverageAtStart.missingPostCount,
	                  streamComplete: streamCoverageAtStart.complete
	                });
	                let streamComplete = !1, missingCanonicalPostCount = 0, repliesComplete = !1;
	                if (localArchivePlan)
	                  streamComplete = localArchivePlan.streamComplete, missingCanonicalPostCount = localArchivePlan.missingCanonicalPostCount, report({
	                    phase: "loading-replies",
	                    completed: localArchivePlan.completed,
	                    total: localArchivePlan.total,
	                    detail: `正在整理 ${archiveAtStart.topic?.status ?? 404} 本地缓存 ${localArchivePlan.completed}/${localArchivePlan.total}`
	                  });
	                else {
	                  const stream = await session.ensurePostStream({
	                    background: !0,
	                    maxAttempts: 2,
	                    beforeNetwork: beforeDownloadNetwork,
	                    beforeBatch: () => waitForTopicDownloadIdle(abort.signal),
	                    onProgress: (progress) => report({
	                      phase: "loading-posts",
	                      completed: progress.loadedCount,
	                      total: progress.totalCount,
	                      detail: `正文已就绪 ${progress.loadedCount}/${progress.totalCount || "?"} · 本轮复用缓存 ${Math.min(cachedAtStart.length, progress.loadedCount)} · 后台联网 ${backgroundNetworkRequestCount}`
	                    })
	                  });
	                  streamComplete = stream.complete, missingCanonicalPostCount = stream.missingPostIds.length;
	                  const canonicalPosts = session.cachedPosts();
	                  report({
	                    phase: "loading-replies",
	                    completed: canonicalPosts.length,
	                    total: canonicalPosts.length,
	                    detail: `已从 canonical 正文整理回复关系 ${canonicalPosts.length}/${canonicalPosts.length} · 后台联网 ${backgroundNetworkRequestCount}`
	                  }), repliesComplete = stream.complete;
	                }
	                const topicRecord = topic, specialContentWarnings = [];
	                if (topicRecord.is_post_voting === !0 && !localArchivePlan) {
	                  const record = (value) => value !== null && typeof value == "object" && !Array.isArray(value) ? value : null, votingPosts = session.cachedPosts().filter((post) => {
	                    const source = post, loaded = Array.isArray(source.post_voting_comments) ? source.post_voting_comments.length : Array.isArray(source.comments) ? source.comments.length : 0;
	                    return Math.max(0, Number(source.comments_count) || 0) > loaded;
	                  });
	                  for (const [postIndex, post] of votingPosts.entries()) {
	                    const source = post, postId = Number(source.id), postNumber = Number(source.post_number), expectedComments = Math.max(
	                      0,
	                      Number(source.comments_count) || 0
	                    );
	                    if (!Number.isSafeInteger(postId) || postId < 1) continue;
	                    const current = Array.isArray(source.post_voting_comments) ? source.post_voting_comments : Array.isArray(source.comments) ? source.comments : [], merged = /* @__PURE__ */ new Map();
	                    for (const value of current) {
	                      const candidate = record(value), id = Number(candidate?.id);
	                      candidate && Number.isSafeInteger(id) && id > 0 && merged.set(id, Object.freeze({ ...candidate }));
	                    }
	                    try {
	                      let pages = 0;
	                      for (; merged.size < expectedComments && pages < 100; ) {
	                        pages += 1, await waitForTopicDownloadIdle(abort.signal), report({
	                          phase: "loading-replies",
	                          completed: postIndex,
	                          total: votingPosts.length,
	                          detail: `正在补齐楼层 #${postNumber} 的投票评论 · 后台联网 ${backgroundNetworkRequestCount}`
	                        });
	                        const afterCommentId = Math.max(0, ...merged.keys()), payload = await bundle.services.requests.loadPostVotingComments(postId, {
	                          afterCommentId,
	                          refresh: !0,
	                          background: !0,
	                          beforeNetwork: beforeDownloadNetwork
	                        }), payloadRecord = record(payload), incoming = Array.isArray(payload) ? payload : Array.isArray(payloadRecord?.comments) ? payloadRecord.comments : [];
	                        let added = 0;
	                        for (const value of incoming) {
	                          const candidate = record(value), id = Number(candidate?.id);
	                          candidate && Number.isSafeInteger(id) && id > 0 && !merged.has(id) && (added += 1), candidate && Number.isSafeInteger(id) && id > 0 && merged.set(id, Object.freeze({ ...candidate }));
	                        }
	                        if (!added) break;
	                      }
	                      session.ingestPosts([Object.freeze({
	                        ...source,
	                        post_voting_comments: Object.freeze([...merged.values()])
	                      })], "action-response"), merged.size < expectedComments && specialContentWarnings.push(
	                        `楼层 #${postNumber} 投票评论仅保存 ${merged.size}/${expectedComments}`
	                      );
	                    } catch (error) {
	                      if (topicDownloadRequestMustPause(error)) throw error;
	                      specialContentWarnings.push(
	                        `楼层 #${postNumber} 投票评论未能补齐`
	                      );
	                    }
	                  }
	                }
	                const archive = session.localArchiveState(), archived = archive.topic !== null || archive.posts.length > 0, coverage = (0, import_reader_topic_download_manager.readerTopicDownloadCoverage)({
	                  selectionMode: selection.mode,
	                  streamComplete,
	                  missingCanonicalPostCount,
	                  repliesComplete,
	                  archived
	                });
	                await session.flush();
	                const availablePosts = session.cachedPosts(), selected = (0, import_reader_topic_download_manager.selectReaderTopicDownloadPosts)(
	                  availablePosts,
	                  selection,
	                  selection.mode === "op" ? (0, import_reader_topic_header.readerTopicOwnerUsername)(topic, availablePosts) : ""
	                ), contextPosts = selected.posts, contextPostNumbers = new Set(contextPosts.map((post) => Number(post.post_number))), availablePostByNumber = new Map(availablePosts.map((post) => [Number(post.post_number), post])), quotedPosts = /* @__PURE__ */ new Map(), quotePayloadPosts = /* @__PURE__ */ new Map(), quoteTargets = (0, import_reader_topic_offline_document.readerTopicOfflineQuoteTargets)(
	                  options.runtime.document,
	                  Number(topicId),
	                  contextPosts
	                );
	                let missingQuoteTargetCount = 0;
	                for (const [quoteIndex, target] of quoteTargets.entries()) {
	                  if (target.topicId === Number(topicId) && contextPostNumbers.has(target.postNumber)) continue;
	                  const quoteKey = `${target.topicId}:${target.postNumber}`;
	                  if (target.topicId === Number(topicId) && streamComplete && missingCanonicalPostCount === 0 && !availablePostByNumber.has(target.postNumber)) {
	                    missingQuoteTargetCount += 1;
	                    continue;
	                  }
	                  report({
	                    phase: "loading-replies",
	                    completed: quoteIndex,
	                    total: quoteTargets.length,
	                    detail: `正在补齐引用正文 ${quoteIndex + 1}/${quoteTargets.length} · 后台联网 ${backgroundNetworkRequestCount}`
	                  });
	                  let quotedPost = target.topicId === Number(topicId) ? availablePostByNumber.get(target.postNumber) ?? null : quotePayloadPosts.get(quoteKey) ?? null;
	                  if (!quotedPost && !localArchivePlan && !topicDownloadUnavailableQuoteTargets.has(quoteKey)) {
	                    await waitForTopicDownloadIdle(abort.signal);
	                    const targetOptions = {
	                      scope: "single",
	                      background: !0,
	                      beforeNetwork: beforeDownloadNetwork
	                    }, candidates = (0, import_reader_topic_offline_document.prioritizeReaderTopicOfflineTargetCandidates)(
	                      bundle.services.requests.targetCandidates(
	                        target.postNumber,
	                        targetOptions,
	                        target.topicId
	                      ),
	                      topicDownloadQuoteEndpointPreferences.get(target.topicId)
	                    );
	                    for (const candidate of candidates)
	                      try {
	                        const payload = await bundle.services.requests.loadTargetCandidate(
	                          candidate,
	                          target.postNumber,
	                          targetOptions,
	                          target.topicId
	                        );
	                        for (const post of (0, import_topic_session.discoursePostsFromPayload)(payload)) {
	                          const postNumber = Number(post.post_number);
	                          !Number.isSafeInteger(postNumber) || postNumber < 1 || quotePayloadPosts.set(
	                            `${target.topicId}:${postNumber}`,
	                            post
	                          );
	                        }
	                        if (quotedPost = quotePayloadPosts.get(quoteKey) ?? null, quotedPost) {
	                          topicDownloadQuoteEndpointPreferences.set(
	                            target.topicId,
	                            candidate.endpoint
	                          );
	                          break;
	                        }
	                      } catch (error) {
	                        if (error instanceof DOMException && error.name === "AbortError" || topicDownloadRequestMustPause(error)) throw error;
	                        const status = Number(
	                          error?.status
	                        );
	                        if ((0, import_native_request_descriptors.discourseNativeTargetFailureIsDefinitive)({
	                          endpoint: candidate.endpoint,
	                          scope: targetOptions.scope,
	                          status
	                        })) {
	                          topicDownloadUnavailableQuoteTargets.add(quoteKey);
	                          break;
	                        }
	                        if ([401, 403].includes(status)) break;
	                      }
	                  }
	                  quotedPost ? (topicDownloadUnavailableQuoteTargets.delete(quoteKey), quotedPosts.set(
	                    quoteKey,
	                    Object.freeze({ topicId: target.topicId, post: quotedPost })
	                  )) : missingQuoteTargetCount += 1;
	                }
	                missingQuoteTargetCount > 0 && specialContentWarnings.push(
	                  `${missingQuoteTargetCount} 个引用正文未能补齐`
	                );
	                let selectedExpectedPostCount = session.postStreamCoverage().expectedPostCount;
	                const selectedComplete = coverage.complete && specialContentWarnings.length === 0, filenameScope = selected.filenameScope;
	                selection.mode !== "all" && (selectedExpectedPostCount = selected.expectedPostCount), report({
	                  phase: "serializing",
	                  completed: selection.mode === "all" ? contextPosts.length : selected.expectedPostCount,
	                  total: selectedExpectedPostCount,
	                  detail: [
	                    selection.mode === "all" ? "正在生成单文件离线 HTML" : `正在生成离线 HTML · 已准备 ${contextPosts.length} 楼讨论上下文`,
	                    coverage.warning,
	                    ...specialContentWarnings
	                  ].filter(Boolean).join(" · ")
	                });
	                const title = String(
	                  topicRecord.fancy_title ?? topicRecord.title ?? fallbackTitle
	                ).replace(/<[^>]+>/g, "").trim() || fallbackTitle, rootNode = runtime.shell.view.root.getRootNode(), readerRoot = runtime.shell.view.root, readerStyleProperties = {};
	                for (let index = 0; index < readerRoot.style.length; index += 1) {
	                  const name = readerRoot.style.item(index);
	                  name.startsWith("--") && (readerStyleProperties[name] = readerRoot.style.getPropertyValue(name));
	                }
	                const stylesheet = rootNode.querySelector(
	                  "style[data-ldp-reader-shadow]"
	                )?.textContent ?? options.runtime.document.getElementById("ldp-mian-lite-styles")?.textContent ?? "", replyTreePreferences = interactionFormOptions ? interactionFormOptions.replyTree.read(
	                  context.readPreferences()
	                ) : import_reader_reply_tree_preferences.DEFAULT_READER_REPLY_TREE_PREFERENCES, header = (0, import_reader_topic_header.normalizeReaderTopicHeader)(
	                  topic,
	                  contextPosts,
	                  queueTopicPresentation,
	                  topicId
	                ), logo = runtime.shell.view.root.querySelector(".ldp-logo"), offlineKatex = options.runtime.media?.katex ? new import_reader_katex_controller.ReaderKatexController({
	                  document: options.runtime.document,
	                  katex: options.runtime.media.katex
	                }) : null, prepareCooked = (cooked) => {
	                  const host = options.runtime.document.createElement("div");
	                  return host.className = "ldp-content cooked", host.innerHTML = cooked, (0, import_reader_cooked_content_feature2.prepareReaderCookedCallouts)(options.runtime.document, host), offlineKatex?.render(host), host.innerHTML;
	                };
	                let artifact;
	                try {
	                  artifact = (0, import_reader_topic_offline_document.createReaderTopicOfflineDocument)({
	                    topicId,
	                    title,
	                    sourceUrl: new URL(
	                      `/t/${topicId}`,
	                      options.runtime.document.baseURI
	                    ).href,
	                    topic,
	                    posts: contextPosts,
	                    quotedPosts: Object.freeze([...quotedPosts.values()]),
	                    ...selection.mode !== "all" && selected.mainPostNumbers ? {
	                      mainPostNumbers: selected.mainPostNumbers,
	                      projectionMode: selection.mode
	                    } : {},
	                    expectedPostCount: selectedExpectedPostCount,
	                    complete: selectedComplete,
	                    archive,
	                    inlineReplyTreeMaxDepth: replyTreePreferences.inlineReplyTreeMaxDepth,
	                    header,
	                    siteLogoUrl: logo?.currentSrc || logo?.src || "",
	                    reactionEmojiUrl: (reactionId) => (0, import_native_host_api.discourseNativeEmojiUrl)(options.runtime.host, reactionId),
	                    presentation: Object.freeze({
	                      theme: readerRoot.dataset.ldpTheme === "dark" ? "dark" : "light",
	                      styleProperties: Object.freeze(readerStyleProperties),
	                      structureColorsDisabled: readerRoot.classList.contains(
	                        "ldp-structure-colors-disabled"
	                      )
	                    }),
	                    stylesheet,
	                    prepareCooked
	                  });
	                } finally {
	                  offlineKatex?.destroy();
	                }
	                return filenameScope ? Object.freeze({
	                  ...artifact,
	                  filename: artifact.filename.replace(
	                    /-lite-offline\.html$/,
	                    `-${filenameScope}-lite-offline.html`
	                  )
	                }) : artifact;
	              } finally {
	                await bundle.prepareClose?.("close"), scope.destroy();
	              }
	            }
	          }
	        } : {},
	        closeReader: () => runtime.close(),
	        composerOpen: () => runtime.composer.isOpen(),
	        readerLightboxOpen: () => !!(0, import_reader_escape_surface.readerSurfaceQuery)(
	          options.runtime.document,
	          ".ldp-lightbox"
	        ),
	        readerSurfaceOpen: () => runtime.readerSurfaceOpen(),
	        closeExpandedReply: () => runtime.closeExpandedReply(),
	        readPreferences: () => queuePreferences.read(
	          context.readPreferences()
	        ),
	        updatePreferences: (patch) => {
	          const current = queuePreferences.read(
	            context.readPreferences()
	          );
	          context.updatePreferences(
	            queuePreferences.createPatch(Object.freeze({
	              ...current,
	              ...patch
	            }))
	          );
	        },
	        notify: (message) => runtime.feedback.show(message),
	        parentScope: runtime.scope
	      }) : null;
	      if (openQueue) {
	        downloadCurrentTopic = () => {
	          openQueue.downloadCurrentTopic();
	        }, openTopicDownloadManager = () => {
	          openQueue.openTopicDownloadManager();
	        }, runtime.scope.add(() => {
	          downloadCurrentTopic = null, openTopicDownloadManager = null;
	        });
	        const syncOpenQueue = () => openQueue.sync();
	        runtime.shell.changes.subscribe(syncOpenQueue, runtime.scope), runtime.history.changes.subscribe(syncOpenQueue, runtime.scope), context.preferenceChanges.subscribe(syncOpenQueue, runtime.scope), workspace.workspace.changes.subscribe(
	          () => openQueue.refreshSurface(),
	          runtime.scope
	        );
	      }
	      const webDavOptions = options.settings ? options.settings.webDav : void 0, translationOptions = options.settings ? options.settings.translationForm : void 0;
	      if (webDavOptions && !settingsView)
	        throw runtime.destroy(), new Error("WebDAV 设置需要启用唯一 Settings View");
	      if (settingsView && webDavOptions) {
	        const coordinator = new import_reader_webdav_coordinator.ReaderWebDavCoordinator({
	          client: webDavOptions.client,
	          repository: webDavOptions.repository,
	          categories: (0, import_reader_webdav_category_ports.createReaderWebDavCategoryPorts)({
	            history: runtime.history,
	            bookmarks: runtime.bookmarkController,
	            queue: openQueue,
	            preferences: {
	              read: context.readPreferences,
	              update: (patch) => {
	                context.updatePreferences(patch);
	              }
	            },
	            topicContext: runtime.threadContextState,
	            customSites: webDavOptions.customSites,
	            connectHistory: runtime.connectHistory,
	            translation: translationOptions ? translationOptions.repository : null,
	            translationCache: options.runtime.translation ? {
	              responses: runtime.data.responses,
	              cache: options.runtime.translation.translationCache
	            } : null,
	            offlineTopics: topicOfflineArtifacts
	          }),
	          hostname: () => options.runtime.document.location.hostname,
	          username: () => (0, import_native_host_api.discourseNativeCurrentUsername)(
	            options.runtime.host
	          )
	        }), webDavSettingsForm = new import_reader_webdav_settings_form.ReaderWebDavSettingsForm({
	          document: options.runtime.document,
	          host: settingsView.panelHost("sync"),
	          repository: webDavOptions.repository,
	          coordinator,
	          unavailableReason: () => (0, import_native_host_api.discourseNativeCurrentUsername)(options.runtime.host) ? "" : "当前未登录 Discourse,WebDAV 同步不可用。请先登录并刷新页面。",
	          parentScope: runtime.scope
	        });
	        settingsView.changes.subscribe((snapshot) => {
	          snapshot.open && snapshot.activePanelId === "sync" && webDavSettingsForm.refreshAvailability();
	        }, runtime.scope), new import_reader_webdav_coordinator.ReaderWebDavAutoSync({
	          repository: webDavOptions.repository,
	          coordinator,
	          visibilityState: () => options.runtime.document.visibilityState,
	          parentScope: runtime.scope
	        });
	      }
	      const notificationTrigger = shell.view.root.querySelector(".ldp-notifications-toggle"), historyTrigger = shell.view.root.querySelector(".ldp-history-toggle"), bookmarkTrigger = shell.view.root.querySelector(".ldp-bookmarks-toggle"), settingsTrigger = shell.view.root.querySelector(".ldp-settings-toggle"), exclusivePanels = [
	        ...runtime.notificationController && notificationTrigger ? [{
	          id: "notifications",
	          trigger: notificationTrigger,
	          isOpen: () => runtime.notificationController.snapshot.open,
	          open: () => runtime.notificationController.open(),
	          close: () => runtime.notificationController.close()
	        }] : [],
	        ...runtime.historyPanelView && historyTrigger ? [{
	          id: "history",
	          trigger: historyTrigger,
	          isOpen: () => !shell.view.root.querySelector(".ldp-history-popover")?.hidden,
	          open: () => runtime.historyPanelView.open(),
	          close: () => runtime.historyPanelView.close()
	        }] : [],
	        ...runtime.bookmarkController && bookmarkTrigger ? [{
	          id: "bookmarks",
	          trigger: bookmarkTrigger,
	          isOpen: () => runtime.bookmarkController.snapshot.open,
	          open: () => runtime.bookmarkController.open(),
	          close: () => runtime.bookmarkController.close()
	        }] : [],
	        ...settingsView && settingsTrigger ? [{
	          id: "settings",
	          trigger: settingsTrigger,
	          isOpen: () => settingsView.snapshot.open,
	          open: () => settingsView.open(),
	          close: () => settingsView.requestClose()
	        }] : []
	      ];
	      exclusivePanels.length > 1 && new import_reader_exclusive_panel_coordinator.ReaderExclusivePanelCoordinator({
	        entries: exclusivePanels,
	        parentScope: runtime.scope,
	        onError: (cause) => {
	          runtime.feedback.show(
	            cause instanceof Error ? cause.message : "面板切换失败"
	          );
	        }
	      });
	      const shortcutPreferences = options.shortcuts || null;
	      if (shortcutPreferences && !context.updatePreferences)
	        throw runtime.destroy(), new Error(
	          "快捷键设置需要 application 唯一偏好写端口"
	        );
	      let fullscreenReturnMode = workspace.workspace.snapshot.presentation.mode === "fullpage" ? "floating" : workspace.workspace.snapshot.presentation.mode;
	      const triggerTopicAction = (selector) => {
	        const button = runtime.shell.activeValue?.topicActionRail?.view?.slots.actions.querySelector(selector);
	        return !button || button.disabled || button.hidden ? !1 : (button.click(), !0);
	      }, triggerHeaderPanel = (selector) => {
	        const trigger = shell.view.root.querySelector(selector);
	        return trigger ? (trigger.click(), !0) : !1;
	      }, shortcuts = shortcutPreferences ? new import_reader_shortcut_controller.ReaderShortcutController({
	        target: options.runtime.document,
	        preferences: shortcutPreferences,
	        readPreferences: context.readPreferences,
	        preferenceChanges: context.preferenceChanges,
	        persist: context.updatePreferences,
	        canExecute: (action, event) => readerSurfaceOnlyCloseEvents.has(event) || action === "refreshHost" && !workspace.workspace.snapshot.presentation.embedded || runtime.readerShortcutContextBlocked() ? !1 : action !== "closeReader" ? !0 : (0, import_reader_shortcut_controller.readerShortcutBindingFromEvent)(event) === "Escape" ? !runtime.readerExitBlocked() : !0,
	        onUnavailable: (_action, label) => {
	          runtime.feedback.show(`“${label}”当前不可用`);
	        },
	        execute: (action, event) => {
	          const active = runtime.shell.activeValue;
	          switch (action) {
	            case "historyBack":
	              return runtime.historyNavigation.navigate("back");
	            case "historyForward":
	              return runtime.historyNavigation.navigate("forward");
	            case "topicTop":
	              return active ? active.topicTimeline.jumpTo(1, {
	                alignment: "start",
	                highlight: !0
	              }) : !1;
	            case "topicBottom":
	              return active ? active.topicTimeline.jumpTo(
	                active.topicTimeline.snapshot.totalPostCount,
	                {
	                  alignment: "start",
	                  highlight: !0
	                }
	              ) : !1;
	            case "floorJump":
	              return active?.topicTimelineView ? (active.topicTimelineView.focusJump(), !0) : !1;
	            case "discussionHorizontalScroll":
	              return active?.topicContextSurface.scrollDiscussionHorizontal(
	                event.type === "wheel" ? event.deltaY || event.deltaX : 0
	              ) ?? !1;
	            case "onlyAuthor":
	              return active ? (active.topicOnlyOp.toggle(), !0) : !1;
	            case "translate":
	              return runtime.translationFeature ? (runtime.translationFeature.controller.cycleMode(), !0) : !1;
	            case "refreshTopic":
	              return !active || refreshTopicButton.disabled ? !1 : (refreshTopicButton.click(), !0);
	            case "refreshHost":
	              return (0, import_native_host_api.discourseNativeHostRouteRefresh)(
	                options.runtime.host
	              );
	            case "openOriginal": {
	              const link = shell.view.root.querySelector("a.ldp-open");
	              return !link?.href || link.hidden ? !1 : (link.click(), !0);
	            }
	            case "settings":
	              return triggerHeaderPanel(".ldp-settings-toggle");
	            case "notifications":
	              return triggerHeaderPanel(".ldp-notifications-toggle");
	            case "historyPanel":
	              return triggerHeaderPanel(".ldp-history-toggle");
	            case "bookmarksPanel":
	              return triggerHeaderPanel(".ldp-bookmarks-toggle");
	            case "likeTopic":
	              return triggerTopicAction("button[data-post-like]");
	            case "replyTopic":
	              return triggerTopicAction("button[data-post-reply]");
	            case "bookmarkTopic":
	              return triggerTopicAction("button[data-post-bookmark]");
	            case "toggleFullscreen": {
	              const mode = workspace.workspace.snapshot.presentation.mode;
	              return mode === "fullpage" ? workspace.setMode(fullscreenReturnMode) : (fullscreenReturnMode = mode, workspace.setMode("fullpage"));
	            }
	            case "toggleQueue":
	              return openQueue ? (openQueue.toggle(), !0) : !1;
	            case "closeReader":
	              return runtime.handleCloseReaderShortcut(event);
	          }
	        },
	        onError: (cause) => {
	          runtime.feedback.show(
	            cause instanceof Error ? cause.message : "快捷操作执行失败"
	          );
	        },
	        parentScope: runtime.scope
	      }) : null;
	      settingsView && shortcuts && new import_reader_shortcut_settings_form.ReaderShortcutSettingsForm({
	        document: options.runtime.document,
	        host: settingsView.panelHost("shortcuts"),
	        shortcuts,
	        parentScope: runtime.scope
	      }), (performancePolicy || navigationPreferences || loadingAnimation) && context.preferenceChanges.subscribe((preferences) => {
	        let performanceSnapshot = null;
	        performancePolicy && options.selectPerformancePreferences && (performanceSnapshot = performancePolicy.apply(
	          options.selectPerformancePreferences(preferences)
	        )), navigationPreferences && selectNavigationPreferences && (navigationPreferences.apply(
	          selectNavigationPreferences(preferences)
	        ), navigationPreferences.refreshPerformance()), loadingAnimation && motionPreferences && loadingAnimation.apply(
	          motionPreferences.read(preferences).loadingAnimation
	        ), performanceSnapshot && runtime.applyPerformance(performanceSnapshot);
	      }, runtime.scope), runtime.historyNavigationView && options.selectHistoryNavigationPreferences && context.preferenceChanges.subscribe((preferences) => {
	        runtime.historyNavigationView?.applyPreferences(
	          options.selectHistoryNavigationPreferences(
	            preferences
	          )
	        );
	      }, runtime.scope), runtime.historyPanelView && options.selectHistoryPanelPreferences && context.preferenceChanges.subscribe((preferences) => {
	        const projection = options.selectHistoryPanelPreferences(preferences);
	        runtime.historyPanelView?.applyPreferences(projection), runtime.historyNavigation.refreshOrder();
	      }, runtime.scope), runtime.bookmarkController && options.selectBookmarkPreferences && context.preferenceChanges.subscribe((preferences) => {
	        runtime.bookmarkController?.applyTabOrder(
	          options.selectBookmarkPreferences(preferences).tabOrder
	        );
	      }, runtime.scope), options.selectTimelineViewPreferences && context.preferenceChanges.subscribe((preferences) => {
	        timelinePreferences = options.selectTimelineViewPreferences(preferences), runtime.shell.activeValue?.topicTimelineView?.applyPreferences(timelinePreferences);
	      }, runtime.scope), runtime.translationFeature && options.selectTranslationMode && context.preferenceChanges.subscribe((preferences) => {
	        runtime.translationFeature?.applyMode(
	          options.selectTranslationMode(preferences)
	        ), runtime.translationFeature?.syncMountedPosts();
	      }, runtime.scope);
	      let readyCleanup;
	      try {
	        readyCleanup = options.onReady?.(
	          runtime,
	          context,
	          settings,
	          settingsView,
	          layout,
	          appearance,
	          font
	        ) || void 0;
	      } catch (error) {
	        throw runtime.destroy(), error;
	      }
	      return () => {
	        try {
	          readyCleanup?.();
	        } finally {
	          runtime.destroy();
	        }
	      };
	    }
	  });
	}
}, "c65c330d22a0d215701e8b9a38f78608dbd345baa290da79718ba6243eb4f3f5");

/* Source: lite/src/app/reader-data-runtime.ts */
runtime.register("src/app/reader-data-runtime.js", function(module, exports, require) {
	var reader_data_runtime_exports = {};
	__export(reader_data_runtime_exports, {
	  READER_CACHE_COORDINATION_CHANNEL: () => READER_CACHE_COORDINATION_CHANNEL,
	  READER_CACHE_COORDINATION_LOCK: () => READER_CACHE_COORDINATION_LOCK,
	  READER_CACHE_COORDINATION_STORAGE_KEY: () => READER_CACHE_COORDINATION_STORAGE_KEY,
	  READER_RESPONSE_CACHE_DATABASE: () => READER_RESPONSE_CACHE_DATABASE,
	  READER_RESPONSE_CACHE_STORE: () => READER_RESPONSE_CACHE_STORE,
	  ReaderDataRuntime: () => ReaderDataRuntime
	});
	module.exports = __toCommonJS(reader_data_runtime_exports);
	var import_cache_coordination = require("../cache/cache-coordination.js"), import_indexeddb_response_cache_store = require("../cache/indexeddb-response-cache-store.js"), import_response_repository = require("../cache/response-repository.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_coordinated_request_client = require("../network/coordinated-request-client.js"), import_domain_request_gateway = require("../network/domain-request-gateway.js"), import_request_rate_limit_policy = require("../network/request-rate-limit-policy.js"), import_request_observer = require("../network/request-observer.js"), import_read_state_coordination = require("../reading/read-state-coordination.js"), import_reader_topic_core_bundle = require("../topic/reader-topic-core-bundle.js");
	const READER_RESPONSE_CACHE_DATABASE = "linuxdo-enhanced-reader:responses:v1", READER_RESPONSE_CACHE_STORE = "responses", READER_CACHE_COORDINATION_STORAGE_KEY = "linuxdo-enhanced-reader:cache-coordination:v1", READER_CACHE_COORDINATION_LOCK = "linuxdo-enhanced-reader:cache-coordination-lock:v1", READER_CACHE_COORDINATION_CHANNEL = "linuxdo-enhanced-reader:cache-coordination-channel:v1";
	function sourceId(value) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error("Reader data runtime sourceId 不能为空");
	  return normalized;
	}
	function browserChannelFactory(value) {
	  return value !== void 0 ? value : typeof BroadcastChannel > "u" ? null : (name) => new BroadcastChannel(name);
	}
	class ReaderDataRuntime {
	  scope;
	  rateLimit;
	  requests;
	  client;
	  responses;
	  gateway;
	  cacheCoordination;
	  readCoordination;
	  #destroyed = !1;
	  constructor(options) {
	    const id = sourceId(options.sourceId), report = (phase, cause) => {
	      options.onDiagnostic?.(Object.freeze({ phase, cause }));
	    };
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    try {
	      const channelFactory = browserChannelFactory(
	        options.broadcastChannelFactory
	      ), cacheChannel = new import_cache_coordination.BroadcastCacheCoordinationChannel({
	        name: READER_CACHE_COORDINATION_CHANNEL,
	        factory: channelFactory,
	        onError: (cause) => report("cache-coordination", cause)
	      }), cacheState = new import_cache_coordination.BrowserCacheCoordinationStatePort({
	        storage: options.storage,
	        storageKey: READER_CACHE_COORDINATION_STORAGE_KEY,
	        lockName: READER_CACHE_COORDINATION_LOCK,
	        locks: options.locks ?? null,
	        onError: (cause) => report("cache-coordination", cause)
	      });
	      this.cacheCoordination = new import_cache_coordination.CrossTabCacheCoordinator({
	        sourceId: id,
	        channel: cacheChannel,
	        state: cacheState,
	        flightTtlMs: options.cacheFlightTtlMs,
	        flightStaleMs: options.cacheFlightStaleMs,
	        ...options.now === void 0 ? {} : { now: options.now },
	        onError: (cause) => report("cache-coordination", cause)
	      }), this.scope.add(() => this.cacheCoordination.close());
	      const store = new import_indexeddb_response_cache_store.IndexedDbResponseCacheStore({
	        databaseName: READER_RESPONSE_CACHE_DATABASE,
	        storeName: READER_RESPONSE_CACHE_STORE,
	        operationTimeoutMs: options.responseOperationTimeoutMs,
	        maxEntries: options.responsePersistentMaxEntries,
	        maxBytes: options.responsePersistentMaxBytes,
	        factory: options.indexedDb ?? null,
	        ...options.now === void 0 ? {} : { now: options.now },
	        onError: (cause) => report("indexeddb", cause)
	      });
	      this.scope.add(() => {
	        store.close();
	      }), this.responses = new import_response_repository.ResponseRepository({
	        store,
	        maxMemoryEntries: options.responseMemoryMaxEntries,
	        maxMemoryBytes: options.responseMemoryMaxBytes,
	        mutationPort: this.cacheCoordination,
	        flightPort: this.cacheCoordination,
	        ...options.cacheFlightHeartbeatMs === void 0 ? {} : { flightHeartbeatMs: options.cacheFlightHeartbeatMs },
	        ...options.cacheFlightWaitTimeoutMs === void 0 ? {} : { flightWaitTimeoutMs: options.cacheFlightWaitTimeoutMs },
	        ...options.now === void 0 ? {} : { now: options.now },
	        onPersistenceError: (cause) => report("indexeddb", cause)
	      }), this.scope.add(this.cacheCoordination.subscribeInvalidation((query) => {
	        this.responses.applyExternalInvalidation(query);
	      }));
	      const readChannel = channelFactory ? new import_read_state_coordination.BroadcastReadStateChannel({
	        createChannel: channelFactory,
	        onListenerError: (cause) => report("read-coordination", cause)
	      }) : void 0, lock = options.locks ? (name, task) => options.locks.request(name, { mode: "exclusive" }, task) : void 0;
	      this.readCoordination = new import_read_state_coordination.BrowserReadStateCoordinator({
	        storage: options.storage,
	        ...readChannel === void 0 ? {} : { channel: readChannel },
	        ...lock === void 0 ? {} : { lock },
	        ...options.now === void 0 ? {} : { now: options.now },
	        ...options.readCoordinationTtlMs === void 0 ? {} : { ttlMs: options.readCoordinationTtlMs },
	        ...options.readCoordinationMaxRecords === void 0 ? {} : { maxRecords: options.readCoordinationMaxRecords },
	        onCoordinationError: (cause) => report("read-coordination", cause)
	      }), this.scope.add(() => this.readCoordination.close()), this.rateLimit = new import_request_rate_limit_policy.RequestRateLimitPolicy(options.rateLimit), this.requests = new import_request_observer.RequestObserver({
	        baseHref: options.rateLimit.baseUrl ?? "https://invalid.local/",
	        retentionMs: 15 * 6e4,
	        maxEntries: 1200,
	        ...options.now === void 0 ? {} : { now: options.now }
	      }), this.client = new import_coordinated_request_client.CoordinatedRequestClient({
	        scheduler: options.scheduler,
	        rateLimitPolicy: this.rateLimit,
	        permitPort: options.permit,
	        observer: this.requests,
	        ...options.now === void 0 ? {} : { now: options.now },
	        ...options.defaultMax429Retries === void 0 ? {} : { defaultMax429Retries: options.defaultMax429Retries },
	        onCoordinationError: (cause) => report("request-coordination", cause)
	      }), this.scope.add(() => this.client.destroy()), this.gateway = new import_domain_request_gateway.DomainRequestGateway(this.client, this.responses);
	    } catch (error) {
	      throw this.scope.destroy(), error;
	    }
	  }
	  createTopicBundle(context, options) {
	    if (this.#destroyed || this.scope.destroyed)
	      throw new Error("ReaderDataRuntime 已销毁");
	    return (0, import_reader_topic_core_bundle.createReaderTopicCoreBundle)(context, {
	      ...options,
	      gateway: this.gateway,
	      responses: this.responses,
	      readCoordination: this.readCoordination
	    });
	  }
	  applyRequestRuntimePolicy(policy) {
	    this.#destroyed || this.scope.destroyed || this.client.applyRuntimePolicy(policy);
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	}
}, "f9a9d054dc7203aa759bf367dfe44a0063643f22727925396089efc5825942b4");

/* Source: lite/src/app/reader-performance-policy.ts */
runtime.register("src/app/reader-performance-policy.js", function(module, exports, require) {
	var reader_performance_policy_exports = {};
	__export(reader_performance_policy_exports, {
	  ReaderPerformancePolicy: () => ReaderPerformancePolicy,
	  readBrowserPerformanceCapabilities: () => readBrowserPerformanceCapabilities,
	  readerBulkBackgroundRequestHasHeadroom: () => readerBulkBackgroundRequestHasHeadroom
	});
	module.exports = __toCommonJS(reader_performance_policy_exports);
	const DEFAULTS = Object.freeze({
	  pageSize: 48,
	  streamOverscanScreens: 1.5,
	  streamMaxMountedPostCount: 80,
	  nestedPrefetchScreens: 2.5,
	  requestMaxConcurrent: 3,
	  requestMinIntervalMs: 100,
	  requestRateTargetPercent: 85
	}), BULK_BACKGROUND_REQUEST_BUDGET_SHARE = 0.5;
	function finiteRange(value, fallback, minimum, maximum) {
	  return Number.isFinite(value) ? Math.min(maximum, Math.max(minimum, value)) : fallback;
	}
	function integerRange(value, fallback, minimum, maximum) {
	  return Math.round(finiteRange(value, fallback, minimum, maximum));
	}
	function positiveInteger(value, name) {
	  const normalized = Number(value);
	  if (!Number.isSafeInteger(normalized) || normalized < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return normalized;
	}
	function readerBulkBackgroundRequestHasHeadroom(input, nestedReplies = !1) {
	  const shortLimit = Math.max(
	    1,
	    Math.min(
	      Math.floor(
	        positiveInteger(input.shortBudget, "shortBudget") * BULK_BACKGROUND_REQUEST_BUDGET_SHARE
	      ),
	      nestedReplies ? 8 : Number.MAX_SAFE_INTEGER
	    )
	  ), longLimit = Math.max(
	    1,
	    Math.min(
	      Math.floor(
	        positiveInteger(input.longBudget, "longBudget") * BULK_BACKGROUND_REQUEST_BUDGET_SHARE
	      ),
	      nestedReplies ? 24 : Number.MAX_SAFE_INTEGER
	    )
	  ), normalizedCount = (value) => Number.isFinite(value) && value >= 0 ? Math.floor(value) : Number.MAX_SAFE_INTEGER;
	  return normalizedCount(input.shortCount) < shortLimit && normalizedCount(input.longCount) < longLimit;
	}
	function positiveFinite(value) {
	  const normalized = Number(value);
	  return Number.isFinite(normalized) && normalized > 0 ? normalized : void 0;
	}
	function readBrowserPerformanceCapabilities(navigatorValue) {
	  const source = navigatorValue, logicalProcessors = positiveFinite(source?.hardwareConcurrency), memoryGiB = positiveFinite(source?.deviceMemory), effectiveType = String(
	    source?.connection?.effectiveType ?? ""
	  ).trim().toLowerCase();
	  return Object.freeze({
	    ...logicalProcessors === void 0 ? {} : { logicalProcessors },
	    ...memoryGiB === void 0 ? {} : { memoryGiB },
	    ...source?.connection?.saveData === !0 ? { saveData: !0 } : {},
	    ...effectiveType ? { effectiveType } : {}
	  });
	}
	function runtimeScales(capabilities) {
	  const cores = positiveFinite(capabilities.logicalProcessors), memory = positiveFinite(capabilities.memoryGiB), coreScale = cores === void 0 ? 1 : cores <= 2 ? 0.55 : cores <= 4 ? 0.75 : cores <= 6 ? 0.9 : 1, memoryScale = memory === void 0 ? 1 : memory <= 2 ? 0.55 : memory <= 4 ? 0.75 : memory <= 6 ? 0.9 : 1, connectionScale = capabilities.saveData === !0 ? 0.55 : capabilities.effectiveType === "slow-2g" ? 0.45 : capabilities.effectiveType === "2g" ? 0.6 : capabilities.effectiveType === "3g" ? 0.8 : 1;
	  return Object.freeze({
	    render: Math.min(coreScale, memoryScale),
	    network: connectionScale
	  });
	}
	function snapshot(preferences, shortBudgetCeiling, longBudgetCeiling, capabilities) {
	  const scales = runtimeScales(capabilities), batchScale = Math.min(scales.render, scales.network), pageSize = integerRange(
	    preferences.performancePageSize,
	    DEFAULTS.pageSize,
	    12,
	    64
	  ), streamMaxMountedPostCount = integerRange(
	    preferences.performanceStreamMaxItems,
	    DEFAULTS.streamMaxMountedPostCount,
	    24,
	    128
	  ), nestedPrefetchScreens = finiteRange(
	    preferences.performanceNestedPrefetch,
	    DEFAULTS.nestedPrefetchScreens,
	    1,
	    3
	  ), requestMaxConcurrent = integerRange(
	    preferences.performanceRequestConcurrency,
	    DEFAULTS.requestMaxConcurrent,
	    1,
	    4
	  ), requestMinIntervalMs = integerRange(
	    preferences.performanceRequestInterval,
	    DEFAULTS.requestMinIntervalMs,
	    80,
	    500
	  ), requestRateTargetPercent = integerRange(
	    preferences.performanceRequestRateTarget,
	    DEFAULTS.requestRateTargetPercent,
	    50,
	    95
	  ), target = requestRateTargetPercent / 100;
	  return Object.freeze({
	    pageSize: integerRange(
	      Math.floor(pageSize * batchScale),
	      pageSize,
	      12,
	      64
	    ),
	    streamOverscanScreens: finiteRange(
	      preferences.performanceStreamOverscan,
	      DEFAULTS.streamOverscanScreens,
	      0.25,
	      3
	    ),
	    streamMaxMountedPostCount: integerRange(
	      Math.floor(streamMaxMountedPostCount * scales.render),
	      streamMaxMountedPostCount,
	      24,
	      128
	    ),
	    nestedPrefetchScreens: finiteRange(
	      nestedPrefetchScreens * scales.network,
	      nestedPrefetchScreens,
	      1,
	      3
	    ),
	    requestMaxConcurrent: integerRange(
	      Math.round(requestMaxConcurrent * batchScale),
	      requestMaxConcurrent,
	      1,
	      4
	    ),
	    requestMinIntervalMs: integerRange(
	      Math.ceil(requestMinIntervalMs / Math.max(0.35, batchScale)),
	      requestMinIntervalMs,
	      80,
	      500
	    ),
	    requestRateTargetPercent,
	    requestShortBudget: Math.max(
	      1,
	      Math.floor(shortBudgetCeiling * target)
	    ),
	    requestLongBudget: Math.max(
	      1,
	      Math.floor(longBudgetCeiling * target)
	    )
	  });
	}
	class ReaderPerformancePolicy {
	  #shortBudgetCeiling;
	  #longBudgetCeiling;
	  #capabilities;
	  #snapshot;
	  constructor(options) {
	    this.#shortBudgetCeiling = positiveInteger(
	      options.shortBudgetCeiling,
	      "shortBudgetCeiling"
	    ), this.#longBudgetCeiling = positiveInteger(
	      options.longBudgetCeiling,
	      "longBudgetCeiling"
	    ), this.#capabilities = Object.freeze({
	      ...options.capabilities ?? {}
	    }), this.#snapshot = snapshot(
	      options.preferences,
	      this.#shortBudgetCeiling,
	      this.#longBudgetCeiling,
	      this.#capabilities
	    );
	  }
	  get value() {
	    return this.#snapshot;
	  }
	  apply(preferences) {
	    const next = snapshot(
	      preferences,
	      this.#shortBudgetCeiling,
	      this.#longBudgetCeiling,
	      this.#capabilities
	    );
	    return Object.entries(next).every(
	      ([key, value]) => this.#snapshot[key] === value
	    ) ? this.#snapshot : (this.#snapshot = next, next);
	  }
	}
}, "ead7b71ce6684b133ddaf00133eead7706b2fa9104aae06dbd69f3feb82f2f11");

/* Source: lite/src/cache/browser-asset-cache.ts */
runtime.register("src/cache/browser-asset-cache.js", function(module, exports, require) {
	var browser_asset_cache_exports = {};
	__export(browser_asset_cache_exports, {
	  READER_ASSET_CACHE_GROUPS: () => READER_ASSET_CACHE_GROUPS,
	  ReaderBrowserAssetCacheRepository: () => ReaderBrowserAssetCacheRepository
	});
	module.exports = __toCommonJS(browser_asset_cache_exports);
	const READER_ASSET_CACHE_GROUPS = Object.freeze([
	  Object.freeze({
	    id: "avatar",
	    label: "头像",
	    cacheName: "linuxdo-enhanced-reader:avatars:v1"
	  }),
	  Object.freeze({
	    id: "emoji",
	    label: "表情",
	    cacheName: "linuxdo-enhanced-reader:emoji-images:v1"
	  }),
	  Object.freeze({
	    id: "original",
	    label: "原图",
	    cacheName: "linuxdo-enhanced-reader:lightbox-images:v1"
	  })
	]), CACHE_STAT_BATCH_SIZE = 32;
	function responseBytes(response) {
	  const header = response.headers.get("content-length"), declared = header === null ? Number.NaN : Number(header);
	  return Number.isFinite(declared) && declared >= 0 ? declared : response.blob().then((blob) => blob.size);
	}
	function emptyGroup(definition, state) {
	  return Object.freeze({
	    ...definition,
	    count: 0,
	    bytes: 0,
	    state
	  });
	}
	class ReaderBrowserAssetCacheRepository {
	  #storage;
	  constructor(storage) {
	    this.#storage = storage;
	  }
	  async stats() {
	    let existing;
	    try {
	      existing = new Set(await this.#storage.keys());
	    } catch {
	      const errors2 = READER_ASSET_CACHE_GROUPS.map(({ label }) => `${label}缓存目录不可用`);
	      return Object.freeze({
	        count: 0,
	        bytes: 0,
	        groups: Object.freeze(
	          READER_ASSET_CACHE_GROUPS.map((group) => emptyGroup(group, "error"))
	        ),
	        errors: Object.freeze(errors2)
	      });
	    }
	    const errors = [], groups = [];
	    for (const definition of READER_ASSET_CACHE_GROUPS) {
	      if (!existing.has(definition.cacheName)) {
	        groups.push(emptyGroup(definition, "missing"));
	        continue;
	      }
	      try {
	        const cache = await this.#storage.open(definition.cacheName), requests = await cache.keys();
	        let bytes = 0;
	        for (let offset = 0; offset < requests.length; offset += CACHE_STAT_BATCH_SIZE) {
	          const batch = requests.slice(
	            offset,
	            offset + CACHE_STAT_BATCH_SIZE
	          ), sizes = await Promise.all(batch.map(async (request) => {
	            const response = await cache.match(request);
	            return response ? responseBytes(response) : 0;
	          }));
	          bytes += sizes.reduce((total, size) => total + size, 0);
	        }
	        groups.push(Object.freeze({
	          ...definition,
	          count: requests.length,
	          bytes,
	          state: "available"
	        }));
	      } catch {
	        errors.push(`${definition.label}缓存统计失败`), groups.push(emptyGroup(definition, "error"));
	      }
	    }
	    return Object.freeze({
	      count: groups.reduce((total, group) => total + group.count, 0),
	      bytes: groups.reduce((total, group) => total + group.bytes, 0),
	      groups: Object.freeze(groups),
	      errors: Object.freeze(errors)
	    });
	  }
	  async clear() {
	    const deleted = [], missing = [], failed = [];
	    return await Promise.all(READER_ASSET_CACHE_GROUPS.map(async (definition) => {
	      try {
	        await this.#storage.delete(definition.cacheName) ? deleted.push(definition.id) : missing.push(definition.id);
	      } catch {
	        failed.push(definition.id);
	      }
	    })), Object.freeze({
	      deleted: Object.freeze(deleted),
	      missing: Object.freeze(missing),
	      failed: Object.freeze(failed)
	    });
	  }
	}
}, "2d25f3dbe9f24884170a8d18afbc4fa6b627c60f06d2f902087cdd418d9a21c7");

/* Source: lite/src/cache/cache-coordination.ts */
runtime.register("src/cache/cache-coordination.js", function(module, exports, require) {
	var cache_coordination_exports = {};
	__export(cache_coordination_exports, {
	  BroadcastCacheCoordinationChannel: () => BroadcastCacheCoordinationChannel,
	  BrowserCacheCoordinationStatePort: () => BrowserCacheCoordinationStatePort,
	  CrossTabCacheCoordinator: () => CrossTabCacheCoordinator,
	  normalizeCacheInvalidation: () => normalizeCacheInvalidation
	});
	module.exports = __toCommonJS(cache_coordination_exports);
	function positiveInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	function uniqueStrings(values) {
	  if (!values) return;
	  const normalized = [...new Set(values.map(String).map((value) => value.trim()).filter(Boolean))].sort();
	  return normalized.length ? Object.freeze(normalized) : void 0;
	}
	function normalizeCacheInvalidation(query) {
	  if (query.all) return Object.freeze({ all: !0 });
	  const ids = uniqueStrings(query.ids), kinds = uniqueStrings(query.kinds), tags = uniqueStrings(query.tags);
	  return !ids && !kinds && !tags ? null : Object.freeze({
	    ...ids ? { ids } : {},
	    ...kinds ? { kinds } : {},
	    ...tags ? { tags } : {}
	  });
	}
	function normalizeState(raw, now, staleAfterMs) {
	  const source = raw && typeof raw == "object" ? raw : {}, flights = Array.isArray(source.flights) ? source.flights.filter((flight) => !flight || typeof flight != "object" ? !1 : String(flight.token || "").length > 0 && String(flight.flightId || "").length > 0 && String(flight.ownerId || "").length > 0 && Number.isFinite(flight.epoch) && Number(flight.expiresAt) > now && Number(flight.heartbeatAt) > now - staleAfterMs) : [], failures = Array.isArray(source.failures) ? source.failures.filter((failure) => !failure || typeof failure != "object" ? !1 : String(failure.token || "").length > 0 && String(failure.flightId || "").length > 0 && Number(failure.expiresAt) > now && (failure.kind === "cloudflare" || failure.kind === "rate-limit") && Number.isSafeInteger(Number(failure.status)) && Number(failure.status) >= 400) : [];
	  return {
	    schemaVersion: 1,
	    epoch: Math.max(0, Math.floor(Number(source.epoch) || 0)),
	    updatedAt: Math.max(0, Number(source.updatedAt) || 0),
	    flights: flights.map((flight) => Object.freeze({
	      token: String(flight.token),
	      flightId: String(flight.flightId),
	      ownerId: String(flight.ownerId),
	      epoch: Math.max(0, Math.floor(Number(flight.epoch) || 0)),
	      heartbeatAt: Number(flight.heartbeatAt),
	      expiresAt: Number(flight.expiresAt)
	    })).sort((left, right) => left.expiresAt - right.expiresAt).slice(-128),
	    failures: failures.map((failure) => Object.freeze({
	      token: String(failure.token),
	      flightId: String(failure.flightId),
	      expiresAt: Number(failure.expiresAt),
	      status: Number(failure.status),
	      cloudflareMitigated: failure.cloudflareMitigated === !0,
	      kind: failure.kind
	    })).sort((left, right) => left.expiresAt - right.expiresAt).slice(-128)
	  };
	}
	function immutableState(state) {
	  return Object.freeze({
	    schemaVersion: 1,
	    epoch: state.epoch,
	    updatedAt: state.updatedAt,
	    flights: Object.freeze([...state.flights]),
	    failures: Object.freeze([...state.failures])
	  });
	}
	class BrowserCacheCoordinationStatePort {
	  atomic;
	  #storage;
	  #storageKey;
	  #lockName;
	  #locks;
	  #onError;
	  constructor(options) {
	    if (this.#storage = options.storage, this.#storageKey = String(options.storageKey).trim(), this.#lockName = String(options.lockName).trim(), !this.#storageKey || !this.#lockName)
	      throw new Error("cache coordination storageKey/lockName 不能为空");
	    this.#locks = options.locks ?? null, this.atomic = !!this.#locks, this.#onError = options.onError ?? (() => {
	    });
	  }
	  async read(now, staleAfterMs) {
	    return immutableState(this.#readMutable(now, staleAfterMs));
	  }
	  async transact(now, staleAfterMs, operation) {
	    const execute = async () => {
	      const state = this.#readMutable(now, staleAfterMs), result = await operation(state);
	      state.updatedAt = now;
	      try {
	        this.#storage.setItem(this.#storageKey, JSON.stringify(state));
	      } catch (error) {
	        throw this.#onError(error), error;
	      }
	      return result;
	    };
	    return this.#locks ? this.#locks.request(this.#lockName, { mode: "exclusive" }, execute) : execute();
	  }
	  #readMutable(now, staleAfterMs) {
	    try {
	      return normalizeState(
	        JSON.parse(this.#storage.getItem(this.#storageKey) || "null"),
	        now,
	        staleAfterMs
	      );
	    } catch (error) {
	      return this.#onError(error), normalizeState(null, now, staleAfterMs);
	    }
	  }
	}
	class BroadcastCacheCoordinationChannel {
	  #channel;
	  #listeners = /* @__PURE__ */ new Set();
	  #onError;
	  constructor(options) {
	    const name = String(options.name).trim();
	    if (!name) throw new Error("BroadcastChannel name 不能为空");
	    this.#onError = options.onError ?? (() => {
	    });
	    const factory = options.factory === void 0 ? typeof BroadcastChannel > "u" ? null : (channelName) => new BroadcastChannel(channelName) : options.factory;
	    let channel = null;
	    try {
	      channel = factory?.(name) ?? null, channel?.addEventListener("message", this.#onMessage);
	    } catch (error) {
	      this.#onError(error);
	    }
	    this.#channel = channel;
	  }
	  publish(message) {
	    try {
	      this.#channel?.postMessage(message);
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  subscribe(listener) {
	    return this.#listeners.add(listener), () => this.#listeners.delete(listener);
	  }
	  close() {
	    this.#channel?.removeEventListener("message", this.#onMessage), this.#channel?.close(), this.#listeners.clear();
	  }
	  #onMessage = (event) => {
	    for (const listener of this.#listeners) listener(event.data);
	  };
	}
	class CrossTabCacheCoordinator {
	  coordinationMode;
	  #sourceId;
	  #channel;
	  #state;
	  #flightTtlMs;
	  #flightStaleMs;
	  #now;
	  #createId;
	  #onError;
	  #listeners = /* @__PURE__ */ new Set();
	  #waiters = /* @__PURE__ */ new Set();
	  #unsubscribe;
	  #closed = !1;
	  constructor(options) {
	    if (this.#sourceId = String(options.sourceId).trim(), !this.#sourceId) throw new Error("cache coordinator sourceId 不能为空");
	    this.#channel = options.channel, this.#state = options.state, this.#flightTtlMs = positiveInteger(options.flightTtlMs, "flightTtlMs"), this.#flightStaleMs = positiveInteger(options.flightStaleMs, "flightStaleMs"), this.#now = options.now ?? Date.now, this.#createId = options.createId ?? (() => `${this.#sourceId}:${this.#now().toString(36)}:${Math.random().toString(36).slice(2)}`), this.#onError = options.onError ?? (() => {
	    }), this.coordinationMode = options.state.atomic ? "atomic" : "mutation-only", this.#unsubscribe = this.#channel.subscribe((message) => this.#receive(message));
	  }
	  publish(query) {
	    const normalized = normalizeCacheInvalidation(query);
	    !normalized || this.#closed || this.#channel.publish(Object.freeze({
	      schemaVersion: 1,
	      sourceId: this.#sourceId,
	      type: "invalidate",
	      query: normalized
	    }));
	  }
	  subscribeInvalidation(listener) {
	    return this.#closed ? () => {
	    } : (this.#listeners.add(listener), () => this.#listeners.delete(listener));
	  }
	  async acquireFlight(rawToken) {
	    const token = String(rawToken).trim();
	    if (!token || !this.#state.atomic) return this.#uncoordinatedLease(token);
	    const now = this.#now();
	    try {
	      const lease = await this.#state.transact(now, this.#flightStaleMs, (state) => {
	        const existing = state.flights.find((flight2) => flight2.token === token);
	        if (existing)
	          return Object.freeze({
	            producer: !1,
	            token,
	            flightId: existing.flightId,
	            epoch: existing.epoch,
	            expiresAt: existing.expiresAt,
	            coordinated: !0
	          });
	        const flightId = this.#createId(), flight = Object.freeze({
	          token,
	          flightId,
	          ownerId: this.#sourceId,
	          epoch: state.epoch,
	          heartbeatAt: now,
	          expiresAt: now + this.#flightTtlMs
	        });
	        return state.flights.push(flight), Object.freeze({
	          producer: !0,
	          token,
	          flightId,
	          epoch: state.epoch,
	          expiresAt: flight.expiresAt,
	          coordinated: !0
	        });
	      });
	      return this.#publishState(), lease;
	    } catch (error) {
	      return this.#onError(error), this.#uncoordinatedLease(token);
	    }
	  }
	  async renewFlight(lease) {
	    if (!lease.coordinated || !lease.producer || !this.#state.atomic) return !1;
	    const now = this.#now();
	    try {
	      const renewed = await this.#state.transact(now, this.#flightStaleMs, (state) => {
	        const index = state.flights.findIndex((flight) => flight.token === lease.token && flight.flightId === lease.flightId && flight.ownerId === this.#sourceId);
	        if (index < 0) return !1;
	        const current = state.flights[index];
	        return current ? (state.flights[index] = Object.freeze({
	          ...current,
	          heartbeatAt: now,
	          expiresAt: now + this.#flightTtlMs
	        }), !0) : !1;
	      });
	      return renewed && this.#publishState(), renewed;
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	  }
	  async releaseFlight(lease) {
	    if (!lease.coordinated || !lease.producer || !this.#state.atomic) return;
	    const now = this.#now();
	    try {
	      await this.#state.transact(now, this.#flightStaleMs, (state) => {
	        state.flights = state.flights.filter((flight) => flight.token !== lease.token || flight.flightId !== lease.flightId || flight.ownerId !== this.#sourceId);
	      }), this.#publishState();
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  async waitForFlight(rawToken, signal, deadline = this.#now() + this.#flightTtlMs * 2) {
	    const token = String(rawToken).trim();
	    if (!token || !this.#state.atomic) return !0;
	    for (; !this.#closed && this.#now() < deadline; ) {
	      if (signal?.aborted) throw this.#abortError(signal);
	      let state;
	      try {
	        state = await this.#state.read(this.#now(), this.#flightStaleMs);
	      } catch (error) {
	        return this.#onError(error), !0;
	      }
	      const flight = state.flights.find((candidate) => candidate.token === token);
	      if (!flight) return !0;
	      await this.#wait(
	        Math.min(1e3, Math.max(25, flight.expiresAt - this.#now()), deadline - this.#now()),
	        signal
	      );
	    }
	    if (signal?.aborted) throw this.#abortError(signal);
	    return !1;
	  }
	  async failFlight(lease, failure) {
	    if (!lease.coordinated || !lease.producer || !this.#state.atomic) return !1;
	    const now = this.#now();
	    try {
	      const committed = await this.#state.transact(
	        now,
	        this.#flightStaleMs,
	        (state) => {
	          const index = state.flights.findIndex((flight) => flight.token === lease.token && flight.flightId === lease.flightId && flight.ownerId === this.#sourceId);
	          return index < 0 ? !1 : (state.flights.splice(index, 1), state.failures = state.failures.filter((candidate) => candidate.flightId !== lease.flightId), state.failures.push(Object.freeze({
	            token: lease.token,
	            flightId: lease.flightId,
	            expiresAt: now + this.#flightTtlMs,
	            status: failure.status,
	            cloudflareMitigated: failure.cloudflareMitigated,
	            kind: failure.kind
	          })), !0);
	        }
	      );
	      return committed && this.#publishState(), committed;
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	  }
	  async readFlightFailure(lease) {
	    if (!lease.coordinated || !this.#state.atomic) return null;
	    try {
	      const failure = (await this.#state.read(this.#now(), this.#flightStaleMs)).failures?.find((candidate) => candidate.token === lease.token && candidate.flightId === lease.flightId);
	      return failure ? Object.freeze({
	        status: failure.status,
	        cloudflareMitigated: failure.cloudflareMitigated,
	        kind: failure.kind
	      }) : null;
	    } catch (error) {
	      return this.#onError(error), null;
	    }
	  }
	  async invalidateWrites() {
	    if (!this.#state.atomic)
	      return this.#publishState(), 0;
	    const now = this.#now();
	    try {
	      const epoch = await this.#state.transact(now, this.#flightStaleMs, (state) => (state.epoch += 1, state.epoch));
	      return this.#publishState(), epoch;
	    } catch (error) {
	      return this.#onError(error), 0;
	    }
	  }
	  async commitFlight(lease, operation) {
	    if (!lease.coordinated || !this.#state.atomic)
	      return await operation(), !0;
	    try {
	      return await this.#state.transact(this.#now(), this.#flightStaleMs, async (state) => state.epoch === lease.epoch && state.flights.some((flight) => flight.token === lease.token && flight.flightId === lease.flightId && flight.ownerId === this.#sourceId) ? (await operation(), !0) : !1);
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	  }
	  close() {
	    this.#closed || (this.#closed = !0, this.#unsubscribe(), this.#channel.close(), this.#listeners.clear(), this.#notifyWaiters());
	  }
	  #receive(raw) {
	    if (!raw || typeof raw != "object") return;
	    const message = raw;
	    if (!(message.schemaVersion !== 1 || message.sourceId === this.#sourceId || message.type !== "invalidate" && message.type !== "state")) {
	      if (message.type === "invalidate") {
	        const query = normalizeCacheInvalidation(message.query ?? {});
	        if (query)
	          for (const listener of this.#listeners)
	            try {
	              listener(query);
	            } catch (error) {
	              this.#onError(error);
	            }
	      }
	      this.#notifyWaiters();
	    }
	  }
	  #publishState() {
	    this.#closed || (this.#channel.publish(Object.freeze({
	      schemaVersion: 1,
	      sourceId: this.#sourceId,
	      type: "state"
	    })), this.#notifyWaiters());
	  }
	  #notifyWaiters() {
	    for (const waiter of this.#waiters) waiter();
	    this.#waiters.clear();
	  }
	  #wait(milliseconds, signal) {
	    return new Promise((resolve, reject) => {
	      let settled = !1;
	      const finish = (error) => {
	        settled || (settled = !0, clearTimeout(timer), this.#waiters.delete(wake), signal?.removeEventListener("abort", abort), error !== void 0 ? reject(error) : resolve());
	      }, wake = () => finish(), abort = () => finish(this.#abortError(signal)), timer = setTimeout(wake, Math.max(0, milliseconds));
	      this.#waiters.add(wake), signal?.addEventListener("abort", abort, { once: !0 });
	    });
	  }
	  #abortError(signal) {
	    return signal?.reason ?? new DOMException("Aborted", "AbortError");
	  }
	  #uncoordinatedLease(token) {
	    return Object.freeze({
	      producer: !0,
	      token,
	      flightId: "",
	      epoch: 0,
	      expiresAt: 0,
	      coordinated: !1
	    });
	  }
	}
}, "426097a26de19d79f96aa759e8005745ffdcb42c7015d0cc7750b2e276348878");

/* Source: lite/src/cache/cache-identity.ts */
runtime.register("src/cache/cache-identity.js", function(module, exports, require) {
	var cache_identity_exports = {};
	__export(cache_identity_exports, {
	  sharedCacheIdToken: () => sharedCacheIdToken
	});
	module.exports = __toCommonJS(cache_identity_exports);
	function sharedCacheIdToken(value) {
	  const source = String(value);
	  let hash = 2166136261;
	  for (let index = 0; index < source.length; index += 1)
	    hash ^= source.charCodeAt(index), hash = Math.imul(hash, 16777619);
	  return (hash >>> 0).toString(36);
	}
}, "fca985f86f39ba26872e25e42a855fbaff32da679df43591f16ca5e46a5e1157");

/* Source: lite/src/cache/discourse-application-cache-invalidation.ts */
runtime.register("src/cache/discourse-application-cache-invalidation.js", function(module, exports, require) {
	var discourse_application_cache_invalidation_exports = {};
	__export(discourse_application_cache_invalidation_exports, {
	  DiscourseApplicationCacheInvalidationCoordinator: () => DiscourseApplicationCacheInvalidationCoordinator
	});
	module.exports = __toCommonJS(discourse_application_cache_invalidation_exports);
	var import_native_host_api = require("../discourse/native-host-api.js"), import_lifecycle = require("../kernel/lifecycle.js");
	function record(value) {
	  if (!value || typeof value != "object" || Array.isArray(value)) return null;
	  const source = value, toJSON = source.toJSON;
	  if (typeof toJSON != "function") return source;
	  try {
	    return record(toJSON.call(value)) ?? source;
	  } catch {
	    return source;
	  }
	}
	function positiveId(value) {
	  const id = Number(value);
	  return Number.isSafeInteger(id) && id > 0 ? id : null;
	}
	function postIdentity(value) {
	  const envelope = record(value), post = record(envelope?.post) ?? envelope;
	  return Object.freeze({
	    postId: positiveId(post?.id),
	    topicId: positiveId(post?.topic_id) ?? positiveId(record(post?.topic)?.id)
	  });
	}
	function eventPost(value) {
	  const envelope = record(value);
	  return record(envelope?.post) ?? envelope;
	}
	class DiscourseApplicationCacheInvalidationCoordinator {
	  scope;
	  #options;
	  #pending = /* @__PURE__ */ new Set();
	  #scheduled = !1;
	  #flushPromise = null;
	  constructor(options) {
	    this.#options = options, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#bindReactionEvents(), this.scope.add(options.composerEvents.subscribe((event) => {
	      this.#onComposerEvent(event);
	    })), this.scope.add(() => {
	      this.#pending.clear(), this.#scheduled = !1;
	    });
	  }
	  flush() {
	    if (this.scope.destroyed) return this.#flushPromise ?? Promise.resolve();
	    if (this.#scheduled = !1, !this.#pending.size) return this.#flushPromise ?? Promise.resolve();
	    const tags = Object.freeze([...this.#pending].sort());
	    this.#pending.clear();
	    const transaction = (this.#flushPromise ?? Promise.resolve()).catch(() => {
	    }).then(async () => {
	      try {
	        await this.#options.cache.invalidate({ tags });
	      } catch (cause) {
	        this.#report(cause);
	      }
	    });
	    return this.#flushPromise = transaction, transaction.finally(() => {
	      this.#flushPromise === transaction && (this.#flushPromise = null), this.#pending.size && this.#schedule();
	    }), transaction;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #bindReactionEvents() {
	    const listener = (payload) => {
	      const post = eventPost(payload), identity = postIdentity(post ?? payload);
	      if (post)
	        try {
	          this.#options.onPostChanged?.(post);
	        } catch (cause) {
	          this.#report(cause);
	        }
	      this.#queue(["reactions-given"], identity);
	    };
	    this.scope.add((0, import_native_host_api.discourseNativeAppEventSubscription)(
	      this.#options.host,
	      "discourse-reactions:reaction-toggled",
	      listener,
	      (cause) => this.#report(cause)
	    ));
	  }
	  #onComposerEvent(event) {
	    const identity = postIdentity(event.payload), fallbackTopicId = event.kind === "edited" ? positiveId(this.#options.currentTopicId()) : null;
	    this.#queue([], Object.freeze({
	      postId: identity.postId,
	      topicId: identity.topicId ?? fallbackTopicId
	    }));
	  }
	  #queue(baseTags, identity) {
	    if (!this.scope.destroyed) {
	      for (const tag of baseTags) this.#pending.add(tag);
	      identity.postId !== null && this.#pending.add(`post:${identity.postId}`), identity.topicId !== null && this.#pending.add(`topic:${identity.topicId}`), this.#schedule();
	    }
	  }
	  #schedule() {
	    this.#scheduled || this.scope.destroyed || !this.#pending.size || (this.#scheduled = !0, queueMicrotask(() => {
	      this.scope.destroyed || (this.#scheduled = !1, this.flush());
	    }));
	  }
	  #report(cause) {
	    try {
	      this.#options.onError?.(cause);
	    } catch {
	    }
	  }
	}
}, "fe45e48ea5b25ee591dcd5cd1a9e7b072dbf709b3b0d371f04232238fac05d59");

/* Source: lite/src/cache/indexeddb-response-cache-store.ts */
runtime.register("src/cache/indexeddb-response-cache-store.js", function(module, exports, require) {
	var indexeddb_response_cache_store_exports = {};
	__export(indexeddb_response_cache_store_exports, {
	  IndexedDbResponseCacheStore: () => IndexedDbResponseCacheStore,
	  selectResponseCachePruneIds: () => selectResponseCachePruneIds
	});
	module.exports = __toCommonJS(indexeddb_response_cache_store_exports);
	function positiveInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	function matches(entry, query) {
	  return !!(query.all || query.ids?.includes(entry.id) || query.kinds?.includes(entry.kind) || query.tags?.some((tag) => entry.tags.includes(tag)));
	}
	function selectResponseCachePruneIds(entries, options) {
	  const sorted = [...entries].sort(
	    (left, right) => left.storedAt - right.storedAt || left.id.localeCompare(right.id)
	  ), remove = /* @__PURE__ */ new Set();
	  let retainedEntries = 0, retainedBytes = 0;
	  for (const entry of sorted) {
	    if (entry.schemaVersion !== 1 || !Number.isFinite(entry.expiresAt) || entry.permanent !== !0 && entry.expiresAt <= options.now) {
	      remove.add(entry.id);
	      continue;
	    }
	    entry.permanent !== !0 && (retainedEntries += 1, retainedBytes += Math.max(0, Number(entry.bytes) || 0));
	  }
	  for (const entry of sorted)
	    if (!remove.has(entry.id)) {
	      if (retainedEntries <= options.maxEntries && retainedBytes <= options.maxBytes) break;
	      entry.permanent !== !0 && (remove.add(entry.id), retainedEntries -= 1, retainedBytes -= Math.max(0, Number(entry.bytes) || 0));
	    }
	  return Object.freeze([...remove]);
	}
	class IndexedDbResponseCacheStore {
	  #databaseName;
	  #storeName;
	  #operationTimeoutMs;
	  #maxEntries;
	  #maxBytes;
	  #factory;
	  #now;
	  #onError;
	  #databasePromise = null;
	  #writesSincePrune = 0;
	  #prunePromise = null;
	  constructor(options) {
	    if (this.#databaseName = String(options.databaseName).trim(), this.#storeName = String(options.storeName).trim(), !this.#databaseName || !this.#storeName)
	      throw new Error("IndexedDB databaseName/storeName 不能为空");
	    this.#operationTimeoutMs = positiveInteger(
	      options.operationTimeoutMs,
	      "operationTimeoutMs"
	    ), this.#maxEntries = positiveInteger(options.maxEntries, "maxEntries"), this.#maxBytes = positiveInteger(options.maxBytes, "maxBytes"), this.#factory = options.factory === void 0 ? typeof indexedDB > "u" ? null : indexedDB : options.factory, this.#now = options.now ?? Date.now, this.#onError = options.onError ?? (() => {
	    });
	  }
	  async read(id) {
	    const result = await this.#transaction(
	      "readonly",
	      null,
	      (store, setValue) => {
	        const request = store.get(id);
	        request.onsuccess = () => {
	          const value = request.result;
	          setValue(value ?? null);
	        };
	      }
	    );
	    return result.ok || this.#report(result.error), result.ok ? result.value : null;
	  }
	  async write(entry) {
	    let result = await this.#put(entry);
	    !result.ok && this.#quotaError(result.error) && (await this.prune(!0), result = await this.#put(entry)), result.ok ? this.#recordSuccessfulWrite() : this.#report(result.error);
	  }
	  async invalidate(query) {
	    if (query.all) {
	      const result2 = await this.#transaction(
	        "readwrite",
	        !1,
	        (store) => {
	          store.clear();
	        }
	      );
	      return result2.ok || this.#report(result2.error), Object.freeze({ ok: result2.ok, error: result2.error });
	    }
	    const result = await this.#transaction(
	      "readwrite",
	      !1,
	      (store, setValue) => {
	        const request = store.openCursor();
	        request.onsuccess = () => {
	          const cursor = request.result;
	          if (!cursor) {
	            setValue(!0);
	            return;
	          }
	          const entry = cursor.value;
	          matches(entry, query) && cursor.delete(), cursor.continue();
	        };
	      }
	    );
	    return result.ok || this.#report(result.error), Object.freeze({ ok: result.ok, error: result.error });
	  }
	  async merge(id, update) {
	    let result = await this.#mergeEntry(id, update);
	    return !result.ok && this.#quotaError(result.error) && (await this.prune(!0), result = await this.#mergeEntry(id, update)), result.ok ? this.#recordSuccessfulWrite() : this.#report(result.error), result.ok ? result.value : null;
	  }
	  #mergeEntry(id, update) {
	    return this.#transaction(
	      "readwrite",
	      null,
	      (store, setValue) => {
	        const request = store.get(id);
	        request.onsuccess = () => {
	          try {
	            const next = update(
	              request.result ?? null
	            ), write = store.put(next);
	            write.onsuccess = () => setValue(next);
	          } catch {
	            try {
	              store.transaction.abort();
	            } catch {
	            }
	          }
	        };
	      }
	    );
	  }
	  async records() {
	    const entries = await this.snapshotEntries();
	    return Object.freeze(entries.map((entry) => Object.freeze({
	      id: entry.id,
	      kind: entry.kind,
	      tags: Object.freeze([...entry.tags]),
	      storedAt: entry.storedAt,
	      expiresAt: entry.expiresAt,
	      bytes: Math.max(0, Number(entry.bytes) || 0),
	      ...entry.permanent === !0 ? { permanent: !0 } : {}
	    })));
	  }
	  async snapshotEntries() {
	    const result = await this.#transaction(
	      "readonly",
	      [],
	      (store, setValue) => {
	        const request = store.getAll();
	        request.onsuccess = () => setValue(
	          Array.isArray(request.result) ? request.result : []
	        );
	      }
	    );
	    if (!result.ok) {
	      const error = result.error ?? new Error("IndexedDB cache directory unavailable");
	      throw this.#report(error), error;
	    }
	    return Object.freeze(result.value.map((entry) => Object.freeze({
	      ...entry,
	      tags: Object.freeze([...entry.tags])
	    })));
	  }
	  async prune(forceQuotaRecovery = !1) {
	    const entries = await this.#allEntries(), ids = selectResponseCachePruneIds(entries, {
	      now: this.#now(),
	      maxEntries: this.#maxEntries,
	      maxBytes: this.#maxBytes
	    }), pruneIds = ids.length ? ids : forceQuotaRecovery ? [...entries].filter((entry) => entry.permanent !== !0).sort((left, right) => left.storedAt - right.storedAt).slice(0, Math.max(1, Math.ceil(entries.length / 4))).map((entry) => entry.id) : [];
	    if (!pruneIds.length) return;
	    const result = await this.#deleteIds(pruneIds);
	    result.ok || this.#report(result.error);
	  }
	  async close() {
	    await this.#prunePromise, (await this.#databasePromise)?.close(), this.#databasePromise = null;
	  }
	  #open() {
	    if (this.#databasePromise) return this.#databasePromise;
	    const factory = this.#factory;
	    if (!factory) return Promise.resolve(null);
	    let promise;
	    return promise = new Promise((resolve) => {
	      let settled = !1, request;
	      const finish = (database, error) => {
	        if (settled) {
	          database?.close();
	          return;
	        }
	        settled = !0, clearTimeout(timeoutId), error !== void 0 && this.#report(error), resolve(database);
	      }, timeoutId = setTimeout(
	        () => finish(null, new Error("IndexedDB open timeout")),
	        this.#operationTimeoutMs
	      );
	      try {
	        request = factory.open(this.#databaseName, 1);
	      } catch (error) {
	        finish(null, error);
	        return;
	      }
	      request.onupgradeneeded = () => {
	        const database = request.result, store = database.objectStoreNames.contains(this.#storeName) ? request.transaction?.objectStore(this.#storeName) : database.createObjectStore(this.#storeName, { keyPath: "id" });
	        store && (store.indexNames.contains("kind") || store.createIndex("kind", "kind"), store.indexNames.contains("tags") || store.createIndex("tags", "tags", { multiEntry: !0 }), store.indexNames.contains("storedAt") || store.createIndex("storedAt", "storedAt"));
	      }, request.onsuccess = () => {
	        const database = request.result;
	        database.onversionchange = () => {
	          database.close(), this.#databasePromise === promise && (this.#databasePromise = null);
	        }, finish(database);
	      }, request.onerror = () => finish(null, request.error), request.onblocked = () => finish(null, new Error("IndexedDB open blocked"));
	    }), this.#databasePromise = promise, promise.then((database) => {
	      !database && this.#databasePromise === promise && (this.#databasePromise = null);
	    }), promise;
	  }
	  async #transaction(mode, initialValue, operation) {
	    const database = await this.#open();
	    return database ? new Promise((resolve) => {
	      let settled = !1, value = initialValue, transaction = null;
	      const finish = (ok, error = null) => {
	        settled || (settled = !0, clearTimeout(timeoutId), resolve({ ok, value, error }));
	      }, timeoutId = setTimeout(() => {
	        try {
	          transaction?.abort();
	        } catch {
	        }
	        finish(!1, new Error("IndexedDB transaction timeout"));
	      }, this.#operationTimeoutMs);
	      try {
	        const activeTransaction = database.transaction(this.#storeName, mode);
	        transaction = activeTransaction, activeTransaction.oncomplete = () => finish(!0), activeTransaction.onerror = () => finish(!1, activeTransaction.error), activeTransaction.onabort = () => finish(!1, activeTransaction.error), operation(activeTransaction.objectStore(this.#storeName), (nextValue) => {
	          value = nextValue;
	        });
	      } catch (error) {
	        finish(!1, error);
	      }
	    }) : { ok: !1, value: initialValue, error: new Error("IndexedDB unavailable") };
	  }
	  #put(entry) {
	    return this.#transaction("readwrite", !1, (store, setValue) => {
	      const request = store.put(entry);
	      request.onsuccess = () => setValue(!0);
	    });
	  }
	  #recordSuccessfulWrite() {
	    if (this.#writesSincePrune += 1, this.#writesSincePrune < 32 || (this.#writesSincePrune = 0, this.#prunePromise)) return;
	    const pruning = this.prune().catch((error) => this.#report(error)).finally(() => {
	      this.#prunePromise === pruning && (this.#prunePromise = null);
	    });
	    this.#prunePromise = pruning;
	  }
	  async #allEntries() {
	    const result = await this.#transaction(
	      "readonly",
	      [],
	      (store, setValue) => {
	        const request = store.getAll();
	        request.onsuccess = () => setValue(
	          Array.isArray(request.result) ? request.result : []
	        );
	      }
	    );
	    return result.ok || this.#report(result.error), result.ok ? result.value : [];
	  }
	  #deleteIds(ids) {
	    return this.#transaction("readwrite", !1, (store, setValue) => {
	      for (const id of ids) store.delete(id);
	      setValue(!0);
	    });
	  }
	  #quotaError(error) {
	    return error instanceof DOMException ? error.name === "QuotaExceededError" : String(error?.name ?? "") === "QuotaExceededError";
	  }
	  #report(error) {
	    error != null && this.#onError(error);
	  }
	}
}, "9e8743dbf8e5060e156abde4e4e0a5e5eb3815966802707f2a91a9c54243500b");

/* Source: lite/src/cache/reader-cache-management-surface.ts */
runtime.register("src/cache/reader-cache-management-surface.js", function(module, exports, require) {
	var reader_cache_management_surface_exports = {};
	__export(reader_cache_management_surface_exports, {
	  ReaderCacheManagementSurface: () => ReaderCacheManagementSurface
	});
	module.exports = __toCommonJS(reader_cache_management_surface_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_reader_settings_dom = require("../settings/reader-settings-dom.js");
	const CATEGORIES = Object.freeze([
	  {
	    id: "history",
	    title: "浏览历史",
	    help: "勾选“浏览历史”后再点下方清理,会删除阅读器保存的主题、最近阅读楼层和查看时间;不会删除浏览器本身的访问历史。",
	    retention: "最多 365 天"
	  },
	  {
	    id: "topics",
	    title: "帖子与楼层内容",
	    help: "勾选“帖子与楼层内容”后清理,会删除本机保存的帖子信息、楼层正文、回复关系、主题快照和相关接口数据;若当前正打开主题,会先安全结束旧会话并立即联网重建,避免旧快照在清理后写回。",
	    retention: "接口 7 天 · 快照 30 天"
	  },
	  {
	    id: "users",
	    title: "用户资料卡",
	    help: "勾选“用户资料卡”后清理,会删除用户名、简介、徽章、用户组、关注列表和账户摘要等可重新获取的资料快照;不会删除 Connect 近 400 天的本机信任观察历史,头像仍归“头像、表情与原图”单独管理。",
	    retention: "临时 5 分钟 · 持久 1 天"
	  },
	  {
	    id: "notifications",
	    title: "通知与消息",
	    help: "勾选“通知与消息”后清理,会删除通知分页、回复展开、已读状态和跳转位置等缓存;不会删除站点账号里的真实消息。",
	    retention: "最多 180 天"
	  },
	  {
	    id: "responses",
	    title: "收藏、回应与其他数据",
	    help: "勾选“收藏、回应与其他数据”后清理,会删除收藏列表、给出的回应与点赞、翻译和其他通用接口结果;不会撤销站点上的真实收藏或回应。",
	    retention: "按接口 8 分钟–180 天"
	  },
	  {
	    id: "assets",
	    title: "头像、表情与原图",
	    help: "勾选“头像、表情与原图”后清理,会删除统一图片响应和旧版头像、Boost 表情、实际查看过的原图资源;不会删除帖子中的线上图片。",
	    retention: "接口 30 天 · 兼容缓存 7–90 天"
	  }
	]);
	function categoryOf(record) {
	  return record.kind === "images" || record.tags.includes("images") ? "assets" : record.kind.includes("notification") || record.tags.includes("notifications") ? "notifications" : record.kind === "topics" || record.kind.startsWith("discourse-topic") || record.tags.some((tag) => tag.startsWith("topic:") || tag.startsWith("post:")) ? "topics" : record.kind === "users" || record.kind === "external-user-summary" || record.tags.includes("users") ? "users" : "responses";
	}
	function formatBytes(rawBytes) {
	  const bytes = Math.max(0, Number(rawBytes) || 0);
	  return bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1048576).toFixed(1)} MB`;
	}
	function assetCacheDetail(snapshot) {
	  return [...snapshot.groups.map((group) => {
	    const state = group.state === "error" ? "统计失败" : `${group.count} 条 · ${formatBytes(group.bytes)}`;
	    return `${group.label}:${state}`;
	  }), ...snapshot.errors].join(";");
	}
	function identityValue(record, key) {
	  const queryOffset = record.id.indexOf("?");
	  if (queryOffset < 0) return "";
	  try {
	    return new URLSearchParams(record.id.slice(queryOffset + 1)).get(key) ?? "";
	  } catch {
	    return "";
	  }
	}
	function taggedValues(records, prefix) {
	  const values = /* @__PURE__ */ new Set();
	  for (const record of records)
	    for (const tag of record.tags)
	      tag.startsWith(prefix) && tag.length > prefix.length && values.add(tag.slice(prefix.length));
	  return values;
	}
	function categoryBytes(records) {
	  return records.reduce(
	    (total, record) => total + Math.max(0, Number(record.bytes) || 0),
	    0
	  );
	}
	function withApplicationCacheDetail(detail, application) {
	  return application?.detail ? `${detail} · ${application.detail}` : detail;
	}
	function categoryStatText(id, records, history, assetCaches, application) {
	  const bytes = categoryBytes(records);
	  if (id === "history") {
	    const topicIds = new Set(history.map(
	      (entry) => Number(entry.topicId)
	    ).filter((topicId) => Number.isSafeInteger(topicId) && topicId > 0));
	    return withApplicationCacheDetail(
	      `${topicIds.size} 个主题 · ${history.length} 条浏览记录 · ${formatBytes(new TextEncoder().encode(JSON.stringify(history)).byteLength)} · 本机保存`,
	      application
	    );
	  }
	  if (id === "topics") {
	    const snapshots = records.filter((record) => record.id.includes("|snapshot:topic:")).length;
	    return withApplicationCacheDetail(`${taggedValues(records, "topic:").size} 个主题 · 已缓存 ${taggedValues(records, "post:").size} 个楼层 · 快照 ${snapshots} / 接口 ${Math.max(0, records.length - snapshots)} · 共 ${records.length} 条记录 · ${formatBytes(bytes)} · 本机保存`, application);
	  }
	  if (id === "users") {
	    const users = new Set(taggedValues(records, "user:"));
	    for (const record of records) {
	      const username = identityValue(record, "username");
	      username && users.add(username.toLocaleLowerCase());
	    }
	    return withApplicationCacheDetail(
	      `${users.size} 个用户 · 共 ${records.length} 条记录 · ${formatBytes(bytes)} · 本机保存`,
	      application
	    );
	  }
	  if (id === "notifications") {
	    const pages = records.filter((record) => record.kind === "discourse-notification-page").length;
	    return withApplicationCacheDetail(
	      `分页 ${pages} / 回复展开 ${Math.max(0, records.length - pages)} · 共 ${records.length} 条记录 · ${formatBytes(bytes)} · 本机保存`,
	      application
	    );
	  }
	  if (id === "responses") {
	    let bookmarks = 0, reactions = 0;
	    for (const record of records) {
	      const collection = identityValue(record, "collection");
	      collection === "bookmarks" ? bookmarks += 1 : collection.includes("reaction") && (reactions += 1);
	    }
	    const other = Math.max(0, records.length - bookmarks - reactions);
	    return withApplicationCacheDetail(
	      `收藏 ${bookmarks} / 回应 ${reactions} / 其他 ${other} · 共 ${records.length} 条响应 · ${formatBytes(bytes)} · 本机保存`,
	      application
	    );
	  }
	  const groups = assetCaches?.groups.map((group) => `${group.label} ${group.count} 个(${formatBytes(group.bytes)})`) ?? [], legacyCount = assetCaches?.count ?? 0, legacyBytes = assetCaches?.bytes ?? 0;
	  return withApplicationCacheDetail([
	    ...groups,
	    `接口图片 ${records.length} 个(${formatBytes(bytes)})`,
	    `共 ${legacyCount + records.length} 个资源 · ${formatBytes(legacyBytes + bytes)} · 本机保存`
	  ].join(" · "), application);
	}
	class ReaderCacheManagementSurface {
	  scope;
	  #options;
	  #root;
	  #selects = /* @__PURE__ */ new Map();
	  #stats = /* @__PURE__ */ new Map();
	  #clear;
	  #refreshCurrent;
	  #configActions;
	  #configFile;
	  #configStatus;
	  #status;
	  #refreshToken = 0;
	  #busy = !1;
	  constructor(options) {
	    this.#options = options, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const document = options.document;
	    if (this.#root = (0, import_reader_settings_dom.settingsElement)(
	      document,
	      "div",
	      "ldp-settings-category-groups"
	    ), options.configuration) {
	      const configSection = (0, import_reader_settings_dom.settingsSection)(
	        document,
	        "导入与导出设置",
	        "配置文件包含当前偏好(性能项仅保存目标值)、其他适用站点、翻译规则与 WebDAV 非敏感选项;导入后仍按当前设备、网络与 429 状态自适应。生效策略、请求与性能记录、阅读队列、浏览历史、帖子内容、缓存和账号数据不会写入文件;翻译 API Key、WebDAV 用户名和密码始终排除。",
	        !0
	      );
	      configSection.dataset.settingCategory = "config-management";
	      const actions = (0, import_reader_settings_dom.settingsElement)(
	        document,
	        "div",
	        "ldp-config-actions"
	      ), exportButton = (0, import_reader_settings_dom.settingsButton)(
	        document,
	        "ldp-config-action ldp-config-export",
	        "",
	        "download",
	        "导出设置"
	      ), importButton = (0, import_reader_settings_dom.settingsButton)(
	        document,
	        "ldp-config-action ldp-config-import",
	        "",
	        "upload",
	        "导入设置"
	      ), resetButton = (0, import_reader_settings_dom.settingsButton)(
	        document,
	        "ldp-config-action ldp-config-reset danger",
	        "",
	        "rotate-ccw",
	        "恢复全部默认"
	      );
	      actions.append(exportButton, importButton, resetButton);
	      const file = (0, import_reader_settings_dom.settingsElement)(
	        document,
	        "input",
	        "ldp-config-file"
	      );
	      file.type = "file", file.accept = "application/json,.json", file.hidden = !0;
	      const status = (0, import_reader_settings_dom.settingsElement)(
	        document,
	        "small",
	        "ldp-cache-note ldp-config-status"
	      );
	      status.role = "status", status.setAttribute("aria-live", "polite");
	      const body = (0, import_reader_settings_dom.settingsElement)(
	        document,
	        "div",
	        "ldp-config-body"
	      );
	      body.append(actions, file, status), configSection.append(body), this.#root.append(configSection), this.#configActions = Object.freeze([
	        exportButton,
	        importButton,
	        resetButton
	      ]), this.#configFile = file, this.#configStatus = status, this.scope.listen(
	        exportButton,
	        "click",
	        () => void this.#exportConfiguration()
	      ), this.scope.listen(importButton, "click", () => {
	        file.value = "", file.click();
	      }), this.scope.listen(
	        file,
	        "change",
	        () => void this.#importConfiguration()
	      ), this.scope.listen(
	        resetButton,
	        "click",
	        () => void this.#resetConfiguration()
	      );
	    } else
	      this.#configActions = Object.freeze([]), this.#configFile = null, this.#configStatus = null;
	    const section = (0, import_reader_settings_dom.settingsSection)(
	      document,
	      "本地缓存",
	      "阅读器会在本机保存浏览历史、帖子与楼层、用户资料、通知、收藏和图片等可复用数据。清理后不会影响站点账号内容,需要时会重新联网获取。",
	      !0
	    );
	    section.dataset.settingCategory = "local-cache";
	    const content = (0, import_reader_settings_dom.settingsElement)(
	      document,
	      "div",
	      "ldp-settings-category-content"
	    ), list = (0, import_reader_settings_dom.settingsElement)(document, "div", "ldp-cache-list");
	    for (const definition of CATEGORIES) {
	      const row = (0, import_reader_settings_dom.settingsElement)(document, "label", "ldp-cache-row");
	      row.dataset.settingHelp = definition.help;
	      const input = (0, import_reader_settings_dom.settingsElement)(document, "input", "ldp-cache-select");
	      input.type = "checkbox", input.value = definition.id;
	      const copy = (0, import_reader_settings_dom.settingsCopy)(
	        document,
	        "",
	        definition.title
	      ), stat = copy.querySelector("small") ?? (0, import_reader_settings_dom.settingsElement)(document, "small");
	      stat.dataset.cacheSize = definition.id, stat.parentElement || copy.append(stat);
	      const retention = (0, import_reader_settings_dom.settingsElement)(document, "em");
	      retention.dataset.cacheRetention = definition.id, retention.textContent = definition.retention, row.append(input, copy, retention), list.append(row), this.#selects.set(definition.id, input), this.#stats.set(definition.id, stat), this.scope.listen(input, "change", () => this.#syncButtons());
	    }
	    this.#clear = (0, import_reader_settings_dom.settingsButton)(
	      document,
	      "ldp-cache-clear",
	      "",
	      "trash",
	      "清理已选缓存"
	    ), this.#clear.disabled = !0, this.#clear.dataset.settingHelp = "只清理上面已经勾选的缓存类型。不会退出登录,也不会删除站点上的帖子、消息或图片;清理后需要时会重新联网获取。", this.#status = (0, import_reader_settings_dom.settingsElement)(document, "small", "ldp-cache-note"), this.#status.role = "status", this.#status.setAttribute("aria-live", "polite"), content.append(list, this.#clear, this.#status), section.append(content), this.#root.append(section), options.host.append(this.#root), this.scope.add(() => this.#root.remove()), this.scope.listen(this.#clear, "click", () => void this.#clearSelected()), options.headerActions ? (this.#refreshCurrent = (0, import_reader_settings_dom.settingsButton)(
	      document,
	      "ldp-reader-refresh ldp-icon-btn",
	      "清除当前帖子缓存并刷新",
	      "rotate-ccw"
	    ), options.headerActions.append(this.#refreshCurrent), this.scope.add(() => this.#refreshCurrent?.remove()), this.scope.listen(
	      this.#refreshCurrent,
	      "click",
	      () => void this.#clearCurrent()
	    )) : this.#refreshCurrent = null, this.sync(), this.refresh();
	  }
	  sync() {
	    this.#refreshCurrent && (this.#refreshCurrent.disabled = this.#busy || !this.#options.currentTopicAvailable());
	  }
	  async refresh() {
	    const token = ++this.#refreshToken;
	    let records, assetCaches, applicationCaches;
	    try {
	      [records, assetCaches, applicationCaches] = await Promise.all([
	        this.#options.responses.records(),
	        this.#options.assetCaches?.stats() ?? null,
	        this.#options.applicationCaches?.stats() ?? null
	      ]);
	    } catch (cause) {
	      if (token !== this.#refreshToken || this.scope.destroyed) return !1;
	      this.#options.onError?.(cause);
	      for (const target of this.#stats.values())
	        target.textContent || (target.textContent = "统计失败");
	      return this.#status.textContent = "本地缓存统计读取失败;现有选择不受影响,可稍后重试。", !1;
	    }
	    if (token !== this.#refreshToken || this.scope.destroyed) return !1;
	    const grouped = new Map(
	      CATEGORIES.map(({ id }) => [id, []])
	    ), history = this.#options.history.snapshot.entries;
	    for (const record of records)
	      grouped.get(categoryOf(record)).push(record);
	    if (assetCaches) {
	      const target = this.#stats.get("assets");
	      target && (target.title = assetCacheDetail(assetCaches));
	    }
	    for (const { id } of CATEGORIES) {
	      const target = this.#stats.get(id);
	      target && (target.textContent = categoryStatText(
	        id,
	        grouped.get(id) ?? [],
	        history,
	        assetCaches,
	        applicationCaches?.categories[id]
	      ));
	    }
	    const total = history.length + records.length + (assetCaches?.count ?? 0) + Object.values(applicationCaches?.categories ?? {}).reduce(
	      (sum, category) => sum + Math.max(0, Number(category?.records) || 0),
	      0
	    );
	    return this.#status.textContent = assetCaches?.errors.length ? `共 ${total} 条本地记录;部分浏览器图片缓存统计失败` : `共 ${total} 条本地记录`, !0;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #clearSelected() {
	    if (this.#busy) return;
	    const selected = new Set(
	      [...this.#selects].filter(([, input]) => input.checked).map(([id]) => id)
	    );
	    if (selected.size) {
	      this.#setBusy(!0, "正在清理已选缓存…");
	      try {
	        const failures = /* @__PURE__ */ new Map(), fail = (categories, message, cause) => {
	          for (const category of categories) {
	            const messages = failures.get(category) ?? [];
	            messages.push(message), failures.set(category, messages);
	          }
	          cause !== void 0 && this.#options.onError?.(cause);
	        };
	        if (selected.has("assets") && this.#options.assetCaches)
	          try {
	            const result = await this.#options.assetCaches.clear();
	            result.failed.length && fail(
	              ["assets"],
	              `兼容图片缓存 ${result.failed.join(",")} 未清理`
	            );
	          } catch (cause) {
	            fail(["assets"], "兼容图片缓存未清理", cause);
	          }
	        const responseCategories = [...selected].filter(
	          (id) => id !== "history"
	        );
	        if (responseCategories.length)
	          try {
	            const query = responseCategories.length === CATEGORIES.length - 1 ? { all: !0 } : {
	              ids: (await this.#options.responses.records()).filter((record) => selected.has(categoryOf(record))).map((record) => record.id)
	            };
	            if (query.all || query.ids?.length) {
	              let report = null;
	              if (this.#options.responses.invalidateWithReport ? report = await this.#options.responses.invalidateWithReport(query) : await this.#options.responses.invalidate(query), report && !report.complete) {
	                for (const failure of report.failures)
	                  this.#options.onError?.(failure.cause);
	                fail(responseCategories, "统一响应缓存未完整失效");
	              }
	            }
	          } catch (cause) {
	            fail(responseCategories, "统一响应缓存未清理", cause);
	          }
	        if (this.#options.applicationCaches)
	          try {
	            const result = await this.#options.applicationCaches.clear([...selected]);
	            for (const category of result.failed)
	              selected.has(category) && fail([category], "应用内存热缓存未清理");
	          } catch (cause) {
	            fail([...selected], "应用内存热缓存未清理", cause);
	          }
	        if (selected.has("history"))
	          try {
	            this.#options.history.clear();
	          } catch (cause) {
	            fail(["history"], "浏览历史未清理", cause);
	          }
	        if (selected.has("assets"))
	          try {
	            this.#options.clearImageObjectUrls?.();
	          } catch (cause) {
	            fail(["assets"], "当前图片对象未释放", cause);
	          }
	        for (const [id, input] of this.#selects)
	          selected.has(id) && !failures.has(id) && (input.checked = !1);
	        const refreshed = await this.refresh();
	        if (failures.size) {
	          const labels = CATEGORIES.filter(({ id }) => failures.has(id)).map(({ title }) => title);
	          this.#options.notify?.("部分本地缓存未能清理"), this.#status.textContent = `部分缓存已清理;未完成:${labels.join("、")}。已保留勾选,可重试。`;
	        } else
	          this.#options.notify?.("已清理所选本地缓存"), this.#status.textContent = refreshed ? "清理完成;需要的数据会按需重新获取。" : "清理已完成,但最新缓存统计读取失败;可稍后重新打开数据管理确认。";
	      } catch (cause) {
	        this.#options.onError?.(cause), this.#status.textContent = "清理失败,请稍后重试。";
	      } finally {
	        this.#setBusy(!1);
	      }
	    }
	  }
	  async #clearCurrent() {
	    if (!(this.#busy || !this.#options.currentTopicAvailable())) {
	      this.#setBusy(!0, "正在重建当前主题缓存…"), this.#refreshCurrent?.classList.add("is-refreshing");
	      try {
	        const result = await this.#options.clearCurrentTopic(), refreshed = await this.refresh();
	        if (result?.restored) {
	          const message = result.message ?? "当前主题刷新失败,已恢复刷新前内容;可稍后重试。";
	          this.#options.notify?.(message), this.#status.textContent = message;
	          return;
	        }
	        if (result && !result.complete) {
	          this.#options.notify?.(
	            "当前主题已重新获取,但部分旧缓存未能清理"
	          ), this.#status.textContent = result.message ?? "当前主题已从原站重新获取,但部分旧缓存未能清理;可再次重试。";
	          return;
	        }
	        this.#options.notify?.("当前主题缓存已清理并重新获取"), this.#status.textContent = refreshed ? "当前主题已从原站重新获取。" : "当前主题已从原站重新获取,但最新缓存统计读取失败。";
	      } catch (cause) {
	        this.#options.onError?.(cause), this.#status.textContent = "当前主题重建失败,原缓存不会作为恢复前提。";
	      } finally {
	        this.#refreshCurrent?.classList.remove("is-refreshing"), this.#setBusy(!1);
	      }
	    }
	  }
	  async #exportConfiguration() {
	    const configuration = this.#options.configuration;
	    if (!(!configuration || this.#busy)) {
	      this.#setBusy(!0, "正在导出设置…"), this.#setConfigStatus("正在导出设置…");
	      try {
	        const payload = await configuration.export(), filename = configuration.filename?.(payload) ?? `awesome-linuxdo-reader-settings-${payload.exportedAt.slice(0, 10)}.json`;
	        await configuration.saveTextFile(
	          `${JSON.stringify(payload, null, 2)}
`,
	          filename
	        ), this.#setConfigStatus(
	          `已导出 ${payload.settingsCount} 项偏好及安全扩展配置;性能项为目标值,不含运行时策略与日志。`
	        ), this.#options.notify?.("设置配置已导出");
	      } catch (cause) {
	        this.#options.onError?.(cause), this.#setConfigStatus("导出失败,请稍后重试。");
	      } finally {
	        this.#setBusy(!1);
	      }
	    }
	  }
	  async #importConfiguration() {
	    const configuration = this.#options.configuration, file = this.#configFile?.files?.[0];
	    if (!(!configuration || !file || this.#busy)) {
	      this.#setBusy(!0);
	      try {
	        let imported;
	        try {
	          imported = configuration.prepare(
	            JSON.parse(await file.text())
	          );
	        } catch (cause) {
	          this.#options.onError?.(cause), this.#setConfigStatus(
	            "配置文件无效或版本不匹配,请选择本阅读器导出的 JSON 文件。"
	          );
	          return;
	        }
	        if (!await configuration.confirm({
	          title: "导入这份设置配置?",
	          message: `将使用“${file.name}”覆盖当前阅读器设置。`,
	          note: imported.includesPortableSections ? "性能目标会立即应用,并继续按当前设备、网络与 429 状态自适应;生效策略和请求/性能记录不会从文件导入。API Key、WebDAV 用户名和密码不会从文件导入;仅复用本机同地址已有凭据,新 WebDAV 地址会关闭定时同步。浏览历史、阅读队列和缓存不会改变。" : "这是旧版配置,只覆盖阅读器偏好(含性能目标);性能目标会立即应用并继续自适应,生效策略和请求/性能记录不会从文件导入。其他适用站点、翻译和 WebDAV 设置保持不变。浏览历史、阅读队列和缓存不会改变。",
	          confirmLabel: "导入设置",
	          tone: "primary",
	          icon: "upload"
	        }) || this.scope.destroyed) return;
	        this.#status.textContent = "正在导入设置…", this.#setConfigStatus("正在导入设置…");
	        try {
	          const result = await configuration.apply(imported), skipped = result.skippedSections.length ? `;当前环境跳过 ${result.skippedSections.join("、")}` : "", autoSync = result.webDavAutoSyncDisabled ? ";WebDAV 缺少本机同地址凭据,定时同步已关闭" : "";
	          this.#setConfigStatus(
	            `导入完成,已应用 ${result.settingsCount} 项偏好(性能项为目标值)${skipped}${autoSync}。`
	          ), this.#options.notify?.("设置配置已导入");
	        } catch (cause) {
	          this.#options.onError?.(cause), this.#setConfigStatus(
	            cause instanceof Error && cause.message.includes("回滚不完整") ? "导入失败,部分设置可能未恢复;请重新打开设置逐项核对。" : "导入失败,当前设置保持不变。"
	          );
	        }
	      } catch (cause) {
	        this.#options.onError?.(cause), this.#setConfigStatus("无法确认导入,当前设置保持不变。");
	      } finally {
	        this.#setBusy(!1);
	      }
	    }
	  }
	  async #resetConfiguration() {
	    const configuration = this.#options.configuration;
	    if (!(!configuration || this.#busy)) {
	      this.#setBusy(!0);
	      try {
	        if (!await configuration.confirm({
	          title: "恢复全部默认设置?",
	          message: "当前偏好(含性能目标)、其他适用站点、翻译和 WebDAV 设置都会恢复默认。",
	          note: "性能项恢复推荐目标,运行时仍会自适应;请求/性能记录不会被删除。翻译 API Key、WebDAV 用户名和密码会从本机设置中清除;浏览历史、阅读队列、帖子缓存和账号数据不会被删除。",
	          confirmLabel: "恢复全部默认",
	          tone: "danger",
	          icon: "rotate-ccw"
	        }) || this.scope.destroyed) return;
	        this.#status.textContent = "正在恢复默认设置…", this.#setConfigStatus("正在恢复默认设置…"), await configuration.reset(), this.#setConfigStatus(
	          "全部设置已恢复默认;性能项将继续按运行环境自适应。"
	        ), this.#options.notify?.("全部设置已恢复默认");
	      } catch (cause) {
	        this.#options.onError?.(cause), this.#setConfigStatus(
	          cause instanceof Error && cause.message.includes("回滚不完整") ? "恢复失败,部分设置可能未恢复;请重新打开设置逐项核对。" : "恢复失败,当前设置保持不变。"
	        );
	      } finally {
	        this.#setBusy(!1);
	      }
	    }
	  }
	  #setBusy(busy, status = "") {
	    this.#busy = busy;
	    for (const action of this.#configActions) action.disabled = busy;
	    this.#configFile && (this.#configFile.disabled = busy), this.#clear.disabled = busy || ![...this.#selects.values()].some((input) => input.checked), status && (this.#status.textContent = status), this.sync();
	  }
	  #syncButtons() {
	    this.#clear.disabled = this.#busy || ![...this.#selects.values()].some((input) => input.checked);
	  }
	  #setConfigStatus(message) {
	    this.#configStatus && (this.#configStatus.textContent = message);
	  }
	}
}, "845798f70fff2618b6f72c99e285d61b48e64e02bce70730f0fafb3a2dbc8c31");

/* Source: lite/src/cache/response-repository.ts */
runtime.register("src/cache/response-repository.js", function(module, exports, require) {
	var response_repository_exports = {};
	__export(response_repository_exports, {
	  ResponseCacheFlightTimeoutError: () => ResponseCacheFlightTimeoutError,
	  ResponseCacheInvalidationError: () => ResponseCacheInvalidationError,
	  ResponseCacheSharedFlightFailureError: () => ResponseCacheSharedFlightFailureError,
	  ResponseRepository: () => ResponseRepository
	});
	module.exports = __toCommonJS(response_repository_exports);
	var import_cache_identity = require("./cache-identity.js");
	class ResponseCacheInvalidationError extends AggregateError {
	  report;
	  constructor(report) {
	    super(
	      report.failures.map((failure) => failure.cause),
	      "响应缓存失效未完整提交"
	    ), this.name = "ResponseCacheInvalidationError", this.report = report;
	  }
	}
	class ResponseCacheSharedFlightFailureError extends Error {
	  status;
	  cloudflareMitigated;
	  kind;
	  sharedFlight = !0;
	  constructor(failure) {
	    super(
	      failure.kind === "cloudflare" ? "同一在途请求已遇到 Cloudflare 验证" : "同一在途请求已收到 429"
	    ), this.name = "ResponseCacheSharedFlightFailureError", this.status = failure.status, this.cloudflareMitigated = failure.cloudflareMitigated, this.kind = failure.kind;
	  }
	}
	function sharedFlightFailure(error) {
	  if (!error || typeof error != "object") return null;
	  const candidate = error, status = Number(candidate.status ?? 0);
	  return candidate.cloudflareMitigated === !0 ? Object.freeze({
	    status: Number.isSafeInteger(status) && status >= 400 ? status : 403,
	    cloudflareMitigated: !0,
	    kind: "cloudflare"
	  }) : status === 429 ? Object.freeze({
	    status: 429,
	    cloudflareMitigated: !1,
	    kind: "rate-limit"
	  }) : null;
	}
	function nonNegativeFinite(value, name) {
	  if (!Number.isFinite(value) || value < 0)
	    throw new RangeError(`${name} 必须是非负有限数值`);
	  return value;
	}
	function positiveInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	function normalizePolicy(policy) {
	  const id = String(policy.id).trim(), kind = String(policy.kind).trim();
	  if (!id) throw new Error("cache id 不能为空");
	  if (!kind) throw new Error("cache kind 不能为空");
	  const freshForMs = nonNegativeFinite(policy.freshForMs, "freshForMs"), retainForMs = nonNegativeFinite(policy.retainForMs, "retainForMs");
	  if (retainForMs < freshForMs) throw new RangeError("retainForMs 不能小于 freshForMs");
	  return Object.freeze({
	    id,
	    kind,
	    tags: Object.freeze(
	      [...new Set(policy.tags.map(String).map((tag) => tag.trim()).filter(Boolean))].sort()
	    ),
	    freshForMs,
	    retainForMs,
	    persist: policy.persist,
	    ...policy.permanent === !0 ? { permanent: !0 } : {}
	  });
	}
	function defaultEstimateBytes(value) {
	  if (value !== null && typeof value == "object" && (typeof Blob < "u" && value instanceof Blob || Object.prototype.toString.call(value) === "[object Blob]")) {
	    const size = Number(value.size);
	    if (Number.isFinite(size) && size >= 0) return size;
	  }
	  if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value.byteLength;
	  try {
	    return new TextEncoder().encode(JSON.stringify(value)).byteLength;
	  } catch {
	    return 0;
	  }
	}
	function matchesInvalidation(entry, query) {
	  return !!(query.all || query.ids?.includes(entry.id) || query.kinds?.includes(entry.kind) || query.tags?.some((tag) => entry.tags.includes(tag)));
	}
	class ResponseRepository {
	  #store;
	  #maxMemoryEntries;
	  #maxMemoryBytes;
	  #now;
	  #estimateBytes;
	  #mutationPort;
	  #flightPort;
	  #flightHeartbeatMs;
	  #flightWaitTimeoutMs;
	  #onPersistenceError;
	  #memory = /* @__PURE__ */ new Map();
	  #inflight = /* @__PURE__ */ new Map();
	  #writes = /* @__PURE__ */ new Map();
	  #epoch = 0;
	  constructor(options) {
	    this.#store = options.store, this.#maxMemoryEntries = positiveInteger(options.maxMemoryEntries, "maxMemoryEntries"), this.#maxMemoryBytes = positiveInteger(options.maxMemoryBytes, "maxMemoryBytes"), this.#now = options.now ?? Date.now, this.#estimateBytes = options.estimateBytes ?? defaultEstimateBytes, this.#mutationPort = options.mutationPort, this.#flightPort = options.flightPort, this.#flightHeartbeatMs = positiveInteger(
	      options.flightHeartbeatMs ?? 15e3,
	      "flightHeartbeatMs"
	    ), this.#flightWaitTimeoutMs = positiveInteger(
	      options.flightWaitTimeoutMs ?? 65e3,
	      "flightWaitTimeoutMs"
	    ), this.#onPersistenceError = options.onPersistenceError ?? (() => {
	    });
	  }
	  async read(rawPolicy) {
	    const policy = normalizePolicy(rawPolicy);
	    let entry = this.#memory.get(policy.id);
	    if (entry)
	      this.#memory.delete(policy.id), this.#memory.set(policy.id, entry);
	    else {
	      const epoch = this.#epoch;
	      try {
	        entry = await this.#store.read(policy.id) ?? void 0;
	      } catch (error) {
	        this.#onPersistenceError(error);
	      }
	      if (epoch !== this.#epoch)
	        entry = void 0;
	      else if (entry && this.#validEntry(entry, policy))
	        this.#remember(entry);
	      else {
	        if (entry)
	          try {
	            await this.#store.invalidate({ ids: [policy.id] });
	          } catch (error) {
	            this.#onPersistenceError(error);
	          }
	        entry = void 0;
	      }
	    }
	    if (!entry || !this.#validEntry(entry, policy))
	      return Object.freeze({ state: "miss" });
	    const age = Math.max(0, this.#now() - entry.storedAt);
	    return entry.permanent !== !0 && (age > policy.retainForMs || entry.expiresAt <= this.#now()) ? (await this.#invalidateWithReport({ ids: [policy.id] }, !0), Object.freeze({ state: "miss" })) : Object.freeze({
	      state: age <= policy.freshForMs ? "fresh" : "stale",
	      value: entry.value,
	      storedAt: entry.storedAt
	    });
	  }
	  /**
	   * 只从持久层读取,不接受当前标签页的 memory LRU 回退。
	   *
	   * 下载存档等用户明确保留的数据必须用它确认 IndexedDB 已真正提交;
	   * 普通响应仍使用 read() 的容错路径,持久层故障不会扩大成正文请求失败。
	   */
	  async readPersistent(rawPolicy) {
	    const policy = normalizePolicy(rawPolicy), pending = this.#writes.get(policy.id);
	    pending && await pending;
	    let entry;
	    try {
	      entry = await this.#store.read(policy.id);
	    } catch (error) {
	      throw this.#onPersistenceError(error), error;
	    }
	    if (!entry || !this.#validEntry(entry, policy))
	      return Object.freeze({ state: "miss" });
	    const age = Math.max(0, this.#now() - entry.storedAt);
	    return entry.permanent !== !0 && (age > policy.retainForMs || entry.expiresAt <= this.#now()) ? Object.freeze({ state: "miss" }) : Object.freeze({
	      state: age <= policy.freshForMs ? "fresh" : "stale",
	      value: entry.value,
	      storedAt: entry.storedAt
	    });
	  }
	  getOrLoad(rawPolicy, loader, options = {}) {
	    const policy = normalizePolicy(rawPolicy);
	    options.signal?.throwIfAborted();
	    const cacheMode = options.cacheMode ?? "default", requestKey = `${cacheMode}:${policy.id}`, existing = this.#inflight.get(requestKey);
	    if (existing && !existing.controller.signal.aborted)
	      return this.#join(existing, options.signal);
	    const controller = new AbortController(), inflight = {
	      promise: Promise.resolve(void 0),
	      policy,
	      controller,
	      consumers: /* @__PURE__ */ new Set(),
	      unabortableConsumer: !1,
	      settled: !1
	    }, promise = this.#load(
	      policy,
	      loader,
	      { ...options, signal: controller.signal },
	      cacheMode
	    ).finally(() => {
	      inflight.settled = !0;
	    });
	    inflight.promise = promise, this.#inflight.set(requestKey, inflight);
	    const clearInflight = () => {
	      this.#inflight.get(requestKey)?.promise === promise && this.#inflight.delete(requestKey);
	    };
	    return promise.then(clearInflight, clearInflight), this.#join(inflight, options.signal);
	  }
	  #join(inflight, signal) {
	    if (!signal)
	      return inflight.unabortableConsumer = !0, inflight.promise;
	    if (signal.aborted) return Promise.reject(signal.reason);
	    const consumer = Symbol("cache-consumer");
	    return inflight.consumers.add(consumer), new Promise((resolve, reject) => {
	      let settled = !1;
	      const finish = (value) => {
	        settled || (settled = !0, signal.removeEventListener("abort", onAbort), inflight.consumers.delete(consumer), resolve(value));
	      }, fail = (cause) => {
	        settled || (settled = !0, signal.removeEventListener("abort", onAbort), inflight.consumers.delete(consumer), reject(cause));
	      }, onAbort = () => {
	        fail(signal.reason), !inflight.settled && !inflight.unabortableConsumer && inflight.consumers.size === 0 && !inflight.controller.signal.aborted && inflight.controller.abort(signal.reason);
	      };
	      signal.addEventListener("abort", onAbort, { once: !0 }), inflight.promise.then(finish, fail);
	    });
	  }
	  async write(rawPolicy, value) {
	    const policy = normalizePolicy(rawPolicy), storedAt = Math.max(
	      this.#now(),
	      (this.#memory.get(policy.id)?.storedAt ?? -1) + 1
	    ), entry = Object.freeze({
	      schemaVersion: 1,
	      id: policy.id,
	      kind: policy.kind,
	      tags: policy.tags,
	      storedAt,
	      expiresAt: policy.permanent === !0 ? Number.MAX_SAFE_INTEGER : storedAt + policy.retainForMs,
	      bytes: Math.max(0, this.#estimateBytes(value)),
	      value,
	      ...policy.permanent === !0 ? { permanent: !0 } : {}
	    });
	    if (this.#remember(entry), !policy.persist) return;
	    const write = (this.#writes.get(policy.id) ?? Promise.resolve()).catch(() => {
	    }).then(() => this.#store.write(entry)).catch((error) => {
	      this.#onPersistenceError(error);
	    });
	    this.#writes.set(policy.id, write), await write, this.#writes.get(policy.id) === write && this.#writes.delete(policy.id);
	  }
	  /** WebDAV 等受控迁移入口:仅在远端版本更新时保留原 storedAt 写回。 */
	  async restore(rawPolicy, value, storedAtValue) {
	    const policy = normalizePolicy(rawPolicy), storedAt = Math.max(0, Math.floor(storedAtValue));
	    if (!Number.isSafeInteger(storedAt) || policy.permanent !== !0 && storedAt + policy.retainForMs <= this.#now() || ((await this.read(policy)).storedAt ?? -1) >= storedAt) return;
	    const entry = Object.freeze({
	      schemaVersion: 1,
	      id: policy.id,
	      kind: policy.kind,
	      tags: policy.tags,
	      storedAt,
	      expiresAt: policy.permanent === !0 ? Number.MAX_SAFE_INTEGER : storedAt + policy.retainForMs,
	      bytes: Math.max(0, this.#estimateBytes(value)),
	      value,
	      ...policy.permanent === !0 ? { permanent: !0 } : {}
	    });
	    if (this.#remember(entry), !policy.persist) return;
	    const write = (this.#writes.get(policy.id) ?? Promise.resolve()).catch(() => {
	    }).then(() => this.#store.write(entry)).catch((error) => {
	      this.#onPersistenceError(error);
	    });
	    this.#writes.set(policy.id, write), await write, this.#writes.get(policy.id) === write && this.#writes.delete(policy.id);
	  }
	  async merge(rawPolicy, incoming, mergeValues) {
	    const policy = normalizePolicy(rawPolicy), memory = this.#memory.get(policy.id), localValue = !this.#store.merge && memory && this.#validEntry(memory, policy) ? mergeValues(memory.value, incoming) : incoming;
	    let committed = Object.freeze({
	      schemaVersion: 1,
	      id: policy.id,
	      kind: policy.kind,
	      tags: policy.tags,
	      storedAt: Math.max(this.#now(), (memory?.storedAt ?? -1) + 1),
	      expiresAt: 0,
	      bytes: Math.max(0, this.#estimateBytes(localValue)),
	      value: localValue,
	      ...policy.permanent === !0 ? { permanent: !0 } : {}
	    });
	    if (committed = Object.freeze({
	      ...committed,
	      expiresAt: policy.permanent === !0 ? Number.MAX_SAFE_INTEGER : committed.storedAt + policy.retainForMs
	    }), this.#remember(committed), !policy.persist) return committed.value;
	    const previous = this.#writes.get(policy.id) ?? Promise.resolve();
	    let persisted = !1;
	    const write = previous.catch(() => {
	    }).then(async () => {
	      if (this.#store.merge) {
	        const merged = await this.#store.merge(
	          policy.id,
	          (current) => {
	            const value = current && this.#validEntry(current, policy) ? mergeValues(current.value, committed.value) : committed.value, storedAt = Math.max(
	              this.#now(),
	              (current?.storedAt ?? -1) + 1,
	              committed.storedAt
	            );
	            return Object.freeze({
	              schemaVersion: 1,
	              id: policy.id,
	              kind: policy.kind,
	              tags: policy.tags,
	              storedAt,
	              expiresAt: policy.permanent === !0 ? Number.MAX_SAFE_INTEGER : storedAt + policy.retainForMs,
	              bytes: Math.max(0, this.#estimateBytes(value)),
	              value,
	              ...policy.permanent === !0 ? { permanent: !0 } : {}
	            });
	          }
	        );
	        if (!merged) return;
	        committed = merged;
	      } else
	        await this.#store.write(committed);
	      persisted = !0;
	    }).catch((error) => {
	      this.#onPersistenceError(error);
	    });
	    if (this.#writes.set(policy.id, write), await write, persisted)
	      try {
	        await this.#mutationPort?.publish({ ids: [policy.id] });
	      } catch (error) {
	        this.#onPersistenceError(error);
	      }
	    return this.#writes.get(policy.id) === write && (this.#writes.delete(policy.id), persisted && this.#remember(committed)), committed.value;
	  }
	  async invalidate(query, publish = !0) {
	    const report = await this.#invalidateWithReport(query, publish);
	    if (!report.complete) throw new ResponseCacheInvalidationError(report);
	  }
	  async invalidateWithReport(query, publish = !0) {
	    return this.#invalidateWithReport(query, publish);
	  }
	  async #invalidateWithReport(query, publish) {
	    this.#epoch += 1;
	    let memoryEntries = 0;
	    for (const [id, entry] of this.#memory)
	      matchesInvalidation(entry, query) && (this.#memory.delete(id), memoryEntries += 1);
	    this.#writes.size && await Promise.all(this.#writes.values());
	    const failures = [];
	    try {
	      await this.#flightPort?.invalidateWrites();
	    } catch (error) {
	      this.#onPersistenceError(error), failures.push(Object.freeze({ stage: "flight", cause: error }));
	    }
	    try {
	      const result = await this.#store.invalidate(query);
	      result && !result.ok && failures.push(Object.freeze({
	        stage: "store",
	        cause: result.error ?? new Error("持久响应缓存失效失败")
	      }));
	    } catch (error) {
	      this.#onPersistenceError(error), failures.push(Object.freeze({ stage: "store", cause: error }));
	    }
	    if (publish)
	      try {
	        await this.#mutationPort?.publish(query);
	      } catch (error) {
	        this.#onPersistenceError(error), failures.push(Object.freeze({ stage: "broadcast", cause: error }));
	      }
	    return Object.freeze({
	      memoryEntries,
	      failures: Object.freeze(failures),
	      complete: failures.length === 0
	    });
	  }
	  applyExternalInvalidation(query) {
	    this.#epoch += 1;
	    for (const [id, entry] of this.#memory)
	      matchesInvalidation(entry, query) && this.#memory.delete(id);
	  }
	  memoryStats() {
	    let bytes = 0;
	    for (const entry of this.#memory.values()) bytes += entry.bytes;
	    return Object.freeze({ entries: this.#memory.size, bytes });
	  }
	  async records() {
	    this.#writes.size && await Promise.all(this.#writes.values());
	    let persistent = [];
	    try {
	      persistent = await this.#store.records?.() ?? [];
	    } catch (error) {
	      throw this.#onPersistenceError(error), error;
	    }
	    const records = new Map(
	      persistent.map((entry) => [entry.id, entry])
	    );
	    for (const entry of this.#memory.values())
	      records.set(entry.id, Object.freeze({
	        id: entry.id,
	        kind: entry.kind,
	        tags: entry.tags,
	        storedAt: entry.storedAt,
	        expiresAt: entry.expiresAt,
	        bytes: entry.bytes,
	        ...entry.permanent === !0 ? { permanent: !0 } : {}
	      }));
	    return Object.freeze([...records.values()]);
	  }
	  /**
	   * 仅供受控的数据迁移 owner 导出仍在保留期内的完整缓存记录。
	   * UI 目录继续使用 records(),避免普通调用方接触响应正文。
	   */
	  async entries(query) {
	    this.#writes.size && await Promise.all(this.#writes.values());
	    let persistent = [];
	    try {
	      persistent = await this.#store.snapshotEntries?.() ?? [];
	    } catch (error) {
	      throw this.#onPersistenceError(error), error;
	    }
	    const now = this.#now(), entries = /* @__PURE__ */ new Map();
	    for (const entry of persistent)
	      entry.schemaVersion !== 1 || !matchesInvalidation(entry, query) || !Number.isFinite(entry.expiresAt) || entry.permanent !== !0 && entry.expiresAt <= now || entries.set(entry.id, entry);
	    for (const entry of this.#memory.values())
	      !matchesInvalidation(entry, query) || entry.permanent !== !0 && entry.expiresAt <= now || entries.set(entry.id, entry);
	    return Object.freeze([...entries.values()].sort((left, right) => right.storedAt - left.storedAt || left.id.localeCompare(right.id)).map((entry) => Object.freeze({
	      ...entry,
	      tags: Object.freeze([...entry.tags])
	    })));
	  }
	  #validEntry(entry, policy) {
	    const tags = Array.isArray(entry.tags) && entry.tags.every((tag) => typeof tag == "string") ? [...new Set(entry.tags)].sort() : null;
	    return entry.schemaVersion === 1 && entry.id === policy.id && entry.kind === policy.kind && Number.isFinite(entry.storedAt) && entry.storedAt >= 0 && Number.isFinite(entry.expiresAt) && entry.expiresAt >= entry.storedAt && Number.isFinite(entry.bytes) && entry.bytes >= 0 && entry.permanent === !0 == (policy.permanent === !0) && tags !== null && tags.length === policy.tags.length && tags.every((tag, index) => tag === policy.tags[index]);
	  }
	  async #load(policy, loader, options, cacheMode) {
	    const throwIfAborted = () => options.signal?.throwIfAborted(), cached = cacheMode === "no-store" ? Object.freeze({ state: "miss" }) : await this.read(policy);
	    if (throwIfAborted(), cacheMode === "default" && cached.state === "fresh") return cached.value;
	    const cachedBeforeRequest = cached.storedAt ?? 0, flightToken = this.#flightPort && policy.persist && cacheMode !== "no-store" ? `v1:${(0, import_cache_identity.sharedCacheIdToken)(policy.id)}:${policy.id.length}` : "";
	    let lease = null, heartbeat = null;
	    try {
	      if (flightToken && this.#flightPort) {
	        const deadline = this.#now() + this.#flightWaitTimeoutMs;
	        for (; lease = await this.#flightPort.acquireFlight(flightToken), throwIfAborted(), !lease.producer; ) {
	          const released = await this.#flightPort.waitForFlight(
	            flightToken,
	            options.signal,
	            deadline
	          );
	          if (throwIfAborted(), !released) throw new ResponseCacheFlightTimeoutError();
	          const shared2 = await this.#readAfterFlight(
	            policy,
	            cacheMode,
	            cachedBeforeRequest
	          );
	          if (throwIfAborted(), shared2.state === "hit") return shared2.value;
	          const failure = await this.#flightPort.readFlightFailure(lease);
	          if (throwIfAborted(), failure) throw new ResponseCacheSharedFlightFailureError(failure);
	        }
	        const shared = await this.#readAfterFlight(
	          policy,
	          cacheMode,
	          cachedBeforeRequest
	        );
	        if (throwIfAborted(), shared.state === "hit") return shared.value;
	        if (lease.coordinated) {
	          const producerLease = lease;
	          heartbeat = setInterval(() => {
	            this.#flightPort?.renewFlight(producerLease).then((renewed) => {
	              !renewed && heartbeat !== null && (clearInterval(heartbeat), heartbeat = null);
	            }).catch((error) => {
	              this.#onPersistenceError(error), heartbeat !== null && (clearInterval(heartbeat), heartbeat = null);
	            });
	          }, this.#flightHeartbeatMs);
	        }
	      }
	      const epoch = this.#epoch, value = await loader(options.signal);
	      return throwIfAborted(), cacheMode !== "no-store" && epoch === this.#epoch && (lease && this.#flightPort ? await this.#flightPort.commitFlight(lease, () => this.write(policy, value)) : await this.write(policy, value)), value;
	    } catch (error) {
	      throwIfAborted();
	      const failure = lease?.producer ? sharedFlightFailure(error) : null;
	      if (failure && this.#flightPort)
	        try {
	          await this.#flightPort.failFlight(lease, failure);
	        } catch (coordinationError) {
	          this.#onPersistenceError(coordinationError);
	        }
	      const canFallback = options.canFallback?.(error) ?? !0;
	      if (options.allowStaleOnError !== !1 && cached.state === "stale" && canFallback) {
	        const value = cached.value;
	        return options.mapStaleFallback ? options.mapStaleFallback(value, error) : value;
	      }
	      throw error;
	    } finally {
	      if (heartbeat !== null && clearInterval(heartbeat), lease?.producer && this.#flightPort)
	        try {
	          await this.#flightPort.releaseFlight(lease);
	        } catch (error) {
	          this.#onPersistenceError(error);
	        }
	    }
	  }
	  async #readAfterFlight(policy, cacheMode, cachedBeforeRequest) {
	    this.#memory.delete(policy.id);
	    const latest = await this.read(policy);
	    return latest.state === "miss" ? Object.freeze({ state: "miss" }) : cacheMode === "refresh" ? (latest.storedAt ?? 0) > cachedBeforeRequest ? Object.freeze({ state: "hit", value: latest.value }) : Object.freeze({ state: "miss" }) : latest.state === "fresh" ? Object.freeze({ state: "hit", value: latest.value }) : Object.freeze({ state: "miss" });
	  }
	  #remember(entry) {
	    if (this.#memory.delete(entry.id), entry.permanent === !0 && entry.bytes > this.#maxMemoryBytes)
	      return;
	    this.#memory.set(entry.id, entry);
	    let bytes = 0;
	    for (const value of this.#memory.values()) bytes += value.bytes;
	    for (; this.#memory.size > this.#maxMemoryEntries || bytes > this.#maxMemoryBytes && this.#memory.size > 1; ) {
	      let evictedId;
	      for (const [id, value] of this.#memory)
	        if (value.permanent === !0) {
	          evictedId = id;
	          break;
	        }
	      if (evictedId ??= this.#memory.keys().next().value, evictedId === void 0) break;
	      const oldest = this.#memory.get(evictedId);
	      this.#memory.delete(evictedId), bytes -= oldest?.bytes ?? 0;
	    }
	  }
	}
	class ResponseCacheFlightTimeoutError extends Error {
	  constructor() {
	    super("等待共享缓存请求超时"), this.name = "TimeoutError";
	  }
	}
}, "49141a0a146319e0a72079c4327b9d751bd5ed0cda3c5f9393adaaa4a5ddfa13");

/* Source: lite/src/cache/topic-snapshot-repository.ts */
runtime.register("src/cache/topic-snapshot-repository.js", function(module, exports, require) {
	var topic_snapshot_repository_exports = {};
	__export(topic_snapshot_repository_exports, {
	  TopicSnapshotRepository: () => TopicSnapshotRepository
	});
	module.exports = __toCommonJS(topic_snapshot_repository_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_ingest_version = require("../discourse/ingest-version.js"), import_reply_tree = require("../dom/reply-tree.js");
	function nonNegativeInteger(value, name) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 0)
	    throw new RangeError(`${name} 必须是非负安全整数`);
	  return numeric;
	}
	function finiteTimestamp(value, name) {
	  const numeric = Number(value);
	  if (!Number.isFinite(numeric) || numeric < 0)
	    throw new RangeError(`${name} 必须是非负有限时间戳`);
	  return numeric;
	}
	function localArchiveStatus(value) {
	  const status = Number(value);
	  if (status !== 403 && status !== 404 && status !== 410)
	    throw new RangeError("本地存档状态必须是 403、404 或 410");
	  return status;
	}
	function normalizeStreamPostIds(values) {
	  return (0, import_identifiers.discoursePostIdStream)(values ?? []);
	}
	function mergePostEntity(current, incoming) {
	  if (!current || current === incoming) return incoming;
	  const merged = {
	    ...current
	  };
	  for (const [key, value] of Object.entries(
	    incoming
	  ))
	    value !== void 0 && (merged[key] = value);
	  return Object.freeze(merged);
	}
	function validTreeSnapshot(value, topicId, posts, removedPostNumbers, onInvalid) {
	  if (value == null) return null;
	  const candidate = value;
	  let valid = candidate.schemaVersion === 2 && candidate.topicId === topicId && Number.isFinite(candidate.savedAt) && Number.isSafeInteger(candidate.expectedPostCount) && Number(candidate.expectedPostCount) >= 0 && !!candidate.tree && Number.isSafeInteger(candidate.tree.revision) && Number(candidate.tree.revision) >= 0 && Array.isArray(candidate.tree.relations) && Array.isArray(candidate.versions) && (candidate.removedVersions === void 0 || Array.isArray(candidate.removedVersions));
	  if (valid) {
	    const relations = candidate.tree.relations, relationNumbers = new Set(relations.map((relation) => relation.postNumber)), versionNumbers = /* @__PURE__ */ new Set();
	    for (const version of candidate.versions) {
	      const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(version?.postNumber);
	      if (postNumber === null || versionNumbers.has(postNumber) || !relationNumbers.has(postNumber) || !Number.isFinite(version.observedAt) || version.observedAt < 0 || (0, import_ingest_version.normalizeDiscourseIngestSource)(version.source) === null) {
	        valid = !1;
	        break;
	      }
	      versionNumbers.add(postNumber);
	    }
	    versionNumbers.size !== relationNumbers.size && (valid = !1);
	    const removedNumbers = /* @__PURE__ */ new Set();
	    for (const version of candidate.removedVersions ?? []) {
	      const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(version?.postNumber);
	      if (postNumber === null || relationNumbers.has(postNumber) || removedNumbers.has(postNumber) || !Number.isFinite(version.observedAt) || version.observedAt < 0 || (0, import_ingest_version.normalizeDiscourseIngestSource)(version.source) === null) {
	        valid = !1;
	        break;
	      }
	      removedNumbers.add(postNumber);
	    }
	  }
	  if (valid)
	    try {
	      const topology = new import_reply_tree.ReplyTreeTopology();
	      topology.replace(candidate.tree);
	      for (const entry of posts) {
	        const rawParentPostNumber = entry.value.reply_to_post_number, parentPostNumber = rawParentPostNumber == null || rawParentPostNumber === "" ? null : (0, import_identifiers.tryDiscoursePostNumber)(rawParentPostNumber);
	        if (parentPostNumber === null && !(rawParentPostNumber == null || rawParentPostNumber === ""))
	          throw new Error(
	            `Topic ${topicId} 楼层 #${entry.postNumber} 的缓存父楼层无效`
	          );
	        const storedParentPostNumber = topology.parentOf(entry.postNumber), parentWasRemoved = parentPostNumber !== null && removedPostNumbers.has(parentPostNumber);
	        if (storedParentPostNumber === void 0 || storedParentPostNumber !== parentPostNumber && !parentWasRemoved)
	          throw new Error(
	            `Topic ${topicId} 楼层 #${entry.postNumber} 的正文与回复树缓存关系不一致`
	          );
	      }
	      return value;
	    } catch (error) {
	      return onInvalid(error), null;
	    }
	  return onInvalid(new Error(`Topic ${topicId} 的内嵌回复树快照无效`)), null;
	}
	class TopicSnapshotRepository {
	  topicId;
	  authScope;
	  #responses;
	  #policy;
	  #archivePolicy;
	  #now;
	  #persistenceIdleMs;
	  #persistenceWait;
	  #onInvalidSnapshot;
	  #onInvalidTreeSnapshot;
	  #posts = /* @__PURE__ */ new Map();
	  #removedPosts = /* @__PURE__ */ new Map();
	  #unavailablePosts = /* @__PURE__ */ new Map();
	  #topic = null;
	  #unavailableTopic = null;
	  #topicObservedAt = 0;
	  #topicSource = null;
	  #streamPostIds = Object.freeze([]);
	  #streamObservedAt = 0;
	  #expectedPostCount = 0;
	  #tree = null;
	  #updatedAt = 0;
	  #restorePromise = null;
	  #pendingWrite = !1;
	  #persisting = null;
	  #persistenceReadyAt = 0;
	  #readPersistenceDelayMs = () => 0;
	  #archiveRecordKnown = !1;
	  constructor(options) {
	    const topicId = String((0, import_identifiers.discourseTopicId)(options.topicId)), authScope = (0, import_identifiers.discourseAuthScope)(options.authScope);
	    if (this.topicId = topicId, this.authScope = authScope, this.#responses = options.responseRepository, this.#now = options.now ?? Date.now, this.#persistenceIdleMs = finiteTimestamp(
	      options.persistenceIdleMs ?? 0,
	      "persistenceIdleMs"
	    ), this.#persistenceWait = options.persistenceWait ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs))), this.#onInvalidSnapshot = options.onInvalidSnapshot ?? (() => {
	    }), this.#onInvalidTreeSnapshot = options.onInvalidTreeSnapshot ?? (() => {
	    }), this.#policy = Object.freeze({
	      id: `${authScope}|snapshot:topic:${topicId}`,
	      kind: "topics",
	      tags: Object.freeze([`topic:${topicId}`]),
	      freshForMs: finiteTimestamp(options.freshForMs, "freshForMs"),
	      retainForMs: finiteTimestamp(options.retainForMs, "retainForMs"),
	      persist: !0
	    }), this.#archivePolicy = Object.freeze({
	      id: `${authScope}|snapshot:topic-archive:${topicId}`,
	      kind: "topics",
	      tags: Object.freeze([`topic:${topicId}`, "topic-local-archive"]),
	      freshForMs: finiteTimestamp(options.freshForMs, "freshForMs"),
	      retainForMs: finiteTimestamp(options.retainForMs, "retainForMs"),
	      persist: !0,
	      permanent: !0
	    }), this.#policy.retainForMs < this.#policy.freshForMs)
	      throw new RangeError("retainForMs 不能小于 freshForMs");
	  }
	  ingest(input) {
	    const observedAt = finiteTimestamp(input.observedAt ?? this.#now(), "observedAt");
	    let topicChanged = !1, streamChanged = !1, ignoredPosts = 0, acceptedPosts = 0;
	    const changedPostNumbers = [];
	    if (input.topic !== void 0 && (0, import_ingest_version.shouldReplaceDiscourseVersion)(
	      { observedAt: this.#topicObservedAt, source: this.#topicSource },
	      { observedAt, source: input.source }
	    ) && (this.#topic = input.topic, this.#topicObservedAt = observedAt, this.#topicSource = input.source, topicChanged = !0, this.#unavailableTopic && observedAt >= this.#unavailableTopic.confirmedAt && (this.#unavailableTopic = null, topicChanged = !0)), input.streamPostIds !== void 0 && (0, import_ingest_version.shouldReplaceDiscourseVersion)(
	      { observedAt: this.#streamObservedAt, source: null },
	      { observedAt, source: input.source }
	    )) {
	      const blockedPostIds = new Set(
	        [...this.#removedPosts.values()].filter((removed) => !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, {
	          observedAt,
	          source: input.source
	        })).map((removed) => removed.postId)
	      ), stream = Object.freeze(
	        normalizeStreamPostIds(input.streamPostIds).filter((postId) => !blockedPostIds.has(postId))
	      );
	      (stream.length !== this.#streamPostIds.length || stream.some((postId, index) => postId !== this.#streamPostIds[index])) && (this.#streamPostIds = stream, streamChanged = !0), this.#streamObservedAt = observedAt;
	    }
	    if (input.expectedPostCount !== void 0) {
	      const blockedRemovalCount = [...this.#removedPosts.values()].filter((removed) => !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, {
	        observedAt,
	        source: input.source
	      })).length;
	      this.#expectedPostCount = Math.max(
	        this.#expectedPostCount,
	        Math.max(
	          0,
	          nonNegativeInteger(input.expectedPostCount, "expectedPostCount") - blockedRemovalCount
	        )
	      );
	    }
	    for (const post of input.posts ?? []) {
	      const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(post.post_number);
	      if (postNumber === null) {
	        ignoredPosts += 1;
	        continue;
	      }
	      const current = this.#posts.get(postNumber), removed = this.#removedPosts.get(postNumber);
	      if (removed && !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, {
	        observedAt,
	        source: input.source
	      }) || current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(
	        current,
	        { observedAt, source: input.source }
	      ) || current && current.value === post && current.observedAt === observedAt) continue;
	      const value = mergePostEntity(current?.value, post);
	      this.#removedPosts.delete(postNumber);
	      const unavailable = this.#unavailablePosts.get(postNumber);
	      unavailable && observedAt >= unavailable.confirmedAt && this.#unavailablePosts.delete(postNumber), this.#posts.set(postNumber, Object.freeze({
	        postNumber,
	        observedAt,
	        source: input.source,
	        value
	      })), acceptedPosts += 1, changedPostNumbers.push(postNumber);
	    }
	    return (topicChanged || streamChanged || changedPostNumbers.length > 0) && (this.#updatedAt = Math.max(this.#updatedAt, observedAt), this.#queuePersistence()), Object.freeze({
	      acceptedPosts,
	      ignoredPosts,
	      changedPostNumbers: Object.freeze(changedPostNumbers.sort((left, right) => left - right)),
	      topicChanged,
	      streamChanged
	    });
	  }
	  markTopicUnavailable(rawStatus, confirmedAt = this.#now()) {
	    if (this.#topic === null && this.#posts.size === 0) return !1;
	    const next = Object.freeze({
	      status: localArchiveStatus(rawStatus),
	      confirmedAt: finiteTimestamp(confirmedAt, "confirmedAt")
	    });
	    return this.#unavailableTopic && this.#unavailableTopic.confirmedAt > next.confirmedAt ? !1 : (this.#unavailableTopic = next, this.#archiveRecordKnown = !0, this.#updatedAt = Math.max(this.#updatedAt, next.confirmedAt), this.#queuePersistence(), !0);
	  }
	  markPostUnavailable(rawPostNumber, rawStatus, confirmedAt = this.#now()) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawPostNumber);
	    if (postNumber === null || !this.#posts.has(postNumber)) return !1;
	    const next = Object.freeze({
	      postNumber,
	      status: localArchiveStatus(rawStatus),
	      confirmedAt: finiteTimestamp(confirmedAt, "confirmedAt")
	    }), current = this.#unavailablePosts.get(postNumber);
	    return current && current.confirmedAt > next.confirmedAt ? !1 : (this.#unavailablePosts.set(postNumber, next), this.#archiveRecordKnown = !0, this.#updatedAt = Math.max(this.#updatedAt, next.confirmedAt), this.#queuePersistence(), !0);
	  }
	  localArchiveState() {
	    return Object.freeze({
	      topic: this.#unavailableTopic,
	      posts: Object.freeze(
	        [...this.#unavailablePosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      )
	    });
	  }
	  hasLocalArchive() {
	    return this.#unavailableTopic !== null || this.#unavailablePosts.size > 0;
	  }
	  removePost(rawPostNumber, rawPostId, source, observedAt = this.#now()) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawPostNumber);
	    if (postNumber === null) throw new RangeError("postNumber 必须是正安全整数");
	    const postId = (0, import_identifiers.discoursePostId)(rawPostId), normalizedObservedAt = finiteTimestamp(observedAt, "observedAt"), current = this.#posts.get(postNumber), currentId = current?.value?.id;
	    if (currentId !== void 0 && (0, import_identifiers.discoursePostId)(currentId) !== postId)
	      throw new Error(`楼层 #${postNumber} 与 post.id ${postId} 不一致`);
	    const removed = this.#removedPosts.get(postNumber), next = Object.freeze({
	      postNumber,
	      postId,
	      observedAt: normalizedObservedAt,
	      source
	    });
	    if (current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, next) || removed && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(removed, next))
	      return Object.freeze({ removed: !1, postNumber, postId, streamChanged: !1 });
	    this.#posts.delete(postNumber), this.#unavailablePosts.delete(postNumber), this.#removedPosts.set(postNumber, next);
	    const nextStream = this.#streamPostIds.filter((candidate) => candidate !== postId), streamChanged = nextStream.length !== this.#streamPostIds.length;
	    return streamChanged && (this.#streamPostIds = Object.freeze(nextStream), this.#streamObservedAt = normalizedObservedAt), (current || streamChanged) && (this.#expectedPostCount = Math.max(0, this.#expectedPostCount - 1)), this.#updatedAt = Math.max(this.#updatedAt, normalizedObservedAt), this.#queuePersistence(), Object.freeze({ removed: !0, postNumber, postId, streamChanged });
	  }
	  restore() {
	    if (this.#restorePromise) return this.#restorePromise;
	    const promise = this.#restoreFromCache();
	    this.#restorePromise = promise;
	    const clearRestore = () => {
	      this.#restorePromise === promise && (this.#restorePromise = null);
	    };
	    return promise.then(clearRestore, clearRestore), promise;
	  }
	  topic() {
	    return this.#topic;
	  }
	  post(postNumber) {
	    const normalized = (0, import_identifiers.tryDiscoursePostNumber)(postNumber);
	    return normalized === null ? void 0 : this.#posts.get(normalized)?.value;
	  }
	  posts() {
	    return Object.freeze(
	      [...this.#posts.values()].sort((left, right) => left.postNumber - right.postNumber).map((entry) => entry.value)
	    );
	  }
	  streamPostIds() {
	    return this.#streamPostIds;
	  }
	  isFresh(now = this.#now()) {
	    return this.#unavailableTopic !== null ? !0 : this.#updatedAt > 0 ? Math.max(0, finiteTimestamp(now, "now") - this.#updatedAt) <= this.#policy.freshForMs : !1;
	  }
	  snapshot(now = this.#now()) {
	    return Object.freeze({
	      schemaVersion: 2,
	      topicId: this.topicId,
	      authScope: this.authScope,
	      savedAt: now,
	      updatedAt: this.#updatedAt,
	      expectedPostCount: this.#expectedPostCount,
	      topicObservedAt: this.#topicObservedAt,
	      topicSource: this.#topicSource,
	      topic: this.#topic,
	      streamObservedAt: this.#streamObservedAt,
	      streamPostIds: this.#streamPostIds,
	      posts: Object.freeze(
	        [...this.#posts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      removedPosts: Object.freeze(
	        [...this.#removedPosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      unavailableTopic: this.#unavailableTopic,
	      unavailablePosts: Object.freeze(
	        [...this.#unavailablePosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      tree: this.#tree
	    });
	  }
	  replyTreeSnapshotStore() {
	    return Object.freeze({
	      load: async (topicId) => String(topicId) !== this.topicId ? null : (await this.restore())?.snapshot.tree ?? null,
	      save: async (topicId, snapshot) => {
	        if (String(topicId) !== this.topicId || snapshot.topicId !== this.topicId)
	          throw new Error(`回复树快照 Topic ${topicId} 与仓储 ${this.topicId} 不匹配`);
	        this.#tree = snapshot, this.#expectedPostCount = Math.max(
	          this.#expectedPostCount,
	          snapshot.expectedPostCount
	        ), this.#queuePersistence(), await this.flush();
	      }
	    });
	  }
	  async flush() {
	    for (; this.#pendingWrite || this.#persisting; )
	      this.#persisting || this.#startPersistence(), await this.#persisting;
	  }
	  /**
	   * 缓存重建失败时,把当前完整快照重新写回唯一 snapshot policy。
	   *
	   * 正常增量持久化仍走 merge/idle;这里仅用于缓存已失效、且必须立即恢复阅读器的
	   * rollback 边界,因此直接覆盖刚被清空的同 Topic 快照。
	   */
	  persistCurrentSnapshot() {
	    return this.#responses.write(
	      this.hasLocalArchive() ? this.#archivePolicy : this.#policy,
	      this.snapshot()
	    );
	  }
	  setPersistenceDelayReader(reader) {
	    return this.#readPersistenceDelayMs = reader, () => {
	      this.#readPersistenceDelayMs === reader && (this.#readPersistenceDelayMs = () => 0);
	    };
	  }
	  async #restoreFromCache() {
	    const read = async (policy) => {
	      const cached2 = await this.#responses.read(
	        policy
	      );
	      if (cached2.state === "miss" || cached2.value === void 0) return null;
	      try {
	        const stored2 = this.#normalizeStoredSnapshot(cached2.value);
	        if (policy === this.#archivePolicy && !(stored2.unavailableTopic || stored2.unavailablePosts?.length))
	          throw new Error(`Topic ${this.topicId} 的永久存档缺少失效标记`);
	        return Object.freeze({
	          policy,
	          cached: cached2,
	          stored: stored2
	        });
	      } catch (error) {
	        return this.#onInvalidSnapshot(error), await this.#responses.invalidate({ ids: [policy.id] }), null;
	      }
	    }, [archived, regular] = await Promise.all([
	      read(this.#archivePolicy),
	      read(this.#policy)
	    ]), restored = archived && regular ? archived.stored.updatedAt >= regular.stored.updatedAt ? archived : regular : archived ?? regular;
	    if (!restored) return null;
	    const { cached, policy: restoredPolicy, stored } = restored;
	    this.#archiveRecordKnown = archived !== null;
	    const staleArchive = archived !== null && restored === regular, discardedInvalidTree = cached.value.tree !== null && cached.value.tree !== void 0 && stored.tree === null, hadLocalState = this.#topic !== null || this.#posts.size > 0 || this.#removedPosts.size > 0 || this.#streamPostIds.length > 0 || this.#tree !== null || this.hasLocalArchive();
	    let topicFilled = !1, streamFilled = !1, removalFilled = !1, availabilityFilled = !1;
	    const addedPostNumbers = [];
	    this.#topic === null && stored.topic !== null && (this.#topic = stored.topic, this.#topicObservedAt = stored.topicObservedAt, this.#topicSource = stored.topicSource, topicFilled = !0), !this.#streamPostIds.length && stored.streamPostIds.length && (this.#streamPostIds = stored.streamPostIds, this.#streamObservedAt = stored.streamObservedAt, streamFilled = !0);
	    for (const removed of stored.removedPosts ?? []) {
	      const current = this.#posts.get(removed.postNumber);
	      if (current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, removed))
	        continue;
	      const existingRemoval = this.#removedPosts.get(removed.postNumber);
	      if (existingRemoval && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(existingRemoval, removed))
	        continue;
	      this.#posts.delete(removed.postNumber), this.#removedPosts.set(removed.postNumber, removed);
	      const nextStream = this.#streamPostIds.filter(
	        (postId) => postId !== removed.postId
	      );
	      nextStream.length !== this.#streamPostIds.length && (this.#streamPostIds = Object.freeze(nextStream), streamFilled = !0), removalFilled = !0;
	    }
	    for (const entry of stored.posts)
	      this.#removedPosts.has(entry.postNumber) || this.#posts.has(entry.postNumber) || (this.#posts.set(entry.postNumber, entry), addedPostNumbers.push(entry.postNumber));
	    stored.unavailableTopic && (!this.#unavailableTopic || stored.unavailableTopic.confirmedAt >= this.#unavailableTopic.confirmedAt) && (this.#unavailableTopic = stored.unavailableTopic, availabilityFilled = !0);
	    for (const unavailable of stored.unavailablePosts ?? []) {
	      if (!this.#posts.has(unavailable.postNumber)) continue;
	      const current = this.#unavailablePosts.get(unavailable.postNumber);
	      current && current.confirmedAt > unavailable.confirmedAt || (this.#unavailablePosts.set(unavailable.postNumber, unavailable), availabilityFilled = !0);
	    }
	    this.hasLocalArchive() && (this.#archiveRecordKnown = !0), this.#tree === null && stored.tree !== null && (this.#tree = stored.tree);
	    const blockedStoredPostCount = stored.posts.filter((entry) => this.#removedPosts.has(entry.postNumber)).length;
	    if (this.#expectedPostCount = Math.max(
	      this.#expectedPostCount,
	      Math.max(0, stored.expectedPostCount - blockedStoredPostCount)
	    ), this.#updatedAt = Math.max(this.#updatedAt, stored.updatedAt), discardedInvalidTree && await this.#responses.invalidate({ ids: [restoredPolicy.id] }), (discardedInvalidTree || hadLocalState && (topicFilled || streamFilled || removalFilled || availabilityFilled || addedPostNumbers.length)) && this.#queuePersistence(), staleArchive) {
	      const cleanup = await this.#responses.invalidateWithReport({
	        ids: [this.#archivePolicy.id]
	      });
	      this.#archiveRecordKnown = !cleanup.complete, cleanup.complete || this.#queuePersistence();
	    }
	    return Object.freeze({
	      snapshot: stored,
	      addedPostNumbers: Object.freeze(addedPostNumbers.sort((left, right) => left - right)),
	      topicFilled,
	      streamFilled
	    });
	  }
	  #normalizeStoredSnapshot(value) {
	    if (!value || value.schemaVersion !== 2 || value.topicId !== this.topicId || value.authScope !== this.authScope || !Array.isArray(value.posts) || value.removedPosts !== void 0 && !Array.isArray(value.removedPosts) || value.unavailablePosts !== void 0 && !Array.isArray(value.unavailablePosts) || !Array.isArray(value.streamPostIds))
	      throw new Error(`Topic ${this.topicId} 的正文快照身份或 schema 无效`);
	    const removedPosts = /* @__PURE__ */ new Map();
	    for (const rawRemoval of value.removedPosts ?? [])
	      try {
	        const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawRemoval?.postNumber), source = (0, import_ingest_version.normalizeDiscourseIngestSource)(rawRemoval?.source);
	        if (postNumber === null || source === null || !Number.isFinite(rawRemoval.observedAt) || rawRemoval.observedAt < 0)
	          throw new Error("删除墓碑字段无效");
	        const removal = Object.freeze({
	          postNumber,
	          postId: (0, import_identifiers.discoursePostId)(rawRemoval.postId),
	          observedAt: rawRemoval.observedAt,
	          source
	        }), current = removedPosts.get(postNumber);
	        (!current || (0, import_ingest_version.shouldReplaceDiscourseVersion)(current, removal)) && removedPosts.set(postNumber, removal);
	      } catch (error) {
	        this.#onInvalidSnapshot(new Error(
	          `Topic ${this.topicId} 的楼层删除墓碑无效`,
	          { cause: error }
	        ));
	      }
	    const posts = /* @__PURE__ */ new Map(), blockedStreamPostIds = new Set(
	      [...removedPosts.values()].map((entry) => entry.postId)
	    );
	    for (const rawEntry of value.posts) {
	      const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawEntry?.postNumber), source = (0, import_ingest_version.normalizeDiscourseIngestSource)(rawEntry?.source), valuePostNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawEntry?.value?.post_number);
	      if (postNumber === null || valuePostNumber !== postNumber || source === null || !Number.isFinite(rawEntry.observedAt))
	        continue;
	      const postVersion = { observedAt: rawEntry.observedAt, source }, removal = removedPosts.get(postNumber);
	      if (removal) {
	        const rawPostId = rawEntry.value.id;
	        if (rawPostId !== void 0)
	          try {
	            const postId = (0, import_identifiers.discoursePostId)(rawPostId);
	            postId !== removal.postId && (blockedStreamPostIds.add(postId), this.#onInvalidSnapshot(new Error(
	              `Topic ${this.topicId} 楼层 #${postNumber} 的删除 post.id 不一致`
	            )));
	          } catch (error) {
	            this.#onInvalidSnapshot(error);
	          }
	        if (!(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removal, postVersion)) continue;
	        removedPosts.delete(postNumber), blockedStreamPostIds.delete(removal.postId);
	      }
	      const current = posts.get(postNumber);
	      (!current || (0, import_ingest_version.shouldReplaceDiscourseVersion)(
	        current,
	        postVersion
	      )) && posts.set(postNumber, Object.freeze({
	        postNumber,
	        observedAt: rawEntry.observedAt,
	        source,
	        value: rawEntry.value
	      }));
	    }
	    const topicSource = value.topicSource === null ? null : (0, import_ingest_version.normalizeDiscourseIngestSource)(value.topicSource);
	    if (value.topic !== null && topicSource === null)
	      throw new Error(`Topic ${this.topicId} 的正文来源无效`);
	    const normalizedPosts = Object.freeze(
	      [...posts.values()].sort((left, right) => left.postNumber - right.postNumber)
	    ), unavailableTopic = value.unavailableTopic ? Object.freeze({
	      status: localArchiveStatus(value.unavailableTopic.status),
	      confirmedAt: finiteTimestamp(
	        value.unavailableTopic.confirmedAt,
	        "unavailableTopic.confirmedAt"
	      )
	    }) : null, unavailablePosts = /* @__PURE__ */ new Map();
	    for (const rawUnavailable of value.unavailablePosts ?? [])
	      try {
	        const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawUnavailable?.postNumber);
	        if (postNumber === null || !posts.has(postNumber)) continue;
	        const unavailable = Object.freeze({
	          postNumber,
	          status: localArchiveStatus(rawUnavailable.status),
	          confirmedAt: finiteTimestamp(
	            rawUnavailable.confirmedAt,
	            "unavailablePost.confirmedAt"
	          )
	        });
	        if (posts.get(postNumber).observedAt > unavailable.confirmedAt) continue;
	        const current = unavailablePosts.get(postNumber);
	        (!current || current.confirmedAt <= unavailable.confirmedAt) && unavailablePosts.set(postNumber, unavailable);
	      } catch (error) {
	        this.#onInvalidSnapshot(error);
	      }
	    return Object.freeze({
	      schemaVersion: 2,
	      topicId: this.topicId,
	      authScope: this.authScope,
	      savedAt: finiteTimestamp(value.savedAt, "savedAt"),
	      updatedAt: finiteTimestamp(value.updatedAt, "updatedAt"),
	      expectedPostCount: nonNegativeInteger(value.expectedPostCount, "expectedPostCount"),
	      topicObservedAt: finiteTimestamp(value.topicObservedAt, "topicObservedAt"),
	      topicSource,
	      topic: value.topic,
	      streamObservedAt: finiteTimestamp(value.streamObservedAt, "streamObservedAt"),
	      streamPostIds: Object.freeze(
	        normalizeStreamPostIds(value.streamPostIds).filter((postId) => !blockedStreamPostIds.has(postId))
	      ),
	      posts: normalizedPosts,
	      removedPosts: Object.freeze(
	        [...removedPosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      unavailableTopic: unavailableTopic && unavailableTopic.confirmedAt >= value.topicObservedAt ? unavailableTopic : null,
	      unavailablePosts: Object.freeze(
	        [...unavailablePosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      tree: validTreeSnapshot(
	        value.tree,
	        this.topicId,
	        normalizedPosts,
	        new Set(removedPosts.keys()),
	        this.#onInvalidTreeSnapshot
	      )
	    });
	  }
	  #mergeStoredSnapshots(storedValue, incomingValue) {
	    let stored;
	    try {
	      stored = this.#normalizeStoredSnapshot(storedValue);
	    } catch (error) {
	      return this.#onInvalidSnapshot(error), this.#normalizeStoredSnapshot(incomingValue);
	    }
	    const incoming = this.#normalizeStoredSnapshot(incomingValue), posts = /* @__PURE__ */ new Map(), removals = /* @__PURE__ */ new Map(), applyRemoval = (removal) => {
	      const post = posts.get(removal.postNumber);
	      if (post && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(post, removal)) return;
	      const current = removals.get(removal.postNumber);
	      current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, removal) || (posts.delete(removal.postNumber), removals.set(removal.postNumber, removal));
	    }, applyPost = (post) => {
	      const removal = removals.get(post.postNumber);
	      if (removal && !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removal, post)) return;
	      const current = posts.get(post.postNumber);
	      current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, post) || (removals.delete(post.postNumber), posts.set(post.postNumber, post));
	    };
	    for (const snapshot of [stored, incoming]) {
	      for (const removal of snapshot.removedPosts ?? []) applyRemoval(removal);
	      for (const post of snapshot.posts) applyPost(post);
	    }
	    let topic = stored.topic, topicObservedAt = stored.topicObservedAt, topicSource = stored.topicSource;
	    incoming.topic !== null && incoming.topicSource !== null && (0, import_ingest_version.shouldReplaceDiscourseVersion)(
	      { observedAt: topicObservedAt, source: topicSource },
	      {
	        observedAt: incoming.topicObservedAt,
	        source: incoming.topicSource
	      }
	    ) && (topic = incoming.topic, topicObservedAt = incoming.topicObservedAt, topicSource = incoming.topicSource);
	    const incomingStreamWins = incoming.streamObservedAt >= stored.streamObservedAt, streamPostIds = incomingStreamWins ? incoming.streamPostIds : stored.streamPostIds, blockedPostIds = new Set(
	      [...removals.values()].map((removal) => removal.postId)
	    ), incomingStateWins = incoming.updatedAt >= stored.updatedAt, unavailableTopic = [stored.unavailableTopic, incoming.unavailableTopic].filter((value) => value != null).sort((left, right) => right.confirmedAt - left.confirmedAt)[0] ?? null, unavailablePosts = /* @__PURE__ */ new Map();
	    for (const snapshot of [stored, incoming])
	      for (const unavailable of snapshot.unavailablePosts ?? []) {
	        const current = unavailablePosts.get(unavailable.postNumber);
	        (!current || current.confirmedAt <= unavailable.confirmedAt) && unavailablePosts.set(unavailable.postNumber, unavailable);
	      }
	    for (const [postNumber, unavailable] of unavailablePosts) {
	      const post = posts.get(postNumber);
	      (!post || post.observedAt > unavailable.confirmedAt) && unavailablePosts.delete(postNumber);
	    }
	    const tree = stored.tree ? incoming.tree && incoming.tree.savedAt >= stored.tree.savedAt ? incoming.tree : stored.tree : incoming.tree;
	    return Object.freeze({
	      schemaVersion: 2,
	      topicId: this.topicId,
	      authScope: this.authScope,
	      savedAt: Math.max(stored.savedAt, incoming.savedAt),
	      updatedAt: Math.max(stored.updatedAt, incoming.updatedAt),
	      expectedPostCount: incomingStateWins && incoming.expectedPostCount > 0 ? incoming.expectedPostCount : stored.expectedPostCount,
	      topicObservedAt,
	      topicSource,
	      topic,
	      streamObservedAt: incomingStreamWins ? incoming.streamObservedAt : stored.streamObservedAt,
	      streamPostIds: Object.freeze(
	        streamPostIds.filter((postId) => !blockedPostIds.has(postId))
	      ),
	      posts: Object.freeze(
	        [...posts.values()].sort(
	          (left, right) => left.postNumber - right.postNumber
	        )
	      ),
	      removedPosts: Object.freeze(
	        [...removals.values()].sort(
	          (left, right) => left.postNumber - right.postNumber
	        )
	      ),
	      unavailableTopic: unavailableTopic && unavailableTopic.confirmedAt >= topicObservedAt ? unavailableTopic : null,
	      unavailablePosts: Object.freeze(
	        [...unavailablePosts.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      tree
	    });
	  }
	  #queuePersistence() {
	    this.#pendingWrite = !0, this.#persistenceReadyAt = Math.max(
	      this.#persistenceReadyAt,
	      this.#now() + this.#persistenceIdleMs
	    ), this.#persisting || this.#startPersistence();
	  }
	  async #waitForPersistenceWindow() {
	    for (; this.#pendingWrite; ) {
	      const repositoryDelay = Math.max(
	        0,
	        this.#persistenceReadyAt - this.#now()
	      ), rawActivityDelay = Number(
	        this.#readPersistenceDelayMs(this.#persistenceIdleMs)
	      ), activityDelay = Number.isFinite(rawActivityDelay) ? Math.max(0, rawActivityDelay) : 0, delayMs = Math.max(repositoryDelay, activityDelay);
	      if (delayMs <= 0) return;
	      await this.#persistenceWait(delayMs);
	    }
	  }
	  #startPersistence() {
	    this.#persisting = Promise.resolve().then(async () => {
	      for (; this.#pendingWrite; ) {
	        if (await this.#waitForPersistenceWindow(), !this.#pendingWrite) continue;
	        this.#pendingWrite = !1;
	        const snapshot = this.snapshot(), archived = !!(snapshot.unavailableTopic || snapshot.unavailablePosts?.length), policy = archived ? this.#archivePolicy : this.#policy;
	        await this.#responses.merge(
	          policy,
	          snapshot,
	          (stored, incoming) => this.#mergeStoredSnapshots(stored, incoming)
	        ), archived ? this.#archiveRecordKnown = !0 : this.#archiveRecordKnown && (await this.#responses.invalidate({ ids: [this.#archivePolicy.id] }), this.#archiveRecordKnown = !1);
	      }
	    }).finally(() => {
	      this.#persisting = null, this.#pendingWrite || (this.#persistenceReadyAt = 0), this.#pendingWrite && this.#startPersistence();
	    });
	  }
	}
}, "ad4eb8518b73dcfd075e62a076d04bdaa84fdbf16470212551d99eaae2394cb5");

/* Source: lite/src/discourse/identifiers.ts */
runtime.register("src/discourse/identifiers.js", function(module, exports, require) {
	var identifiers_exports = {};
	__export(identifiers_exports, {
	  discourseAuthScope: () => discourseAuthScope,
	  discoursePostId: () => discoursePostId,
	  discoursePostIdStream: () => discoursePostIdStream,
	  discoursePostIds: () => discoursePostIds,
	  discoursePostNumber: () => discoursePostNumber,
	  discoursePostNumbers: () => discoursePostNumbers,
	  discoursePostReference: () => discoursePostReference,
	  discourseReplyCursor: () => discourseReplyCursor,
	  discourseTopicId: () => discourseTopicId,
	  tryDiscoursePostId: () => tryDiscoursePostId,
	  tryDiscoursePostNumber: () => tryDiscoursePostNumber,
	  tryDiscourseTopicId: () => tryDiscourseTopicId
	});
	module.exports = __toCommonJS(identifiers_exports);
	function positiveInteger(value, name) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return numeric;
	}
	function discourseTopicId(value) {
	  return positiveInteger(value, "topicId");
	}
	function discoursePostId(value) {
	  return positiveInteger(value, "postId");
	}
	function discoursePostNumber(value) {
	  return positiveInteger(value, "postNumber");
	}
	function discourseReplyCursor(value) {
	  const numeric = Number(value ?? 0);
	  if (!Number.isSafeInteger(numeric) || numeric < 0)
	    throw new RangeError("replyCursor 必须是非负安全整数");
	  return numeric;
	}
	function discourseAuthScope(value) {
	  const normalized = String(value ?? "").trim();
	  if (!normalized) throw new Error("authScope 不能为空");
	  return normalized;
	}
	function tryDiscourseTopicId(value) {
	  try {
	    return discourseTopicId(value);
	  } catch {
	    return null;
	  }
	}
	function tryDiscoursePostId(value) {
	  try {
	    return discoursePostId(value);
	  } catch {
	    return null;
	  }
	}
	function tryDiscoursePostNumber(value) {
	  try {
	    return discoursePostNumber(value);
	  } catch {
	    return null;
	  }
	}
	function discoursePostReference(input) {
	  const postNumber = discoursePostNumber(input.post_number), parent = tryDiscoursePostNumber(input.reply_to_post_number);
	  if (parent === postNumber)
	    throw new Error(`楼层 #${postNumber} 不能回复自身`);
	  return Object.freeze({
	    postId: tryDiscoursePostId(input.id),
	    topicId: tryDiscourseTopicId(input.topic_id),
	    postNumber,
	    replyToPostNumber: parent
	  });
	}
	function discoursePostIds(values) {
	  const normalized = [...new Set(values.map(discoursePostId))].sort((left, right) => left - right);
	  if (!normalized.length) throw new Error("postIds 不能为空");
	  return Object.freeze(normalized);
	}
	function discoursePostIdStream(values) {
	  const seen = /* @__PURE__ */ new Set(), stream = [];
	  for (const value of values) {
	    const postId = discoursePostId(value);
	    seen.has(postId) || (seen.add(postId), stream.push(postId));
	  }
	  return Object.freeze(stream);
	}
	function discoursePostNumbers(values) {
	  const normalized = [...new Set(values.map(discoursePostNumber))].sort((left, right) => left - right);
	  if (!normalized.length) throw new Error("postNumbers 不能为空");
	  return Object.freeze(normalized);
	}
}, "df62eb641b1752e66e82842fdbbd0c8d27ee6ca3384dbfb60a23197a629599a9");

/* Source: lite/src/discourse/ingest-version.ts */
runtime.register("src/discourse/ingest-version.js", function(module, exports, require) {
	var ingest_version_exports = {};
	__export(ingest_version_exports, {
	  discourseObservedAt: () => discourseObservedAt,
	  normalizeDiscourseIngestSource: () => normalizeDiscourseIngestSource,
	  shouldReplaceDiscourseRemoval: () => shouldReplaceDiscourseRemoval,
	  shouldReplaceDiscourseVersion: () => shouldReplaceDiscourseVersion
	});
	module.exports = __toCommonJS(ingest_version_exports);
	const SOURCE_RANK = Object.freeze({
	  "loader-batch": 1,
	  "topic-json": 2,
	  "target-refresh": 3,
	  "action-response": 4,
	  "message-bus": 5
	});
	function discourseObservedAt(value, name = "observedAt") {
	  const numeric = Number(value);
	  if (!Number.isFinite(numeric) || numeric < 0)
	    throw new RangeError(`${name} 必须是非负有限时间戳`);
	  return numeric;
	}
	function normalizeDiscourseIngestSource(value) {
	  return typeof value == "string" && Object.hasOwn(SOURCE_RANK, value) ? value : null;
	}
	function shouldReplaceDiscourseVersion(current, next) {
	  return current ? next.source === "loader-batch" && current.source !== null && current.source !== "loader-batch" ? !1 : next.observedAt !== current.observedAt ? next.observedAt > current.observedAt : current.source === null ? !0 : SOURCE_RANK[next.source] >= SOURCE_RANK[current.source] : !0;
	}
	function shouldReplaceDiscourseRemoval(current, next) {
	  return current.source === "action-response" && (next.source === "loader-batch" || next.source === "topic-json") ? !1 : shouldReplaceDiscourseVersion(current, next);
	}
}, "329717cc7f84a32026374e649cf6c00b22be5286b8e69dc5d2178a55f025dcd1");

/* Source: lite/src/discourse/native-composer.ts */
runtime.register("src/discourse/native-composer.js", function(module, exports, require) {
	var native_composer_exports = {};
	__export(native_composer_exports, {
	  DiscourseComposerCoordinator: () => DiscourseComposerCoordinator,
	  DiscourseComposerEventPort: () => DiscourseComposerEventPort,
	  DiscourseComposerHostIsolation: () => DiscourseComposerHostIsolation,
	  DiscourseComposerTopicSyncController: () => DiscourseComposerTopicSyncController
	});
	module.exports = __toCommonJS(native_composer_exports);
	var import_identifiers = require("./identifiers.js"), import_native_post_model_factory = require("./native-post-model-factory.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_repeat_action_gate = require("../kernel/repeat-action-gate.js"), import_signal = require("../kernel/signal.js"), import_value_record = require("../kernel/value-record.js");
	function modelValue(model, key) {
	  const value = (0, import_value_record.valueRecord)(model);
	  if (!value) return;
	  const getter = value.get;
	  return typeof getter == "function" ? getter.call(value, key) : value[key];
	}
	function composerModelTopicId(model) {
	  const post = modelValue(model, "post"), topic = modelValue(model, "topic") ?? modelValue(post, "topic");
	  return Number(
	    modelValue(topic, "id") ?? modelValue(model, "topic_id") ?? modelValue(model, "topicId") ?? modelValue(post, "topic_id") ?? modelValue(post, "topicId")
	  );
	}
	function setModelValue(model, key, value) {
	  const target = (0, import_value_record.valueRecord)(model);
	  if (!target) throw new Error("Discourse composer model 不可写");
	  const setter = target.set;
	  typeof setter == "function" ? setter.call(target, key, value) : target[key] = value;
	}
	function moduleDefault(host, name) {
	  const module2 = (0, import_value_record.valueRecord)(host.lookupModule(name)), value = (0, import_value_record.valueRecord)(module2?.default);
	  if (!value) throw new Error(`Discourse 原生模块未就绪:${name}`);
	  return value;
	}
	function normalizedDraftSequence(value) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 0)
	    throw new Error("Topic 缺少合法 draft_sequence");
	  return numeric;
	}
	function normalizedDraftKey(value) {
	  const normalized = String(value ?? "").trim();
	  if (!normalized) throw new Error("Topic 缺少 draft_key");
	  return normalized;
	}
	function normalizedMentionUsername(value) {
	  return String(value ?? "").trim().replace(/^@+/, "");
	}
	function rawMentionsUsername(raw, usernameValue) {
	  const username = normalizedMentionUsername(usernameValue);
	  if (!raw || !username) return !1;
	  const escaped = username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
	  return new RegExp(
	    `(^|[^A-Za-z0-9_@-])@${escaped}(?=$|[^A-Za-z0-9_-])`,
	    "i"
	  ).test(raw);
	}
	function composerTextInsertion(currentValue, blockValue, startValue, endValue) {
	  const current = String(currentValue ?? ""), start = Math.min(Math.max(0, startValue), current.length), end = Math.min(Math.max(start, endValue), current.length), before = current.slice(0, start), after = current.slice(end);
	  let inserted = String(blockValue ?? "").trim();
	  return before && !/\s$/.test(before) && (inserted = `

${inserted}`), after && !/^\s/.test(after) && (inserted += `

`), Object.freeze({
	    next: `${before}${inserted}${after}`,
	    inserted,
	    cursor: before.length + inserted.length
	  });
	}
	function composerRequestKey(input) {
	  const topicId = (0, import_identifiers.discourseTopicId)(input.topic.id), post = (0, import_identifiers.discoursePostReference)(input.post);
	  return JSON.stringify([
	    "reply",
	    topicId,
	    post.postId,
	    post.postNumber,
	    String(input.initialRaw ?? "").trim(),
	    String(input.initialRichHtml ?? "").trim(),
	    normalizedMentionUsername(input.dedupeMention),
	    input.replaceRaw === !0
	  ]);
	}
	function composerEditRequestKey(input) {
	  const topicId = (0, import_identifiers.discourseTopicId)(input.topic.id), post = (0, import_identifiers.discoursePostReference)(input.post);
	  return JSON.stringify([
	    "edit",
	    topicId,
	    post.postId,
	    post.postNumber
	  ]);
	}
	function privateMessageRequestKey(username) {
	  return JSON.stringify(["private-message", username]);
	}
	function parseDraft(value) {
	  if (value == null || value === "") return { reply: "" };
	  const parsed = JSON.parse(String(value));
	  if (!parsed || typeof parsed != "object" || Array.isArray(parsed))
	    throw new Error("Discourse composer draft 格式非法");
	  const source = parsed;
	  return Object.freeze({
	    reply: String(source.reply ?? ""),
	    ...source.whisper === void 0 ? {} : { whisper: source.whisper }
	  });
	}
	class DiscourseComposerCoordinator {
	  scope;
	  #host;
	  #document;
	  #models;
	  #isolation;
	  #waitForDelay;
	  #composerOpenTimeoutMs;
	  #onError;
	  #requests = /* @__PURE__ */ new Map();
	  #queue = Promise.resolve();
	  #session = null;
	  #submitPromise = null;
	  #windowPort = null;
	  constructor(options) {
	    this.#host = options.host, this.#document = options.document ?? null, this.#models = new import_native_post_model_factory.DiscourseNativePostModelFactory(options.host), this.#isolation = options.isolation ?? null, this.#waitForDelay = options.waitForDelay ?? ((milliseconds) => new Promise((resolve) => globalThis.setTimeout(resolve, milliseconds))), this.#composerOpenTimeoutMs = Math.max(
	      0,
	      Number(options.composerOpenTimeoutMs) || 1600
	    ), this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => {
	      this.#requests.clear(), this.#queue = Promise.resolve(), this.#session = null, this.#submitPromise = null;
	    });
	  }
	  isOpen() {
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), model = modelValue(composer, "model");
	    return modelValue(model, "viewOpen") === !0 || String(modelValue(model, "composeState") ?? "").toLowerCase() === "open";
	  }
	  /**
	   * 在用户点击回复前只读解析宿主 Composer service、Draft 与原生 model。
	   *
	   * BrowserDiscourseHostApiPort 会缓存成功解析的 service/module,因此应用存续期间的
	   * 首次回复不再承担 module resolver 冷启动;这里不会调用 Draft.get、composer.open
	   * 或创建任何 Topic/Post model。
	   */
	  warmReply() {
	    this.#assertActive();
	    try {
	      return this.#replyRuntime(), this.#models.prepareComposerBindings(), !0;
	    } catch {
	      return !1;
	    }
	  }
	  installCloseGuard(options) {
	    this.#assertActive();
	    const scope = options.parentScope ? options.parentScope.child() : this.scope.child(), gate = new import_repeat_action_gate.RepeatActionGate(), handledEvents = /* @__PURE__ */ new WeakSet(), consume = (event) => {
	      event.preventDefault(), event.stopImmediatePropagation();
	    }, requiresConfirmation = (key, message) => options.enabled() ? gate.confirm(key) ? !1 : (options.notify?.(message), !0) : (gate.clear(), !1), discardComposer = (event) => {
	      consume(event), this.discard().catch((error) => {
	        this.#onError(error), options.notify?.("舍弃回复失败,请重试");
	      });
	    }, ownsActiveComposer = () => {
	      const session = this.#session, composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer"));
	      return !!(session && modelValue(composer, "model") === session.model);
	    }, onClick = (event) => {
	      if (handledEvents.has(event)) return;
	      const control = event.target?.closest(
	        "#reply-control .toggle-save-and-close,#reply-control .discard-button"
	      );
	      if (!control) return;
	      handledEvents.add(event);
	      const shouldDiscard = control.classList.contains("discard-button");
	      if (requiresConfirmation(
	        shouldDiscard ? "composer:discard" : "composer:close",
	        shouldDiscard ? "再点一次舍弃回复" : "再点一次关闭回复窗口"
	      )) {
	        consume(event);
	        return;
	      }
	      shouldDiscard && ownsActiveComposer() && discardComposer(event);
	    }, onKeyDown = (event) => {
	      if (handledEvents.has(event)) return;
	      const keyboard = event;
	      if (!(keyboard.key !== "Escape" || keyboard.repeat || keyboard.defaultPrevented || !this.isOpen())) {
	        if (handledEvents.add(event), requiresConfirmation("composer:escape", "再按一次 Esc 舍弃回复")) {
	          consume(event);
	          return;
	        }
	        ownsActiveComposer() && discardComposer(event);
	      }
	    }, captureTarget = options.document.defaultView ?? options.document;
	    return scope.listen(captureTarget, "click", onClick, !0), scope.listen(captureTarget, "keydown", onKeyDown, !0), captureTarget !== options.document && (scope.listen(options.document, "click", onClick, !0), scope.listen(options.document, "keydown", onKeyDown, !0)), scope.add(() => gate.clear()), scope;
	  }
	  async discard() {
	    this.#assertActive();
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), model = modelValue(composer, "model");
	    if (!composer || !(0, import_value_record.valueRecord)(model)) {
	      this.#session = null;
	      return;
	    }
	    const destroyDraft = composer.destroyDraft;
	    if (typeof destroyDraft != "function")
	      throw new Error("Discourse composer.destroyDraft 尚未就绪");
	    const cleanupErrors = [];
	    try {
	      composer.skipAutoSave = !0;
	      try {
	        const runloop = (0, import_value_record.valueRecord)(this.#host.lookupModule("@ember/runloop")), cancel = runloop?.cancel;
	        typeof cancel == "function" && composer._saveDraftDebounce && cancel.call(runloop, composer._saveDraftDebounce);
	      } catch (error) {
	        cleanupErrors.push(error);
	      }
	      if (await destroyDraft.call(composer), modelValue(composer, "model") === model) {
	        const mutableModel = (0, import_value_record.valueRecord)(model);
	        try {
	          typeof mutableModel?.clearState == "function" && mutableModel.clearState.call(mutableModel);
	        } catch (error) {
	          cleanupErrors.push(error);
	        }
	        try {
	          typeof composer.close == "function" && composer.close.call(composer);
	        } catch (error) {
	          cleanupErrors.push(error);
	        }
	        try {
	          const appEvents = (0, import_value_record.valueRecord)(
	            composer.appEvents ?? this.#host.lookup("service:app-events")
	          );
	          typeof appEvents?.trigger == "function" && appEvents.trigger.call(appEvents, "composer:cancelled");
	        } catch (error) {
	          cleanupErrors.push(error);
	        }
	      }
	      this.#session = null;
	    } finally {
	      try {
	        composer.skipAutoSave = !1;
	      } catch (error) {
	        cleanupErrors.push(error);
	      }
	    }
	    if (cleanupErrors.length)
	      throw new AggregateError(
	        cleanupErrors,
	        "Discourse composer 舍弃后清理失败"
	      );
	  }
	  installSubmitGuard(options) {
	    if (this.#assertActive(), !this.#isolation)
	      throw new Error("Discourse Composer submit guard 缺少宿主隔离 owner");
	    const scope = options.parentScope ? options.parentScope.child() : this.scope.child(), submit = (event) => {
	      const button = event.target?.closest(
	        "#reply-control .save-or-cancel button.create"
	      ) ?? null;
	      !button || button !== this.#submitButton(options.document) || (event.preventDefault(), event.stopImmediatePropagation(), this.#submit(button));
	    };
	    return scope.listen(options.document, "click", submit, !0), scope.listen(options.document, "keydown", (event) => {
	      const keyboard = event;
	      if (keyboard.isComposing || !(keyboard.ctrlKey || keyboard.metaKey) || keyboard.key !== "Enter" && keyboard.code !== "Enter") return;
	      const button = this.#submitButton(options.document);
	      button && (keyboard.preventDefault(), keyboard.stopImmediatePropagation(), this.#submit(button));
	    }, !0), scope;
	  }
	  openReply(input) {
	    return this.#enqueue(
	      composerRequestKey(input),
	      () => this.#openReply(input)
	    );
	  }
	  openEdit(input) {
	    return this.#enqueue(
	      composerEditRequestKey(input),
	      () => this.#openEdit(input)
	    );
	  }
	  openPrivateMessage(usernameValue) {
	    const username = String(usernameValue).trim().replace(/^@+/, "");
	    if (!username) throw new Error("私信 username 不能为空");
	    return this.#enqueue(
	      privateMessageRequestKey(username),
	      () => this.#openPrivateMessage(username)
	    );
	  }
	  bindWindow(port) {
	    return this.#assertActive(), this.#windowPort = port, this.#presentComposerWindow(), () => {
	      this.#windowPort === port && (this.#windowPort = null);
	    };
	  }
	  #enqueue(key, execute) {
	    this.#assertActive();
	    const existing = this.#requests.get(key);
	    if (existing) return existing;
	    const request = this.#queue.then(
	      () => (this.#assertActive(), execute()),
	      () => (this.#assertActive(), execute())
	    ).catch((error) => {
	      throw this.#onError(error), error;
	    }).finally(() => {
	      this.#requests.get(key) === request && this.#requests.delete(key);
	    });
	    return this.#requests.set(key, request), this.#queue = request.then(
	      () => {
	      },
	      () => {
	      }
	    ), request;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #openPrivateMessage(username) {
	    if (this.isOpen())
	      throw new Error("Discourse 原生 composer 正在处理另一项编辑,请先完成或关闭");
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), openNewMessage = composer?.openNewMessage;
	    if (!composer || typeof openNewMessage != "function")
	      throw new Error("Discourse 原生 composer.openNewMessage 尚未就绪");
	    if (await openNewMessage.call(composer, { recipients: username }), this.#assertActive(), !await this.#waitForComposerPopup(this.#composerOpenTimeoutMs))
	      throw new Error("Discourse 原生私信浮窗未显示");
	    if (!this.#presentComposerWindow())
	      throw typeof composer.close == "function" && composer.close.call(composer), new Error("Reader 私信浮窗未接管");
	    return this.#focusComposerInput(), Object.freeze({ username });
	  }
	  async #openReply(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topic.id), postReference = (0, import_identifiers.discoursePostReference)(input.post);
	    if (input.post.topic_id !== void 0 && (0, import_identifiers.discourseTopicId)(input.post.topic_id) !== topicId)
	      throw new Error("composer 回复楼层不属于目标 Topic");
	    const {
	      composer,
	      appEvents,
	      replyAction,
	      draft: Draft
	    } = this.#replyRuntime(), topicModel = this.#models.createTopic(input.topic), postModel = this.#models.createPost(input.topic, input.post, topicModel), currentModel = modelValue(composer, "model"), currentTopicId = composerModelTopicId(currentModel), currentOpen = modelValue(currentModel, "viewOpen") === !0 || String(modelValue(currentModel, "composeState") ?? "").toLowerCase() === "open", currentAction = modelValue(currentModel, "action"), currentReply = String(modelValue(currentModel, "reply") ?? ""), initialRaw = String(input.initialRaw ?? "").trim(), initialRichHtml = String(input.initialRichHtml ?? "").trim(), dedupeMention = normalizedMentionUsername(input.dedupeMention);
	    if (currentOpen && currentTopicId === topicId && currentAction === replyAction && await this.#waitForComposerPopup(640)) {
	      setModelValue(
	        currentModel,
	        "post",
	        postReference.postNumber === 1 ? null : postModel
	      ), this.#presentComposerWindow();
	      const duplicateMention = input.replaceRaw !== !0 && initialRaw && rawMentionsUsername(currentReply, dedupeMention);
	      if (!duplicateMention) {
	        if (initialRaw && input.replaceRaw === !0)
	          setModelValue(currentModel, "reply", initialRaw);
	        else if (initialRaw) {
	          const composerInput = await this.#waitForComposerInput(640);
	          if (this.#document && !composerInput)
	            throw new Error("Discourse 原生回复编辑器未就绪");
	          this.#insertComposerBlock(
	            appEvents,
	            currentModel,
	            initialRaw,
	            initialRichHtml,
	            composerInput
	          );
	        }
	      }
	      const session2 = Object.freeze({
	        topicId,
	        parentPostNumber: postReference.postNumber,
	        action: "reply",
	        reused: !0,
	        model: currentModel,
	        ...duplicateMention ? { insertionSkipped: "duplicate-mention" } : {}
	      });
	      return this.#session = session2, this.#focusComposerInput(), session2;
	    }
	    if (currentOpen) {
	      const popupAvailable = this.#composerPopupAvailable();
	      if ((currentTopicId === topicId || !popupAvailable) && typeof composer.close == "function")
	        composer.close.call(composer), await this.#waitForDelay(0);
	      else
	        throw new Error(
	          "Discourse 原生 composer 正在处理另一项编辑,请先完成或关闭"
	        );
	    }
	    const key = normalizedDraftKey(input.topic.draft_key), sequence = normalizedDraftSequence(input.topic.draft_sequence);
	    let insertAfterOpen = !1, insertionSkipped;
	    const options = {
	      action: replyAction,
	      draftKey: key,
	      draftSequence: sequence,
	      skipJumpOnSave: !0,
	      ...postReference.postNumber === 1 ? { topic: topicModel } : { post: postModel }
	    }, draftResult = (0, import_value_record.valueRecord)(await Draft.get.call(Draft, key));
	    if (draftResult?.draft) {
	      const draft = parseDraft(draftResult.draft);
	      options.draftSequence = normalizedDraftSequence(
	        draftResult.draft_sequence ?? sequence
	      ), input.replaceRaw !== !0 && initialRaw && rawMentionsUsername(draft.reply, dedupeMention) ? (options.reply = draft.reply, insertionSkipped = "duplicate-mention") : input.replaceRaw === !0 ? options.reply = initialRaw : initialRaw && initialRichHtml ? (options.reply = draft.reply, insertAfterOpen = !0) : options.reply = `${draft.reply}${initialRaw ? `
${initialRaw}` : ""}`, draft.whisper !== void 0 && (options.whisper = draft.whisper);
	    } else initialRaw && (input.replaceRaw === !0 ? options.reply = initialRaw : initialRichHtml ? insertAfterOpen = !0 : options.quote = initialRaw);
	    if (currentReply && currentOpen && currentTopicId === topicId && (input.replaceRaw !== !0 && initialRaw && rawMentionsUsername(currentReply, dedupeMention) ? (options.reply = currentReply, insertAfterOpen = !1, insertionSkipped = "duplicate-mention") : input.replaceRaw === !0 ? (options.reply = initialRaw, insertAfterOpen = !1) : initialRaw && initialRichHtml ? (options.reply = currentReply, insertAfterOpen = !0) : options.reply = `${currentReply}${initialRaw ? `
${initialRaw}` : ""}`, delete options.quote), await composer.open.call(composer, options), this.#assertActive(), !await this.#waitForComposerPopup(this.#composerOpenTimeoutMs))
	      throw new Error("Discourse 原生回复浮窗未显示");
	    const model = modelValue(composer, "model");
	    if (!(0, import_value_record.valueRecord)(model)) throw new Error("Discourse composer.open 未生成 model");
	    if (this.#presentComposerWindow(), insertAfterOpen && initialRaw) {
	      const composerInput = await this.#waitForComposerInput(
	        this.#composerOpenTimeoutMs
	      );
	      if (this.#document && !composerInput)
	        throw new Error("Discourse 原生回复编辑器未就绪");
	      this.#insertComposerBlock(
	        appEvents,
	        model,
	        initialRaw,
	        initialRichHtml,
	        composerInput
	      );
	    }
	    const session = Object.freeze({
	      topicId,
	      parentPostNumber: postReference.postNumber,
	      action: "reply",
	      reused: !1,
	      model,
	      ...insertionSkipped ? { insertionSkipped } : {}
	    });
	    return this.#session = session, this.#focusComposerInput(), session;
	  }
	  #replyRuntime() {
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), appEvents = (0, import_value_record.valueRecord)(
	      composer?.appEvents ?? this.#host.lookup("service:app-events")
	    );
	    if (!composer || typeof composer.open != "function" || !appEvents)
	      throw new Error("Discourse 原生 composer service 未就绪");
	    const replyAction = moduleDefault(this.#host, "discourse/models/composer").REPLY;
	    if (!replyAction) throw new Error("Discourse Composer.REPLY 未就绪");
	    const draft = moduleDefault(this.#host, "discourse/models/draft");
	    if (typeof draft.get != "function")
	      throw new Error("Discourse Draft.get 未就绪");
	    return Object.freeze({
	      composer,
	      appEvents,
	      replyAction,
	      draft
	    });
	  }
	  #presentComposerWindow() {
	    const popup = this.#document?.querySelector("#reply-control") ?? null;
	    return !popup || !this.#composerPopupAvailable() ? !1 : this.#windowPort?.open(popup) ?? !1;
	  }
	  #composerPopupAvailable() {
	    if (!this.#document) return !0;
	    const popup = this.#document.querySelector("#reply-control");
	    return !(!popup?.isConnected || popup.hidden || popup.classList.contains("closed") || popup.classList.contains("hidden") || popup.classList.contains("d-none") || popup.getAttribute("aria-hidden") === "true" || popup.closest('[hidden],[aria-hidden="true"]'));
	  }
	  async #waitForComposerPopup(timeoutMs) {
	    if (!this.#document) return !0;
	    const deadline = Date.now() + Math.max(0, timeoutMs);
	    do {
	      if (this.#composerPopupAvailable()) return !0;
	      if (Date.now() >= deadline) return !1;
	      await this.#waitForDelay(80), this.#assertActive();
	    } while (Date.now() <= deadline);
	    return !1;
	  }
	  #composerInput() {
	    const document = this.#document;
	    if (!document || !this.#composerPopupAvailable()) return null;
	    const selector = '#reply-control textarea.d-editor-input,#reply-control input.d-editor-input,#reply-control .ProseMirror.d-editor-input[contenteditable="true"],#reply-control [contenteditable="true"].d-editor-input,#reply-control [role="textbox"][contenteditable="true"],#reply-control .ProseMirror[contenteditable="true"],#reply-control textarea', available = (candidate) => {
	      if (!candidate.isConnected || candidate.hidden || candidate.style.display === "none" || candidate.style.visibility === "hidden" || candidate.matches(
	        '[disabled],[aria-disabled="true"],[aria-hidden="true"],.hidden,.d-none'
	      ) || candidate.closest('[hidden],[aria-hidden="true"],.hidden,.d-none')) return !1;
	      const view = document.defaultView;
	      if (typeof view?.getComputedStyle == "function") {
	        const style = view.getComputedStyle(candidate);
	        if (style.display === "none" || style.visibility === "hidden" || typeof candidate.getClientRects == "function" && candidate.getClientRects().length === 0) return !1;
	      }
	      return !0;
	    }, active = document.activeElement;
	    return active?.matches(selector) && available(active) ? active : [...document.querySelectorAll(selector)].find(available) ?? null;
	  }
	  async #waitForComposerInput(timeoutMs) {
	    if (!this.#document) return null;
	    const deadline = Date.now() + Math.max(0, timeoutMs);
	    do {
	      const input = this.#composerInput();
	      if (input) return input;
	      if (Date.now() >= deadline) return null;
	      await this.#waitForDelay(80), this.#assertActive();
	    } while (Date.now() <= deadline);
	    return null;
	  }
	  #focusComposerInput() {
	    const document = this.#document;
	    if (!document) return;
	    const focus = () => {
	      if (!this.#composerPopupAvailable()) return;
	      const input = this.#composerInput();
	      if (input)
	        try {
	          input.focus({ preventScroll: !0 });
	        } catch {
	          input.focus();
	        }
	    }, viewport = document.defaultView;
	    viewport?.requestAnimationFrame ? viewport.requestAnimationFrame(focus) : queueMicrotask(focus);
	  }
	  #insertComposerBlock(appEvents, model, raw, richHtml, composerInput = this.#composerInput()) {
	    const richEditor = richHtml && composerInput?.matches(
	      '.ProseMirror[contenteditable="true"]'
	    ) ? composerInput : null;
	    if (richEditor) {
	      if (this.#insertRichText(richEditor, richHtml, raw)) return;
	      throw new Error("Discourse 富文本 Composer 引用插入失败");
	    }
	    if (composerInput?.matches("textarea,input.d-editor-input")) {
	      this.#insertTextControlBlock(
	        composerInput,
	        model,
	        raw
	      );
	      return;
	    }
	    const trigger = appEvents.trigger;
	    if (typeof trigger != "function")
	      throw new Error("Discourse app-events 缺少 composer insert-block");
	    trigger.call(appEvents, "composer:insert-block", raw);
	  }
	  #insertTextControlBlock(editor, model, raw) {
	    const modelRaw = String(modelValue(model, "reply") ?? ""), current = editor.value || modelRaw, wasFocused = editor.ownerDocument.activeElement === editor;
	    let start = Number.isInteger(editor.selectionStart) ? Number(editor.selectionStart) : current.length, end = Number.isInteger(editor.selectionEnd) ? Number(editor.selectionEnd) : start;
	    !wasFocused && current && start === 0 && end === 0 && (start = current.length, end = current.length);
	    const insertion = composerTextInsertion(
	      current,
	      raw,
	      start,
	      end
	    );
	    editor.focus(), this.#setTextControlValue(editor, insertion.next), this.#dispatchTextControlInput(editor, insertion.inserted), setModelValue(model, "reply", insertion.next);
	    try {
	      editor.setSelectionRange(insertion.cursor, insertion.cursor);
	    } catch {
	    }
	    if (String(modelValue(model, "reply") ?? "") !== insertion.next || editor.value !== insertion.next)
	      throw new Error("Discourse Markdown Composer 引用插入失败");
	  }
	  #setTextControlValue(editor, value) {
	    try {
	      if (editor.value = value, editor.value === value) return;
	    } catch {
	    }
	    const view = editor.ownerDocument.defaultView;
	    for (const prototype of [
	      view?.HTMLTextAreaElement?.prototype,
	      view?.HTMLInputElement?.prototype,
	      Object.getPrototypeOf(editor)
	    ]) {
	      if (!prototype) continue;
	      const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
	      if (setter)
	        try {
	          if (setter.call(editor, value), editor.value === value) return;
	        } catch {
	        }
	    }
	    throw new Error("Discourse Markdown Composer value 不可写");
	  }
	  #dispatchTextControlInput(editor, inserted) {
	    const InputEventConstructor = editor.ownerDocument.defaultView?.InputEvent;
	    if (typeof InputEventConstructor == "function")
	      try {
	        editor.dispatchEvent(new InputEventConstructor("input", {
	          bubbles: !0,
	          inputType: "insertText",
	          data: inserted
	        }));
	      } catch {
	        this.#dispatchComposerInput(editor);
	      }
	    else
	      this.#dispatchComposerInput(editor);
	    const change = editor.ownerDocument.createEvent("Event");
	    change.initEvent("change", !0, !1), editor.dispatchEvent(change);
	  }
	  #insertRichText(editor, html, raw) {
	    const document = this.#document;
	    if (!document) return !1;
	    try {
	      editor.focus({ preventScroll: !0 });
	    } catch {
	      editor.focus();
	    }
	    const view = document.defaultView, DataTransferConstructor = view?.DataTransfer, ClipboardEventConstructor = view?.ClipboardEvent;
	    if (typeof DataTransferConstructor == "function" && typeof ClipboardEventConstructor == "function")
	      try {
	        const before = editor.innerHTML, data = new DataTransferConstructor();
	        data.setData("text/html", html), data.setData("text/plain", raw), data.setData("text/markdown", raw);
	        const event = new ClipboardEventConstructor("paste", {
	          bubbles: !0,
	          cancelable: !0,
	          clipboardData: data
	        });
	        if (editor.dispatchEvent(event), editor.innerHTML !== before) return !0;
	      } catch {
	      }
	    const execute = document.execCommand;
	    if (typeof execute == "function")
	      try {
	        const before = editor.innerHTML;
	        if (execute.call(document, "insertHTML", !1, html) && editor.innerHTML !== before)
	          return this.#dispatchComposerInput(editor), !0;
	      } catch {
	      }
	    const selection = document.getSelection?.() ?? view?.getSelection?.() ?? null;
	    if (!selection) return !1;
	    let range;
	    selection.rangeCount > 0 && editor.contains(selection.anchorNode) ? range = selection.getRangeAt(0) : (range = document.createRange(), range.selectNodeContents(editor), range.collapse(!1), selection.removeAllRanges(), selection.addRange(range));
	    try {
	      const before = editor.innerHTML;
	      range.deleteContents();
	      const fragment = range.createContextualFragment(html), last = fragment.lastChild;
	      return range.insertNode(fragment), last && (range.setStartAfter(last), range.collapse(!0), selection.removeAllRanges(), selection.addRange(range)), this.#dispatchComposerInput(editor), editor.innerHTML !== before;
	    } catch {
	      return !1;
	    }
	  }
	  #dispatchComposerInput(editor) {
	    const event = editor.ownerDocument.createEvent("Event");
	    event.initEvent("input", !0, !1), editor.dispatchEvent(event);
	  }
	  async #openEdit(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topic.id), postReference = (0, import_identifiers.discoursePostReference)(input.post);
	    if (input.post.topic_id !== void 0 && (0, import_identifiers.discourseTopicId)(input.post.topic_id) !== topicId)
	      throw new Error("composer 编辑楼层不属于目标 Topic");
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer"));
	    if (!composer || typeof composer.open != "function")
	      throw new Error("Discourse 原生 composer service 未就绪");
	    const editAction = moduleDefault(this.#host, "discourse/models/composer").EDIT;
	    if (!editAction) throw new Error("Discourse Composer.EDIT 未就绪");
	    const topicModel = this.#models.createTopic(input.topic), postModel = this.#models.createPost(
	      input.topic,
	      input.post,
	      topicModel
	    ), currentModel = modelValue(composer, "model"), currentTopicId = Number(
	      modelValue(
	        modelValue(currentModel, "topic") ?? modelValue(modelValue(currentModel, "post"), "topic"),
	        "id"
	      )
	    ), currentOpen = modelValue(currentModel, "viewOpen") === !0 || String(modelValue(currentModel, "composeState") ?? "").toLowerCase() === "open", currentAction = modelValue(currentModel, "action");
	    if (currentOpen) {
	      const currentPostId = Number(
	        modelValue(modelValue(currentModel, "post"), "id")
	      );
	      if (currentTopicId !== topicId || currentAction !== editAction || currentPostId !== postReference.postId)
	        throw new Error("Discourse 原生 composer 正在处理另一项编辑,请先完成或关闭");
	      const session2 = Object.freeze({
	        topicId,
	        parentPostNumber: postReference.postNumber,
	        action: "edit",
	        reused: !0,
	        model: currentModel
	      });
	      if (!await this.#waitForComposerPopup(this.#composerOpenTimeoutMs))
	        throw new Error("Discourse 原生编辑浮窗未显示");
	      return this.#session = session2, this.#presentComposerWindow(), this.#focusComposerInput(), session2;
	    }
	    if (await composer.open.call(composer, {
	      action: editAction,
	      post: postModel,
	      draftKey: normalizedDraftKey(input.topic.draft_key),
	      draftSequence: normalizedDraftSequence(input.topic.draft_sequence),
	      skipJumpOnSave: !0
	    }), this.#assertActive(), !await this.#waitForComposerPopup(this.#composerOpenTimeoutMs))
	      throw new Error("Discourse 原生编辑浮窗未显示");
	    const model = modelValue(composer, "model");
	    if (!(0, import_value_record.valueRecord)(model)) throw new Error("Discourse composer.open 未生成 edit model");
	    const session = Object.freeze({
	      topicId,
	      parentPostNumber: postReference.postNumber,
	      action: "edit",
	      reused: !1,
	      model
	    });
	    return this.#session = session, this.#presentComposerWindow(), this.#focusComposerInput(), session;
	  }
	  #submitButton(document) {
	    const session = this.#session, composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer"));
	    if (!session || modelValue(composer, "model") !== session.model || typeof composer?.save != "function") return null;
	    const button = document.querySelector(
	      "#reply-control .save-or-cancel button.create"
	    );
	    return button && !button.disabled ? button : null;
	  }
	  #submit(button) {
	    if (button.disabled || this.#submitPromise) return;
	    const session = this.#session, composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), save = composer?.save;
	    if (!session || !this.#isolation || modelValue(composer, "model") !== session.model || typeof save != "function") return;
	    const transaction = this.#isolation.run(
	      session.topicId,
	      session.action === "edit" ? "edited" : "created",
	      () => save.call(composer, !0, { jump: !1 })
	    );
	    this.#submitPromise = transaction, transaction.catch((error) => {
	      this.#onError(error);
	    }).finally(() => {
	      this.#submitPromise === transaction && (this.#submitPromise = null);
	    });
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("DiscourseComposerCoordinator 已销毁");
	  }
	}
	const COMPOSER_SAVE_EVENTS = Object.freeze([
	  Object.freeze({
	    eventName: "composer:created-post",
	    kind: "created"
	  }),
	  Object.freeze({
	    eventName: "composer:edited-post",
	    kind: "edited"
	  }),
	  Object.freeze({
	    eventName: "post:created",
	    kind: "created"
	  })
	]);
	function topicRouteTarget(value) {
	  let segments;
	  try {
	    segments = new URL(String(value ?? ""), "https://reader.invalid").pathname.split("/").filter(Boolean);
	  } catch {
	    return null;
	  }
	  const topicIndex = segments.indexOf("t");
	  if (topicIndex < 0) return null;
	  const tail = segments.slice(topicIndex + 1), topicOffset = /^\d+$/.test(tail[0] ?? "") ? 0 : 1, topicId = Number(tail[topicOffset]), postNumber = Number(tail[topicOffset + 1]);
	  return Number.isSafeInteger(topicId) && topicId > 0 && Number.isSafeInteger(postNumber) && postNumber > 0 ? Object.freeze({ topicId, postNumber }) : null;
	}
	class DiscourseComposerHostIsolation {
	  scope;
	  #host;
	  #events = new import_signal.Signal();
	  #onError;
	  #releaseActive = null;
	  #suspended = null;
	  #running = !1;
	  constructor(options) {
	    this.#host = options.host, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => {
	      this.#releaseActive?.(), this.#releaseActive = null, this.#resumeTopicController(), this.#events.clear();
	    });
	  }
	  subscribe(listener, scope) {
	    const owned = import_lifecycle.LifecycleScope.ownedBy(scope ?? this.scope);
	    return new DiscourseComposerEventPort(this.#host).subscribe(listener, owned), this.#events.subscribe(listener, owned), () => owned.destroy();
	  }
	  async run(topicIdValue, kind, execute) {
	    if (this.scope.destroyed)
	      throw new Error("Discourse Composer 宿主隔离 owner 已销毁");
	    if (this.#running)
	      throw new Error("Discourse Composer save 事务正在进行");
	    const topicId = Number((0, import_identifiers.discourseTopicId)(topicIdValue)), appEvents = appEventsPort(
	      (0, import_value_record.valueRecord)(this.#host.lookup("service:composer"))?.appEvents ?? this.#host.lookup("service:app-events")
	    ), originalTrigger = appEvents.trigger;
	    if (typeof originalTrigger != "function")
	      throw new Error("Discourse app-events 缺少 trigger");
	    const expectedNames = new Set(
	      kind === "edited" ? ["composer:edited-post"] : ["composer:created-post", "post:created"]
	    ), suppressedNames = /* @__PURE__ */ new Set([
	      "composer:created-post",
	      "composer:edited-post",
	      "post:created",
	      "post:highlight"
	    ]);
	    let observed = !1;
	    const trigger = (eventName, payload) => {
	      const name = String(eventName ?? "");
	      return expectedNames.has(name) && (observed = !0, this.#emit(Object.freeze({
	        kind,
	        payload,
	        eventName: name
	      }))), suppressedNames.has(name) ? appEvents : originalTrigger.call(appEvents, eventName, payload);
	    }, releases = [], release = () => {
	      for (const cleanup of releases.splice(0).reverse())
	        try {
	          cleanup();
	        } catch (cause) {
	          this.#report(cause);
	        }
	      this.#releaseActive === release && (this.#releaseActive = null);
	    };
	    this.#running = !0;
	    try {
	      if (appEvents.trigger = trigger, appEvents.trigger !== trigger)
	        throw new Error("Discourse app-events trigger 无法建立有界隔离");
	      releases.push(() => {
	        appEvents.trigger === trigger && (appEvents.trigger = originalTrigger);
	      });
	      const routeModule = (0, import_value_record.valueRecord)(
	        this.#host.lookupModule("discourse/lib/url")
	      ), routeOwner = (0, import_value_record.valueRecord)(routeModule?.default) ?? routeModule, originalRouteTo = routeOwner?.routeTo;
	      if (routeOwner && typeof originalRouteTo == "function") {
	        const guardedRouteTo = (value, ...args) => {
	          if (topicRouteTarget(value)?.topicId !== topicId)
	            return originalRouteTo.call(routeOwner, value, ...args);
	        };
	        routeOwner.routeTo = guardedRouteTo, routeOwner.routeTo === guardedRouteTo && releases.push(() => {
	          routeOwner.routeTo === guardedRouteTo && (routeOwner.routeTo = originalRouteTo);
	        });
	      }
	      this.#suspendTopicController(topicId), this.#releaseActive = release;
	      const result = await execute();
	      return observed || this.#emit(Object.freeze({
	        kind,
	        payload: result,
	        eventName: kind === "edited" ? "composer:edited-post" : "composer:created-post"
	      })), result;
	    } finally {
	      release(), this.#running = !1;
	    }
	  }
	  runActive(kind, execute) {
	    const composer = (0, import_value_record.valueRecord)(this.#host.lookup("service:composer")), model = modelValue(composer, "model"), topic = modelValue(model, "topic") ?? modelValue(modelValue(model, "post"), "topic");
	    return this.run(
	      Number((0, import_identifiers.discourseTopicId)(modelValue(topic, "id"))),
	      kind,
	      execute
	    );
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #emit(event) {
	    for (const cause of this.#events.emit(event)) this.#report(cause);
	  }
	  #suspendTopicController(topicId) {
	    if (this.#suspended?.topicId === topicId) return;
	    this.#resumeTopicController();
	    const controller = (0, import_value_record.valueRecord)(this.#host.lookup("controller:topic")), hostTopicId = Number(modelValue(modelValue(controller, "model"), "id"));
	    !controller || hostTopicId !== topicId || typeof controller.unsubscribe != "function" || (controller.unsubscribe.call(controller), this.#suspended = Object.freeze({ controller, topicId }));
	  }
	  #resumeTopicController() {
	    const suspended = this.#suspended;
	    if (this.#suspended = null, !(!suspended || typeof suspended.controller.subscribe != "function" || Number(
	      modelValue(modelValue(suspended.controller, "model"), "id")
	    ) !== suspended.topicId))
	      try {
	        suspended.controller.subscribe.call(suspended.controller);
	      } catch (cause) {
	        this.#report(cause);
	      }
	  }
	  #report(cause) {
	    try {
	      this.#onError(cause);
	    } catch {
	    }
	  }
	}
	function appEventsPort(value) {
	  const target = (0, import_value_record.valueRecord)(value);
	  if (!target || typeof target.on != "function" || typeof target.off != "function")
	    throw new Error("Discourse app-events 缺少 on/off");
	  return target;
	}
	function modelJson(value) {
	  const target = (0, import_value_record.valueRecord)(value);
	  if (!target || typeof target.toJSON != "function") return value;
	  try {
	    return target.toJSON.call(target);
	  } catch {
	    return value;
	  }
	}
	function eventPostCandidate(value) {
	  const normalized = modelJson(value), nested = modelValue(normalized, "post");
	  return nested === void 0 ? normalized : modelJson(nested);
	}
	function topicIdForPost(value) {
	  const direct = Number(modelValue(value, "topic_id"));
	  if (Number.isSafeInteger(direct) && direct > 0) return direct;
	  const topic = modelValue(value, "topic"), nested = Number(modelValue(topic, "id"));
	  return Number.isSafeInteger(nested) && nested > 0 ? nested : null;
	}
	class DiscourseComposerEventPort {
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  subscribe(listener, scope) {
	    let appEvents;
	    try {
	      appEvents = appEventsPort(
	        (0, import_value_record.valueRecord)(this.#host.lookup("service:composer"))?.appEvents ?? this.#host.lookup("service:app-events")
	      );
	    } catch {
	      return () => {
	      };
	    }
	    const bindings = [];
	    try {
	      for (const descriptor of COMPOSER_SAVE_EVENTS) {
	        const bound = (payload) => {
	          listener(Object.freeze({
	            kind: descriptor.kind,
	            payload,
	            eventName: descriptor.eventName
	          }));
	        };
	        appEvents.on(descriptor.eventName, bound), bindings.push(Object.freeze({
	          eventName: descriptor.eventName,
	          listener: bound
	        }));
	      }
	    } catch (error) {
	      for (const binding of bindings.reverse())
	        try {
	          appEvents.off(binding.eventName, binding.listener);
	        } catch {
	        }
	      throw error;
	    }
	    let active = !0;
	    const cleanup = () => {
	      if (active) {
	        active = !1;
	        for (const binding of bindings.reverse())
	          try {
	            appEvents.off(binding.eventName, binding.listener);
	          } catch {
	          }
	      }
	    };
	    return scope?.add(cleanup), cleanup;
	  }
	}
	class DiscourseComposerTopicSyncController {
	  scope;
	  changes = new import_signal.Signal();
	  #topicId;
	  #events;
	  #session;
	  #now;
	  #schedule;
	  #cancel;
	  #onError;
	  #pending = /* @__PURE__ */ new Map();
	  #unsubscribe = null;
	  constructor(options) {
	    this.#topicId = (0, import_identifiers.discourseTopicId)(options.topicId), this.#events = options.events, this.#session = options.session, this.#now = options.now ?? Date.now, this.#schedule = options.schedule ?? ((callback) => setTimeout(callback, 0)), this.#cancel = options.cancel ?? ((handle) => clearTimeout(handle)), this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => {
	      this.stop(), this.changes.clear();
	    });
	  }
	  get active() {
	    return this.#unsubscribe !== null;
	  }
	  start() {
	    this.#assertActive(), !this.#unsubscribe && (this.#unsubscribe = this.#events.subscribe((event) => {
	      this.#handle(event);
	    }, this.scope));
	  }
	  stop() {
	    const unsubscribe = this.#unsubscribe;
	    this.#unsubscribe = null, unsubscribe?.();
	    for (const pending of this.#pending.values())
	      "handle" in pending && this.#cancel(pending.handle);
	    this.#pending.clear();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #handle(event) {
	    if (this.scope.destroyed) return;
	    const candidate = eventPostCandidate(event.payload), topicId = topicIdForPost(candidate);
	    if (topicId !== null && topicId !== this.#topicId) return;
	    let reference = null;
	    if (topicId === this.#topicId)
	      try {
	        reference = (0, import_identifiers.discoursePostReference)(
	          candidate
	        );
	      } catch {
	        reference = null;
	      }
	    if (reference && reference.postId !== null) {
	      this.#cancelPending(event.kind);
	      try {
	        event.kind === "created" ? this.#session.ingestCreatedPost(
	          candidate,
	          "action-response",
	          this.#now()
	        ) : this.#session.ingestPosts(
	          Object.freeze([candidate]),
	          "action-response",
	          this.#now()
	        ), this.#emit(Object.freeze({
	          kind: event.kind,
	          post: candidate,
	          postNumber: reference.postNumber,
	          source: "native-event"
	        }));
	      } catch (error) {
	        this.#onError(error);
	      }
	      return;
	    }
	    this.#scheduleFallback(event.kind);
	  }
	  #scheduleFallback(kind) {
	    if (this.#pending.has(kind)) return;
	    const pending = {};
	    this.#pending.set(kind, pending);
	    try {
	      pending.handle = this.#schedule(() => {
	        this.#pending.get(kind) === pending && (this.#pending.delete(kind), Promise.resolve().then(async () => {
	          await this.#session.refresh({ background: !1 }), kind === "created" && await this.#session.loadLastPost({ refresh: !0 });
	        }).then(() => {
	          this.scope.destroyed || this.#emit(Object.freeze({
	            kind,
	            post: null,
	            postNumber: null,
	            source: "canonical-refresh"
	          }));
	        }).catch(this.#onError));
	      });
	    } catch (error) {
	      this.#pending.get(kind) === pending && this.#pending.delete(kind), this.#onError(error);
	    }
	  }
	  #cancelPending(kind) {
	    const pending = this.#pending.get(kind);
	    pending && (this.#pending.delete(kind), "handle" in pending && this.#cancel(pending.handle));
	  }
	  #emit(commit) {
	    if (!this.scope.destroyed)
	      for (const error of this.changes.emit(commit)) this.#onError(error);
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("DiscourseComposerTopicSyncController 已销毁");
	  }
	}
}, "76776dc0758a7cd10d2e27ec465a8a7fb10760d27b6e27435e7e9771629576bf");

/* Source: lite/src/discourse/native-host-api.ts */
runtime.register("src/discourse/native-host-api.js", function(module, exports, require) {
	var native_host_api_exports = {};
	__export(native_host_api_exports, {
	  BrowserDiscourseBookmarkNativeState: () => BrowserDiscourseBookmarkNativeState,
	  BrowserDiscourseHostApiPort: () => BrowserDiscourseHostApiPort,
	  BrowserDiscourseNativeBookmarkForm: () => BrowserDiscourseNativeBookmarkForm,
	  BrowserDiscourseNotificationNativeState: () => BrowserDiscourseNotificationNativeState,
	  discourseAvatarTemplateUrl: () => discourseAvatarTemplateUrl,
	  discourseNativeAppEventSubscription: () => discourseNativeAppEventSubscription,
	  discourseNativeBoostsAvailable: () => discourseNativeBoostsAvailable,
	  discourseNativeCurrentUserBindingAvailable: () => discourseNativeCurrentUserBindingAvailable,
	  discourseNativeCurrentUsername: () => discourseNativeCurrentUsername,
	  discourseNativeEmojiMenu: () => discourseNativeEmojiMenu,
	  discourseNativeEmojiUrl: () => discourseNativeEmojiUrl,
	  discourseNativeExactTimeFormatter: () => discourseNativeExactTimeFormatter,
	  discourseNativeFlagCatalog: () => discourseNativeFlagCatalog,
	  discourseNativeFollowRoute: () => discourseNativeFollowRoute,
	  discourseNativeHostRouteRefresh: () => discourseNativeHostRouteRefresh,
	  discourseNativeIconRenderer: () => discourseNativeIconRenderer,
	  discourseNativeJqueryModule: () => discourseNativeJqueryModule,
	  discourseNativeMenuCloser: () => discourseNativeMenuCloser,
	  discourseNativePostAdminMenu: () => discourseNativePostAdminMenu,
	  discourseNativePostEventModel: () => discourseNativePostEventModel,
	  discourseNativePostRuntimeBindings: () => discourseNativePostRuntimeBindings,
	  discourseNativeRelativeTimeFormatter: () => discourseNativeRelativeTimeFormatter,
	  discourseNativeSiteLogoUrl: () => discourseNativeSiteLogoUrl,
	  discourseNativeTheme: () => discourseNativeTheme,
	  discourseNativeTopicEditCatalog: () => discourseNativeTopicEditCatalog,
	  discourseNativeTopicLinks: () => discourseNativeTopicLinks,
	  discourseNativeTopicPresentation: () => discourseNativeTopicPresentation,
	  discourseNativeUserActionBinding: () => discourseNativeUserActionBinding,
	  discourseNativeUserModel: () => discourseNativeUserModel
	});
	module.exports = __toCommonJS(native_host_api_exports);
	var import_value_record = require("../kernel/value-record.js");
	function discourseNativeAppEventSubscription(host, eventNameValue, listener, onError) {
	  const eventName = String(eventNameValue).trim();
	  if (!eventName) return () => {
	  };
	  const appEvents = (0, import_value_record.objectRecord)(host.lookup("service:app-events")), on = appEvents?.on, off = appEvents?.off;
	  if (typeof on != "function" || typeof off != "function") return () => {
	  };
	  const owner = Object.freeze({});
	  try {
	    on.call(appEvents, eventName, owner, listener);
	  } catch (cause) {
	    return onError?.(cause), () => {
	    };
	  }
	  let active = !0;
	  return () => {
	    if (active) {
	      active = !1;
	      try {
	        off.call(appEvents, eventName, owner, listener);
	      } catch (cause) {
	        onError?.(cause);
	      }
	    }
	  };
	}
	function discourseNativeUserModel(host) {
	  return host.lookupModule("discourse/models/user");
	}
	function discourseNativeFollowRoute(host, kind) {
	  return host.lookupModule(
	    `discourse/plugins/discourse-follow/discourse/routes/${kind}`
	  );
	}
	function discourseNativePostEventModel(host) {
	  return host.lookupModule(
	    "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-event"
	  );
	}
	function discourseNativeBoostsAvailable(host) {
	  const settings = (0, import_value_record.objectRecord)(host.lookup("service:site-settings"));
	  return settings && Object.hasOwn(settings, "discourse_boosts_enabled") ? settings.discourse_boosts_enabled === !0 : !!host.lookupModule(
	    "discourse/plugins/discourse-boosts/discourse/lib/create-boost"
	  );
	}
	function discourseNativeMenuCloser(host) {
	  const menu = (0, import_value_record.objectRecord)(host.lookup("service:menu")), close = menu?.close;
	  return typeof close != "function" ? null : (identifier) => {
	    const normalized = String(identifier).trim();
	    if (!normalized) throw new Error("Discourse menu identifier 不能为空");
	    return close.call(menu, normalized);
	  };
	}
	function discourseNativeHostRouteRefresh(host) {
	  const router = (0, import_value_record.objectRecord)(host.lookup("service:router")), refresh = router?.refresh;
	  if (typeof refresh != "function") return !1;
	  try {
	    const transition = refresh.call(router), rejection = (0, import_value_record.objectRecord)(transition)?.catch;
	    return typeof rejection == "function" && rejection.call(transition, () => {
	    }), !0;
	  } catch {
	    return !1;
	  }
	}
	function discourseNativeThemeMode(service) {
	  const colorMode = String(service?.colorMode ?? "").trim();
	  if (colorMode === "light" || colorMode === "dark") return colorMode;
	  if (colorMode === "auto") return "system";
	  if (service?.lightModeForced) return "light";
	  if (service?.darkModeForced) return "dark";
	  if (service?.selectorAvailable) return "system";
	  const session = (0, import_value_record.objectRecord)(service?.session);
	  return session ? session.defaultColorSchemeIsDark ? "dark" : "light" : null;
	}
	function discourseNativeTheme(host) {
	  return Object.freeze({
	    apply(mode) {
	      const service = (0, import_value_record.objectRecord)(
	        host.lookup("service:interface-color")
	      ), action = service?.[mode === "light" ? "forceLightMode" : mode === "dark" ? "forceDarkMode" : "useAutoMode"];
	      if (typeof action != "function") return !1;
	      try {
	        return action.call(service), !0;
	      } catch {
	        return !1;
	      }
	    },
	    subscribe(listener, scope) {
	      const service = (0, import_value_record.objectRecord)(
	        host.lookup("service:interface-color")
	      ), appEvents = (0, import_value_record.objectRecord)(service?.appEvents), on = appEvents?.on, off = appEvents?.off;
	      if (typeof on != "function" || typeof off != "function")
	        return () => {
	        };
	      const context = Object.freeze({}), onChanged = () => {
	        const mode = discourseNativeThemeMode(service);
	        mode && listener(mode);
	      };
	      try {
	        on.call(
	          appEvents,
	          "interface-color:changed",
	          context,
	          onChanged
	        );
	      } catch {
	        return () => {
	        };
	      }
	      let active = !0;
	      const cleanup = () => {
	        if (active) {
	          active = !1;
	          try {
	            off.call(
	              appEvents,
	              "interface-color:changed",
	              context,
	              onChanged
	            );
	          } catch {
	          }
	        }
	      };
	      return scope.add(cleanup), cleanup;
	    }
	  });
	}
	function discourseNativeTopicEditCatalog(host) {
	  return Object.freeze({
	    categories() {
	      const site = host.lookup("service:site"), source = nativeModelValue(site, "categories");
	      if (!Array.isArray(source)) return Object.freeze([]);
	      const categories = source.map((value) => {
	        const id = Number(nativeModelValue(value, "id")), name = String(nativeModelValue(value, "name") ?? "").trim();
	        if (!Number.isSafeInteger(id) || id < 1 || !name) return null;
	        const parentId = Number(
	          nativeModelValue(value, "parent_category_id")
	        );
	        return Object.freeze({
	          id,
	          name,
	          slug: String(nativeModelValue(value, "slug") ?? "").trim(),
	          color: String(nativeModelValue(value, "color") ?? "").trim().replace(/^#/, ""),
	          parentCategoryId: Number.isSafeInteger(parentId) && parentId > 0 ? parentId : null
	        });
	      }).filter((value) => value !== null);
	      return Object.freeze(categories);
	    },
	    async searchTags(request) {
	      const tagUtils = (0, import_value_record.objectRecord)(host.lookup("service:tag-utils")), search = tagUtils?.searchTags, settings = host.lookup("service:site-settings");
	      if (!tagUtils || typeof search != "function" || !(0, import_value_record.objectRecord)(settings))
	        throw new Error("Discourse 标签搜索服务尚未就绪");
	      const tagUtilsOwner = tagUtils, selectedIds = request.selected.map((tag) => Number(tag.id)).filter((id) => Number.isSafeInteger(id) && id > 0), selectedNames = request.selected.filter((tag) => !(Number(tag.id) > 0)).map((tag) => tag.name), result = await search.call(
	        tagUtils,
	        "/tags/filter/search",
	        Object.freeze({
	          q: String(request.query ?? "").trim(),
	          limit: Math.max(
	            1,
	            Number(nativeModelValue(
	              settings,
	              "max_tag_search_results"
	            )) || 20
	          ),
	          categoryId: request.categoryId > 0 ? request.categoryId : void 0,
	          filterForInput: !0,
	          ...selectedIds.length ? { selected_tag_ids: selectedIds.slice(0, 100) } : {},
	          ...selectedNames.length ? { selected_tags: selectedNames.slice(0, 100) } : {}
	        }),
	        (json) => {
	          const payload = (0, import_value_record.objectRecord)(json), incoming = Array.isArray(payload?.results) ? payload.results : [], sort = tagUtilsOwner.sortSearchResults;
	          return typeof sort == "function" ? sort.call(tagUtilsOwner, incoming) : incoming;
	        }
	      ), values = Array.isArray(result) ? result : [], byName = /* @__PURE__ */ new Map();
	      for (const value of values) {
	        const name = String(
	          nativeModelValue(value, "name") ?? nativeModelValue(value, "text") ?? ""
	        ).trim();
	        if (!name) continue;
	        const id = Number(nativeModelValue(value, "id"));
	        byName.set(name.toLocaleLowerCase(), Object.freeze({
	          id: Number.isSafeInteger(id) && id > 0 ? id : null,
	          name
	        }));
	      }
	      return Object.freeze([...byName.values()]);
	    }
	  });
	}
	const DISCOURSE_ICON_ALIASES = Object.freeze({
	  "alert-triangle": "triangle-exclamation",
	  boost: "rocket",
	  "check-square": "square-check",
	  "circle-x": "circle-xmark",
	  "external-link": "arrow-up-right-from-square",
	  "eye-off": "eye-slash",
	  "header-bell": "far-bell",
	  "header-bookmark": "far-bookmark",
	  "header-settings": "sliders",
	  history: "clock-rotate-left",
	  languages: "language",
	  "list-checks": "list-check",
	  mail: "envelope",
	  "maximize-2": "up-right-and-down-left-from-center",
	  "minimize-2": "down-left-and-up-right-to-center",
	  "message-square": "message",
	  "panel-left": "table-columns",
	  "panel-right": "table-columns",
	  "floating-window": "window-maximize",
	  pin: "thumbtack",
	  "rotate-ccw": "arrow-rotate-left",
	  search: "magnifying-glass",
	  settings: "gear",
	  share: "share-nodes",
	  smile: "face-smile",
	  trash: "trash-can",
	  x: "xmark"
	}), SVG_NAMESPACE = "http://www.w3.org/2000/svg", SAFE_SVG_FRAGMENT_ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
	function sanitizeInlinedIcon(root) {
	  const queryAll = root.querySelectorAll;
	  if (typeof queryAll != "function") return;
	  const elements = [root, ...Array.from(queryAll.call(root, "*"))];
	  for (const element of elements) {
	    const localName = String(element.localName ?? element.tagName).toLowerCase();
	    if (localName === "script" || localName === "foreignobject") {
	      element.remove();
	      continue;
	    }
	    for (const attribute of Array.from(element.attributes ?? [])) {
	      const name = attribute.name.toLowerCase();
	      if (name.startsWith("on")) {
	        element.removeAttribute(attribute.name);
	        continue;
	      }
	      (name === "href" || name === "xlink:href") && attribute.value && !attribute.value.startsWith("#") && element.removeAttribute(attribute.name);
	    }
	  }
	}
	function inlineDiscourseIconUse(value, document) {
	  const svg = value;
	  if (svg?.nodeType !== 1 || typeof svg.querySelector != "function" || typeof svg.replaceChildren != "function")
	    return;
	  const use = svg.querySelector("use"), href = use?.getAttribute("href") ?? use?.getAttribute("xlink:href") ?? "";
	  if (!href.startsWith("#")) return;
	  const fragmentId = href.slice(1);
	  if (!SAFE_SVG_FRAGMENT_ID.test(fragmentId)) return;
	  const symbol = document.getElementById(fragmentId);
	  if (!symbol || String(symbol.localName ?? symbol.tagName).toLowerCase() !== "symbol")
	    return;
	  const group = document.createElementNS(SVG_NAMESPACE, "g");
	  for (const child of Array.from(symbol.childNodes))
	    group.append(child.cloneNode(!0));
	  if (sanitizeInlinedIcon(group), !group.childNodes.length) return;
	  const viewBox = symbol.getAttribute("viewBox");
	  viewBox && !svg.getAttribute("viewBox") && svg.setAttribute("viewBox", viewBox), svg.replaceChildren(group);
	}
	function discourseNativeIconRenderer(host) {
	  return (nameValue, document) => {
	    const name = String(nameValue).trim();
	    if (!name) return null;
	    const candidates = Object.freeze([
	      DISCOURSE_ICON_ALIASES[name],
	      name
	    ].filter((candidate2) => !!candidate2)), module2 = (0, import_value_record.objectRecord)(
	      host.lookupModule("discourse/lib/icon-library")
	    ), defaultExport = (0, import_value_record.objectRecord)(module2?.default), owner = typeof module2?.iconElement == "function" ? module2 : defaultExport, iconElement = owner?.iconElement;
	    if (typeof iconElement == "function")
	      for (const candidate2 of candidates)
	        try {
	          const rendered = iconElement.call(owner, candidate2), node = rendered;
	          if (node?.nodeType !== 1 || !node.classList) continue;
	          return node.classList.add("ldp-icon", `ldp-icon-${name}`), node.dataset && (node.dataset.icon = name), node.setAttribute?.("aria-hidden", "true"), inlineDiscourseIconUse(rendered, document), rendered;
	        } catch {
	        }
	    const candidate = candidates[0];
	    if (!candidate) return null;
	    const svg = document.createElementNS(
	      SVG_NAMESPACE,
	      "svg"
	    );
	    svg.classList.add(
	      "svg-icon",
	      "icon",
	      "d-icon",
	      `d-icon-${candidate}`,
	      "ldp-icon",
	      `ldp-icon-${name}`
	    ), svg.dataset.icon = name, svg.setAttribute("aria-hidden", "true");
	    const use = document.createElementNS(
	      SVG_NAMESPACE,
	      "use"
	    );
	    return use.setAttribute("href", `#${candidate}`), svg.append(use), svg;
	  };
	}
	function discourseAvatarTemplateUrl(template, size, baseUrl) {
	  const source = String(template).replace(/\{size\}/g, String(size)).trim();
	  if (!source) return null;
	  try {
	    return new URL(source, baseUrl).href;
	  } catch {
	    return null;
	  }
	}
	function discourseNativeCurrentUser(host) {
	  const serviceUser = host.lookup("service:current-user");
	  if (serviceUser) return serviceUser;
	  const userModule = (0, import_value_record.valueRecord)(host.lookupModule("discourse/models/user"));
	  for (const owner of [(0, import_value_record.valueRecord)(userModule?.default), userModule]) {
	    const current = owner?.current;
	    if (typeof current == "function")
	      try {
	        const user = current.call(owner);
	        if (user) return user;
	      } catch {
	      }
	  }
	  return null;
	}
	function discourseNativeCurrentUserBindingAvailable(host) {
	  const currentUser = host.lookup("service:current-user");
	  if (currentUser != null) return !0;
	  const userModule = (0, import_value_record.valueRecord)(host.lookupModule("discourse/models/user"));
	  return [(0, import_value_record.valueRecord)(userModule?.default), userModule].some((owner) => typeof owner?.current == "function");
	}
	function discourseNativeCurrentUsername(host) {
	  return String(
	    nativeModelValue(
	      discourseNativeCurrentUser(host),
	      "username"
	    ) ?? ""
	  ).trim();
	}
	function discourseNativeSiteLogoUrl(host, baseUrl, fallbackCandidates = []) {
	  const settings = (0, import_value_record.objectRecord)(host.lookup("service:site-settings")), candidates = [
	    settings?.large_icon,
	    settings?.largeIcon,
	    settings?.apple_touch_icon,
	    settings?.appleTouchIcon,
	    settings?.favicon,
	    ...fallbackCandidates
	  ];
	  for (const candidate of candidates) {
	    const value = String(candidate ?? "").trim();
	    if (value)
	      try {
	        const url = new URL(value, baseUrl);
	        if (url.protocol === "https:" || url.protocol === "http:")
	          return url.href;
	      } catch {
	      }
	  }
	  return new URL("/favicon.ico", baseUrl).href;
	}
	function discourseNativeUserActionBinding(host, usernameValue) {
	  const username = String(usernameValue).trim().replace(/^@+/, "");
	  if (!username) throw new Error("User action username 不能为空");
	  const store = (0, import_value_record.objectRecord)(host.lookup("service:store")), createRecord = store?.createRecord;
	  if (typeof createRecord != "function")
	    throw new Error("Discourse 原生 store.createRecord 尚未就绪");
	  const user = createRecord.call(
	    store,
	    "user",
	    /* Ember Data 会在 createRecord 期间向属性袋补入 store 等内部字段。 */
	    { username }
	  ), actingUser = host.lookup("service:current-user");
	  if (!(0, import_value_record.objectRecord)(user) || !(0, import_value_record.objectRecord)(actingUser))
	    throw new Error("Discourse 原生用户动作 binding 尚未就绪");
	  return Object.freeze({
	    user,
	    actingUser
	  });
	}
	function discourseNativeJqueryModule(host) {
	  return host.lookupModule("jquery");
	}
	function discourseNativeFlagCatalog(host) {
	  const site = host.lookup("service:site"), postActionTypeModule = (0, import_value_record.objectRecord)(
	    host.lookupModule("discourse/models/post-action-type")
	  ), postActionTypeDefault = (0, import_value_record.objectRecord)(postActionTypeModule?.default), maxMessageLength = Number(
	    postActionTypeModule?.MAX_MESSAGE_LENGTH ?? postActionTypeDefault?.MAX_MESSAGE_LENGTH
	  ), list = (value) => {
	    if (Array.isArray(value)) return value;
	    if (typeof (value !== null && (typeof value == "object" || typeof value == "function") ? value[Symbol.iterator] : void 0) != "function") return Object.freeze([]);
	    try {
	      return Object.freeze(Array.from(value));
	    } catch {
	      return Object.freeze([]);
	    }
	  };
	  return Object.freeze({
	    flagTypes() {
	      return Object.freeze(list(nativeModelValue(site, "flagTypes")).map((value) => {
	        const id = Number(nativeModelValue(value, "id")), nameKey = String(
	          nativeModelValue(value, "name_key") ?? ""
	        ).trim();
	        if (!Number.isSafeInteger(id) || id <= 0 || !nameKey)
	          return null;
	        const appliesToValue = nativeModelValue(value, "applies_to"), appliesTo = Array.isArray(appliesToValue) ? appliesToValue.map(String).map((entry) => entry.trim()).filter(Boolean) : [];
	        return Object.freeze({
	          id,
	          nameKey,
	          label: String(
	            nativeModelValue(value, "name") ?? nameKey
	          ).trim() || nameKey,
	          description: String(
	            nativeModelValue(value, "description") ?? nativeModelValue(value, "short_description") ?? ""
	          ),
	          requireMessage: nativeModelValue(value, "require_message") === !0,
	          enabled: nativeModelValue(value, "enabled") !== !1,
	          appliesTo: Object.freeze(appliesTo)
	        });
	      }).filter(
	        (value) => value !== null
	      ));
	    },
	    messageMaxLength() {
	      return Number.isSafeInteger(maxMessageLength) && maxMessageLength > 0 ? maxMessageLength : 500;
	    }
	  });
	}
	function discourseNativeEmojiMenu(host) {
	  return Object.freeze({
	    async show(anchor, request) {
	      const menu = (0, import_value_record.objectRecord)(host.lookup("service:menu")), component = (0, import_value_record.objectRecord)(host.lookupModule(
	        "discourse/components/emoji-picker/detached"
	      ))?.default, show = menu?.show;
	      if (typeof show != "function" || !component)
	        throw new Error("Discourse 原生表情组件尚未就绪");
	      const identifier = String(request.identifier).trim(), context = String(request.context).trim();
	      if (!identifier || !context)
	        throw new Error("emoji menu identifier/context 不能为空");
	      await Promise.resolve(show.call(menu, anchor, {
	        identifier,
	        groupIdentifier: identifier,
	        component,
	        modalForMobile: !1,
	        strategy: "fixed",
	        fallbackPlacements: Object.freeze([
	          "bottom-start",
	          "top-start",
	          "bottom-end",
	          "top-end"
	        ]),
	        ...request.computePosition ? { computePosition: request.computePosition } : {},
	        data: Object.freeze({
	          term: "",
	          context,
	          didSelectEmoji: request.didSelectEmoji
	        })
	      }));
	    },
	    close(identifier) {
	      const menu = (0, import_value_record.objectRecord)(host.lookup("service:menu")), close = menu?.close, normalized = String(identifier).trim();
	      if (!(typeof close != "function" || !normalized))
	        try {
	          const result = close.call(menu, normalized);
	          result && typeof result.then == "function" && Promise.resolve(result).catch(() => {
	          });
	        } catch {
	        }
	    }
	  });
	}
	function discourseNativePostAdminMenu(host) {
	  return Object.freeze({
	    async show(anchor, post, scheduleRerender) {
	      const menu = (0, import_value_record.objectRecord)(host.lookup("service:menu")), component = (0, import_value_record.objectRecord)(
	        host.lookupModule("discourse/components/admin-post-menu")
	      )?.default, topicController = (0, import_value_record.objectRecord)(host.lookup("controller:topic")), show = menu?.show, send = topicController?.send;
	      if (typeof show != "function" || !component || typeof send != "function")
	        throw new Error("Discourse 原生楼层管理菜单尚未就绪");
	      const topicAction = (name) => () => send.call(topicController, name, post);
	      await Promise.resolve(show.call(menu, anchor, {
	        identifier: "admin-post-menu",
	        component,
	        modalForMobile: !0,
	        autofocus: !0,
	        data: Object.freeze({
	          post,
	          changeNotice: topicAction("changeNotice"),
	          changePostOwner: topicAction("changePostOwner"),
	          grantBadge: topicAction("grantBadge"),
	          lockPost: topicAction("lockPost"),
	          permanentlyDeletePost: topicAction("permanentlyDeletePost"),
	          rebakePost: topicAction("rebakePost"),
	          showPagePublish: topicAction("showPagePublish"),
	          togglePostType: topicAction("togglePostType"),
	          toggleWiki: topicAction("toggleWiki"),
	          unhidePost: topicAction("unhidePost"),
	          unlockPost: topicAction("unlockPost"),
	          scheduleRerender
	        })
	      }));
	    }
	  });
	}
	class BrowserDiscourseNativeBookmarkForm {
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  build(subjectType, subjectIdValue) {
	    if (subjectType !== "Post" && subjectType !== "Topic")
	      throw new Error(`未知收藏目标:${String(subjectType)}`);
	    const subjectId = Number(subjectIdValue);
	    if (!Number.isSafeInteger(subjectId) || subjectId < 1)
	      throw new RangeError("bookmark subjectId 必须是正安全整数");
	    const api = (0, import_value_record.objectRecord)(this.#host.lookup("service:bookmark-api")), buildNewBookmark = api?.buildNewBookmark;
	    if (!api || typeof buildNewBookmark != "function")
	      throw new Error(
	        "Discourse 原生收藏依赖未就绪:service:bookmark-api"
	      );
	    const module2 = (0, import_value_record.objectRecord)(
	      this.#host.lookupModule("discourse/lib/bookmark-form-data")
	    ), defaultExport = (0, import_value_record.objectRecord)(module2?.default), Constructor = module2?.BookmarkFormData ?? defaultExport?.BookmarkFormData ?? module2?.default;
	    if (typeof Constructor != "function")
	      throw new Error("Discourse BookmarkFormData 构造器未就绪");
	    const bookmark = (0, import_value_record.objectRecord)(
	      buildNewBookmark.call(api, subjectType, subjectId)
	    );
	    if (!bookmark)
	      throw new Error("bookmark-api.buildNewBookmark 未返回原生模型");
	    return new Constructor(bookmark);
	  }
	}
	function discourseNativePostRuntimeBindings(host) {
	  return Object.freeze({
	    topicModel: host.lookupModule("discourse/models/topic"),
	    topicDetailsModel: host.lookupModule("discourse/models/topic-details"),
	    postModel: host.lookupModule("discourse/models/post"),
	    appEvents: host.lookup("service:app-events"),
	    currentUser: host.lookup("service:current-user"),
	    siteSettings: host.lookup("service:site-settings")
	  });
	}
	function discourseNativeEmojiUrl(host, idValue) {
	  const id = String(idValue).trim().replace(/^:+|:+$/g, "");
	  if (!id) return "";
	  const module2 = (0, import_value_record.objectRecord)(host.lookupModule("discourse/lib/text")), defaultExport = (0, import_value_record.objectRecord)(module2?.default), owner = typeof module2?.emojiUrlFor == "function" ? module2 : defaultExport, emojiUrlFor = owner?.emojiUrlFor;
	  if (typeof emojiUrlFor != "function") return "";
	  try {
	    return String(emojiUrlFor.call(owner, id) ?? "").trim();
	  } catch {
	    return "";
	  }
	}
	function discourseNativeRelativeTimeFormatter(host) {
	  let owner = null;
	  return (timestamp) => {
	    const date = new Date(timestamp);
	    if (!Number.isFinite(date.getTime())) return "";
	    if (!owner || typeof owner.relativeAge != "function") {
	      const module2 = (0, import_value_record.objectRecord)(
	        host.lookupModule("discourse/lib/formatter")
	      ), defaultExport = (0, import_value_record.objectRecord)(module2?.default);
	      owner = typeof module2?.relativeAge == "function" ? module2 : defaultExport;
	    }
	    const relativeAge = owner?.relativeAge;
	    if (typeof relativeAge != "function") return "";
	    try {
	      return String(relativeAge.call(owner, date, {
	        format: "medium-with-ago",
	        wrapInSpan: !1
	      }) ?? "");
	    } catch {
	      return "";
	    }
	  };
	}
	function discourseNativeExactTimeFormatter(host) {
	  let owner = null;
	  return (timestamp) => {
	    const date = new Date(timestamp);
	    if (!Number.isFinite(date.getTime())) return "";
	    if (!owner || typeof owner.longDate != "function") {
	      const module2 = (0, import_value_record.objectRecord)(
	        host.lookupModule("discourse/lib/formatter")
	      ), defaultExport = (0, import_value_record.objectRecord)(module2?.default);
	      owner = typeof module2?.longDate == "function" ? module2 : defaultExport;
	    }
	    const longDate = owner?.longDate;
	    if (typeof longDate != "function") return "";
	    try {
	      return String(longDate.call(owner, date) ?? "");
	    } catch {
	      return "";
	    }
	  };
	}
	function discourseNativeTopicPresentation(host) {
	  const urlModule = (0, import_value_record.objectRecord)(host.lookupModule("discourse/lib/url")), urlDefault = (0, import_value_record.objectRecord)(urlModule?.default), urlOwner = typeof urlModule?.getCategoryAndTagUrl == "function" ? urlModule : urlDefault, avatarModule = (0, import_value_record.objectRecord)(
	    host.lookupModule("discourse/lib/avatar-utils")
	  ), avatarDefault = (0, import_value_record.objectRecord)(avatarModule?.default), avatarOwner = typeof avatarModule?.avatarUrl == "function" ? avatarModule : avatarDefault, categoryModel = (categoryId) => {
	    const categories = nativeModelValue(
	      host.lookup("service:site"),
	      "categories"
	    );
	    return Array.isArray(categories) ? categories.find((candidate) => Number(nativeModelValue(candidate, "id")) === categoryId) : void 0;
	  }, categoryAndTagUrl = (categoryId, tag) => {
	    const method = urlOwner?.getCategoryAndTagUrl, category = categoryId > 0 ? categoryModel(categoryId) : null;
	    if (categoryId > 0 && !category) return "";
	    if (typeof method == "function")
	      try {
	        const resolved = String(method.call(
	          urlOwner,
	          category,
	          !0,
	          tag
	        ) ?? "");
	        if (resolved) return resolved;
	      } catch {
	      }
	    const normalizedTag = String(tag ?? "").trim();
	    if (normalizedTag) return `/tag/${encodeURIComponent(normalizedTag)}`;
	    const slug = String(nativeModelValue(category, "slug") ?? "").trim();
	    return categoryId > 0 && slug ? `/c/${encodeURIComponent(slug)}/${categoryId}` : "";
	  };
	  return Object.freeze({
	    avatarSource(template, size) {
	      const normalized = String(template ?? "").trim();
	      if (!normalized) return "";
	      const method = avatarOwner?.avatarUrl;
	      if (typeof method != "function")
	        return normalized.replace(/\{size\}/g, String(size));
	      try {
	        return (String(
	          method.call(avatarOwner, normalized, size) ?? ""
	        ).trim() || normalized).replace(/\{size\}/g, String(size));
	      } catch {
	        return normalized.replace(/\{size\}/g, String(size));
	      }
	    },
	    categoryName(categoryId) {
	      return !Number.isSafeInteger(categoryId) || categoryId < 1 ? "" : String(
	        nativeModelValue(categoryModel(categoryId), "name") ?? ""
	      ).trim();
	    },
	    categoryIcon(categoryId) {
	      return !Number.isSafeInteger(categoryId) || categoryId < 1 ? "" : String(
	        nativeModelValue(categoryModel(categoryId), "icon") ?? ""
	      ).trim();
	    },
	    categoryHref(categoryId, tag) {
	      const normalizedId = Number.isSafeInteger(categoryId) && categoryId > 0 ? categoryId : 0, normalizedTag = String(tag ?? "").trim();
	      return categoryAndTagUrl(
	        normalizedId,
	        normalizedTag || void 0
	      );
	    },
	    tagHref(tag) {
	      const normalized = String(tag ?? "").trim();
	      return normalized ? categoryAndTagUrl(0, normalized) : "";
	    },
	    userHref(username) {
	      const normalized = String(username ?? "").trim();
	      if (!normalized) return "";
	      const userPath = urlOwner?.userPath;
	      if (typeof userPath == "function")
	        try {
	          return String(userPath.call(urlOwner, normalized) ?? "");
	        } catch {
	          return "";
	        }
	      return `/u/${encodeURIComponent(normalized)}`;
	    }
	  });
	}
	function discourseNativeTopicLinks(host, baseUrl) {
	  let normalizedBase = "";
	  try {
	    normalizedBase = new URL(baseUrl).href;
	  } catch {
	    normalizedBase = "";
	  }
	  return Object.freeze({
	    topicHref(topicIdValue, postNumberValue = 0) {
	      const topicId = Number(topicIdValue), postNumber = Number(postNumberValue);
	      if (!Number.isSafeInteger(topicId) || topicId < 1 || !Number.isSafeInteger(postNumber) || postNumber < 0 || !normalizedBase)
	        return "";
	      const path = `/t/${topicId}${postNumber ? `/${postNumber}` : ""}`;
	      try {
	        const module2 = (0, import_value_record.objectRecord)(
	          host.lookupModule("discourse/lib/get-url")
	        ), defaultExport = module2?.default, getUrl = typeof defaultExport == "function" ? defaultExport : typeof module2?.getURL == "function" ? module2.getURL : null, nativeValue = getUrl ? String(getUrl.call(module2, path) ?? "").trim() : "", candidate = new URL(nativeValue || path, normalizedBase), segments = candidate.pathname.split("/").filter(Boolean), topicSegmentIndex = segments.indexOf("t"), candidateTopicId = Number(segments[topicSegmentIndex + 1]);
	        return candidate.origin === new URL(normalizedBase).origin && topicSegmentIndex >= 0 && candidateTopicId === topicId ? candidate.href : new URL(path, normalizedBase).href;
	      } catch {
	        return new URL(path, normalizedBase).href;
	      }
	    }
	  });
	}
	function nativeModelValue(value, key) {
	  const record = (0, import_value_record.objectRecord)(value), getter = record?.get;
	  if (typeof getter == "function")
	    try {
	      return getter.call(value, key);
	    } catch {
	      return;
	    }
	  return record?.[key];
	}
	function setNativeModelValues(value, values) {
	  const record = (0, import_value_record.objectRecord)(value), setProperties = record?.setProperties;
	  if (typeof setProperties == "function") {
	    setProperties.call(value, values);
	    return;
	  }
	  const set = record?.set;
	  if (typeof set == "function")
	    for (const [key, entry] of Object.entries(values))
	      set.call(value, key, entry);
	}
	function nativeCount(value) {
	  if (value == null || value === "") return null;
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
	}
	function currentUserUnreadCount(currentUser) {
	  const all = nativeCount(nativeModelValue(
	    currentUser,
	    "all_unread_notifications_count"
	  ));
	  if (all !== null) return all;
	  const normal = nativeCount(nativeModelValue(
	    currentUser,
	    "unread_notifications"
	  )), high = nativeCount(nativeModelValue(
	    currentUser,
	    "unread_high_priority_notifications"
	  ));
	  return normal !== null || high !== null ? (normal ?? 0) + (high ?? 0) : nativeCount(nativeModelValue(
	    currentUser,
	    "unread_notification_count"
	  )) ?? 0;
	}
	function nativeNotificationRecord(value) {
	  return (0, import_value_record.objectRecord)(value) ?? Object.freeze({});
	}
	function nativeNotificationData(value) {
	  const raw = nativeNotificationRecord(value).data;
	  if (raw !== null && typeof raw == "object") return raw;
	  if (typeof raw != "string" || !raw.trim()) return Object.freeze({});
	  try {
	    return nativeNotificationRecord(JSON.parse(raw));
	  } catch {
	    return Object.freeze({});
	  }
	}
	function nativePresentationText(value) {
	  const toHTML = (0, import_value_record.objectRecord)(value)?.toHTML;
	  let source = value;
	  if (typeof toHTML == "function")
	    try {
	      source = toHTML.call(value);
	    } catch {
	      return "";
	    }
	  return String(source ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
	}
	function fallbackNotificationPresentation(notification, typeName) {
	  const source = nativeNotificationRecord(notification), data = nativeNotificationData(source);
	  return Object.freeze({
	    actor: data.display_username ?? data.username ?? source.username,
	    typeName,
	    typeLabel: typeName || "通知",
	    summary: data.topic_title ?? typeName ?? "通知",
	    href: data.post_url ?? data.topic_url ?? data.url ?? "",
	    topicId: source.topic_id ?? data.topic_id,
	    postNumber: source.post_number ?? data.post_number
	  });
	}
	class BrowserDiscourseNotificationNativeState {
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  username() {
	    return String(nativeModelValue(
	      discourseNativeCurrentUser(this.#host),
	      "username"
	    ) ?? "").trim().replace(/^@/, "");
	  }
	  unreadCount() {
	    return currentUserUnreadCount(
	      this.#host.lookup("service:current-user")
	    );
	  }
	  markAllRead() {
	    const currentUser = this.#host.lookup("service:current-user");
	    if (!currentUser) return;
	    const values = {
	      all_unread_notifications_count: 0,
	      unread_notifications: 0,
	      unread_high_priority_notifications: 0,
	      grouped_unread_notifications: Object.freeze({})
	    };
	    nativeModelValue(currentUser, "unread_notification_count") !== void 0 && (values.unread_notification_count = 0), setNativeModelValues(currentUser, Object.freeze(values));
	  }
	  markRead(input) {
	    const currentUser = this.#host.lookup("service:current-user");
	    if (!currentUser) return;
	    const normal = nativeCount(nativeModelValue(
	      currentUser,
	      "unread_notifications"
	    )) ?? 0, high = nativeCount(nativeModelValue(
	      currentUser,
	      "unread_high_priority_notifications"
	    )) ?? 0, nextNormal = input.highPriority ? normal : Math.max(0, normal - 1), nextHigh = input.highPriority ? Math.max(0, high - 1) : high, grouped = { ...(0, import_value_record.objectRecord)(nativeModelValue(
	      currentUser,
	      "grouped_unread_notifications"
	    )) ?? {} };
	    if (input.notificationTypeId !== null) {
	      const key = String(input.notificationTypeId), count = nativeCount(grouped[key]) ?? 0;
	      count > 0 && (grouped[key] = count - 1);
	    }
	    const values = {
	      grouped_unread_notifications: Object.freeze(grouped),
	      unread_notifications: nextNormal,
	      unread_high_priority_notifications: nextHigh,
	      all_unread_notifications_count: nextNormal + nextHigh
	    }, legacy = nativeCount(nativeModelValue(
	      currentUser,
	      "unread_notification_count"
	    ));
	    legacy !== null && (values.unread_notification_count = Math.max(0, legacy - 1)), setNativeModelValues(currentUser, Object.freeze(values));
	  }
	  async present(notifications) {
	    const site = this.#host.lookup("service:site"), lookup = (0, import_value_record.objectRecord)(nativeModelValue(site, "notificationLookup")), typeName = (notification) => {
	      const source = nativeNotificationRecord(notification);
	      return String(lookup?.[String(Number(source.notification_type))] ?? "");
	    }, notificationModule = (0, import_value_record.objectRecord)(
	      this.#host.lookupModule("discourse/models/notification")
	    ), Notification = (0, import_value_record.objectRecord)(notificationModule?.default), initialize = Notification?.initializeNotifications, managerModule = (0, import_value_record.objectRecord)(
	      this.#host.lookupModule("discourse/lib/notification-types-manager")
	    ), manager = typeof managerModule?.getRenderDirector == "function" ? managerModule : (0, import_value_record.objectRecord)(managerModule?.default), getRenderDirector = manager?.getRenderDirector, currentUser = this.#host.lookup("service:current-user"), siteSettings = this.#host.lookup("service:site-settings");
	    if (typeof initialize != "function" || typeof getRenderDirector != "function" || !currentUser || !siteSettings || !site)
	      return Object.freeze(notifications.map((notification) => fallbackNotificationPresentation(
	        notification,
	        typeName(notification)
	      )));
	    let models;
	    try {
	      const source = notifications.map((notification) => Object.freeze({
	        ...nativeNotificationRecord(notification),
	        data: nativeNotificationData(notification)
	      })), initialized = await initialize.call(Notification, source);
	      models = Array.isArray(initialized) ? initialized : source;
	    } catch {
	      return Object.freeze(notifications.map((notification) => fallbackNotificationPresentation(
	        notification,
	        typeName(notification)
	      )));
	    }
	    return Object.freeze(notifications.map((notification, index) => {
	      const model = models[index] ?? notification, modelData = nativeNotificationData(model), resolvedType = typeName(model) || typeName(notification);
	      try {
	        const director = (0, import_value_record.objectRecord)(getRenderDirector.call(
	          manager,
	          resolvedType,
	          model,
	          currentUser,
	          siteSettings,
	          site
	        ));
	        if (!director)
	          return fallbackNotificationPresentation(
	            notification,
	            resolvedType
	          );
	        const label = nativePresentationText(director.label), description = nativePresentationText(director.description);
	        return Object.freeze({
	          actor: modelData.display_username ?? modelData.username ?? nativeModelValue(model, "username"),
	          typeName: resolvedType,
	          typeLabel: nativePresentationText(director.linkTitle) || resolvedType || "通知",
	          summary: [label, description].filter(Boolean).join(" · "),
	          href: String(director.linkHref ?? "").trim(),
	          topicId: nativeModelValue(model, "topic_id") ?? nativeModelValue(model, "topicId") ?? modelData.topic_id,
	          postNumber: nativeModelValue(model, "post_number") ?? nativeModelValue(model, "postNumber") ?? modelData.post_number
	        });
	      } catch {
	        return fallbackNotificationPresentation(
	          notification,
	          resolvedType
	        );
	      }
	    }));
	  }
	  subscribeChanged(listener) {
	    const appEvents = (0, import_value_record.objectRecord)(
	      this.#host.lookup("service:app-events")
	    ), on = appEvents?.on, off = appEvents?.off;
	    if (typeof on != "function" || typeof off != "function") return () => {
	    };
	    const context = Object.freeze({});
	    try {
	      on.call(appEvents, "notifications:changed", context, listener);
	    } catch {
	      return () => {
	      };
	    }
	    let active = !0;
	    return () => {
	      if (active) {
	        active = !1;
	        try {
	          off.call(appEvents, "notifications:changed", context, listener);
	        } catch {
	        }
	      }
	    };
	  }
	}
	class BrowserDiscourseBookmarkNativeState {
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  username() {
	    return discourseNativeCurrentUsername(this.#host).replace(/^@/, "");
	  }
	  findGivenReactions(usernameValue, beforeReactionUserId) {
	    const username = String(usernameValue).trim().replace(/^@/, "");
	    if (!username)
	      return Promise.reject(new Error("回应记录需要当前登录用户名"));
	    const module2 = (0, import_value_record.objectRecord)(this.#host.lookupModule(
	      "discourse/plugins/discourse-reactions/discourse/models/discourse-reactions-custom-reaction"
	    )), model = (0, import_value_record.valueRecord)(module2?.default), findReactions = model?.findReactions;
	    if (typeof findReactions != "function")
	      return Promise.reject(new Error("Discourse 回应记录接口尚未就绪"));
	    const cursor = Number(beforeReactionUserId);
	    return Promise.resolve(findReactions.call(
	      model,
	      "reactions",
	      username,
	      Object.freeze({
	        ...Number.isSafeInteger(cursor) && cursor > 0 ? { beforeReactionUserId: cursor } : {}
	      })
	    ));
	  }
	  subscribeChanged(listener) {
	    const appEvents = (0, import_value_record.objectRecord)(
	      this.#host.lookup("service:app-events")
	    ), on = appEvents?.on, off = appEvents?.off;
	    if (typeof on != "function" || typeof off != "function") return () => {
	    };
	    const context = Object.freeze({}), subscriptions = Object.freeze([
	      ["bookmarks:changed", "bookmarks"],
	      ["discourse-reactions:reaction-toggled", "reactions"]
	    ]), attached = [];
	    for (const [name, source] of subscriptions) {
	      const handler = () => listener(source);
	      try {
	        on.call(appEvents, name, context, handler), attached.push(Object.freeze({ name, handler }));
	      } catch {
	      }
	    }
	    let active = !0;
	    return () => {
	      if (active) {
	        active = !1;
	        for (const entry of attached)
	          try {
	            off.call(
	              appEvents,
	              entry.name,
	              context,
	              entry.handler
	            );
	          } catch {
	          }
	      }
	    };
	  }
	}
	function readyModule(name, value) {
	  if (typeof value == "function") return !0;
	  const module2 = (0, import_value_record.objectRecord)(value);
	  if (module2 === null || Reflect.ownKeys(module2).length === 0) return !1;
	  if (name !== "discourse/lib/text") return !0;
	  const defaultExport = (0, import_value_record.objectRecord)(module2.default);
	  return typeof module2.emojiUrlFor == "function" || typeof defaultExport?.emojiUrlFor == "function";
	}
	function nonEmptyName(value, kind) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error(`Discourse ${kind} 名称不能为空`);
	  return normalized;
	}
	function moduleName(value) {
	  const normalized = nonEmptyName(value, "module");
	  if (normalized !== "jquery" && !normalized.startsWith("discourse/"))
	    throw new Error(`拒绝解析非 Discourse 原生 module:${normalized}`);
	  return normalized;
	}
	function callable(owner, name) {
	  const candidate = owner?.[name];
	  return typeof candidate == "function" ? candidate : null;
	}
	function moduleContainer(value) {
	  const module2 = (0, import_value_record.objectRecord)(value), defaultExport = (0, import_value_record.objectRecord)(module2?.default), candidate = (0, import_value_record.objectRecord)(defaultExport?.container ?? module2?.container);
	  return typeof candidate?.lookup == "function" ? candidate : null;
	}
	class BrowserDiscourseHostApiPort {
	  #pageWindow;
	  #modules = /* @__PURE__ */ new Map();
	  #lookups = /* @__PURE__ */ new Map();
	  #container = null;
	  constructor(options) {
	    const pageWindow = (0, import_value_record.objectRecord)(options.pageWindow);
	    if (!pageWindow) throw new Error("Discourse page window 不可用");
	    this.#pageWindow = pageWindow;
	  }
	  lookup(nameValue) {
	    const name = nonEmptyName(nameValue, "container lookup"), volatile = name === "service:current-user";
	    if (!volatile && this.#lookups.has(name)) return this.#lookups.get(name);
	    const container = this.#resolveContainer();
	    if (!container) return null;
	    try {
	      const value = container.lookup(name);
	      return !volatile && value !== null && value !== void 0 && this.#lookups.set(name, value), value ?? null;
	    } catch {
	      return null;
	    }
	  }
	  lookupModule(nameValue) {
	    const name = moduleName(nameValue);
	    if (this.#modules.has(name)) return this.#modules.get(name);
	    const broker = (0, import_value_record.objectRecord)(this.#pageWindow.moduleBroker), brokerLookup = callable(broker, "lookup"), requireModule = callable(this.#pageWindow, "require"), requireJsModule = callable(this.#pageWindow, "requirejs"), resolvers = Object.freeze([
	      () => brokerLookup?.call(broker, name, !0),
	      () => requireModule?.call(this.#pageWindow, name),
	      () => requireJsModule?.call(this.#pageWindow, name)
	    ]);
	    for (const resolve of resolvers)
	      try {
	        const value = resolve();
	        if (readyModule(name, value))
	          return this.#modules.set(name, value), value;
	      } catch {
	      }
	    return null;
	  }
	  #resolveContainer() {
	    if (this.#container) return this.#container;
	    const urlContainer = moduleContainer(this.lookupModule("discourse/lib/url")), discourse = (0, import_value_record.objectRecord)(this.#pageWindow.Discourse), fallback = (0, import_value_record.objectRecord)(discourse?.__container__ ?? discourse?.container), fallbackContainer = typeof fallback?.lookup == "function" ? fallback : null;
	    return this.#container = urlContainer ?? fallbackContainer, this.#container;
	  }
	}
}, "e604ebb94db5ea5c86eaa0715d6c79b7453fef8f284a2639ea0c32e9948659fe");

/* Source: lite/src/discourse/native-message-bus.ts */
runtime.register("src/discourse/native-message-bus.js", function(module, exports, require) {
	var native_message_bus_exports = {};
	__export(native_message_bus_exports, {
	  BrowserDiscourseMessageBusPort: () => BrowserDiscourseMessageBusPort
	});
	module.exports = __toCommonJS(native_message_bus_exports);
	function channelName(value) {
	  const channel = String(value).trim();
	  if (!channel.startsWith("/") || channel.includes("://"))
	    throw new Error("Discourse MessageBus channel 必须是站内 /channel");
	  return channel;
	}
	function resolveMessageBus(host) {
	  const service = host.lookup("service:message-bus");
	  if (!service || typeof service != "object" || typeof service.subscribe != "function" || typeof service.unsubscribe != "function")
	    throw new Error("Discourse 原生 service:message-bus 不可用");
	  return {
	    owner: service,
	    subscribe: service.subscribe,
	    unsubscribe: service.unsubscribe
	  };
	}
	class BrowserDiscourseMessageBusPort {
	  nativeBinding = "service:message-bus";
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  subscribe(channelValue, handler) {
	    const channel = channelName(channelValue);
	    if (typeof handler != "function") throw new TypeError("MessageBus handler 必须是函数");
	    const resolved = resolveMessageBus(this.#host);
	    resolved.subscribe.call(resolved.owner, channel, handler);
	  }
	  unsubscribe(channelValue, handler) {
	    const channel = channelName(channelValue);
	    if (typeof handler != "function") throw new TypeError("MessageBus handler 必须是函数");
	    const resolved = resolveMessageBus(this.#host);
	    resolved.unsubscribe.call(resolved.owner, channel, handler);
	  }
	}
}, "dfd0d9c34f350565f27200d1252e0f64980464c9690b96689242c7a3f21346ef");

/* Source: lite/src/discourse/native-post-model-factory.ts */
runtime.register("src/discourse/native-post-model-factory.js", function(module, exports, require) {
	var native_post_model_factory_exports = {};
	__export(native_post_model_factory_exports, {
	  DiscourseNativePostModelFactory: () => DiscourseNativePostModelFactory
	});
	module.exports = __toCommonJS(native_post_model_factory_exports);
	var import_identifiers = require("./identifiers.js"), import_native_host_api = require("./native-host-api.js"), import_native_message_bus = require("./native-message-bus.js"), import_value_record = require("../kernel/value-record.js");
	function moduleDefault(moduleValue, name) {
	  const module2 = (0, import_value_record.valueRecord)(moduleValue), value = (0, import_value_record.valueRecord)(module2?.default);
	  if (!value) throw new Error(`Discourse 原生模块未就绪:${name}`);
	  return value;
	}
	function modelValue(value, key) {
	  const target = (0, import_value_record.valueRecord)(value), getter = target?.get;
	  return typeof getter == "function" ? getter.call(target, key) : target?.[key];
	}
	function setModelValue(value, key, next) {
	  const target = (0, import_value_record.valueRecord)(value), set = target?.set;
	  if (typeof set == "function") {
	    set.call(target, key, next);
	    return;
	  }
	  target && (target[key] = next);
	}
	function reactionId(value) {
	  return String(value ?? "").trim().replace(/^:+|:+$/g, "");
	}
	function modelFactory(moduleValue, name) {
	  const model = moduleDefault(moduleValue, name);
	  if (typeof model.create != "function")
	    throw new Error(`Discourse 原生 model 缺少 create:${name}`);
	  return model;
	}
	class DiscourseNativePostModelFactory {
	  #host;
	  #bindings = null;
	  constructor(host) {
	    this.#host = host;
	  }
	  /**
	   * 只解析并校验回复 Composer 必需的 Topic/Post 原生 model。
	   *
	   * 该预热入口不创建 model、不读取 draft,也不触发任何宿主请求;实际回复仍由
	   * DiscourseComposerCoordinator 在用户操作后创建对应 Topic/Post 实例。
	   */
	  prepareComposerBindings() {
	    const bindings = this.#runtimeBindings();
	    if (modelFactory(bindings.topicModel, "discourse/models/topic"), typeof modelFactory(bindings.postModel, "discourse/models/post").munge != "function")
	      throw new Error("Discourse Post.munge 未就绪");
	  }
	  createTopic(topic) {
	    const bindings = this.#runtimeBindings();
	    return this.#createTopic(topic, bindings.topicModel);
	  }
	  createTopicDetails(topic) {
	    const bindings = this.#runtimeBindings(), TopicDetails = modelFactory(
	      bindings.topicDetailsModel,
	      "discourse/models/topic-details"
	    ), details = (0, import_value_record.valueRecord)(topic.details), topicRecord = topic;
	    return TopicDetails.create({
	      ...details ?? {},
	      topic: this.#createTopic(topic, bindings.topicModel),
	      notification_level: topicRecord.notification_level ?? modelValue(details, "notification_level") ?? 1
	    });
	  }
	  createPost(topic, post, topicModel) {
	    const bindings = this.#runtimeBindings(), owner = topicModel ?? this.#createTopic(topic, bindings.topicModel);
	    return this.#createPost(topic, post, owner, bindings.postModel);
	  }
	  createContext(topic, post) {
	    const bindings = this.#runtimeBindings(), appEvents = (0, import_value_record.valueRecord)(bindings.appEvents);
	    if (!appEvents) throw new Error("Discourse app-events service 未就绪");
	    const topicModel = this.#createTopic(topic, bindings.topicModel);
	    return Object.freeze({
	      topic: topicModel,
	      post: this.#createPost(topic, post, topicModel, bindings.postModel),
	      appEvents
	    });
	  }
	  /**
	   * discourse-calendar 的活动报名必须携带插件原生 Event model。
	   *
	   * 特殊正文 feature 只提交 canonical event JSON;module lookup 与 model.create 继续
	   * 收口在同一原生 model 工厂,不能由 DOM 组件自行解析插件模块。
	   */
	  createPostEvent(event, fallbackPostId) {
	    const EventModel = modelFactory(
	      (0, import_native_host_api.discourseNativePostEventModel)(this.#host),
	      "discourse-post-event-event"
	    ), id = Number(event.id) || Number(fallbackPostId);
	    if (!Number.isSafeInteger(id) || id < 1)
	      throw new RangeError("活动必须具有正安全整数 ID");
	    return EventModel.create({
	      ...event,
	      id
	    });
	  }
	  /**
	   * Discourse `/client_settings` 是回应目录热更新的唯一宿主事件源。
	   *
	   * 收到消息后先更新原生 site-settings service,再通知现有 PostView 重投;本方法
	   * 不请求 emoji 目录、不维护第二份设置快照,也不暴露 MessageBus 给 UI。
	   */
	  subscribeClientSettings(listener) {
	    if (typeof listener != "function")
	      throw new TypeError("client settings listener 必须是函数");
	    const messageBus = new import_native_message_bus.BrowserDiscourseMessageBusPort(this.#host), handler = (message) => {
	      const input = (0, import_value_record.valueRecord)(message), name = String(input?.name ?? "").trim();
	      [
	        "discourse_reactions_enabled_reactions",
	        "discourse_reactions_reaction_for_like"
	      ].includes(name) && (setModelValue(
	        this.#runtimeBindings().siteSettings,
	        name,
	        input?.value
	      ), listener());
	    };
	    try {
	      messageBus.subscribe("/client_settings", handler);
	    } catch {
	      return () => {
	      };
	    }
	    return () => {
	      try {
	        messageBus.unsubscribe("/client_settings", handler);
	      } catch {
	      }
	    };
	  }
	  /**
	   * 需要原生 current-user model 作为参数的动作共用这一窄入口。
	   *
	   * 不把 service lookup 暴露给 UI;匿名态返回 null,由动作组件保持原按钮不可用。
	   */
	  currentUser() {
	    return (0, import_value_record.valueRecord)(this.#runtimeBindings().currentUser);
	  }
	  sharedIssueAllowsMultipleSolutions() {
	    return modelValue(
	      this.#runtimeBindings().siteSettings,
	      "solved_allow_multiple_solutions"
	    ) === !0;
	  }
	  minimumPostLength() {
	    const value = Number(modelValue(
	      this.#runtimeBindings().siteSettings,
	      "min_post_length"
	    ));
	    return Number.isSafeInteger(value) && value > 0 ? value : 16;
	  }
	  reactionRegistry() {
	    const bindings = this.#runtimeBindings(), settings = (0, import_value_record.valueRecord)(bindings.siteSettings), configuredValue = modelValue(
	      settings,
	      "discourse_reactions_enabled_reactions"
	    ), source = Array.isArray(configuredValue) ? configuredValue : String(configuredValue ?? "").split("|"), configuredIds = Object.freeze(
	      source.map(reactionId).filter(Boolean)
	    ), mainReaction = reactionId(modelValue(
	      settings,
	      "discourse_reactions_reaction_for_like"
	    ));
	    return Object.freeze({
	      configuredIds,
	      mainReaction,
	      emojiUrl: (id) => (0, import_native_host_api.discourseNativeEmojiUrl)(this.#host, reactionId(id))
	    });
	  }
	  reportContext(topic, post, nameKeys) {
	    const native = this.createContext(topic, post), actionByName = modelValue(native.post, "actionByName"), actions = [...new Set(nameKeys.map(String).map((name) => name.trim()).filter(Boolean))].map((nameKey) => {
	      const action = modelValue(actionByName, nameKey), actionRecord = (0, import_value_record.valueRecord)(action);
	      return modelValue(action, "can_act") !== !0 || !actionRecord ? null : (Number(post.post_number) === 1 && (actionRecord.flagTopic = native.post), Object.freeze({
	        nameKey,
	        action
	      }));
	    }).filter(
	      (value) => value !== null
	    );
	    return Object.freeze({
	      post: native.post,
	      actions: Object.freeze(actions)
	    });
	  }
	  #createTopic(topic, topicModule) {
	    const topicId = (0, import_identifiers.discourseTopicId)(topic.id), Topic = modelFactory(topicModule, "discourse/models/topic"), details = (0, import_value_record.valueRecord)(topic.details);
	    return Topic.create({
	      id: topicId,
	      title: String(topic.title ?? ""),
	      fancy_title: String(topic.fancy_title ?? topic.title ?? ""),
	      slug: String(topic.slug ?? "topic"),
	      category_id: topic.category_id,
	      archetype: String(topic.archetype ?? "regular"),
	      draft_key: topic.draft_key,
	      draft_sequence: topic.draft_sequence,
	      posts_count: topic.posts_count,
	      highest_post_number: topic.highest_post_number,
	      last_posted_at: topic.last_posted_at,
	      chunk_size: topic.chunk_size,
	      details: { can_create_post: !0, ...details ?? {} },
	      tags: Array.isArray(topic.tags) ? [...topic.tags] : []
	    });
	  }
	  #createPost(topic, post, topicModel, postModule) {
	    const topicId = (0, import_identifiers.discourseTopicId)(topic.id);
	    if (post.topic_id !== void 0 && (0, import_identifiers.discourseTopicId)(post.topic_id) !== topicId)
	      throw new Error("楼层不属于目标 Topic");
	    const Post = modelFactory(postModule, "discourse/models/post");
	    if (typeof Post.munge != "function")
	      throw new Error("Discourse Post.munge 未就绪");
	    const reference = (0, import_identifiers.discoursePostReference)(post), actions = Array.isArray(post.actions_summary) ? post.actions_summary.map((action) => action && typeof action == "object" ? { ...action } : action) : [], attributes = {
	      ...post,
	      id: reference.postId,
	      topic_id: topicId,
	      post_number: reference.postNumber,
	      username: String(post.username ?? ""),
	      name: String(post.name ?? ""),
	      avatar_template: String(post.avatar_template ?? ""),
	      post_type: Number(post.post_type ?? 1),
	      reply_count: Number(post.reply_count ?? 0),
	      topic: topicModel,
	      actions_summary: actions
	    }, munge = Post.munge;
	    return Post.create(munge.call(Post, attributes));
	  }
	  #runtimeBindings() {
	    if (this.#bindings) return this.#bindings;
	    const bindings = (0, import_native_host_api.discourseNativePostRuntimeBindings)(this.#host);
	    return Object.values(bindings).every((value) => value != null) && (this.#bindings = bindings), bindings;
	  }
	}
}, "c492019f71ca4690d179e142f690ae7cd181e8b77625f562c85b5bff3e4330fe");

/* Source: lite/src/discourse/native-presence.ts */
runtime.register("src/discourse/native-presence.js", function(module, exports, require) {
	var native_presence_exports = {};
	__export(native_presence_exports, {
	  BrowserDiscoursePresencePort: () => BrowserDiscoursePresencePort
	});
	module.exports = __toCommonJS(native_presence_exports);
	var import_value_record = require("../kernel/value-record.js");
	function modelValue(value, key) {
	  const source = (0, import_value_record.valueRecord)(value);
	  if (!source) return;
	  const getter = source.get;
	  if (typeof getter == "function")
	    try {
	      const result = getter.call(value, key);
	      if (result !== void 0) return result;
	    } catch {
	    }
	  return source[key];
	}
	function normalizeUsers(value) {
	  let source = [];
	  if (Array.isArray(value)) source = value;
	  else {
	    const toArray = (0, import_value_record.valueRecord)(value)?.toArray;
	    if (typeof toArray == "function")
	      try {
	        const result = toArray.call(value);
	        Array.isArray(result) && (source = result);
	      } catch {
	        source = [];
	      }
	    else value && typeof value[Symbol.iterator] == "function" && (source = [...value]);
	  }
	  const users = source.map((candidate) => {
	    const username = String(
	      modelValue(candidate, "username") ?? ""
	    ).trim();
	    return username ? Object.freeze({
	      username,
	      name: String(
	        modelValue(candidate, "name") ?? username
	      ).trim() || username,
	      avatarTemplate: String(
	        modelValue(candidate, "avatar_template") ?? modelValue(candidate, "avatarTemplate") ?? ""
	      ).trim()
	    }) : null;
	  }).filter((user) => user !== null);
	  return Object.freeze(users);
	}
	function topicIdentifier(value) {
	  const topicId = Number(value);
	  if (!Number.isSafeInteger(topicId) || topicId < 1)
	    throw new RangeError("Presence topicId 必须是正安全整数");
	  return topicId;
	}
	class BrowserDiscoursePresencePort {
	  nativeBinding = "service:presence";
	  #host;
	  constructor(host) {
	    this.#host = host;
	  }
	  watchReplying(topicIdValue, listener, onError = () => {
	  }) {
	    const topicId = topicIdentifier(topicIdValue);
	    if (typeof listener != "function")
	      throw new TypeError("Presence listener 必须是函数");
	    const service = (0, import_value_record.valueRecord)(this.#host.lookup("service:presence")), getChannel = service?.getChannel;
	    if (typeof getChannel != "function")
	      return listener(Object.freeze([])), () => {
	      };
	    let channel = null;
	    try {
	      channel = (0, import_value_record.valueRecord)(
	        getChannel.call(
	          service,
	          `/discourse-presence/reply/${topicId}`
	        )
	      );
	    } catch (error) {
	      onError(error);
	    }
	    if (!channel || typeof channel.subscribe != "function" || typeof channel.unsubscribe != "function")
	      return listener(Object.freeze([])), () => {
	      };
	    const nativeChannel = channel, subscribe = nativeChannel.subscribe, unsubscribe = nativeChannel.unsubscribe;
	    let active = !0, subscriptionSettled = !1, unsubscribeIssued = !1;
	    const publish = () => {
	      if (active)
	        try {
	          listener(normalizeUsers(modelValue(nativeChannel, "users")));
	        } catch (error) {
	          onError(error);
	        }
	    }, change = () => publish();
	    if (typeof nativeChannel.on == "function")
	      try {
	        nativeChannel.on.call(nativeChannel, "change", change);
	      } catch (error) {
	        onError(error);
	      }
	    const issueUnsubscribe = () => {
	      if (!unsubscribeIssued) {
	        unsubscribeIssued = !0;
	        try {
	          Promise.resolve(
	            unsubscribe.call(nativeChannel)
	          ).catch(onError);
	        } catch (error) {
	          onError(error);
	        }
	      }
	    };
	    try {
	      Promise.resolve(
	        subscribe.call(nativeChannel)
	      ).then(() => {
	        subscriptionSettled = !0, active ? publish() : issueUnsubscribe();
	      }).catch((error) => {
	        subscriptionSettled = !0, active && (onError(error), listener(Object.freeze([]))), issueUnsubscribe();
	      });
	    } catch (error) {
	      subscriptionSettled = !0, onError(error), listener(Object.freeze([])), issueUnsubscribe();
	    }
	    return () => {
	      if (active) {
	        if (active = !1, typeof nativeChannel.off == "function")
	          try {
	            nativeChannel.off.call(nativeChannel, "change", change);
	          } catch (error) {
	            onError(error);
	          }
	        subscriptionSettled && issueUnsubscribe();
	      }
	    };
	  }
	}
}, "785adf7cee061f235c01912b31517054fd9e90a0c2a16c7bc51fd86ae56433d1");

/* Source: lite/src/discourse/native-request-descriptors.ts */
runtime.register("src/discourse/native-request-descriptors.js", function(module, exports, require) {
	var native_request_descriptors_exports = {};
	__export(native_request_descriptors_exports, {
	  DISCOURSE_DIRECT_REPLIES_PAGE_SIZE: () => DISCOURSE_DIRECT_REPLIES_PAGE_SIZE,
	  DiscourseNativeRequests: () => DiscourseNativeRequests,
	  assertDiscourseNativeMutationDescriptor: () => assertDiscourseNativeMutationDescriptor,
	  assertDiscourseNativeReadDescriptor: () => assertDiscourseNativeReadDescriptor,
	  discourseBasePath: () => discourseBasePath,
	  discourseNativeTargetFailureIsDefinitive: () => discourseNativeTargetFailureIsDefinitive
	});
	module.exports = __toCommonJS(native_request_descriptors_exports);
	var import_identifiers = require("./identifiers.js");
	const nativeReadDescriptorBrand = Symbol("DiscourseNativeReadDescriptor"), nativeMutationDescriptorBrand = Symbol("DiscourseNativeMutationDescriptor"), nativeReadDescriptors = /* @__PURE__ */ new WeakSet(), nativeMutationDescriptors = /* @__PURE__ */ new WeakSet(), DISCOURSE_DIRECT_REPLIES_PAGE_SIZE = 20;
	function discourseNativeTargetFailureIsDefinitive(input) {
	  return input.scope === "single" && input.endpoint === "post-by-number" && (input.status === 404 || input.status === 410);
	}
	function discourseBasePath(value) {
	  const normalized = String(value ?? "").trim().replace(/\/+$/, "");
	  if (normalized && !normalized.startsWith("/") && !/^https?:\/\//i.test(normalized))
	    throw new Error("basePath 必须是绝对 URL 或以 / 开头的路径");
	  return normalized;
	}
	function encodedSlug(value) {
	  const slug = String(value ?? "topic").trim();
	  if (!slug) throw new Error("slug 不能为空");
	  return encodeURIComponent(slug);
	}
	function readDescriptor(operation, path, options = {}) {
	  const descriptor = Object.freeze({
	    operation,
	    path,
	    headers: Object.freeze({ ...options.headers ?? {} }),
	    browserCache: options.browserCache ?? "default",
	    [nativeReadDescriptorBrand]: !0
	  });
	  return nativeReadDescriptors.add(descriptor), descriptor;
	}
	function assertDiscourseNativeReadDescriptor(value) {
	  if (value === null || typeof value != "object" || !nativeReadDescriptors.has(value))
	    throw new Error("读取请求必须来自 Discourse 原生请求目录");
	}
	function assertDiscourseNativeMutationDescriptor(value) {
	  if (value === null || typeof value != "object" || !nativeMutationDescriptors.has(value))
	    throw new Error("写请求必须来自 Discourse 原生请求目录");
	}
	function targetDescriptor(endpoint, url, refresh) {
	  return Object.freeze({
	    endpoint,
	    url,
	    descriptor: readDescriptor("target-post", url, {
	      headers: { Accept: "application/json" },
	      browserCache: refresh ? "no-store" : "default"
	    })
	  });
	}
	const DiscourseNativeRequests = Object.freeze({
	  topic(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topicId), basePath = discourseBasePath(input.basePath);
	    return readDescriptor(
	      "topic",
	      `${basePath}/t/${topicId}.json?track_visit=true&forceLoad=true`,
	      {
	        headers: {
	          Accept: "application/json",
	          "Discourse-Track-View": "true",
	          "Discourse-Track-View-Topic-Id": String(topicId)
	        }
	      }
	    );
	  },
	  postsById(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topicId), query = (0, import_identifiers.discoursePostIds)(input.postIds).map((postId) => `post_ids[]=${encodeURIComponent(postId)}`).join("&");
	    return readDescriptor(
	      "posts-by-id",
	      `${discourseBasePath(input.basePath)}/t/${topicId}/posts.json?${query}`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  postById(input) {
	    const postId = (0, import_identifiers.discoursePostId)(input.postId);
	    return readDescriptor(
	      "post-by-id",
	      `${discourseBasePath(input.basePath)}/posts/${postId}.json`,
	      {
	        headers: { Accept: "application/json" },
	        browserCache: "no-store"
	      }
	    );
	  },
	  targetCandidates(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topicId), postNumber = (0, import_identifiers.discoursePostNumber)(input.postNumber), basePath = discourseBasePath(input.basePath), slug = encodedSlug(input.slug), refresh = input.refresh === !0, paths = Object.freeze({
	      "post-by-number": `${basePath}/posts/by_number/${topicId}/${postNumber}.json`,
	      "topic-floor": `${basePath}/t/${slug}/${topicId}/${postNumber}.json`,
	      "topic-query": `${basePath}/t/${slug}/${topicId}.json?post_number=${postNumber}`,
	      "topic-id-query": `${basePath}/t/${topicId}.json?post_number=${postNumber}`
	    }), order = input.scope === "around" ? ["topic-floor", "topic-query", "topic-id-query", "post-by-number"] : ["post-by-number", "topic-floor", "topic-id-query"];
	    return Object.freeze(order.map((endpoint) => targetDescriptor(endpoint, paths[endpoint], refresh)));
	  },
	  directReplies(input) {
	    const parentPostId = (0, import_identifiers.discoursePostId)(input.parentPostId), after = (0, import_identifiers.discourseReplyCursor)(input.after), suffix = after > 0 ? `?after=${after}` : "";
	    return readDescriptor(
	      "direct-replies",
	      `${discourseBasePath(input.basePath)}/posts/${parentPostId}/replies.json${suffix}`,
	      {
	        headers: { Accept: "application/json" },
	        browserCache: "no-store"
	      }
	    );
	  },
	  postVotingComments(input) {
	    const postId = (0, import_identifiers.discoursePostId)(input.postId), afterCommentId = Number(input.afterCommentId ?? 0);
	    if (!Number.isSafeInteger(afterCommentId) || afterCommentId < 0)
	      throw new RangeError("afterCommentId 必须是非负安全整数");
	    const query = new URLSearchParams({ post_id: String(postId) });
	    return afterCommentId > 0 && query.set("last_comment_id", String(afterCommentId)), readDescriptor(
	      "post-voting-comments",
	      `${discourseBasePath(input.basePath)}/post_voting/comments?${query}`,
	      {
	        headers: { Accept: "application/json" },
	        browserCache: "no-store"
	      }
	    );
	  },
	  boostReportAccess(input) {
	    const boostId = Number(input.boostId);
	    if (!Number.isSafeInteger(boostId) || boostId <= 0)
	      throw new RangeError("boostId 必须是正安全整数");
	    return readDescriptor(
	      "boost-report-access",
	      `${discourseBasePath(input.basePath)}/discourse-boosts/boosts/${boostId}.json`,
	      {
	        headers: { Accept: "application/json" },
	        browserCache: "no-store"
	      }
	    );
	  },
	  endorsableCategories(input) {
	    const username = String(input.username).trim().replace(/^@+/, "");
	    if (!username) throw new Error("username 不能为空");
	    return readDescriptor(
	      "endorsable-categories",
	      `${discourseBasePath(input.basePath)}/category-experts/endorsable-categories/${encodeURIComponent(username)}.json`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  userFollowList(input) {
	    const username = String(input.username).trim().replace(/^@+/, "");
	    if (!username) throw new Error("username 不能为空");
	    if (input.kind !== "following" && input.kind !== "followers")
	      throw new Error("关注列表类型无效");
	    return readDescriptor(
	      "user-follow-list",
	      `${discourseBasePath(input.basePath)}/u/${encodeURIComponent(username)}/follow/${input.kind}`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  userBadges(input) {
	    const username = String(input.username).trim().replace(/^@+/, "");
	    if (!username) throw new Error("username 不能为空");
	    return readDescriptor(
	      "user-badges",
	      `${discourseBasePath(input.basePath)}/user-badges/${encodeURIComponent(username)}.json`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  userSummary(input) {
	    const username = String(input.username).trim().replace(/^@+/, "");
	    if (!username) throw new Error("username 不能为空");
	    return readDescriptor(
	      "user-summary",
	      `${discourseBasePath(input.basePath)}/u/${encodeURIComponent(username)}/summary.json`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  userDirectoryStats(input) {
	    const username = String(input.username).trim().replace(/^@+/, "");
	    if (!username) throw new Error("username 不能为空");
	    const query = new URLSearchParams({
	      period: "all",
	      order: "likes_received",
	      username
	    });
	    return readDescriptor(
	      "user-directory-stats",
	      `${discourseBasePath(input.basePath)}/directory_items.json?${query}`,
	      { headers: { Accept: "application/json" } }
	    );
	  },
	  topicTimings(input) {
	    const topicId = (0, import_identifiers.discourseTopicId)(input.topicId), postNumbers = (0, import_identifiers.discoursePostNumbers)(input.postNumbers), readTimeMs = Number(input.readTimeMs);
	    if (!Number.isSafeInteger(readTimeMs) || readTimeMs < 1 || readTimeMs > 6e4)
	      throw new RangeError("readTimeMs 必须是 1..60000 的安全整数");
	    const timings = Object.fromEntries(postNumbers.map((postNumber) => [
	      String(postNumber),
	      readTimeMs
	    ])), data = Object.freeze({
	      topic_id: topicId,
	      topic_time: readTimeMs * postNumbers.length,
	      timings: Object.freeze(timings)
	    }), descriptor = Object.freeze({
	      operation: "topic-timings",
	      path: `${discourseBasePath(input.basePath)}/topics/timings`,
	      method: "POST",
	      headers: Object.freeze({
	        "Discourse-Background": "true",
	        "X-SILENCE-LOGGER": "true"
	      }),
	      data,
	      [nativeMutationDescriptorBrand]: !0
	    });
	    return nativeMutationDescriptors.add(descriptor), descriptor;
	  }
	});
}, "921bba71979f25d4c7bcf7b1dfbaa8354e25d7c589db3d7addfadca0a3ec858d");

/* Source: lite/src/discourse/native-topic-notification-action.ts */
runtime.register("src/discourse/native-topic-notification-action.js", function(module, exports, require) {
	var native_topic_notification_action_exports = {};
	__export(native_topic_notification_action_exports, {
	  BrowserDiscourseTopicNotificationLevelMutationPort: () => BrowserDiscourseTopicNotificationLevelMutationPort
	});
	module.exports = __toCommonJS(native_topic_notification_action_exports);
	var import_native_post_model_factory = require("./native-post-model-factory.js"), import_discourse_action_descriptors = require("../post/discourse-action-descriptors.js"), import_discourse_action_transport = require("../post/discourse-action-transport.js"), import_value_record = require("../kernel/value-record.js");
	function modelValue(value, key) {
	  const source = (0, import_value_record.valueRecord)(value), getter = source?.get;
	  return typeof getter == "function" ? getter.call(value, key) : source?.[key];
	}
	class BrowserDiscourseTopicNotificationLevelMutationPort {
	  #models;
	  #descriptors = new import_discourse_action_descriptors.DiscourseActionDescriptors();
	  #actions;
	  constructor(host) {
	    this.#models = new import_native_post_model_factory.DiscourseNativePostModelFactory(host), this.#actions = new import_discourse_action_transport.BrowserDiscourseNativeActionPort(host);
	  }
	  async setLevel(topic, level) {
	    const topicId = Number(modelValue(topic, "id"));
	    if (!Number.isSafeInteger(topicId) || topicId < 1)
	      throw new RangeError("宿主话题通知动作缺少有效 Topic ID");
	    const topicDetails = this.#models.createTopicDetails(
	      topic
	    ), mutation = this.#descriptors.topicNotificationLevel({
	      topicId,
	      topicDetails,
	      level
	    }), definition = (0, import_discourse_action_transport.discourseActionTransportDefinition)(
	      mutation.operation,
	      mutation.targetType
	    ), response = await this.#actions.execute({
	      definition,
	      targetId: mutation.targetId,
	      variant: mutation.variant ?? null,
	      payload: mutation.payload,
	      signal: new AbortController().signal,
	      attempt: 0
	    });
	    if (!response.ok)
	      throw new Error(`宿主话题通知设置失败(HTTP ${response.status || 0})`);
	  }
	}
}, "70e2eaa947da735f595616c706700d003b7deb8e12729a0093c9cc1f662f72f7");

/* Source: lite/src/discourse/reader-native-composer-window.ts */
runtime.register("src/discourse/reader-native-composer-window.js", function(module, exports, require) {
	var reader_native_composer_window_exports = {};
	__export(reader_native_composer_window_exports, {
	  DISCOURSE_NATIVE_FLOATING_SELECTOR: () => DISCOURSE_NATIVE_FLOATING_SELECTOR,
	  ReaderNativeComposerWindowController: () => ReaderNativeComposerWindowController,
	  discourseNativeFloatingSurfaceVisible: () => discourseNativeFloatingSurfaceVisible,
	  normalizeReaderNativeComposerGeometry: () => normalizeReaderNativeComposerGeometry,
	  readerNativeComposerFontPixels: () => readerNativeComposerFontPixels,
	  readerNativeTopLayerPort: () => readerNativeTopLayerPort,
	  visibleDiscourseNativeFloatingSurface: () => visibleDiscourseNativeFloatingSurface
	});
	module.exports = __toCommonJS(reader_native_composer_window_exports);
	var import_floating_surface_wheel = require("../dom/floating-surface-wheel.js"), import_event_target = require("../dom/event-target.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_reader_preferences_schema = require("../state/reader-preferences-schema.js");
	const GEOMETRY_PROPERTIES = Object.freeze([
	  "--ldp-composer-left",
	  "--ldp-composer-top",
	  "--ldp-composer-width",
	  "--ldp-composer-height",
	  "--ldp-composer-transform"
	]), APPEARANCE_PROPERTIES = Object.freeze([
	  "--tertiary",
	  "--tertiary-low",
	  "--d-link-color"
	]), RESIZE_DIRECTIONS = Object.freeze([
	  "n",
	  "s",
	  "e",
	  "w",
	  "ne",
	  "nw",
	  "se",
	  "sw"
	]), DISCOURSE_NATIVE_FLOATING_SELECTOR = ".fk-d-menu,.emoji-picker";
	function discourseNativeFloatingSurfaceVisible(element, window = element.ownerDocument.defaultView) {
	  if (!element.isConnected || element.hidden || element.disabled || element.getAttribute("aria-hidden") === "true" || element.classList.contains("hidden") || element.classList.contains("d-none") || element.classList.contains("closed") || element.closest('[hidden],[aria-hidden="true"]')) return !1;
	  try {
	    const style = window?.getComputedStyle?.(element);
	    if (style?.display === "none" || style?.visibility === "hidden" || style?.contentVisibility === "hidden") return !1;
	  } catch {
	  }
	  const rect = element.getBoundingClientRect(), viewportWidth = Number(window?.innerWidth) || Number(element.ownerDocument.documentElement.clientWidth) || 0, viewportHeight = Number(window?.innerHeight) || Number(element.ownerDocument.documentElement.clientHeight) || 0;
	  return rect.width > 0 && rect.height > 0 && rect.right > 0 && rect.bottom > 0 && (viewportWidth <= 0 || rect.left < viewportWidth) && (viewportHeight <= 0 || rect.top < viewportHeight);
	}
	function visibleDiscourseNativeFloatingSurface(document, window = document.defaultView) {
	  return [...document.querySelectorAll(
	    DISCOURSE_NATIVE_FLOATING_SELECTOR
	  )].find(
	    (element) => discourseNativeFloatingSurfaceVisible(element, window)
	  ) ?? null;
	}
	function clamp(value, minimum, maximum) {
	  return Math.max(minimum, Math.min(maximum, value));
	}
	function finite(value, fallback) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? numeric : fallback;
	}
	function pixels(value) {
	  return `${Math.round(value * 100) / 100}px`;
	}
	function viewportLimits(viewport) {
	  const width = Math.max(1, finite(viewport.width, 1)), height = Math.max(1, finite(viewport.height, 1)), margin = Math.max(0, Math.min(
	    16,
	    Math.floor((width - 1) / 2),
	    Math.floor((height - 1) / 2)
	  )), maxWidth = Math.max(1, width - margin * 2), maxHeight = Math.max(1, height - margin * 2);
	  return Object.freeze({
	    left: margin,
	    top: margin,
	    right: width - margin,
	    bottom: height - margin,
	    maxWidth,
	    maxHeight,
	    minWidth: Math.min(520, maxWidth),
	    minHeight: Math.min(300, maxHeight)
	  });
	}
	function normalizeReaderNativeComposerGeometry(viewport, value) {
	  const bounds = viewportLimits(viewport), fallbackWidth = Math.min(1200, bounds.maxWidth), fallbackHeight = clamp(
	    bounds.maxHeight * 0.7,
	    bounds.minHeight,
	    bounds.maxHeight
	  ), fallbackLeft = bounds.left + (bounds.maxWidth - fallbackWidth) / 2, fallbackTop = bounds.top + (bounds.maxHeight - fallbackHeight) / 2, width = clamp(
	    finite(value?.width, fallbackWidth),
	    bounds.minWidth,
	    bounds.maxWidth
	  ), height = clamp(
	    finite(value?.height, fallbackHeight),
	    bounds.minHeight,
	    bounds.maxHeight
	  );
	  return Object.freeze({
	    left: clamp(
	      finite(value?.left, fallbackLeft),
	      bounds.left,
	      bounds.right - width
	    ),
	    top: clamp(
	      finite(value?.top, fallbackTop),
	      bounds.top,
	      bounds.bottom - height
	    ),
	    width,
	    height
	  });
	}
	function readerNativeComposerFontPixels(viewport, geometry, profile) {
	  const bounds = viewportLimits(viewport), widthTarget = Math.min(980, bounds.maxWidth), heightTarget = Math.min(720, bounds.maxHeight), widthProgress = clamp(
	    (geometry.width - bounds.minWidth) / Math.max(1, widthTarget - bounds.minWidth),
	    0,
	    1
	  ), heightProgress = clamp(
	    (geometry.height - bounds.minHeight) / Math.max(1, heightTarget - bounds.minHeight),
	    0,
	    1
	  ), automatic = 12 + Math.min(widthProgress, heightProgress) * 4, ratio = finite(profile.composer, import_reader_preferences_schema.READER_FONT_DEFAULT.composer) / import_reader_preferences_schema.READER_FONT_DEFAULT.composer;
	  return Math.round(clamp(automatic * ratio, 8, 40) * 100) / 100;
	}
	function readerNativeTopLayerPort() {
	  return Object.freeze({
	    isOpen: (element) => {
	      try {
	        return element.matches(":popover-open");
	      } catch {
	        return !1;
	      }
	    },
	    show: (element) => element.showPopover?.(),
	    hide: (element) => element.hidePopover?.()
	  });
	}
	class ReaderNativeComposerWindowController {
	  scope;
	  #document;
	  #window;
	  #mount;
	  #pageRoot;
	  #readPreferences;
	  #updatePreferences;
	  #readFontProfile;
	  #readAppearance;
	  #createMutationObserver;
	  #requestFrame;
	  #cancelFrame;
	  #setTimer;
	  #clearTimer;
	  #topLayer;
	  #onError;
	  #ownedTopLayers = /* @__PURE__ */ new Set();
	  #composer = null;
	  #composerRoot = null;
	  #composerAppearanceOriginal = /* @__PURE__ */ new Map();
	  #composerScope = null;
	  #hostObserver = null;
	  #floatingObserver = null;
	  #overflowLayers = [];
	  #chrome = null;
	  #geometry = null;
	  #pointer = null;
	  #pointerX = 0;
	  #pointerY = 0;
	  #pointerFrame = 0;
	  #chromeFrame = 0;
	  #overflowFrame = 0;
	  #floatingFrame = 0;
	  #persistTimer = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#document = options.document, this.#window = options.window, this.#mount = options.mount, this.#pageRoot = options.pageRoot ?? options.document.documentElement, this.#readPreferences = options.readPreferences, this.#updatePreferences = options.updatePreferences ?? null, this.#readFontProfile = options.readFontProfile ?? (() => this.#readPreferences().fontProfile), this.#readAppearance = options.readAppearance ?? null, this.#createMutationObserver = options.createMutationObserver ?? ((callback) => new MutationObserver(callback)), this.#requestFrame = options.requestFrame ?? ((callback) => this.#window.requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((frameId) => this.#window.cancelAnimationFrame(frameId)), this.#setTimer = options.setTimer ?? ((callback, milliseconds) => this.#window.setTimeout(callback, milliseconds)), this.#clearTimer = options.clearTimer ?? ((timerId) => this.#window.clearTimeout(timerId)), this.#topLayer = options.topLayer ?? readerNativeTopLayerPort(), this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => this.#teardown()), this.scope.listen(this.#window, "resize", () => {
	      this.#stopPointer(), this.#geometry ? this.#applyGeometry(this.#geometry) : this.#scheduleChrome(), this.#syncTopLayers(), this.#scheduleOverflow();
	    }), options.preferenceChanges.subscribe(() => {
	      this.#pointer || !this.#composerVisible() || (this.#geometry = this.#preferredGeometry(), this.#applyGeometry(this.#geometry));
	    }, this.scope), options.fontChanges?.subscribe((profile) => {
	      this.syncFont(profile);
	    }, this.scope), options.appearanceChanges?.subscribe((appearance) => {
	      this.#syncAppearance(appearance);
	    }, this.scope), this.#document.body && (this.#hostObserver = this.#createMutationObserver(() => {
	      this.sync(), this.#observeHostTargets();
	    }), this.#observeHostTargets(), this.scope.add(() => this.#hostObserver?.disconnect())), this.sync();
	  }
	  get geometry() {
	    return this.#geometry;
	  }
	  open(element = null) {
	    if (this.#destroyed) return !1;
	    const candidate = element ?? this.#document.querySelector("#reply-control");
	    return candidate !== this.#composer && this.#bindComposer(candidate), this.#composerAvailable() ? (this.#activate(), !0) : (this.#deactivate(), !1);
	  }
	  sync() {
	    if (this.#destroyed) return;
	    const candidate = this.#document.querySelector("#reply-control");
	    if (candidate !== this.#composer && this.#bindComposer(candidate), !this.#composerVisible()) {
	      this.#deactivate();
	      return;
	    }
	    this.#activate();
	  }
	  syncFont(profile = this.#readFontProfile()) {
	    !this.#composer || !this.#geometry || this.#composer.style.setProperty(
	      "--ldp-composer-font-size",
	      `${readerNativeComposerFontPixels(
	        this.#viewport(),
	        this.#geometry,
	        profile
	      )}px`
	    );
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #viewport() {
	    return Object.freeze({
	      width: this.#window.innerWidth,
	      height: this.#window.innerHeight
	    });
	  }
	  #desktop() {
	    return this.#window.innerWidth > 760;
	  }
	  #preferredGeometry() {
	    const preferences = this.#readPreferences(), preferred = {
	      ...preferences.composerWindowWidth ? { width: preferences.composerWindowWidth } : {},
	      ...preferences.composerWindowHeight ? { height: preferences.composerWindowHeight } : {},
	      ...preferences.composerWindowX ? { left: preferences.composerWindowX } : {},
	      ...preferences.composerWindowY ? { top: preferences.composerWindowY } : {}
	    };
	    return normalizeReaderNativeComposerGeometry(
	      this.#viewport(),
	      preferred
	    );
	  }
	  #composerVisible() {
	    const composer = this.#composer;
	    if (!composer || !this.#composerAvailable()) return !1;
	    try {
	      const style = this.#window.getComputedStyle?.(composer), stagedByReader = style?.visibility === "hidden" && this.#pageRoot.classList.contains("ldp-reader-open") && !composer.dataset.ldpReaderComposerPositioned;
	      if (style?.display === "none" || style?.visibility === "hidden" && !stagedByReader)
	        return !1;
	    } catch {
	    }
	    return !0;
	  }
	  #composerAvailable() {
	    const composer = this.#composer;
	    return !!(composer?.isConnected && !composer.hidden && !composer.classList.contains("closed") && !composer.classList.contains("hidden") && !composer.classList.contains("d-none") && composer.getAttribute("aria-hidden") !== "true" && !composer.closest('[hidden],[aria-hidden="true"]'));
	  }
	  #bindComposer(composer) {
	    if (this.#composerScope?.destroy(), this.#composerScope = null, this.#composer && this.#deactivate(), this.#composer = composer, !composer) return;
	    const scope = this.scope.child();
	    this.#composerScope = scope;
	    const observer = this.#createMutationObserver(() => this.sync());
	    observer.observe(composer, {
	      attributes: !0,
	      attributeFilter: ["class", "hidden", "aria-hidden"],
	      childList: !0,
	      subtree: !0
	    }), scope.add(() => observer.disconnect()), scope.listen(composer, "wheel", (event) => {
	      (0, import_floating_surface_wheel.containFloatingSurfaceWheel)(composer, event);
	    }, { capture: !0, passive: !1 }), scope.add(() => {
	      this.#composer === composer && (this.#deactivate(), this.#composer = null);
	    });
	  }
	  #observeHostTargets() {
	    const observer = this.#hostObserver, body = this.#document.body;
	    if (!observer || !body) return;
	    if (observer.disconnect(), !this.#composer) {
	      const bootstrap = this.#document.querySelector("#ember-app") ?? body;
	      observer.observe(bootstrap, { childList: !0, subtree: !0 });
	      let ancestor2 = bootstrap.parentElement;
	      for (; ancestor2 && (observer.observe(ancestor2, { childList: !0 }), ancestor2 !== body); )
	        ancestor2 = ancestor2.parentElement;
	      return;
	    }
	    let ancestor = this.#composer.parentElement;
	    for (; ancestor && (observer.observe(ancestor, { childList: !0 }), ancestor !== body); )
	      ancestor = ancestor.parentElement;
	  }
	  #activate() {
	    const composer = this.#composer;
	    composer && (this.#ensureChrome(), this.#syncComposerRoot(), this.#applyGeometry(this.#geometry ?? this.#preferredGeometry()), composer.classList.contains("fullscreen") && (this.#stopPointer(), composer.style.removeProperty("translate")), this.#syncTopLayers(), this.#scheduleOverflow());
	  }
	  #deactivate() {
	    this.#stopPointer(), this.#persistGeometry(), this.#releaseTopLayers(), this.#chrome && (this.#chrome.hidden = !0), this.#geometry = null, this.#clearGeometry(), this.#clearOverflow(), this.#clearComposerRoot();
	  }
	  #applyGeometry(value) {
	    const composer = this.#composer;
	    if (!composer) return;
	    const previous = this.#geometry;
	    this.#geometry = normalizeReaderNativeComposerGeometry(
	      this.#viewport(),
	      value
	    );
	    const sizeChanged = !previous || previous.width !== this.#geometry.width || previous.height !== this.#geometry.height;
	    previous || (composer.style.setProperty("--ldp-composer-left", "0px"), composer.style.setProperty("--ldp-composer-top", "0px"), composer.style.setProperty("--ldp-composer-transform", "none")), sizeChanged && (composer.style.setProperty(
	      "--ldp-composer-width",
	      pixels(this.#geometry.width)
	    ), composer.style.setProperty(
	      "--ldp-composer-height",
	      pixels(this.#geometry.height)
	    )), this.#desktop() && !composer.classList.contains("fullscreen") ? composer.style.setProperty(
	      "translate",
	      `${pixels(this.#geometry.left)} ${pixels(this.#geometry.top)}`
	    ) : composer.style.removeProperty("translate"), composer.dataset.ldpReaderComposerPositioned = "1", sizeChanged && this.syncFont(), this.#syncChrome(sizeChanged);
	  }
	  #clearGeometry() {
	    const composer = this.#composer;
	    if (composer) {
	      delete composer.dataset.ldpReaderComposerPositioned;
	      for (const property of GEOMETRY_PROPERTIES)
	        composer.style.removeProperty(property);
	      composer.style.removeProperty("--ldp-composer-font-size"), composer.style.removeProperty("translate");
	    }
	  }
	  #ensureChrome() {
	    if (this.#chrome) return this.#chrome;
	    const chrome = this.#document.createElement("div");
	    chrome.className = "ldp-composer-window-chrome sciapp-ldp-owned", chrome.hidden = !0;
	    const drag = this.#document.createElement("button");
	    drag.type = "button", drag.className = "ldp-composer-drag-handle", drag.setAttribute(
	      "aria-label",
	      "拖动回复窗口;方向键可微调位置"
	    ), chrome.append(drag);
	    for (const direction of RESIZE_DIRECTIONS) {
	      const handle = this.#document.createElement(
	        direction === "se" ? "button" : "span"
	      );
	      handle.className = "ldp-composer-resize-handle", handle.dataset.resize = direction, direction === "se" ? (handle.type = "button", handle.setAttribute(
	        "aria-label",
	        "拖动调整回复窗口大小;方向键可微调"
	      )) : handle.setAttribute("aria-hidden", "true"), chrome.append(handle);
	    }
	    this.#mount.append(chrome), this.scope.listen(chrome, "pointerdown", (event) => {
	      const pointer = event, eventTarget = (0, import_event_target.eventElement)(pointer), target = eventTarget ? eventTarget.closest(
	        ".ldp-composer-drag-handle,[data-resize]"
	      ) : null;
	      !target || !chrome.contains(target) || this.#startPointer(
	        pointer,
	        target.dataset.resize ?? "move",
	        target
	      );
	    }), this.scope.listen(chrome, "pointermove", (event) => {
	      this.#movePointer(event);
	    });
	    for (const type of ["pointerup", "pointercancel", "lostpointercapture"])
	      this.scope.listen(chrome, type, (event) => {
	        this.#stopPointer(event);
	      });
	    return this.scope.listen(chrome, "keydown", (event) => {
	      this.#handleChromeKey(event);
	    }), this.#chrome = chrome, chrome;
	  }
	  #syncChrome(syncSize = !1) {
	    const chrome = this.#chrome, composer = this.#composer, geometry = this.#geometry;
	    if (!chrome) return;
	    const available = !!(geometry && this.#desktop() && this.#composerVisible() && !composer?.classList.contains("fullscreen"));
	    chrome.hidden = !available, !(!available || !geometry) && (syncSize && (chrome.style.setProperty("--ldp-composer-chrome-width", pixels(geometry.width)), chrome.style.setProperty("--ldp-composer-chrome-height", pixels(geometry.height))), chrome.style.transform = `translate3d(${pixels(geometry.left)},${pixels(geometry.top)},0)`);
	  }
	  #scheduleChrome() {
	    this.#chromeFrame || (this.#chromeFrame = this.#requestFrame(() => {
	      this.#chromeFrame = 0, this.#syncChrome();
	    }));
	  }
	  #startPointer(event, mode, target) {
	    !this.#geometry || !this.#desktop() || !this.#composerVisible() || this.#composer?.classList.contains("fullscreen") || event.button !== 0 || (this.#pointer = Object.freeze({
	      id: event.pointerId,
	      mode,
	      startX: event.clientX,
	      startY: event.clientY,
	      geometry: this.#geometry,
	      target
	    }), this.#pointerX = event.clientX, this.#pointerY = event.clientY, target.setPointerCapture?.(event.pointerId), this.#chrome?.classList.add("is-interacting"), this.#pageRoot.classList.add("ldp-composer-window-interacting"), event.preventDefault(), event.stopPropagation());
	  }
	  #movePointer(event) {
	    !this.#pointer || event.pointerId !== this.#pointer.id || (this.#pointerX = event.clientX, this.#pointerY = event.clientY, this.#pointerFrame || (this.#pointerFrame = this.#requestFrame(() => {
	      this.#pointerFrame = 0, this.#applyPointer();
	    })), event.preventDefault());
	  }
	  #stopPointer(event) {
	    const pointer = this.#pointer;
	    if (!pointer) {
	      this.#chrome?.classList.remove("is-interacting"), this.#pageRoot.classList.remove("ldp-composer-window-interacting");
	      return;
	    }
	    if (!(event && event.pointerId !== pointer.id)) {
	      event && Number.isFinite(event.clientX) && Number.isFinite(event.clientY) && (this.#pointerX = event.clientX, this.#pointerY = event.clientY), this.#pointerFrame && this.#cancelFrame(this.#pointerFrame), this.#pointerFrame = 0, this.#applyPointer(), this.#pointer = null;
	      try {
	        pointer.target.hasPointerCapture?.(pointer.id) && pointer.target.releasePointerCapture?.(pointer.id);
	      } catch {
	      }
	      this.#chrome?.classList.remove("is-interacting"), this.#pageRoot.classList.remove("ldp-composer-window-interacting"), this.#scheduleOverflow(), this.#persistGeometry();
	    }
	  }
	  #applyPointer() {
	    const pointer = this.#pointer;
	    if (!pointer) return;
	    const deltaX = this.#pointerX - pointer.startX, deltaY = this.#pointerY - pointer.startY;
	    if (pointer.mode === "move") {
	      const bounds = viewportLimits(this.#viewport());
	      this.#applyGeometry({
	        ...pointer.geometry,
	        left: clamp(
	          pointer.geometry.left + deltaX,
	          bounds.left,
	          bounds.right - pointer.geometry.width
	        ),
	        top: clamp(
	          pointer.geometry.top + deltaY,
	          bounds.top,
	          bounds.bottom - pointer.geometry.height
	        )
	      });
	      return;
	    }
	    this.#applyGeometry(this.#resizeGeometry(
	      pointer.geometry,
	      pointer.mode,
	      deltaX,
	      deltaY
	    ));
	  }
	  #resizeGeometry(start, direction, deltaX, deltaY) {
	    const bounds = viewportLimits(this.#viewport());
	    let left = start.left, top = start.top, right = start.left + start.width, bottom = start.top + start.height;
	    return direction.includes("w") && (left = clamp(start.left + deltaX, bounds.left, right - bounds.minWidth)), direction.includes("e") && (right = clamp(start.left + start.width + deltaX, left + bounds.minWidth, bounds.right)), direction.includes("n") && (top = clamp(start.top + deltaY, bounds.top, bottom - bounds.minHeight)), direction.includes("s") && (bottom = clamp(start.top + start.height + deltaY, top + bounds.minHeight, bounds.bottom)), Object.freeze({
	      left,
	      top,
	      width: right - left,
	      height: bottom - top
	    });
	  }
	  #handleChromeKey(event) {
	    if (!this.#geometry || !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
	    const target = (0, import_event_target.eventElement)(event), step = event.shiftKey ? 40 : 12;
	    if (target?.matches(".ldp-composer-drag-handle")) {
	      const next = { ...this.#geometry };
	      event.key === "ArrowLeft" ? next.left -= step : event.key === "ArrowRight" ? next.left += step : event.key === "ArrowUp" ? next.top -= step : next.top += step, this.#applyGeometry(next);
	    } else if (target?.matches('[data-resize="se"]'))
	      this.#applyGeometry(this.#resizeGeometry(
	        this.#geometry,
	        "se",
	        event.key === "ArrowLeft" ? -step : event.key === "ArrowRight" ? step : 0,
	        event.key === "ArrowUp" ? -step : event.key === "ArrowDown" ? step : 0
	      ));
	    else
	      return;
	    this.#schedulePersist(), event.preventDefault();
	  }
	  #schedulePersist() {
	    this.#persistTimer && this.#clearTimer(this.#persistTimer), this.#persistTimer = this.#setTimer(() => {
	      this.#persistTimer = 0, this.#persistGeometry();
	    }, 160);
	  }
	  #persistGeometry() {
	    if (this.#persistTimer && this.#clearTimer(this.#persistTimer), this.#persistTimer = 0, !(!this.#geometry || !this.#desktop() || !this.#updatePreferences))
	      try {
	        this.#updatePreferences({
	          composerWindowWidth: Math.round(this.#geometry.width),
	          composerWindowHeight: Math.round(this.#geometry.height),
	          composerWindowX: Math.round(this.#geometry.left),
	          composerWindowY: Math.round(this.#geometry.top)
	        });
	      } catch (cause) {
	        this.#onError(cause);
	      }
	  }
	  #syncComposerRoot() {
	    let next = this.#composer;
	    for (; next?.parentElement && next.parentElement !== this.#document.body; )
	      next = next.parentElement;
	    if ((!next || next.parentElement !== this.#document.body) && (next = null), next !== this.#composerRoot && (this.#clearComposerRoot(), this.#composerRoot = next, next)) {
	      next.dataset.ldpReaderComposerRoot = "1";
	      for (const property of APPEARANCE_PROPERTIES) {
	        const priorityReader = next.style;
	        this.#composerAppearanceOriginal.set(property, Object.freeze({
	          value: next.style.getPropertyValue(property),
	          priority: typeof priorityReader.getPropertyPriority == "function" ? priorityReader.getPropertyPriority(property) : ""
	        }));
	      }
	      this.#syncAppearance();
	    }
	  }
	  #clearComposerRoot() {
	    if (this.#composerRoot) {
	      for (const [property, previous] of this.#composerAppearanceOriginal)
	        previous.value ? this.#composerRoot.style.setProperty(
	          property,
	          previous.value,
	          previous.priority
	        ) : this.#composerRoot.style.removeProperty(property);
	      delete this.#composerRoot.dataset.ldpReaderComposerRoot;
	    }
	    this.#composerAppearanceOriginal.clear(), this.#composerRoot = null;
	  }
	  #syncAppearance(appearance = this.#readAppearance?.() ?? null) {
	    !this.#composerRoot || !appearance || (this.#composerRoot.style.setProperty(
	      "--tertiary",
	      appearance.accentColor
	    ), this.#composerRoot.style.setProperty(
	      "--tertiary-low",
	      appearance.accentLowColor
	    ), this.#composerRoot.style.setProperty(
	      "--d-link-color",
	      appearance.linkColor
	    ));
	  }
	  #scheduleOverflow() {
	    this.#overflowFrame || (this.#overflowFrame = this.#requestFrame(() => {
	      this.#overflowFrame = 0, this.#syncOverflow();
	    }));
	  }
	  #syncOverflow() {
	    this.#clearOverflow(!1);
	    const composer = this.#composer;
	    if (!composer || !this.#composerVisible()) return;
	    const composerRect = composer.getBoundingClientRect?.();
	    if (!composerRect || composerRect.width <= 0 || composerRect.height <= 0) return;
	    const candidates = [composer, ...composer.querySelectorAll("*")].map((candidate) => {
	      if (candidate.clientWidth <= 0 || candidate.scrollWidth <= candidate.clientWidth + 1 || candidate.closest('.d-editor-button-bar,[role="toolbar"]')) return null;
	      let overflowX = "";
	      try {
	        overflowX = this.#window.getComputedStyle(candidate).overflowX;
	      } catch {
	        return null;
	      }
	      if (!/(auto|scroll)/.test(overflowX)) return null;
	      const rect = candidate.getBoundingClientRect(), coveredWidth = Math.max(0, Math.min(rect.right, composerRect.right) - Math.max(rect.left, composerRect.left)), coveredHeight = Math.max(0, Math.min(rect.bottom, composerRect.bottom) - Math.max(rect.top, composerRect.top)), widthCoverage = coveredWidth / composerRect.width, heightCoverage = coveredHeight / composerRect.height;
	      return widthCoverage >= 0.85 && heightCoverage >= 0.75 ? Object.freeze({
	        candidate,
	        coverage: widthCoverage * heightCoverage
	      }) : null;
	    }).filter((value) => value !== null).sort((left, right) => right.coverage - left.coverage);
	    this.#overflowLayers = candidates.length ? [candidates[0].candidate] : [];
	    for (const layer of this.#overflowLayers)
	      layer.dataset.ldpReaderComposerOverflow = "1";
	  }
	  #clearOverflow(cancelFrame = !0) {
	    cancelFrame && this.#overflowFrame && this.#cancelFrame(this.#overflowFrame), cancelFrame && (this.#overflowFrame = 0);
	    for (const layer of this.#overflowLayers)
	      delete layer.dataset.ldpReaderComposerOverflow;
	    this.#overflowLayers = [];
	  }
	  #promoteTopLayer(element, kind) {
	    if (element.hasAttribute("popover") && !this.#ownedTopLayers.has(element))
	      return this.#topLayer.isOpen(element);
	    this.#ownedTopLayers.has(element) || (element.setAttribute("popover", "manual"), element.dataset.ldpReaderTopLayer = kind, this.#ownedTopLayers.add(element));
	    try {
	      if (this.#topLayer.isOpen(element) || this.#topLayer.show(element), this.#topLayer.isOpen(element)) return !0;
	    } catch (cause) {
	      this.#onError(cause);
	    }
	    return this.#releaseTopLayer(element), !1;
	  }
	  #releaseTopLayer(element) {
	    if (this.#ownedTopLayers.delete(element)) {
	      try {
	        this.#topLayer.isOpen(element) && this.#topLayer.hide(element);
	      } catch (cause) {
	        this.#onError(cause);
	      }
	      element.removeAttribute("popover"), delete element.dataset.ldpReaderTopLayer;
	    }
	  }
	  #syncTopLayers() {
	    const composer = this.#composer;
	    if (!composer || !this.#composerVisible()) {
	      this.#releaseTopLayers();
	      return;
	    }
	    if (!this.#promoteTopLayer(composer, "composer")) {
	      this.#releaseFloatingTopLayers();
	      return;
	    }
	    this.#chrome && !this.#chrome.hidden && this.#promoteTopLayer(this.#chrome, "chrome"), this.#startFloatingObservation(), this.#scheduleFloating();
	  }
	  #startFloatingObservation() {
	    this.#floatingObserver || !this.#document.body || (this.#floatingObserver = this.#createMutationObserver((mutations) => {
	      mutations.some((mutation) => this.#floatingMutationRelevant(mutation)) && this.#scheduleFloating();
	    }), this.#floatingObserver.observe(this.#document.body, {
	      childList: !0,
	      subtree: !0,
	      attributes: !0,
	      attributeFilter: ["class", "style", "hidden", "aria-hidden", "open"]
	    }));
	  }
	  #floatingMutationRelevant(mutation) {
	    return (mutation.type === "attributes" ? [mutation.target] : [...mutation.addedNodes, ...mutation.removedNodes]).some((node) => {
	      const element = node.nodeType === 1 ? node : node.parentNode?.nodeType === 1 ? node.parentNode : null;
	      return !!(element?.matches(DISCOURSE_NATIVE_FLOATING_SELECTOR) || element?.closest(DISCOURSE_NATIVE_FLOATING_SELECTOR) || element?.querySelector(DISCOURSE_NATIVE_FLOATING_SELECTOR));
	    });
	  }
	  #scheduleFloating() {
	    this.#floatingFrame || (this.#floatingFrame = this.#requestFrame(() => {
	      this.#floatingFrame = 0, this.#syncFloatingTopLayers();
	    }));
	  }
	  #syncFloatingTopLayers() {
	    const composer = this.#composer;
	    if (!composer || !this.#topLayer.isOpen(composer)) return;
	    const visible = [...this.#document.querySelectorAll(
	      DISCOURSE_NATIVE_FLOATING_SELECTOR
	    )].filter((element) => !(element.closest("#reply-control") || element.contains(composer) || element.closest("dialog[open]") || !discourseNativeFloatingSurfaceVisible(element, this.#window))), surfaces = visible.filter((element) => !visible.some((parent) => parent !== element && parent.contains(element)));
	    for (const element of [...this.#ownedTopLayers])
	      element.dataset.ldpReaderTopLayer === "portal" && !surfaces.includes(element) && this.#releaseTopLayer(element);
	    for (const surface of surfaces) this.#promoteTopLayer(surface, "portal");
	  }
	  #releaseFloatingTopLayers() {
	    this.#floatingFrame && this.#cancelFrame(this.#floatingFrame), this.#floatingFrame = 0, this.#floatingObserver?.disconnect(), this.#floatingObserver = null;
	    for (const element of [...this.#ownedTopLayers])
	      element.dataset.ldpReaderTopLayer === "portal" && this.#releaseTopLayer(element);
	  }
	  #releaseTopLayers() {
	    this.#releaseFloatingTopLayers();
	    for (const element of [...this.#ownedTopLayers].reverse())
	      this.#releaseTopLayer(element);
	  }
	  #teardown() {
	    if (!this.#destroyed) {
	      this.#destroyed = !0, this.#stopPointer(), this.#persistGeometry(), this.#composerScope?.destroy(), this.#composerScope = null, this.#hostObserver?.disconnect(), this.#hostObserver = null, this.#releaseTopLayers();
	      for (const frame of [
	        this.#pointerFrame,
	        this.#chromeFrame,
	        this.#overflowFrame,
	        this.#floatingFrame
	      ])
	        frame && this.#cancelFrame(frame);
	      this.#pointerFrame = 0, this.#chromeFrame = 0, this.#overflowFrame = 0, this.#floatingFrame = 0, this.#persistTimer && this.#clearTimer(this.#persistTimer), this.#persistTimer = 0, this.#pageRoot.classList.remove(
	        "ldp-composer-window-interacting"
	      ), this.#clearGeometry(), this.#clearOverflow(), this.#clearComposerRoot(), this.#chrome?.remove(), this.#chrome = null, this.#composer = null, this.#geometry = null;
	    }
	  }
	}
}, "d674a438db35fb347aa846080f5daf9be522cab21542eae7a11a226266457c9b");

/* Source: lite/src/dom/event-target.ts */
runtime.register("src/dom/event-target.js", function(module, exports, require) {
	var event_target_exports = {};
	__export(event_target_exports, {
	  deepActiveElement: () => deepActiveElement,
	  eventElement: () => eventElement,
	  eventPath: () => eventPath,
	  eventPathIncludes: () => eventPathIncludes,
	  usesNativeLinkNavigation: () => usesNativeLinkNavigation
	});
	module.exports = __toCommonJS(event_target_exports);
	function eventPath(event) {
	  try {
	    const path = event.composedPath?.();
	    if (path?.length) return path;
	  } catch {
	  }
	  return event.target ? Object.freeze([event.target]) : Object.freeze([]);
	}
	function eventElement(event) {
	  for (const target of eventPath(event))
	    if (target !== null && typeof target == "object" && target.nodeType === 1) return target;
	  return null;
	}
	function eventPathIncludes(event, node) {
	  if (!node) return !1;
	  if (eventPath(event).includes(node)) return !0;
	  const target = event.target;
	  return target !== null && typeof target == "object" && typeof target.nodeType == "number" && node.contains(target);
	}
	function usesNativeLinkNavigation(event) {
	  return event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
	}
	function deepActiveElement(document) {
	  let active = document.activeElement;
	  for (; active?.shadowRoot?.activeElement; )
	    active = active.shadowRoot.activeElement;
	  return active;
	}
}, "7e486dde4a1615d7509486f91752765d59762d7ee30e373d3a730e51ec3f997d");

/* Source: lite/src/dom/floating-surface-wheel.ts */
runtime.register("src/dom/floating-surface-wheel.js", function(module, exports, require) {
	var floating_surface_wheel_exports = {};
	__export(floating_surface_wheel_exports, {
	  containFloatingSurfaceWheel: () => containFloatingSurfaceWheel
	});
	module.exports = __toCommonJS(floating_surface_wheel_exports);
	var import_event_target = require("./event-target.js");
	function defaultStyle(element) {
	  const view = element.ownerDocument.defaultView;
	  return typeof view?.getComputedStyle == "function" ? view.getComputedStyle(element) : { overflowX: "", overflowY: "" };
	}
	function wheelDelta(event, surface) {
	  return event.deltaMode === 1 ? { x: event.deltaX * 40, y: event.deltaY * 40 } : event.deltaMode === 2 ? {
	    x: event.deltaX * surface.clientWidth,
	    y: event.deltaY * surface.clientHeight
	  } : { x: event.deltaX, y: event.deltaY };
	}
	function containFloatingSurfaceWheel(surface, event, environment = {}) {
	  event.stopPropagation();
	  const target = (0, import_event_target.eventElement)(event), { x: deltaX, y: deltaY } = wheelDelta(event, surface);
	  if (!target || !surface.contains(target)) {
	    (deltaX || deltaY) && event.preventDefault();
	    return;
	  }
	  const style = environment.style ?? defaultStyle;
	  let scrollTarget = target;
	  for (; scrollTarget && surface.contains(scrollTarget); ) {
	    const computed = style(scrollTarget), maxScrollLeft = scrollTarget.scrollWidth - scrollTarget.clientWidth, maxScrollTop = scrollTarget.scrollHeight - scrollTarget.clientHeight, canScrollX = !!(deltaX && maxScrollLeft > 1 && /(auto|scroll|overlay)/.test(computed.overflowX) && (deltaX < 0 ? scrollTarget.scrollLeft > 0 : scrollTarget.scrollLeft < maxScrollLeft - 1)), canScrollY = !!(deltaY && maxScrollTop > 1 && /(auto|scroll|overlay)/.test(computed.overflowY) && (deltaY < 0 ? scrollTarget.scrollTop > 0 : scrollTarget.scrollTop < maxScrollTop - 1));
	    if (canScrollX || canScrollY) return;
	    if (scrollTarget === surface) break;
	    scrollTarget = scrollTarget.parentElement;
	  }
	  (deltaX || deltaY) && event.preventDefault();
	}
}, "904b3651bafb4f8fb0ea30db869f3a91fd9147a3ff899ac88645019fb68fd66f");

/* Source: lite/src/dom/html-element.ts */
runtime.register("src/dom/html-element.js", function(module, exports, require) {
	var html_element_exports = {};
	__export(html_element_exports, {
	  htmlElement: () => htmlElement
	});
	module.exports = __toCommonJS(html_element_exports);
	function htmlElement(document, tagName, className = "", textContent) {
	  const node = document.createElement(tagName);
	  return node.className = className, textContent !== void 0 && (node.textContent = textContent), node;
	}
}, "67cadf2903ac7d3c55b0e65cd9828d3b33fcc50916b623b29b38b675f72b3d09");

/* Source: lite/src/dom/post-view.ts */
runtime.register("src/dom/post-view.js", function(module, exports, require) {
	var post_view_exports = {};
	__export(post_view_exports, {
	  PostView: () => PostView
	});
	module.exports = __toCommonJS(post_view_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_identifiers = require("../discourse/identifiers.js"), import_html_element = require("./html-element.js");
	const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
	class PostView {
	  identity;
	  scope;
	  slots;
	  #unbindActionManifest = null;
	  constructor(document, identity, parentScope) {
	    const postId = (0, import_identifiers.discoursePostId)(identity.postId), postNumber = (0, import_identifiers.discoursePostNumber)(identity.postNumber);
	    this.identity = Object.freeze({ ...identity, postId, postNumber }), this.scope = import_lifecycle.LifecycleScope.ownedBy(parentScope);
	    const root = (0, import_html_element.htmlElement)(document, "article", "ldp-post");
	    root.dataset.postId = String(postId), root.dataset.postNumber = String(postNumber), root.dataset.username = identity.username, identity.createdAt && (root.dataset.createdAt = identity.createdAt);
	    const header = (0, import_html_element.htmlElement)(document, "header", "ldp-post-head"), body = (0, import_html_element.htmlElement)(document, "div", "ldp-post-body"), content = (0, import_html_element.htmlElement)(document, "div", "ldp-content cooked"), bodyLayer = (0, import_html_element.htmlElement)(document, "div", "ldp-post-body-layer"), boost = (0, import_html_element.htmlElement)(document, "div", "ldp-boost-list");
	    boost.hidden = !0;
	    const actions = (0, import_html_element.htmlElement)(document, "div", "ldp-reactions ldp-post-actions"), topicFooter = (0, import_html_element.htmlElement)(document, "footer", "ldp-topic-footer-slot");
	    topicFooter.hidden = !0, body.append(content, bodyLayer, boost, actions, topicFooter);
	    const replyTree = (0, import_html_element.htmlElement)(document, "section", "ldp-children ldp-reply-tree"), branchOverlay = document.createElementNS(SVG_NAMESPACE, "svg");
	    branchOverlay.classList.add("ldp-branch-overlay"), branchOverlay.setAttribute("hidden", ""), branchOverlay.setAttribute("aria-hidden", "true"), branchOverlay.setAttribute("focusable", "false");
	    const visiblePath = document.createElementNS(SVG_NAMESPACE, "path");
	    visiblePath.classList.add("ldp-branch-visible-path");
	    const hitPath = document.createElementNS(SVG_NAMESPACE, "path");
	    hitPath.classList.add("ldp-branch-hit-path"), branchOverlay.append(visiblePath, hitPath);
	    const replyControls = (0, import_html_element.htmlElement)(document, "div", "ldp-reply-controls ldp-sub-actions"), replyList = (0, import_html_element.htmlElement)(document, "div", "ldp-reply-list");
	    replyTree.append(branchOverlay, replyList, replyControls), root.append(header, body, replyTree), this.slots = Object.freeze({
	      root,
	      header,
	      body,
	      content,
	      bodyLayer,
	      boost,
	      actions,
	      topicFooter,
	      replyTree,
	      branchOverlay,
	      replyControls,
	      replyList
	    });
	  }
	  get postNumber() {
	    return this.identity.postNumber;
	  }
	  setTreePosition(parentPostNumber, depth) {
	    if (parentPostNumber !== null && (0, import_identifiers.discoursePostNumber)(parentPostNumber), depth !== void 0 && (!Number.isSafeInteger(depth) || depth < 0))
	      throw new RangeError("depth 必须是非负安全整数或 undefined");
	    const parentValue = parentPostNumber === null ? void 0 : String(parentPostNumber);
	    parentValue === void 0 ? this.slots.root.dataset.parentPostNumber !== void 0 && delete this.slots.root.dataset.parentPostNumber : this.slots.root.dataset.parentPostNumber !== parentValue && (this.slots.root.dataset.parentPostNumber = parentValue);
	    const depthValue = depth === void 0 ? void 0 : String(depth);
	    depthValue === void 0 ? this.slots.root.dataset.ldpNestDepth !== void 0 && delete this.slots.root.dataset.ldpNestDepth : this.slots.root.dataset.ldpNestDepth !== depthValue && (this.slots.root.dataset.ldpNestDepth = depthValue);
	    const nested = depth !== void 0 && depth > 0;
	    this.slots.root.classList.contains("ldp-nested-preview") !== nested && this.slots.root.classList.toggle("ldp-nested-preview", nested), nested && this.slots.root.classList.contains("ldp-zebra-alt") && this.slots.root.classList.remove("ldp-zebra-alt");
	  }
	  /**
	   * 将普通、嵌套、实时新增和回屏楼层接到同一份动作状态。
	   *
	   * PostView 只提供命名槽位与生命周期;具体按钮 DOM 仍由 renderer 组件拥有。
	   */
	  bindActionManifest(source, renderer) {
	    this.#unbindActionManifest?.(), renderer(this.slots, source.snapshot());
	    const unsubscribe = source.subscribe((snapshot) => {
	      renderer(this.slots, snapshot);
	    }, this.scope);
	    let active = !0;
	    const cleanup = () => {
	      active && (active = !1, unsubscribe(), this.#unbindActionManifest === cleanup && (this.#unbindActionManifest = null));
	    };
	    return this.#unbindActionManifest = cleanup, this.scope.add(cleanup), cleanup;
	  }
	  destroy() {
	    try {
	      this.scope.destroy();
	    } finally {
	      this.slots.root.remove();
	    }
	  }
	}
}, "8687ab8a5ffb375a62325deed0d15b34a1366e58f9157f99a6e9ba24c1a30edf");

/* Source: lite/src/dom/reply-tree-dom-owner.ts */
runtime.register("src/dom/reply-tree-dom-owner.js", function(module, exports, require) {
	var reply_tree_dom_owner_exports = {};
	__export(reply_tree_dom_owner_exports, {
	  ReplyTreeDomOwner: () => ReplyTreeDomOwner
	});
	module.exports = __toCommonJS(reply_tree_dom_owner_exports);
	function directPostNumber(element) {
	  const raw = element.getAttribute("data-post-number");
	  if (raw === null) return null;
	  const postNumber = Number(raw);
	  return Number.isSafeInteger(postNumber) ? postNumber : null;
	}
	function insertPostInOrder(container, view) {
	  const root = view.slots.root;
	  if (root.parentElement === container) {
	    let previousPostNumber = null;
	    for (let previous = root.previousElementSibling; previous && (previousPostNumber = directPostNumber(previous), previousPostNumber === null); previous = previous.previousElementSibling)
	      ;
	    let nextPostNumber = null;
	    for (let next2 = root.nextElementSibling; next2 && (nextPostNumber = directPostNumber(next2), nextPostNumber === null); next2 = next2.nextElementSibling)
	      ;
	    if ((previousPostNumber === null || previousPostNumber < view.postNumber) && (nextPostNumber === null || nextPostNumber > view.postNumber)) return;
	  }
	  const next = Array.from(container.children).find((candidate) => {
	    const postNumber = directPostNumber(candidate);
	    return postNumber !== null && postNumber > view.postNumber;
	  });
	  container.insertBefore(root, next ?? null);
	}
	function virtualSpacer(document, blockSize) {
	  const spacer = document.createElement("div");
	  return spacer.className = "ldp-tree-virtual-spacer", spacer.setAttribute("aria-hidden", "true"), spacer.style.blockSize = `${Math.max(0, blockSize)}px`, spacer;
	}
	function removeVirtualSpacers(container) {
	  for (const child of Array.from(container.children))
	    child.classList.contains("ldp-tree-virtual-spacer") && child.remove();
	}
	function childLayoutKey(layout) {
	  return `${layout.postNumbers.join(".")}@${layout.beforeSizes.map((size) => size.toFixed(2)).join(".")}@` + layout.afterSize.toFixed(2);
	}
	class ReplyTreeDomOwner {
	  topology;
	  rootList;
	  #views = /* @__PURE__ */ new Map();
	  #viewRevision = 0;
	  #lastSyncKey = "";
	  #lastCommit = null;
	  #childLayoutKeys = /* @__PURE__ */ new Map();
	  constructor(topology, rootList) {
	    this.topology = topology, this.rootList = rootList;
	  }
	  register(view, sync = !0) {
	    const existing = this.#views.get(view.postNumber);
	    if (existing && existing !== view)
	      throw new Error(`楼层 #${view.postNumber} 已由另一个 PostView 持有`);
	    this.#views.set(view.postNumber, view);
	    const parentPostNumber = this.topology.parentOf(view.postNumber);
	    parentPostNumber != null && this.#childLayoutKeys.delete(parentPostNumber), this.#viewRevision += 1, this.#lastSyncKey = "", sync && this.sync();
	  }
	  view(postNumber) {
	    return this.#views.get(postNumber);
	  }
	  views() {
	    return Object.freeze([...this.#views.values()]);
	  }
	  willSyncChange(mountedRootPostNumbers, mountPlan) {
	    return !this.#lastCommit || this.#syncKey(mountedRootPostNumbers, mountPlan) !== this.#lastSyncKey;
	  }
	  unregister(postNumber, destroy = !0, resync = !0) {
	    const view = this.#views.get(postNumber);
	    if (!view) return;
	    this.#views.delete(postNumber), this.#childLayoutKeys.delete(postNumber);
	    const parentPostNumber = this.topology.parentOf(postNumber);
	    return parentPostNumber != null && this.#childLayoutKeys.delete(parentPostNumber), this.#viewRevision += 1, this.#lastSyncKey = "", destroy ? view.destroy() : view.slots.root.remove(), resync && this.sync(), view;
	  }
	  sync(mountedRootPostNumbers, mountPlan) {
	    const syncKey = this.#syncKey(mountedRootPostNumbers, mountPlan);
	    if (this.#lastCommit && syncKey === this.#lastSyncKey)
	      return Object.freeze({ ...this.#lastCommit, changed: !1 });
	    const mountedRoots = [], mountedReplies = [], parked = [], missingParents = [], viewEntries = [...this.#views.values()].map((view) => ({
	      view,
	      parentPostNumber: this.topology.parentOf(view.postNumber),
	      depth: this.topology.depthOf(view.postNumber),
	      rootPostNumber: this.topology.rootOf(view.postNumber)
	    })).sort(
	      (left, right) => (left.depth ?? Number.MAX_SAFE_INTEGER) - (right.depth ?? Number.MAX_SAFE_INTEGER) || left.view.postNumber - right.view.postNumber
	    );
	    for (const {
	      view,
	      parentPostNumber,
	      depth,
	      rootPostNumber
	    } of viewEntries) {
	      const selectedByNodeWindow = !mountPlan || mountPlan.mountedPostNumbers.has(view.postNumber), ancestorShell = !!mountPlan && mountPlan.shellPostNumbers.has(view.postNumber);
	      view.slots.root.classList.contains("ldp-virtual-ancestor-shell") !== ancestorShell && view.slots.root.classList.toggle(
	        "ldp-virtual-ancestor-shell",
	        ancestorShell
	      );
	      const ownSize = mountPlan?.ownSizes.get(view.postNumber);
	      if (ownSize === void 0)
	        view.slots.root.style.getPropertyValue("--ldp-virtual-own-size") && view.slots.root.style.removeProperty("--ldp-virtual-own-size");
	      else {
	        const ownSizeValue = `${Math.max(0, ownSize)}px`;
	        view.slots.root.style.getPropertyValue("--ldp-virtual-own-size") !== ownSizeValue && view.slots.root.style.setProperty(
	          "--ldp-virtual-own-size",
	          ownSizeValue
	        );
	      }
	      if (parentPostNumber === void 0) {
	        view.slots.root.remove(), this.#childLayoutKeys.delete(view.postNumber), view.setTreePosition(null, void 0), parked.push(view.postNumber);
	        continue;
	      }
	      if (!selectedByNodeWindow || mountedRootPostNumbers && (rootPostNumber === void 0 || !mountedRootPostNumbers.has(rootPostNumber))) {
	        view.slots.root.remove(), this.#childLayoutKeys.delete(view.postNumber), view.setTreePosition(parentPostNumber, depth), parked.push(view.postNumber);
	        continue;
	      }
	      if (parentPostNumber === null) {
	        view.setTreePosition(null, depth), insertPostInOrder(this.rootList, view), mountedRoots.push(view.postNumber);
	        continue;
	      }
	      const parent = this.#views.get(parentPostNumber);
	      if (view.setTreePosition(parentPostNumber, depth), !parent) {
	        view.slots.root.remove(), this.#childLayoutKeys.delete(view.postNumber), parked.push(view.postNumber), missingParents.push(parentPostNumber);
	        continue;
	      }
	      insertPostInOrder(parent.slots.replyList, view), mountedReplies.push(view.postNumber);
	    }
	    if (mountPlan)
	      for (const { view } of viewEntries) {
	        if (!mountPlan.mountedPostNumbers.has(view.postNumber)) continue;
	        const childLayout = mountPlan.childLayouts.get(view.postNumber);
	        if (!childLayout) {
	          this.#childLayoutKeys.has(view.postNumber) && (removeVirtualSpacers(view.slots.replyList), this.#childLayoutKeys.delete(view.postNumber));
	          continue;
	        }
	        const nextLayoutKey = childLayoutKey(childLayout);
	        if (this.#childLayoutKeys.get(view.postNumber) === nextLayoutKey)
	          continue;
	        removeVirtualSpacers(view.slots.replyList);
	        const fragment = view.slots.replyList.ownerDocument.createDocumentFragment();
	        for (let index = 0; index < childLayout.postNumbers.length; index += 1) {
	          const beforeSize = childLayout.beforeSizes[index] ?? 0;
	          beforeSize > 0 && fragment.append(virtualSpacer(
	            view.slots.replyList.ownerDocument,
	            beforeSize
	          ));
	          const childPostNumber = childLayout.postNumbers[index], childView = this.#views.get(childPostNumber);
	          childView && mountPlan.mountedPostNumbers.has(childPostNumber) && fragment.append(childView.slots.root);
	        }
	        childLayout.afterSize > 0 && fragment.append(virtualSpacer(
	          view.slots.replyList.ownerDocument,
	          childLayout.afterSize
	        )), view.slots.replyList.append(fragment), this.#childLayoutKeys.set(view.postNumber, nextLayoutKey);
	      }
	    else if (this.#childLayoutKeys.size) {
	      for (const postNumber of this.#childLayoutKeys.keys()) {
	        const view = this.#views.get(postNumber);
	        view && removeVirtualSpacers(view.slots.replyList);
	      }
	      this.#childLayoutKeys.clear();
	    }
	    const commit = Object.freeze({
	      changed: !0,
	      mountedRoots: Object.freeze(mountedRoots),
	      mountedReplies: Object.freeze(mountedReplies),
	      parked: Object.freeze(parked),
	      missingParents: Object.freeze([...new Set(missingParents)].sort((a, b) => a - b))
	    });
	    return this.#lastSyncKey = syncKey, this.#lastCommit = commit, commit;
	  }
	  #syncKey(mountedRootPostNumbers, mountPlan) {
	    const roots = mountedRootPostNumbers ? [...mountedRootPostNumbers].sort((left, right) => left - right).join(",") : "*";
	    if (!mountPlan)
	      return `${String(this.topology.revision ?? "")}|${this.#viewRevision}|${roots}`;
	    const mounted = [...mountPlan.mountedPostNumbers].sort((left, right) => left - right).join(","), shells = [...mountPlan.shellPostNumbers].sort((left, right) => left - right).join(","), ownSizes = [...mountPlan.ownSizes].sort(([left], [right]) => left - right).map(([postNumber, size]) => `${postNumber}:${size.toFixed(2)}`).join(","), layouts = [...mountPlan.childLayouts].sort(([left], [right]) => left - right).map(
	      ([postNumber, layout]) => `${postNumber}:${layout.postNumbers.join(".")}@${layout.beforeSizes.map((size) => size.toFixed(2)).join(".")}@` + layout.afterSize.toFixed(2)
	    ).join("|");
	    return `${String(this.topology.revision ?? "")}|${this.#viewRevision}|${roots}|${mounted}|${shells}|${ownSizes}|${layouts}`;
	  }
	  destroy() {
	    const views = [...this.#views.values()].sort((left, right) => right.postNumber - left.postNumber);
	    this.#views.clear(), this.#childLayoutKeys.clear(), this.#lastCommit = null, this.#lastSyncKey = "";
	    for (const view of views) view.destroy();
	  }
	}
}, "c512ca38a1e5d7b7eb8c1d4585a46169687310d97d0f98ab70912937ac736af5");

/* Source: lite/src/dom/reply-tree-repository.ts */
runtime.register("src/dom/reply-tree-repository.js", function(module, exports, require) {
	var reply_tree_repository_exports = {};
	__export(reply_tree_repository_exports, {
	  ReplyTreeRepository: () => ReplyTreeRepository
	});
	module.exports = __toCommonJS(reply_tree_repository_exports);
	var import_signal = require("../kernel/signal.js"), import_identifiers = require("../discourse/identifiers.js"), import_ingest_version = require("../discourse/ingest-version.js"), import_reply_tree = require("./reply-tree.js");
	function relationFromPost(post) {
	  const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(post.post_number);
	  if (postNumber === null) return null;
	  const parentPostNumber = (0, import_identifiers.tryDiscoursePostNumber)(post.reply_to_post_number);
	  return Object.freeze({ postNumber, parentPostNumber });
	}
	function assertExpectedPostCount(value) {
	  if (!Number.isSafeInteger(value) || value < 0)
	    throw new RangeError("expectedPostCount 必须是非负安全整数");
	  return value;
	}
	function validSource(value) {
	  return (0, import_ingest_version.normalizeDiscourseIngestSource)(value) !== null;
	}
	function shouldApplyRelation(current, observedAt, source) {
	  return (0, import_ingest_version.shouldReplaceDiscourseVersion)(current, { observedAt, source });
	}
	function assertStoredSnapshot(value, topicId) {
	  if (value === null) return null;
	  if (value.schemaVersion !== 2 || value.topicId !== topicId || !Number.isFinite(value.savedAt) || !Number.isSafeInteger(value.expectedPostCount) || value.expectedPostCount < 0 || !value.tree || !Array.isArray(value.tree.relations) || !Array.isArray(value.versions))
	    throw new Error(`Topic ${topicId} 的回复树快照无效`);
	  const relationPostNumbers = new Set(value.tree.relations.map((relation) => relation.postNumber)), versionPostNumbers = /* @__PURE__ */ new Set();
	  for (const version of value.versions) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(version?.postNumber);
	    if (postNumber === null || versionPostNumbers.has(postNumber) || !relationPostNumbers.has(postNumber) || !Number.isFinite(version.observedAt) || version.observedAt < 0 || !validSource(version.source))
	      throw new Error(`Topic ${topicId} 的回复树关系版本无效`);
	    versionPostNumbers.add(postNumber);
	  }
	  if (versionPostNumbers.size !== relationPostNumbers.size)
	    throw new Error(`Topic ${topicId} 的回复树关系版本不完整`);
	  const removedPostNumbers = /* @__PURE__ */ new Set();
	  for (const version of value.removedVersions ?? []) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(version?.postNumber);
	    if (postNumber === null || removedPostNumbers.has(postNumber) || relationPostNumbers.has(postNumber) || !Number.isFinite(version.observedAt) || version.observedAt < 0 || !validSource(version.source))
	      throw new Error(`Topic ${topicId} 的回复树删除版本无效`);
	    removedPostNumbers.add(postNumber);
	  }
	  return value;
	}
	class ReplyTreeRepository {
	  topicId;
	  topology;
	  changes = new import_signal.Signal();
	  #store;
	  #onPersistenceError;
	  #now;
	  #versions = /* @__PURE__ */ new Map();
	  #removedVersions = /* @__PURE__ */ new Map();
	  #expectedPostCount = 0;
	  #pendingSnapshot = null;
	  #persisting = null;
	  #lastPersistenceError = null;
	  #lastImplicitObservedAt = -1;
	  constructor(topicId, store, options = {}) {
	    const normalizedTopicId = String((0, import_identifiers.discourseTopicId)(topicId));
	    this.topicId = normalizedTopicId, this.#store = store, this.topology = options.topology ?? new import_reply_tree.ReplyTreeTopology(), this.#now = options.now ?? Date.now, this.#onPersistenceError = options.onPersistenceError ?? (() => {
	    });
	    for (const relation of this.topology.snapshot().relations)
	      this.#versions.set(relation.postNumber, Object.freeze({
	        postNumber: relation.postNumber,
	        observedAt: 0,
	        source: "loader-batch"
	      }));
	  }
	  coverage() {
	    const knownPostCount = this.topology.snapshot().relations.length;
	    return Object.freeze({
	      knownPostCount,
	      expectedPostCount: this.#expectedPostCount,
	      complete: this.#expectedPostCount > 0 && knownPostCount >= this.#expectedPostCount
	    });
	  }
	  setExpectedPostCount(expectedPostCount) {
	    const normalized = assertExpectedPostCount(expectedPostCount);
	    return normalized !== this.#expectedPostCount && (this.#expectedPostCount = normalized, this.#queuePersistence()), this.coverage();
	  }
	  ingest(posts, source, options = {}) {
	    const observedAt = options.observedAt === void 0 ? Math.max(
	      (0, import_ingest_version.discourseObservedAt)(this.#now()),
	      this.#lastImplicitObservedAt + 1
	    ) : (0, import_ingest_version.discourseObservedAt)(options.observedAt);
	    options.observedAt === void 0 && (this.#lastImplicitObservedAt = observedAt);
	    const relationByPost = /* @__PURE__ */ new Map(), acceptedByPost = /* @__PURE__ */ new Map();
	    let ignored = 0;
	    for (const post of posts) {
	      const relation = relationFromPost(post);
	      relation ? (relationByPost.delete(relation.postNumber), relationByPost.set(relation.postNumber, relation), acceptedByPost.delete(relation.postNumber), acceptedByPost.set(
	        relation.postNumber,
	        Object.freeze({ postNumber: relation.postNumber, post })
	      )) : ignored += 1;
	    }
	    const relations = [...relationByPost.values()], acceptedPosts = [...acceptedByPost.values()];
	    if (!relations.length)
	      return Object.freeze({
	        accepted: 0,
	        appliedRelations: 0,
	        acceptedPosts: Object.freeze([]),
	        ignored,
	        event: null,
	        listenerErrors: Object.freeze([])
	      });
	    const applicable = relations.filter((relation) => {
	      const removed = this.#removedVersions.get(relation.postNumber);
	      return removed && !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, { observedAt, source }) ? !1 : shouldApplyRelation(this.#versions.get(relation.postNumber), observedAt, source);
	    }), applicablePostNumbers = new Set(
	      applicable.map((relation) => relation.postNumber)
	    ), applicablePosts = acceptedPosts.filter((entry) => applicablePostNumbers.has(entry.postNumber));
	    if (!applicable.length)
	      return Object.freeze({
	        accepted: 0,
	        appliedRelations: 0,
	        acceptedPosts: Object.freeze([]),
	        ignored,
	        event: null,
	        listenerErrors: Object.freeze([])
	      });
	    const change = this.topology.commit(applicable);
	    for (const relation of applicable)
	      this.#removedVersions.delete(relation.postNumber), this.#versions.set(relation.postNumber, Object.freeze({
	        postNumber: relation.postNumber,
	        observedAt,
	        source
	      }));
	    const event = this.#event(source, change), listenerErrors = this.changes.emit(event);
	    return this.#queuePersistence(), Object.freeze({
	      accepted: applicable.length,
	      appliedRelations: applicable.length,
	      acceptedPosts: Object.freeze(applicablePosts),
	      ignored,
	      event,
	      listenerErrors
	    });
	  }
	  remove(rawPostNumber, source, options = {}) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(rawPostNumber);
	    if (postNumber === null) throw new RangeError("postNumber 必须是正安全整数");
	    const observedAt = options.observedAt === void 0 ? Math.max(
	      (0, import_ingest_version.discourseObservedAt)(this.#now()),
	      this.#lastImplicitObservedAt + 1
	    ) : (0, import_ingest_version.discourseObservedAt)(options.observedAt);
	    options.observedAt === void 0 && (this.#lastImplicitObservedAt = observedAt);
	    const current = this.#versions.get(postNumber), removed = this.#removedVersions.get(postNumber), nextVersion = Object.freeze({ postNumber, observedAt, source });
	    if (current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, nextVersion) || removed && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(removed, nextVersion))
	      return null;
	    const hadRelation = this.topology.has(postNumber), directChildren = this.topology.childrenOf(postNumber), change = this.topology.remove(postNumber);
	    this.#versions.delete(postNumber), this.#removedVersions.set(postNumber, nextVersion);
	    for (const childPostNumber of directChildren)
	      this.#versions.set(childPostNumber, Object.freeze({
	        postNumber: childPostNumber,
	        observedAt,
	        source
	      }));
	    (current || hadRelation) && (this.#expectedPostCount = Math.max(0, this.#expectedPostCount - 1));
	    const event = this.#event(source, change), listenerErrors = this.changes.emit(event);
	    for (const error of listenerErrors) this.#onPersistenceError(error);
	    return this.#queuePersistence(), event;
	  }
	  async restore() {
	    const revisionBeforeLoad = this.topology.revision;
	    let stored;
	    try {
	      stored = assertStoredSnapshot(await this.#store.load(this.topicId), this.topicId);
	    } catch (error) {
	      return this.#onPersistenceError(error), null;
	    }
	    if (!stored) return null;
	    const previousExpectedPostCount = this.#expectedPostCount, versionByPost = new Map(
	      stored.versions.map((version) => [version.postNumber, version])
	    );
	    let change, restoredRelations;
	    try {
	      if (this.topology.revision === revisionBeforeLoad && this.topology.snapshot().relations.length === 0)
	        change = this.topology.replace(stored.tree), restoredRelations = stored.tree.relations;
	      else {
	        const missingRelations = stored.tree.relations.filter(
	          (relation) => {
	            if (this.topology.has(relation.postNumber)) return !1;
	            const removed = this.#removedVersions.get(relation.postNumber), version = versionByPost.get(relation.postNumber);
	            return !removed || !version || (0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, version);
	          }
	        );
	        change = this.topology.commit(missingRelations), restoredRelations = missingRelations;
	      }
	    } catch (error) {
	      return this.#onPersistenceError(error), null;
	    }
	    for (const relation of restoredRelations) {
	      const version = versionByPost.get(relation.postNumber);
	      version && this.#versions.set(relation.postNumber, version);
	    }
	    const removalChanged = /* @__PURE__ */ new Set(), removalDetached = /* @__PURE__ */ new Set();
	    for (const removedVersion of stored.removedVersions ?? []) {
	      const current = this.#versions.get(removedVersion.postNumber), currentRemoval = this.#removedVersions.get(removedVersion.postNumber);
	      if (current && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(current, removedVersion) || currentRemoval && !(0, import_ingest_version.shouldReplaceDiscourseVersion)(currentRemoval, removedVersion))
	        continue;
	      const directChildren = this.topology.childrenOf(removedVersion.postNumber), removalChange = this.topology.remove(removedVersion.postNumber);
	      for (const postNumber of removalChange.changedPostNumbers)
	        removalChanged.add(postNumber);
	      for (const postNumber of removalChange.detachedPostNumbers)
	        removalDetached.add(postNumber);
	      this.#versions.delete(removedVersion.postNumber), this.#removedVersions.set(removedVersion.postNumber, removedVersion);
	      for (const childPostNumber of directChildren)
	        this.#versions.set(childPostNumber, Object.freeze({
	          postNumber: childPostNumber,
	          observedAt: removedVersion.observedAt,
	          source: removedVersion.source
	        }));
	    }
	    removalChanged.size && (change = Object.freeze({
	      revision: this.topology.revision,
	      changedPostNumbers: Object.freeze(
	        [.../* @__PURE__ */ new Set([
	          ...change.changedPostNumbers,
	          ...removalChanged
	        ])].sort((left, right) => left - right)
	      ),
	      detachedPostNumbers: Object.freeze(
	        [.../* @__PURE__ */ new Set([
	          ...change.detachedPostNumbers,
	          ...removalDetached
	        ])].sort((left, right) => left - right)
	      )
	    }));
	    const blockedStoredCount = stored.tree.relations.filter((relation) => {
	      const removed = this.#removedVersions.get(relation.postNumber), version = versionByPost.get(relation.postNumber);
	      return !!removed && !!version && !(0, import_ingest_version.shouldReplaceDiscourseRemoval)(removed, version);
	    }).length;
	    this.#expectedPostCount = Math.max(
	      this.#expectedPostCount,
	      Math.max(0, stored.expectedPostCount - blockedStoredCount)
	    );
	    const event = this.#event("cache-snapshot", change);
	    return this.changes.emit(event), (change.changedPostNumbers.length || this.#expectedPostCount !== previousExpectedPostCount) && this.#queuePersistence(), event;
	  }
	  snapshot(now = this.#now()) {
	    return Object.freeze({
	      schemaVersion: 2,
	      topicId: this.topicId,
	      savedAt: now,
	      expectedPostCount: this.#expectedPostCount,
	      tree: this.topology.snapshot(),
	      versions: Object.freeze(
	        [...this.#versions.values()].sort((left, right) => left.postNumber - right.postNumber)
	      ),
	      removedVersions: Object.freeze(
	        [...this.#removedVersions.values()].sort((left, right) => left.postNumber - right.postNumber)
	      )
	    });
	  }
	  async flush() {
	    for (; this.#pendingSnapshot || this.#persisting; )
	      this.#persisting || this.#startPersistence(), await this.#persisting;
	    if (this.#lastPersistenceError !== null) {
	      const error = this.#lastPersistenceError;
	      throw this.#lastPersistenceError = null, error;
	    }
	  }
	  #event(source, change) {
	    return Object.freeze({
	      topicId: this.topicId,
	      source,
	      change,
	      coverage: this.coverage()
	    });
	  }
	  #queuePersistence() {
	    this.#pendingSnapshot = this.snapshot(), this.#persisting || this.#startPersistence();
	  }
	  #startPersistence() {
	    this.#persisting = Promise.resolve().then(async () => {
	      for (; this.#pendingSnapshot; ) {
	        const snapshot = this.#pendingSnapshot;
	        this.#pendingSnapshot = null;
	        try {
	          await this.#store.save(this.topicId, snapshot), this.#lastPersistenceError = null;
	        } catch (error) {
	          this.#lastPersistenceError = error, this.#onPersistenceError(error);
	        }
	      }
	    }).finally(() => {
	      this.#persisting = null, this.#pendingSnapshot && this.#startPersistence();
	    });
	  }
	}
}, "23a6e27565f0b410969e9712da2c67fea0bfd06ccbdc7b01940a2aa4ad5dffb0");

/* Source: lite/src/dom/reply-tree.ts */
runtime.register("src/dom/reply-tree.js", function(module, exports, require) {
	var reply_tree_exports = {};
	__export(reply_tree_exports, {
	  ReplyTreeTopology: () => ReplyTreeTopology
	});
	module.exports = __toCommonJS(reply_tree_exports);
	var import_identifiers = require("../discourse/identifiers.js");
	function assertPostNumber(value, field) {
	  try {
	    (0, import_identifiers.discoursePostNumber)(value);
	  } catch {
	    throw new RangeError(`${field} 必须是正安全整数`);
	  }
	}
	function sorted(values) {
	  return [...values].sort((left, right) => left - right);
	}
	class ReplyTreeTopology {
	  #revision = 0;
	  #parentByPost = /* @__PURE__ */ new Map();
	  #childrenByParent = /* @__PURE__ */ new Map();
	  #subtreePostCountByPost = /* @__PURE__ */ new Map();
	  #snapshotCache = null;
	  get revision() {
	    return this.#revision;
	  }
	  parentOf(postNumber) {
	    return assertPostNumber(postNumber, "postNumber"), this.#parentByPost.get(postNumber);
	  }
	  childrenOf(parentPostNumber) {
	    return assertPostNumber(parentPostNumber, "parentPostNumber"), sorted(this.#childrenByParent.get(parentPostNumber) ?? []);
	  }
	  roots() {
	    return Object.freeze(
	      sorted(
	        [...this.#parentByPost].filter(([, parentPostNumber]) => parentPostNumber === null).map(([postNumber]) => postNumber)
	      )
	    );
	  }
	  rootBranches() {
	    return Object.freeze(
	      this.roots().map((postNumber) => Object.freeze({
	        postNumber,
	        subtreePostCount: this.#subtreePostCountByPost.get(postNumber) ?? 1
	      }))
	    );
	  }
	  clone() {
	    const clone = new ReplyTreeTopology();
	    return clone.#revision = this.#revision, clone.#parentByPost = new Map(this.#parentByPost), clone.#childrenByParent = new Map(
	      [...this.#childrenByParent].map(([postNumber, children]) => [
	        postNumber,
	        new Set(children)
	      ])
	    ), clone.#subtreePostCountByPost = new Map(
	      this.#subtreePostCountByPost
	    ), clone;
	  }
	  subtreePostCountOf(postNumber) {
	    return assertPostNumber(postNumber, "postNumber"), this.#subtreePostCountByPost.get(postNumber);
	  }
	  has(postNumber) {
	    return assertPostNumber(postNumber, "postNumber"), this.#parentByPost.has(postNumber);
	  }
	  depthOf(postNumber) {
	    if (assertPostNumber(postNumber, "postNumber"), !this.#parentByPost.has(postNumber)) return;
	    let depth = 0, current = this.#parentByPost.get(postNumber);
	    for (; current !== null; ) {
	      if (current === void 0 || !this.#parentByPost.has(current)) return;
	      depth += 1, current = this.#parentByPost.get(current);
	    }
	    return depth;
	  }
	  rootOf(postNumber) {
	    if (assertPostNumber(postNumber, "postNumber"), !this.#parentByPost.has(postNumber)) return;
	    let current = postNumber, parent = this.#parentByPost.get(current);
	    for (; parent !== null; ) {
	      if (parent === void 0 || !this.#parentByPost.has(parent)) return;
	      current = parent, parent = this.#parentByPost.get(current);
	    }
	    return current;
	  }
	  commit(relations) {
	    if (!relations.length)
	      return Object.freeze({
	        revision: this.#revision,
	        changedPostNumbers: Object.freeze([]),
	        detachedPostNumbers: Object.freeze([])
	      });
	    const nextParents = new Map(this.#parentByPost), touched = /* @__PURE__ */ new Set();
	    for (const relation of relations) {
	      if (assertPostNumber(relation.postNumber, "postNumber"), relation.parentPostNumber !== null && (assertPostNumber(relation.parentPostNumber, "parentPostNumber"), relation.parentPostNumber === relation.postNumber))
	        throw new Error(`楼层 #${relation.postNumber} 不能回复自身`);
	      nextParents.set(relation.postNumber, relation.parentPostNumber), touched.add(relation.postNumber);
	    }
	    this.#assertAcyclic(nextParents, touched);
	    const changed = /* @__PURE__ */ new Set(), detached = /* @__PURE__ */ new Set();
	    for (const postNumber of touched) {
	      const previousParent = this.#parentByPost.get(postNumber), nextParent = nextParents.get(postNumber);
	      previousParent === nextParent && this.#parentByPost.has(postNumber) || (changed.add(postNumber), previousParent != null && detached.add(postNumber));
	    }
	    return changed.size ? (this.#parentByPost = nextParents, this.#childrenByParent = this.#buildChildren(nextParents), this.#subtreePostCountByPost = this.#buildSubtreePostCounts(
	      nextParents,
	      this.#childrenByParent
	    ), this.#revision += 1, this.#snapshotCache = null, Object.freeze({
	      revision: this.#revision,
	      changedPostNumbers: Object.freeze(sorted(changed)),
	      detachedPostNumbers: Object.freeze(sorted(detached))
	    })) : Object.freeze({
	      revision: this.#revision,
	      changedPostNumbers: Object.freeze([]),
	      detachedPostNumbers: Object.freeze([])
	    });
	  }
	  /**
	   * 删除一个关系,并把直属子楼层提升到被删楼层原父级。
	   *
	   * 这避免子孙因父 DOM 消失而变成无根悬挂节点;返回的 changed/detached 同时包含被删
	   * 楼层与需要重新挂载的直属子楼层。
	   */
	  remove(postNumber) {
	    if (assertPostNumber(postNumber, "postNumber"), !this.#parentByPost.has(postNumber))
	      return Object.freeze({
	        revision: this.#revision,
	        changedPostNumbers: Object.freeze([]),
	        detachedPostNumbers: Object.freeze([])
	      });
	    const previousParent = this.#parentByPost.get(postNumber) ?? null, directChildren = this.childrenOf(postNumber), nextParents = new Map(this.#parentByPost);
	    nextParents.delete(postNumber);
	    for (const childPostNumber of directChildren)
	      nextParents.set(childPostNumber, previousParent);
	    return this.#assertAcyclic(nextParents, new Set(directChildren)), this.#parentByPost = nextParents, this.#childrenByParent = this.#buildChildren(nextParents), this.#subtreePostCountByPost = this.#buildSubtreePostCounts(
	      nextParents,
	      this.#childrenByParent
	    ), this.#revision += 1, this.#snapshotCache = null, Object.freeze({
	      revision: this.#revision,
	      changedPostNumbers: Object.freeze(sorted([postNumber, ...directChildren])),
	      detachedPostNumbers: Object.freeze(sorted([postNumber, ...directChildren]))
	    });
	  }
	  replace(snapshot) {
	    if (!Number.isSafeInteger(snapshot.revision) || snapshot.revision < 0)
	      throw new RangeError("回复树快照 revision 必须是非负安全整数");
	    const nextParents = /* @__PURE__ */ new Map();
	    for (const relation of snapshot.relations) {
	      if (assertPostNumber(relation.postNumber, "postNumber"), nextParents.has(relation.postNumber))
	        throw new Error(`快照重复定义楼层 #${relation.postNumber}`);
	      relation.parentPostNumber !== null && assertPostNumber(relation.parentPostNumber, "parentPostNumber"), nextParents.set(relation.postNumber, relation.parentPostNumber);
	    }
	    this.#assertAcyclic(nextParents, new Set(nextParents.keys()));
	    const changed = /* @__PURE__ */ new Set(), detached = /* @__PURE__ */ new Set();
	    for (const postNumber of /* @__PURE__ */ new Set([...this.#parentByPost.keys(), ...nextParents.keys()])) {
	      const previousParent = this.#parentByPost.get(postNumber), nextParent = nextParents.get(postNumber);
	      previousParent === nextParent && this.#parentByPost.has(postNumber) === nextParents.has(postNumber) || (changed.add(postNumber), nextParents.has(postNumber) || detached.add(postNumber));
	    }
	    return this.#parentByPost = nextParents, this.#childrenByParent = this.#buildChildren(nextParents), this.#subtreePostCountByPost = this.#buildSubtreePostCounts(
	      nextParents,
	      this.#childrenByParent
	    ), this.#revision = Math.max(this.#revision + 1, snapshot.revision), this.#snapshotCache = null, Object.freeze({
	      revision: this.#revision,
	      changedPostNumbers: Object.freeze(sorted(changed)),
	      detachedPostNumbers: Object.freeze(sorted(detached))
	    });
	  }
	  snapshot() {
	    if (this.#snapshotCache) return this.#snapshotCache;
	    const relations = sorted(this.#parentByPost.keys()).map(
	      (postNumber) => Object.freeze({
	        postNumber,
	        parentPostNumber: this.#parentByPost.get(postNumber) ?? null
	      })
	    );
	    return this.#snapshotCache = Object.freeze({
	      revision: this.#revision,
	      relations: Object.freeze(relations)
	    }), this.#snapshotCache;
	  }
	  #buildChildren(parents) {
	    const children = /* @__PURE__ */ new Map();
	    for (const [postNumber, parentPostNumber] of parents) {
	      if (parentPostNumber === null) continue;
	      const siblings = children.get(parentPostNumber) ?? /* @__PURE__ */ new Set();
	      siblings.add(postNumber), children.set(parentPostNumber, siblings);
	    }
	    return children;
	  }
	  #buildSubtreePostCounts(parents, children) {
	    const counts = /* @__PURE__ */ new Map(), remainingChildren = /* @__PURE__ */ new Map(), queue = [];
	    for (const postNumber of parents.keys()) {
	      const count = children.get(postNumber)?.size ?? 0;
	      remainingChildren.set(postNumber, count), count === 0 && queue.push(postNumber);
	    }
	    for (; queue.length; ) {
	      const postNumber = queue.pop(), subtreePostCount = counts.get(postNumber) ?? 1;
	      counts.set(postNumber, subtreePostCount);
	      const parentPostNumber = parents.get(postNumber);
	      if (parentPostNumber == null || !parents.has(parentPostNumber)) continue;
	      counts.set(
	        parentPostNumber,
	        (counts.get(parentPostNumber) ?? 1) + subtreePostCount
	      );
	      const remaining = (remainingChildren.get(parentPostNumber) ?? 1) - 1;
	      remainingChildren.set(parentPostNumber, remaining), remaining === 0 && queue.push(parentPostNumber);
	    }
	    return counts;
	  }
	  #assertAcyclic(parents, starts) {
	    for (const start of starts) {
	      const path = /* @__PURE__ */ new Set();
	      let current = start;
	      for (; current != null; ) {
	        if (path.has(current))
	          throw new Error(`楼层关系存在环,经过 #${current}`);
	        path.add(current), current = parents.get(current);
	      }
	    }
	  }
	}
}, "1895338830c0da6c624fff9b04da89cf664e9747700d1070194208cc79ac06b8");

/* Source: lite/src/dom/required-element.ts */
runtime.register("src/dom/required-element.js", function(module, exports, require) {
	var required_element_exports = {};
	__export(required_element_exports, {
	  requiredElementQuery: () => requiredElementQuery
	});
	module.exports = __toCommonJS(required_element_exports);
	function requiredElementQuery(owner) {
	  return function(root, selector) {
	    const node = root.querySelector(selector);
	    if (!node) throw new Error(`${owner}缺少 ${selector}`);
	    return node;
	  };
	}
}, "7f4374644d667871f13c79db93a71b41820a21560f1fddeeb6c081f26c783f8e");

/* Source: lite/src/kernel/lifecycle.ts */
runtime.register("src/kernel/lifecycle.js", function(module, exports, require) {
	var lifecycle_exports = {};
	__export(lifecycle_exports, {
	  LifecycleScope: () => LifecycleScope
	});
	module.exports = __toCommonJS(lifecycle_exports);
	class LifecycleScope {
	  #cleanups = [];
	  #destroyed = !1;
	  /**
	   * 为业务 owner 创建唯一作用域:有父级时建立受控 child,否则建立独立 root。
	   */
	  static ownedBy(parent) {
	    return parent ? parent.child() : new LifecycleScope();
	  }
	  get destroyed() {
	    return this.#destroyed;
	  }
	  add(cleanup) {
	    let active = !0;
	    const runOnce = () => {
	      if (!active) return;
	      active = !1;
	      const index = this.#cleanups.indexOf(runOnce);
	      index >= 0 && this.#cleanups.splice(index, 1), cleanup();
	    };
	    return this.#destroyed ? (runOnce(), runOnce) : (this.#cleanups.push(runOnce), runOnce);
	  }
	  child() {
	    const child = new LifecycleScope(), detach = this.add(() => child.destroy());
	    return child.add(detach), child;
	  }
	  /** 创建由本 scope 独占、可选转发上游取消原因的 AbortController。 */
	  abortController(destroyReason, upstream) {
	    const controller = new AbortController(), abort = (reason) => {
	      controller.signal.aborted || controller.abort(reason);
	    }, forwardAbort = () => abort(upstream?.reason);
	    return upstream?.aborted ? forwardAbort() : upstream?.addEventListener("abort", forwardAbort, { once: !0 }), this.add(() => {
	      upstream?.removeEventListener("abort", forwardAbort), abort(destroyReason);
	    }), controller;
	  }
	  listen(target, type, listener, options) {
	    return target.addEventListener(type, listener, options), this.add(() => target.removeEventListener(type, listener, options));
	  }
	  observe(observer, target, options) {
	    return target && "observe" in observer && typeof observer.observe == "function" && observer.observe(target, options), this.add(() => observer.disconnect());
	  }
	  timer(timerId, clear = clearTimeout) {
	    return this.add(() => clear(timerId));
	  }
	  destroy() {
	    if (this.#destroyed) return;
	    this.#destroyed = !0;
	    const errors = [], cleanups = this.#cleanups.splice(0);
	    for (let index = cleanups.length - 1; index >= 0; index -= 1)
	      try {
	        cleanups[index]();
	      } catch (error) {
	        errors.push(error);
	      }
	    if (errors.length) throw new AggregateError(errors, "LifecycleScope cleanup failed");
	  }
	}
}, "8b8b2ccae7437d794799c0a26fd4258ce04e126181396d2ee18e7dea48d2cd62");

/* Source: lite/src/kernel/repeat-action-gate.ts */
runtime.register("src/kernel/repeat-action-gate.js", function(module, exports, require) {
	var repeat_action_gate_exports = {};
	__export(repeat_action_gate_exports, {
	  RepeatActionGate: () => RepeatActionGate
	});
	module.exports = __toCommonJS(repeat_action_gate_exports);
	class RepeatActionGate {
	  #deadlines = /* @__PURE__ */ new Map();
	  #windowMs;
	  #now;
	  constructor(options = {}) {
	    const windowMs = Number(options.windowMs ?? 1500);
	    if (!Number.isFinite(windowMs) || windowMs <= 0)
	      throw new RangeError("重复动作确认期限必须为正数");
	    this.#windowMs = windowMs, this.#now = options.now ?? Date.now;
	  }
	  confirm(rawKey) {
	    const key = String(rawKey).trim();
	    if (!key) throw new TypeError("重复动作确认 key 不能为空");
	    const now = this.#now(), confirmed = (this.#deadlines.get(key) ?? 0) >= now;
	    return this.#deadlines.clear(), confirmed || this.#deadlines.set(key, now + this.#windowMs), confirmed;
	  }
	  clear() {
	    this.#deadlines.clear();
	  }
	}
}, "405ed67abebe6114e3bf920269d0e06acd24f7aa7089e329bd65a7eefffa467a");

/* Source: lite/src/kernel/signal.ts */
runtime.register("src/kernel/signal.js", function(module, exports, require) {
	var signal_exports = {};
	__export(signal_exports, {
	  Signal: () => Signal
	});
	module.exports = __toCommonJS(signal_exports);
	class Signal {
	  #listeners = /* @__PURE__ */ new Set();
	  get size() {
	    return this.#listeners.size;
	  }
	  subscribe(listener, scope) {
	    this.#listeners.add(listener);
	    const unsubscribe = () => {
	      this.#listeners.delete(listener);
	    };
	    return scope && scope.add(unsubscribe), unsubscribe;
	  }
	  emit(value) {
	    const errors = [];
	    for (const listener of [...this.#listeners])
	      try {
	        listener(value);
	      } catch (error) {
	        errors.push(error);
	      }
	    return Object.freeze(errors);
	  }
	  clear() {
	    this.#listeners.clear();
	  }
	}
}, "9e0cff36c2177073a516fade2a21fee65bb7ac09607649e2c719742a2c129ae4");

/* Source: lite/src/kernel/value-record.ts */
runtime.register("src/kernel/value-record.js", function(module, exports, require) {
	var value_record_exports = {};
	__export(value_record_exports, {
	  objectRecord: () => objectRecord,
	  valueRecord: () => valueRecord
	});
	module.exports = __toCommonJS(value_record_exports);
	function valueRecord(value) {
	  return value !== null && (typeof value == "object" || typeof value == "function") ? value : null;
	}
	function objectRecord(value) {
	  return value !== null && typeof value == "object" ? value : null;
	}
}, "e86289a6fd7f06e9a422bff290f94ee01c33fd53a37836fa46491175d6dd1356");

/* Source: lite/src/layout/branch-overlay.ts */
runtime.register("src/layout/branch-overlay.js", function(module, exports, require) {
	var branch_overlay_exports = {};
	__export(branch_overlay_exports, {
	  ReaderBranchOverlayController: () => ReaderBranchOverlayController,
	  deriveBranchGeometry: () => deriveBranchGeometry
	});
	module.exports = __toCommonJS(branch_overlay_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_reader_icon = require("../components/reader-icon.js");
	const CHILD_AVATAR_GAP = 5, PARENT_AVATAR_DROP_GAP = CHILD_AVATAR_GAP, MAX_CONTINUOUS_BRANCH_SPAN_PX = 2048, BRANCH_PAINT_PADDING_PX = 8;
	function finite(value, field) {
	  if (!Number.isFinite(value)) throw new RangeError(`${field} 必须是有限数值`);
	  return value;
	}
	function deriveBranchGeometry(input) {
	  const parentAxisX = finite(input.parentAxisX, "parentAxisX"), parentStartY = finite(input.parentStartY, "parentStartY"), childAxisX = finite(input.childAxisX, "childAxisX"), childCenterY = finite(input.childCenterY, "childCenterY"), requestedRadius = Math.max(0, finite(input.cornerRadius, "cornerRadius")), horizontalDistance = Math.abs(childAxisX - parentAxisX), verticalDistance = Math.abs(childCenterY - parentStartY), radius = Math.min(requestedRadius, horizontalDistance, verticalDistance), turnY = childCenterY - Math.sign(childCenterY - parentStartY || 1) * radius, turnX = parentAxisX + Math.sign(childAxisX - parentAxisX || 1) * radius, path = [
	    `M ${parentAxisX} ${parentStartY}`,
	    `L ${parentAxisX} ${turnY}`,
	    `Q ${parentAxisX} ${childCenterY} ${turnX} ${childCenterY}`,
	    `L ${childAxisX} ${childCenterY}`
	  ].join(" ");
	  return Object.freeze({
	    path
	  });
	}
	function deriveSharedBranchGeometry(input) {
	  const parentAxisX = finite(input.parentAxisX, "parentAxisX"), parentStartY = finite(input.parentStartY, "parentStartY"), requestedRadius = Math.max(
	    0,
	    finite(input.cornerRadius, "cornerRadius")
	  ), branches = input.children.map((child) => {
	    const childAxisX = finite(child.childAxisX, "childAxisX"), childCenterY = finite(child.childCenterY, "childCenterY"), horizontalDirection = Math.sign(childAxisX - parentAxisX || 1), verticalDirection = Math.sign(childCenterY - parentStartY || 1), radius = Math.min(
	      requestedRadius,
	      Math.abs(childAxisX - parentAxisX),
	      Math.abs(childCenterY - parentStartY)
	    );
	    return Object.freeze({
	      childAxisX,
	      childCenterY,
	      turnX: parentAxisX + horizontalDirection * radius,
	      turnY: childCenterY - verticalDirection * radius
	    });
	  });
	  if (!branches.length) return Object.freeze({ path: "" });
	  const railEnd = branches.reduce(
	    (farthest, branch) => Math.abs(branch.turnY - parentStartY) > Math.abs(farthest - parentStartY) ? branch.turnY : farthest,
	    branches[0].turnY
	  ), parts = [
	    `M ${parentAxisX} ${parentStartY}`,
	    `L ${parentAxisX} ${railEnd}`
	  ];
	  for (const branch of branches)
	    parts.push(
	      `M ${parentAxisX} ${branch.turnY}`,
	      `Q ${parentAxisX} ${branch.childCenterY} ${branch.turnX} ${branch.childCenterY}`,
	      `L ${branch.childAxisX} ${branch.childCenterY}`
	    );
	  return Object.freeze({ path: parts.join(" ") });
	}
	function branchScrollContainer(element) {
	  const view = element.ownerDocument.defaultView;
	  for (let candidate = element.parentElement; candidate; candidate = candidate.parentElement) {
	    const overflowY = view?.getComputedStyle(candidate).overflowY ?? "";
	    if (/^(auto|scroll|overlay)$/.test(overflowY) && candidate.scrollHeight > candidate.clientHeight) return candidate;
	  }
	  return null;
	}
	function pointerAnchor(event, root, wasCollapsed) {
	  const pointer = event;
	  return wasCollapsed || pointer.detail <= 0 || !Number.isFinite(pointer.clientY) ? null : Object.freeze({
	    clientY: pointer.clientY,
	    scrollContainer: branchScrollContainer(root)
	  });
	}
	function restorePointerAnchor(anchor, toggle) {
	  if (!anchor || !toggle?.isConnected) return;
	  const rect = toggle.getBoundingClientRect(), delta = rect.top + rect.height / 2 - anchor.clientY;
	  if (!(!Number.isFinite(delta) || Math.abs(delta) < 0.5)) {
	    if (anchor.scrollContainer?.isConnected) {
	      anchor.scrollContainer.scrollTop += delta;
	      return;
	    }
	    toggle.ownerDocument.defaultView?.scrollBy({
	      top: delta,
	      behavior: "auto"
	    });
	  }
	}
	function defaultReadAvatar(slots) {
	  const collapsedToggle = slots.root.querySelector(
	    ':scope > .ldp-reader-branch-toggle[aria-expanded="false"]'
	  );
	  return collapsedToggle || slots.header.querySelector(
	    "[data-reader-avatar],.ldp-avatar-link,.ldp-avatar,img.avatar"
	  );
	}
	function finiteRect(rect) {
	  return Number.isFinite(rect.left) && Number.isFinite(rect.right) && Number.isFinite(rect.top) && Number.isFinite(rect.bottom) && Number.isFinite(rect.width) && Number.isFinite(rect.height);
	}
	function visiblePath(slots) {
	  return slots.branchOverlay.querySelector(
	    ".ldp-branch-visible-path"
	  );
	}
	function hitPath(slots) {
	  return slots.branchOverlay.querySelector(
	    ".ldp-branch-hit-path"
	  );
	}
	class ReaderBranchOverlayController {
	  scope;
	  #domOwner;
	  #renderMode;
	  #allowLongBranchSpans;
	  #readAvatar;
	  #onLayoutChange;
	  #onToggleBranch;
	  #readCollapsed;
	  #owned = /* @__PURE__ */ new Map();
	  #collapsed = /* @__PURE__ */ new Set();
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#domOwner = options.domOwner, this.#renderMode = options.renderMode ?? "svg", this.#allowLongBranchSpans = options.allowLongBranchSpans ?? !1, this.#readAvatar = options.readAvatar ?? defaultReadAvatar, this.#onLayoutChange = options.onLayoutChange ?? (() => {
	    }), this.#onToggleBranch = options.onToggleBranch ?? null, this.#readCollapsed = options.readCollapsed ?? null, this.#renderMode === "segmented-css" && (this.#domOwner.rootList.classList.add("ldp-segmented-branches"), this.scope.add(() => {
	      this.#domOwner.rootList.classList.remove("ldp-segmented-branches");
	    })), this.scope.listen(this.#domOwner.rootList, "click", (event) => {
	      const target = event.target;
	      if (!target || target.nodeType !== 1) return;
	      const element = target, toggle = element.closest(
	        "[data-reader-branch-toggle]"
	      );
	      if (toggle?.disabled) return;
	      const hit = element.closest(
	        ".ldp-branch-hit-path"
	      ), root = (toggle ?? hit)?.closest(
	        ".ldp-post[data-post-number]"
	      );
	      if (!root) return;
	      const postNumber = Number(
	        toggle?.dataset.readerBranchToggle ?? root.dataset.postNumber
	      );
	      if (!Number.isSafeInteger(postNumber) || postNumber <= 0) return;
	      const wasCollapsed = this.#isCollapsed(postNumber), anchor = !wasCollapsed && (options.preserveCollapseAnchor?.(root) ?? !1) ? null : pointerAnchor(event, root, wasCollapsed);
	      event.preventDefault(), event.stopPropagation(), this.toggle(postNumber), restorePointerAnchor(
	        anchor,
	        this.#owned.get(postNumber)?.toggle ?? null
	      );
	    });
	    const resizeObserver = options.createResizeObserver?.(() => {
	      if (!this.scope.destroyed) {
	        if (options.onObservedResize) {
	          options.onObservedResize();
	          return;
	        }
	        this.paint(), this.#onLayoutChange();
	      }
	    }) ?? null;
	    resizeObserver && (resizeObserver.observe(this.#domOwner.rootList), this.scope.add(() => resizeObserver.disconnect())), this.scope.add(() => this.#restore());
	  }
	  paint() {
	    if (this.scope.destroyed)
	      return Object.freeze({
	        paintedBranches: 0
	      });
	    if (this.#sweep(), this.#renderMode === "segmented-css")
	      return this.#paintSegmentedBranches();
	    const { measurements, invalidViews } = this.#measure();
	    let paintedBranches = 0;
	    for (const view of invalidViews) this.#clear(view);
	    for (const measurement of measurements) {
	      const { view } = measurement, slots = view.slots, hasChildren = measurement.hasChildren;
	      if (this.#setClass(slots.root, "ldp-has-child-branches", hasChildren), !hasChildren) {
	        this.#clear(view);
	        continue;
	      }
	      const owned = this.#own(view), projectedOverlayLeft = measurement.overlayRect.left, projectedOverlayWidth = Math.max(1, measurement.overlayRect.width);
	      owned.toggle.hidden === hasChildren && (owned.toggle.hidden = !hasChildren);
	      const parentAxisX = measurement.avatarRect.left + measurement.avatarRect.width / 2 - projectedOverlayLeft, toggleLeft = `${measurement.avatarRect.left + measurement.avatarRect.width / 2 - measurement.rootRect.left}px`;
	      owned.toggle.style.left !== toggleLeft && (owned.toggle.style.left = toggleLeft);
	      const toggleTop = `${measurement.toggleCenterY}px`;
	      if (owned.toggle.style.top !== toggleTop && (owned.toggle.style.top = toggleTop), this.#syncCollapsed(view, owned.toggle), this.#isCollapsed(view.postNumber)) {
	        this.#clearPaths(slots);
	        continue;
	      }
	      const parentStartY = measurement.avatarRect.bottom - measurement.overlayRect.top + PARENT_AVATAR_DROP_GAP;
	      if (!measurement.childAvatarRects.length) {
	        this.#clearPaths(slots);
	        continue;
	      }
	      const childGeometry = measurement.childAvatarRects.map(
	        (childAvatarRect) => Object.freeze({
	          childAxisX: childAvatarRect.left - measurement.overlayRect.left - CHILD_AVATAR_GAP,
	          childCenterY: childAvatarRect.top + childAvatarRect.height / 2 - measurement.overlayRect.top
	        })
	      ), geometry = deriveSharedBranchGeometry({
	        parentAxisX,
	        parentStartY,
	        children: childGeometry,
	        cornerRadius: measurement.cornerRadius
	      });
	      paintedBranches += measurement.childAvatarRects.length;
	      const path = geometry.path, visible = visiblePath(slots), hit = hitPath(slots);
	      slots.branchOverlay.hasAttribute("hidden") && slots.branchOverlay.removeAttribute("hidden"), this.#setAttribute(visible, "d", path), this.#setAttribute(hit, "d", path), this.#setStyle(
	        visible,
	        "stroke-width",
	        "var(--ldp-reply-line-width,1px)"
	      ), this.#setStyle(
	        hit,
	        "stroke-width",
	        "var(--ldp-reply-line-hit-width,8px)"
	      );
	      const paintBlockSize = Math.max(
	        1,
	        Math.ceil(
	          Math.max(
	            parentStartY,
	            ...childGeometry.map((child) => child.childCenterY)
	          ) + measurement.cornerRadius + BRANCH_PAINT_PADDING_PX
	        )
	      );
	      this.#setStyle(
	        slots.branchOverlay,
	        "height",
	        `${paintBlockSize}px`
	      ), this.#setAttribute(
	        slots.branchOverlay,
	        "viewBox",
	        `0 0 ${projectedOverlayWidth} ${paintBlockSize}`
	      );
	    }
	    return Object.freeze({ paintedBranches });
	  }
	  #paintSegmentedBranches() {
	    const views = this.#domOwner.views(), childrenByParent = /* @__PURE__ */ new Map();
	    for (const child of views) {
	      if (!child.slots.root.isConnected) continue;
	      const parentPostNumber = this.#domOwner.topology.parentOf(
	        child.postNumber
	      );
	      if (parentPostNumber == null) continue;
	      const children = childrenByParent.get(parentPostNumber) ?? [];
	      children.push(child), childrenByParent.set(parentPostNumber, children);
	    }
	    const lastChildByParent = /* @__PURE__ */ new Map();
	    for (const [parentPostNumber, mountedChildren] of childrenByParent) {
	      const lastChildPostNumber = this.#domOwner.topology.childrenOf?.(parentPostNumber)?.at(-1) ?? [...mountedChildren].sort((left, right) => left.postNumber - right.postNumber).at(-1)?.postNumber;
	      lastChildPostNumber !== void 0 && lastChildByParent.set(parentPostNumber, lastChildPostNumber);
	    }
	    const segmentedRootToggleTop = this.#measureSegmentedRootToggleTop(
	      views,
	      childrenByParent
	    );
	    let paintedBranches = 0;
	    for (const view of views) {
	      const parentPostNumber = this.#domOwner.topology.parentOf(
	        view.postNumber
	      );
	      this.#setClass(
	        view.slots.root,
	        "ldp-segmented-branch-last",
	        view.slots.root.isConnected && parentPostNumber !== null && parentPostNumber !== void 0 && lastChildByParent.get(parentPostNumber) === view.postNumber
	      );
	      const children = childrenByParent.get(view.postNumber) ?? [], hasChildren = view.slots.root.isConnected && children.length > 0;
	      if (this.#setClass(
	        view.slots.root,
	        "ldp-has-child-branches",
	        hasChildren
	      ), this.#clearPaths(view.slots), !hasChildren) {
	        this.#clear(view);
	        continue;
	      }
	      const owned = this.#own(view);
	      this.#syncSegmentedRailToggles(owned, children), owned.toggle.style.left && owned.toggle.style.removeProperty("left"), view.postNumber !== 1 && owned.toggle.style.top && owned.toggle.style.removeProperty("top"), view.postNumber === 1 && segmentedRootToggleTop !== null && this.#setStyle(
	        owned.toggle,
	        "top",
	        `${segmentedRootToggleTop}px`
	      ), owned.toggle.hidden && (owned.toggle.hidden = !1), this.#syncCollapsed(view, owned.toggle), !this.#isCollapsed(view.postNumber) && (paintedBranches += children.filter(
	        (child) => !child.slots.root.classList.contains("ldp-virtual-ancestor-shell")
	      ).length);
	    }
	    return Object.freeze({ paintedBranches });
	  }
	  #measureSegmentedRootToggleTop(views, childrenByParent) {
	    const rootView = views.find((view) => view.postNumber === 1);
	    if (!rootView?.slots.root.isConnected || this.#isCollapsed(rootView.postNumber)) return null;
	    const childByRoot = new Map(
	      (childrenByParent.get(rootView.postNumber) ?? []).map((child) => [
	        child.slots.root,
	        child
	      ])
	    );
	    let firstChild = null;
	    for (const element of rootView.slots.replyList.children) {
	      const child = childByRoot.get(element);
	      if (child?.slots.root.isConnected && !child.slots.root.classList.contains("ldp-virtual-ancestor-shell")) {
	        firstChild = child;
	        break;
	      }
	    }
	    if (!firstChild) return null;
	    const rootReplyTreeRect = rootView.slots.replyTree.getBoundingClientRect(), childRootRect = firstChild.slots.root.getBoundingClientRect(), childActionsRect = firstChild.slots.actions.getBoundingClientRect(), childReplyTreeRect = firstChild.slots.replyTree.getBoundingClientRect();
	    if (!finiteRect(rootReplyTreeRect) || !finiteRect(childRootRect) || !finiteRect(childActionsRect) || !finiteRect(childReplyTreeRect) || childActionsRect.height <= 0) return null;
	    const ordinaryToggleCenterY = childActionsRect.top + childActionsRect.height / 2, ordinaryTailDistance = childReplyTreeRect.top - ordinaryToggleCenterY;
	    if (!Number.isFinite(ordinaryTailDistance) || ordinaryTailDistance < 0)
	      return null;
	    const top = childRootRect.top - rootReplyTreeRect.top - ordinaryTailDistance;
	    return Number.isFinite(top) ? top : null;
	  }
	  toggle(postNumber) {
	    if (this.scope.destroyed) return;
	    const view = this.#domOwner.view(postNumber);
	    !view || !this.#hasChildren(postNumber) || (this.#onToggleBranch ? this.#onToggleBranch(postNumber) : this.#collapsed.has(postNumber) ? this.#collapsed.delete(postNumber) : this.#collapsed.add(postNumber), this.#syncCollapsed(view, this.#own(view).toggle), this.#onLayoutChange());
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  /**
	   * 释放当前树投影,但保留 controller 供同一 surface 下次重新挂载。
	   *
	   * 完整讨论关闭时不会再等待下一次 paint 才 sweep 已销毁的 PostView;主流切帖也可
	   * 用同一路径立即恢复接管前 margin/折叠/按钮状态并丢弃旧 View 引用。
	   */
	  releaseProjection() {
	    this.scope.destroyed || this.#restore();
	  }
	  #measure() {
	    const measurements = [], invalidViews = [], views = this.#domOwner.views(), childrenByParent = /* @__PURE__ */ new Map(), geometryByPost = /* @__PURE__ */ new Map(), avatarByPost = /* @__PURE__ */ new Map(), transientPostNumbers = /* @__PURE__ */ new Set(), invalidPostNumbers = /* @__PURE__ */ new Set(), styleGeometryByVariant = /* @__PURE__ */ new Map(), invalidate = (view) => {
	      invalidPostNumbers.add(view.postNumber), invalidViews.includes(view) || invalidViews.push(view);
	    };
	    for (const child of views) {
	      const parentPostNumber = this.#domOwner.topology.parentOf(
	        child.postNumber
	      );
	      if (parentPostNumber == null)
	        continue;
	      const children = childrenByParent.get(parentPostNumber) ?? [];
	      children.push(child), childrenByParent.set(parentPostNumber, children);
	    }
	    for (const view of views) {
	      const slots = view.slots;
	      if (!slots.root.isConnected) {
	        invalidate(view);
	        continue;
	      }
	      const avatar = this.#readAvatar(slots);
	      if (!avatar) {
	        invalidate(view);
	        continue;
	      }
	      const avatarRect = avatar.getBoundingClientRect(), rootRect = slots.root.getBoundingClientRect(), overlayRect = slots.replyTree.getBoundingClientRect();
	      if (!finiteRect(avatarRect) || !finiteRect(rootRect) || !finiteRect(overlayRect)) {
	        invalidate(view);
	        continue;
	      }
	      if (avatarRect.width > 0 && avatarRect.height > 0 ? avatarByPost.set(view.postNumber, avatarRect) : transientPostNumbers.add(view.postNumber), avatarRect.width <= 0 || avatarRect.height <= 0 || rootRect.width <= 0 || rootRect.height <= 0 || overlayRect.width <= 0 || overlayRect.height <= 0) {
	        transientPostNumbers.add(view.postNumber);
	        continue;
	      }
	      geometryByPost.set(view.postNumber, Object.freeze({
	        view,
	        avatarRect,
	        rootRect,
	        overlayRect
	      }));
	    }
	    for (const snapshot of geometryByPost.values()) {
	      const { view, avatarRect, rootRect, overlayRect } = snapshot, slots = view.slots, collapsed = this.#isCollapsed(view.postNumber), styleKey = this.#styleGeometryKey(slots.root);
	      let styleGeometry = styleGeometryByVariant.get(styleKey);
	      if (!styleGeometry) {
	        const rootStyle = slots.root.ownerDocument.defaultView?.getComputedStyle?.(slots.root);
	        styleGeometry = Object.freeze({
	          cornerRadius: this.#cornerRadius(rootStyle)
	        }), styleGeometryByVariant.set(styleKey, styleGeometry);
	      }
	      const childAvatarRects = [], actionsRect = slots.actions.getBoundingClientRect(), retainedToggleTop = Number.parseFloat(
	        this.#owned.get(view.postNumber)?.toggle.style.top ?? ""
	      );
	      let toggleCenterY = collapsed && Number.isFinite(retainedToggleTop) ? retainedToggleTop : finiteRect(actionsRect) && actionsRect.height > 0 ? actionsRect.top + actionsRect.height / 2 - rootRect.top : 0;
	      const children = [...childrenByParent.get(view.postNumber) ?? []].sort((left, right) => left.postNumber - right.postNumber);
	      if (!collapsed) {
	        const drawableChildren = children.filter(
	          (child) => child.slots.root.isConnected && !child.slots.root.classList.contains(
	            "ldp-virtual-ancestor-shell"
	          )
	        );
	        if (drawableChildren.filter(
	          (child) => !avatarByPost.has(child.postNumber)
	        ).some(
	          (child) => transientPostNumbers.has(child.postNumber) && !invalidPostNumbers.has(child.postNumber)
	        ))
	          continue;
	        for (const child of drawableChildren) {
	          const childAvatarRect = avatarByPost.get(child.postNumber);
	          childAvatarRect && (this.#allowLongBranchSpans || Math.abs(
	            childAvatarRect.top + childAvatarRect.height / 2 - (avatarRect.bottom + PARENT_AVATAR_DROP_GAP)
	          ) <= MAX_CONTINUOUS_BRANCH_SPAN_PX) && childAvatarRects.push(childAvatarRect);
	        }
	        childAvatarRects.sort((left, right) => left.top - right.top);
	      }
	      !collapsed && view.postNumber === 1 && childAvatarRects[0] && (toggleCenterY = childAvatarRects[0].top - rootRect.top - 10), measurements.push(Object.freeze({
	        view,
	        avatarRect,
	        rootRect,
	        overlayRect,
	        toggleCenterY,
	        hasChildren: children.length > 0,
	        childAvatarRects: Object.freeze(childAvatarRects),
	        cornerRadius: styleGeometry.cornerRadius
	      }));
	    }
	    return Object.freeze({
	      measurements: Object.freeze(measurements),
	      invalidViews: Object.freeze(invalidViews)
	    });
	  }
	  #own(view) {
	    const existing = this.#owned.get(view.postNumber);
	    if (existing?.view === view) return existing;
	    existing && this.#releaseOwned(existing);
	    const toggle = view.slots.root.ownerDocument.createElement("button");
	    toggle.type = "button", toggle.className = "ldp-collapse-replies ldp-reply-rail-control ldp-reader-branch-toggle show", toggle.dataset.readerBranchToggle = String(view.postNumber);
	    const count = view.slots.root.ownerDocument.createElement("span");
	    count.className = "ldp-collapsed-branch-count", count.hidden = !0, view.slots.replyControls.classList.add("ldp-branch-controls"), view.slots.root.insertBefore(toggle, view.slots.header), view.slots.header.after(count);
	    const trunkToggle = this.#renderMode === "segmented-css" ? view.slots.root.ownerDocument.createElement("button") : null;
	    trunkToggle && (trunkToggle.type = "button", trunkToggle.className = "ldp-reader-branch-trunk-toggle", trunkToggle.dataset.readerBranchToggle = String(view.postNumber), trunkToggle.tabIndex = -1, trunkToggle.setAttribute("aria-hidden", "true"), view.slots.body.append(trunkToggle));
	    const owned = Object.freeze({
	      view,
	      toggle,
	      count,
	      trunkToggle,
	      railToggles: /* @__PURE__ */ new Map()
	    });
	    return this.#owned.set(view.postNumber, owned), owned;
	  }
	  #syncSegmentedRailToggles(owned, children) {
	    const retained = /* @__PURE__ */ new Set();
	    for (const child of children) {
	      if (child.slots.root.classList.contains("ldp-virtual-ancestor-shell"))
	        continue;
	      retained.add(child.postNumber);
	      let railToggle = owned.railToggles.get(child.postNumber);
	      railToggle?.parentElement !== child.slots.root && (railToggle?.remove(), railToggle = child.slots.root.ownerDocument.createElement("button"), railToggle.type = "button", railToggle.className = "ldp-reader-branch-rail-toggle", railToggle.dataset.readerBranchToggle = String(
	        owned.view.postNumber
	      ), railToggle.tabIndex = -1, railToggle.setAttribute("aria-hidden", "true"), child.slots.root.prepend(railToggle), owned.railToggles.set(child.postNumber, railToggle));
	    }
	    for (const [postNumber, railToggle] of owned.railToggles)
	      retained.has(postNumber) || (railToggle.remove(), owned.railToggles.delete(postNumber));
	  }
	  #clearRailToggles(owned) {
	    for (const railToggle of owned.railToggles.values())
	      railToggle.remove();
	    owned.railToggles.clear();
	  }
	  #syncCollapsed(view, toggle) {
	    const collapsed = this.#isCollapsed(view.postNumber);
	    if (this.#renderMode === "segmented-css") {
	      const owner = collapsed ? view.slots.root : view.postNumber === 1 ? view.slots.replyTree : view.slots.actions.hidden ? view.slots.root : view.slots.actions;
	      toggle.parentElement !== owner && (owner === view.slots.root ? view.slots.root.insertBefore(toggle, view.slots.header) : owner === view.slots.replyTree ? view.slots.replyTree.prepend(toggle) : view.slots.actions.prepend(toggle));
	    }
	    this.#setClass(
	      view.slots.root,
	      "ldp-branch-parent-collapsed",
	      collapsed
	    ), view.slots.replyList.hidden !== collapsed && (view.slots.replyList.hidden = collapsed), collapsed && this.#clearPaths(view.slots);
	    const owned = this.#owned.get(view.postNumber), descendantCount = Math.max(
	      this.#subtreePostCount(view.postNumber) - 1,
	      1
	    );
	    owned && (owned.count.textContent = `(${descendantCount})`, owned.count.hidden = !collapsed, owned.trunkToggle && owned.trunkToggle.disabled !== collapsed && (owned.trunkToggle.disabled = collapsed));
	    for (const railToggle of owned?.railToggles.values() ?? [])
	      railToggle.disabled !== collapsed && (railToggle.disabled = collapsed);
	    const iconName = collapsed ? "plus" : "minus";
	    toggle.querySelector(":scope > .ldp-icon")?.classList.contains(`ldp-icon-${iconName}`) || toggle.replaceChildren((0, import_reader_icon.createReaderIcon)(
	      toggle.ownerDocument,
	      iconName
	    )), this.#setAttribute(toggle, "aria-expanded", String(!collapsed)), this.#setAttribute(
	      toggle,
	      "aria-label",
	      collapsed ? `展开 ${descendantCount} 条回复` : `收起 ${descendantCount} 条回复`
	    );
	  }
	  #subtreePostCount(parentPostNumber) {
	    const visited = /* @__PURE__ */ new Set(), pending = [parentPostNumber];
	    for (; pending.length; ) {
	      const postNumber = pending.pop();
	      if (visited.has(postNumber)) continue;
	      visited.add(postNumber);
	      const children = this.#domOwner.topology.childrenOf?.(postNumber) ?? this.#domOwner.views().filter(
	        (view) => this.#domOwner.topology.parentOf(view.postNumber) === postNumber
	      ).map((view) => view.postNumber);
	      pending.push(...children);
	    }
	    return Math.max(1, visited.size);
	  }
	  #isCollapsed(postNumber) {
	    return this.#readCollapsed?.(postNumber) ?? this.#collapsed.has(postNumber);
	  }
	  #setAttribute(element, name, value) {
	    element.getAttribute(name) !== value && element.setAttribute(name, value);
	  }
	  #setStyle(element, name, value) {
	    element.style.getPropertyValue(name) !== value && element.style.setProperty(name, value);
	  }
	  #setClass(element, name, enabled) {
	    element.classList.contains(name) !== enabled && element.classList.toggle(name, enabled);
	  }
	  #styleGeometryKey(root) {
	    return `${root.classList.contains("ldp-nested-preview") ? "nested" : "root"}|${root.getAttribute("style") ?? ""}`;
	  }
	  #cornerRadius(style) {
	    const value = style?.getPropertyValue("--ldp-reply-line-radius"), parsed = Number.parseFloat(value ?? "");
	    return Number.isFinite(parsed) ? Math.max(0, parsed) : 15;
	  }
	  #clear(view) {
	    this.#setClass(view.slots.root, "ldp-has-child-branches", !1), this.#setClass(view.slots.root, "ldp-branch-parent-collapsed", !1), view.slots.replyList.hidden && (view.slots.replyList.hidden = !1), this.#collapsed.delete(view.postNumber);
	    const owned = this.#owned.get(view.postNumber);
	    owned && (owned.toggle.hidden || (owned.toggle.hidden = !0), owned.count.hidden = !0, this.#clearRailToggles(owned)), this.#clearPaths(view.slots);
	  }
	  #clearPaths(slots) {
	    const visible = visiblePath(slots), hit = hitPath(slots);
	    visible.hasAttribute("d") && visible.removeAttribute("d"), hit.hasAttribute("d") && hit.removeAttribute("d"), slots.branchOverlay.hasAttribute("hidden") || slots.branchOverlay.setAttribute("hidden", ""), slots.branchOverlay.style.height && slots.branchOverlay.style.removeProperty("height"), slots.branchOverlay.hasAttribute("viewBox") && slots.branchOverlay.removeAttribute("viewBox");
	  }
	  #sweep() {
	    for (const [postNumber, owned] of this.#owned)
	      this.#domOwner.view(postNumber) !== owned.view && (this.#releaseOwned(owned), this.#owned.delete(postNumber));
	  }
	  #hasChildren(postNumber) {
	    return this.#domOwner.views().some(
	      (view) => this.#domOwner.topology.parentOf(view.postNumber) === postNumber
	    );
	  }
	  #restore() {
	    for (const view of this.#domOwner.views())
	      this.#setClass(
	        view.slots.root,
	        "ldp-segmented-branch-last",
	        !1
	      );
	    for (const owned of this.#owned.values())
	      this.#releaseOwned(owned);
	    this.#owned.clear(), this.#collapsed.clear();
	  }
	  #releaseOwned(owned) {
	    const slots = owned.view.slots;
	    slots.replyList.hidden = !1, this.#setClass(slots.root, "ldp-has-child-branches", !1), this.#setClass(slots.root, "ldp-branch-parent-collapsed", !1), this.#setClass(slots.root, "ldp-segmented-branch-last", !1), this.#clearPaths(slots), owned.toggle.remove(), owned.count.remove(), owned.trunkToggle?.remove(), this.#clearRailToggles(owned), slots.replyControls.classList.remove("ldp-branch-controls");
	  }
	}
}, "454d387511638ec7108df44be2bf8411859286b566e535375e9c50d63271ff2f");

/* Source: lite/src/layout/reader-layout-style-controller.ts */
runtime.register("src/layout/reader-layout-style-controller.js", function(module, exports, require) {
	var reader_layout_style_controller_exports = {};
	__export(reader_layout_style_controller_exports, {
	  ReaderLayoutStyleController: () => ReaderLayoutStyleController,
	  readerPreferencesLayoutAdapter: () => readerPreferencesLayoutAdapter
	});
	module.exports = __toCommonJS(reader_layout_style_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_reader_preferences_schema = require("../state/reader-preferences-schema.js");
	const readerPreferencesLayoutAdapter = Object.freeze({
	  readProfile: (preferences, mode) => mode === "fullpage" ? preferences.fullpageLayoutProfile : preferences.layoutProfile,
	  createPatch: (profile, mode) => mode === "fullpage" ? { fullpageLayoutProfile: profile } : { layoutProfile: profile }
	});
	function sameProfile(left, right) {
	  return import_reader_preferences_schema.READER_LAYOUT_REGIONS.every(
	    (region) => left[region] === right[region]
	  );
	}
	class ReaderLayoutStyleController {
	  scope;
	  changes = new import_signal.Signal();
	  #root;
	  #adapter;
	  #modePort;
	  #original = /* @__PURE__ */ new Map();
	  #previews = /* @__PURE__ */ new Map();
	  #preferences;
	  #mode;
	  #snapshot;
	  constructor(options) {
	    this.#root = options.root, this.#adapter = options.preferences, this.#modePort = options.mode, this.#preferences = options.readPreferences(), this.#mode = this.#modePort.read(), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    for (const region of import_reader_preferences_schema.READER_LAYOUT_REGIONS) {
	      const property = `--ldp-layout-${region}`;
	      this.#original.set(property, Object.freeze({
	        value: this.#root.style.getPropertyValue(property),
	        priority: typeof this.#root.style.getPropertyPriority == "function" ? this.#root.style.getPropertyPriority(property) : ""
	      }));
	    }
	    this.#snapshot = this.#commit(), options.preferenceChanges.subscribe((preferences) => {
	      this.#preferences = preferences, this.#publish();
	    }, this.scope), this.#modePort.subscribe((mode) => {
	      mode !== this.#mode && (this.#mode = mode, this.#publish());
	    }, this.scope), this.scope.add(() => {
	      this.changes.clear(), this.#previews.clear();
	      for (const [property, previous] of this.#original)
	        previous.value ? this.#root.style.setProperty(
	          property,
	          previous.value,
	          previous.priority
	        ) : this.#root.style.removeProperty(property);
	    });
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  get mode() {
	    return this.#mode;
	  }
	  profile(mode) {
	    return this.#adapter.readProfile(this.#preferences, mode);
	  }
	  readProfile(preferences, mode) {
	    return this.#adapter.readProfile(preferences, mode);
	  }
	  createPatch(profile, mode) {
	    return this.#adapter.createPatch(profile, mode);
	  }
	  preview(profile, mode = this.#mode) {
	    if (this.scope.destroyed) return;
	    const previous = this.#previews.get(mode);
	    previous && sameProfile(previous, profile) || (this.#previews.set(mode, Object.freeze({ ...profile })), mode === this.#mode && this.#publish());
	  }
	  clearPreview(mode) {
	    if (this.scope.destroyed) return;
	    if (mode) {
	      if (!this.#previews.delete(mode)) return;
	      mode === this.#mode && this.#publish();
	      return;
	    }
	    if (this.#previews.size === 0) return;
	    const currentChanged = this.#previews.has(this.#mode);
	    this.#previews.clear(), currentChanged && this.#publish();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #publish() {
	    this.#snapshot = this.#commit(), this.changes.emit(this.#snapshot);
	  }
	  #commit() {
	    const preview = this.#previews.get(this.#mode), profile = preview ?? this.profile(this.#mode);
	    for (const region of import_reader_preferences_schema.READER_LAYOUT_REGIONS)
	      this.#root.style.setProperty(
	        `--ldp-layout-${region}`,
	        `${profile[region]}%`
	      );
	    return Object.freeze({
	      mode: this.#mode,
	      profile,
	      previewing: !!preview
	    });
	  }
	}
}, "4feaeadcb3666626a6f01d91293edf33547d4b33c8ece5408edcd3840f98e1ee");

/* Source: lite/src/live/reader-topic-live-navigation-controller.ts */
runtime.register("src/live/reader-topic-live-navigation-controller.js", function(module, exports, require) {
	var reader_topic_live_navigation_controller_exports = {};
	__export(reader_topic_live_navigation_controller_exports, {
	  ReaderTopicLiveNavigationController: () => ReaderTopicLiveNavigationController
	});
	module.exports = __toCommonJS(reader_topic_live_navigation_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function postNumberFromChange(change) {
	  return change.kind !== "post" || change.created !== !0 || change.wasKnown === !0 ? null : (0, import_identifiers.tryDiscoursePostNumber)(
	    change.post.post_number
	  );
	}
	class ReaderTopicLiveNavigationController {
	  scope;
	  changes = new import_signal.Signal();
	  #navigation;
	  #onError;
	  #snapshot = Object.freeze({
	    nearEnd: !1,
	    pendingPostNumbers: Object.freeze([]),
	    targetPostNumber: null,
	    pendingCount: 0,
	    dismissed: !1,
	    jumping: !1
	  });
	  #jumpEpoch = 0;
	  constructor(options) {
	    this.#navigation = options.navigation, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), options.live.changes.subscribe((change) => {
	      const postNumber = postNumberFromChange(change);
	      if (postNumber !== null) {
	        if (this.#snapshot.nearEnd) {
	          this.#jump(postNumber, !0);
	          return;
	        }
	        this.#queue(postNumber);
	      }
	    }, this.scope), this.scope.add(() => {
	      this.#jumpEpoch += 1, this.changes.clear();
	    });
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  syncViewport(input) {
	    const nearEnd = input.atEnd === !0;
	    return this.#snapshot.nearEnd === nearEnd ? this.#snapshot : this.#commit({
	      ...this.#snapshot,
	      nearEnd
	    });
	  }
	  jumpPending() {
	    this.#assertActive();
	    const target = this.#snapshot.targetPostNumber;
	    return target === null ? Promise.resolve(null) : this.#jump(target, !1);
	  }
	  dismiss() {
	    return this.#assertActive(), this.#snapshot.pendingCount ? this.#commit({
	      ...this.#snapshot,
	      dismissed: !0
	    }) : this.#snapshot;
	  }
	  clear() {
	    return this.#assertActive(), this.#jumpEpoch += 1, this.#commit({
	      ...this.#snapshot,
	      pendingPostNumbers: Object.freeze([]),
	      targetPostNumber: null,
	      pendingCount: 0,
	      dismissed: !1,
	      jumping: !1
	    });
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #queue(postNumber) {
	    const pending = new Set(this.#snapshot.pendingPostNumbers);
	    pending.add(postNumber);
	    const pendingPostNumbers = Object.freeze(
	      [...pending].sort((left, right) => left - right)
	    );
	    this.#commit({
	      ...this.#snapshot,
	      pendingPostNumbers,
	      targetPostNumber: pendingPostNumbers[0] ?? null,
	      pendingCount: pendingPostNumbers.length,
	      dismissed: !1
	    });
	  }
	  async #jump(postNumber, automatic) {
	    this.#assertActive();
	    const epoch = ++this.#jumpEpoch, jumpedBatch = /* @__PURE__ */ new Set([
	      ...this.#snapshot.pendingPostNumbers,
	      postNumber
	    ]);
	    this.#commit({
	      ...this.#snapshot,
	      jumping: !0
	    });
	    try {
	      const result = await this.#navigation.navigate({
	        postNumber,
	        source: "message",
	        alignment: automatic ? "nearest" : "center",
	        highlight: !0
	      });
	      if (epoch !== this.#jumpEpoch || this.scope.destroyed) return result;
	      if (result.status === "revealed") {
	        const pendingPostNumbers = Object.freeze(
	          this.#snapshot.pendingPostNumbers.filter(
	            (pendingPostNumber) => !jumpedBatch.has(pendingPostNumber)
	          )
	        );
	        this.#commit({
	          ...this.#snapshot,
	          pendingPostNumbers,
	          targetPostNumber: pendingPostNumbers[0] ?? null,
	          pendingCount: pendingPostNumbers.length,
	          dismissed: pendingPostNumbers.length ? this.#snapshot.dismissed : !1,
	          jumping: !1
	        });
	      } else
	        this.#queue(postNumber), this.#commit({
	          ...this.#snapshot,
	          jumping: !1
	        });
	      return result;
	    } catch (cause) {
	      throw epoch === this.#jumpEpoch && !this.scope.destroyed && (this.#queue(postNumber), this.#commit({
	        ...this.#snapshot,
	        jumping: !1
	      }), this.#onError(cause)), cause;
	    }
	  }
	  #commit(input) {
	    const snapshot = Object.freeze({
	      nearEnd: input.nearEnd,
	      pendingPostNumbers: Object.freeze([...input.pendingPostNumbers]),
	      targetPostNumber: input.targetPostNumber,
	      pendingCount: input.pendingCount,
	      dismissed: input.dismissed,
	      jumping: input.jumping
	    });
	    this.#snapshot = snapshot;
	    for (const cause of this.changes.emit(snapshot)) this.#onError(cause);
	    return snapshot;
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("ReaderTopicLiveNavigationController 已销毁");
	  }
	}
}, "7f09e77612805e96f6be1113157d49f8b6fa5b96495168f9146e0245932a5067");

/* Source: lite/src/live/reader-topic-live-navigation-view.ts */
runtime.register("src/live/reader-topic-live-navigation-view.js", function(module, exports, require) {
	var reader_topic_live_navigation_view_exports = {};
	__export(reader_topic_live_navigation_view_exports, {
	  ReaderTopicLiveNavigationView: () => ReaderTopicLiveNavigationView
	});
	module.exports = __toCommonJS(reader_topic_live_navigation_view_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderTopicLiveNavigationView {
	  scope;
	  #navigation;
	  #elements;
	  #notify;
	  constructor(options) {
	    this.#navigation = options.navigation, this.#elements = options.elements, this.#notify = options.notify ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.listen(this.#elements.jump, "click", () => {
	      this.#jump();
	    }), this.scope.listen(this.#elements.dismiss, "click", () => {
	      this.#navigation.dismiss();
	    }), this.#navigation.changes.subscribe((snapshot) => {
	      this.#sync(snapshot);
	    }, this.scope), this.scope.add(() => {
	      this.#elements.root.hidden = !0, this.#elements.root.removeAttribute("aria-busy"), this.#elements.label.textContent = "";
	    }), this.#sync(this.#navigation.snapshot);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #sync(snapshot) {
	    const hidden = snapshot.pendingCount <= 0 || snapshot.dismissed || snapshot.targetPostNumber === null;
	    this.#elements.root.hidden !== hidden && (this.#elements.root.hidden = hidden);
	    const busy = String(snapshot.jumping);
	    this.#elements.root.getAttribute("aria-busy") !== busy && this.#elements.root.setAttribute("aria-busy", busy);
	    const disabled = snapshot.jumping || hidden;
	    this.#elements.jump.disabled !== disabled && (this.#elements.jump.disabled = disabled), this.#elements.dismiss.disabled !== disabled && (this.#elements.dismiss.disabled = disabled);
	    const label = snapshot.pendingCount === 1 ? "查看 1 个新回复" : `查看 ${snapshot.pendingCount} 个新回复`, labelText = hidden ? "" : label;
	    this.#elements.label.textContent !== labelText && (this.#elements.label.textContent = labelText);
	    const ariaLabel = snapshot.targetPostNumber === null ? "查看新回复" : `${label},从楼层 #${snapshot.targetPostNumber} 开始`;
	    this.#elements.jump.getAttribute("aria-label") !== ariaLabel && this.#elements.jump.setAttribute("aria-label", ariaLabel);
	  }
	  #jump() {
	    this.#navigation.snapshot.jumping || this.#navigation.jumpPending().then((result) => {
	      this.scope.destroyed || result && result.status !== "revealed" && result.status !== "superseded" && this.#notify("新回复暂时无法定位,请重试");
	    }).catch(() => {
	      this.scope.destroyed || this.#notify("新回复加载失败,请重试");
	    });
	  }
	}
}, "169a4f1c1c301e73f26e1abb3ebb6237cfe32f43074316d856f0f023a5cb4516");

/* Source: lite/src/live/topic-live-controller.ts */
runtime.register("src/live/topic-live-controller.js", function(module, exports, require) {
	var topic_live_controller_exports = {};
	__export(topic_live_controller_exports, {
	  TopicLiveController: () => TopicLiveController,
	  normalizeTopicLiveMessage: () => normalizeTopicLiveMessage
	});
	module.exports = __toCommonJS(topic_live_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	const TARGETED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
	  "acted",
	  "created",
	  "rebaked",
	  "recovered",
	  "revised"
	]), DELETION_MESSAGE_TYPES = /* @__PURE__ */ new Set(["deleted", "destroyed"]), TOPIC_STATS_MESSAGE_TYPE = "stats", BOOST_ADDED_MESSAGE_TYPE = "boost_added", BOOST_REMOVED_MESSAGE_TYPE = "boost_removed";
	function delay(value, fallback, name) {
	  const numeric = Number(value ?? fallback);
	  if (!Number.isFinite(numeric) || numeric < 0)
	    throw new RangeError(`${name} 必须是非负有限毫秒`);
	  return numeric;
	}
	function payloadFromMessage(message) {
	  if (!message || typeof message != "object") return {};
	  const record = message;
	  return record.payload && typeof record.payload == "object" ? record.payload : record;
	}
	function objectRecord(value) {
	  return value && typeof value == "object" && !Array.isArray(value) ? value : null;
	}
	function positiveInteger(value) {
	  const numeric = Number(value);
	  return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : null;
	}
	function username(value) {
	  return String(value ?? "").trim().replace(/^@+/, "").toLocaleLowerCase();
	}
	function boostUser(boost) {
	  const user = objectRecord(boost.user);
	  return Object.freeze({
	    id: positiveInteger(user?.id ?? boost.user_id),
	    username: username(user?.username ?? boost.username)
	  });
	}
	function normalizeTopicLiveMessage(message, rawTopicId) {
	  const topicId = (0, import_identifiers.discourseTopicId)(rawTopicId), envelope = message && typeof message == "object" ? message : {}, payload = payloadFromMessage(message), messageTopicId = (0, import_identifiers.tryDiscourseTopicId)(
	    envelope.topic_id ?? payload.topic_id
	  );
	  if (messageTopicId !== null && messageTopicId !== topicId)
	    return Object.freeze({ kind: "ignore" });
	  const reactionPostId = Array.isArray(payload.reactions) ? (0, import_identifiers.tryDiscoursePostId)(payload.post_id) : null, messageType = reactionPostId === null ? String(payload.type ?? "").trim() : "acted", typedPostId = TARGETED_MESSAGE_TYPES.has(messageType) || DELETION_MESSAGE_TYPES.has(messageType) ? (0, import_identifiers.tryDiscoursePostId)(payload.post_id) : null, postId = reactionPostId ?? typedPostId ?? (0, import_identifiers.tryDiscoursePostId)(payload.id);
	  if (messageType === "read") return Object.freeze({ kind: "ignore" });
	  if (postId !== null && messageType === BOOST_ADDED_MESSAGE_TYPE) {
	    const boost = objectRecord(payload.boost);
	    if (boost && positiveInteger(boost.id) !== null)
	      return Object.freeze({ kind: "boost-added", postId, boost });
	  }
	  if (postId !== null && messageType === BOOST_REMOVED_MESSAGE_TYPE) {
	    const boostId = positiveInteger(payload.boost_id);
	    if (boostId !== null)
	      return Object.freeze({ kind: "boost-removed", postId, boostId });
	  }
	  return postId !== null && DELETION_MESSAGE_TYPES.has(messageType) ? Object.freeze({
	    kind: "post-delete",
	    postId
	  }) : postId !== null && TARGETED_MESSAGE_TYPES.has(messageType) ? Object.freeze({
	    kind: "post",
	    postId,
	    created: messageType === "created"
	  }) : Object.freeze(messageType === TOPIC_STATS_MESSAGE_TYPE ? { kind: "topic-stats" } : {
	    kind: "refresh-topic",
	    reason: messageType || "unknown-message"
	  });
	}
	class TopicLiveController {
	  topicId;
	  scope;
	  changes = new import_signal.Signal();
	  #messageBus;
	  #session;
	  #cache;
	  #currentUsername;
	  #postDelayMs;
	  #topicDelayMs;
	  #setTimer;
	  #clearTimer;
	  #onError;
	  #subscriptions = [];
	  #pendingPosts = /* @__PURE__ */ new Map();
	  #fullRefreshReasons = /* @__PURE__ */ new Set();
	  #tasks = /* @__PURE__ */ new Set();
	  #fullRefreshTimer = 0;
	  #fullRefreshRunning = !1;
	  #activationEpoch = 0;
	  #active = !1;
	  #closed = !1;
	  constructor(options) {
	    if (this.topicId = (0, import_identifiers.discourseTopicId)(options.topicId), options.session.topicId !== this.topicId)
	      throw new Error("TopicLiveController 与 TopicSession topicId 不一致");
	    this.#messageBus = options.messageBus, this.#session = options.session, this.#cache = options.cache ?? null, this.#currentUsername = username(options.currentUsername), this.#postDelayMs = delay(options.postDelayMs, 120, "postDelayMs"), this.#topicDelayMs = delay(options.topicDelayMs, 350, "topicDelayMs"), this.#setTimer = options.setTimer ?? ((callback, milliseconds) => setTimeout(callback, milliseconds)), this.#clearTimer = options.clearTimer ?? clearTimeout, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.scope), this.scope.add(() => {
	      this.#closed = !0, this.#deactivate(), this.changes.clear();
	    });
	  }
	  get active() {
	    return this.#active;
	  }
	  start() {
	    if (this.#assertOpen(), this.#active) return !0;
	    const handler = (message) => {
	      this.#handleMessage(message);
	    }, topicSubscription = {
	      channel: `/topic/${this.topicId}`,
	      handler
	    };
	    try {
	      this.#messageBus.subscribe(
	        topicSubscription.channel,
	        topicSubscription.handler
	      );
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	    this.#subscriptions.push(topicSubscription);
	    const reactionSubscription = {
	      channel: `/topic/${this.topicId}/reactions`,
	      handler
	    };
	    try {
	      this.#messageBus.subscribe(
	        reactionSubscription.channel,
	        reactionSubscription.handler
	      ), this.#subscriptions.push(reactionSubscription);
	    } catch (error) {
	      this.#onError(error);
	    }
	    return this.#activationEpoch += 1, this.#active = !0, !0;
	  }
	  setActive(active, options = {}) {
	    if (this.#assertOpen(), !active)
	      return this.#deactivate(), !1;
	    const started = this.start();
	    return started && options.refresh === !0 && this.scheduleTopicRefresh("resume"), started;
	  }
	  scheduleTopicRefresh(reason = "explicit") {
	    if (!this.#active || this.#closed || (this.#fullRefreshReasons.add(String(reason || "explicit")), this.#fullRefreshTimer || this.#fullRefreshRunning)) return;
	    const epoch = this.#activationEpoch;
	    this.#fullRefreshTimer = this.#setTimer(() => {
	      this.#fullRefreshTimer = 0, this.#track(this.#refreshTopic(epoch));
	    }, this.#topicDelayMs);
	  }
	  /**
	   * 宿主 app-event 已携带完整 Post model 时直接提交当前 canonical。
	   * 这里只消费已加载楼层,不创建 stream、不发请求;缺少权威字段的 MessageBus
	   * 事件继续沿既有单帖刷新路径处理。
	   */
	  ingestPostDelta(value) {
	    if (!this.#active || this.#closed) return !1;
	    const ingest = this.#session.ingestPosts, delta = objectRecord(value), postId = (0, import_identifiers.tryDiscoursePostId)(delta?.id);
	    if (!ingest || !delta || postId === null) return !1;
	    const current = this.#session.postById(postId);
	    if (!current) return !1;
	    const currentRecord = current;
	    if ((0, import_identifiers.tryDiscourseTopicId)(
	      delta.topic_id ?? currentRecord.topic_id
	    ) !== this.topicId) return !1;
	    const next = Object.freeze({
	      ...currentRecord,
	      ...delta
	    });
	    try {
	      ingest.call(this.#session, [next], "message-bus");
	      const canonical = this.#session.postById(postId) ?? next;
	      return this.#emit(Object.freeze({
	        kind: "post",
	        postId,
	        post: canonical,
	        created: !1,
	        wasKnown: !0
	      })), this.#track(this.#invalidate([
	        `topic:${this.topicId}`,
	        `post:${postId}`
	      ])), !0;
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	  }
	  async flush() {
	    for (; this.#tasks.size; )
	      await Promise.allSettled([...this.#tasks]);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #handleMessage(message) {
	    if (!this.#active || this.#closed) return;
	    let normalized;
	    try {
	      normalized = normalizeTopicLiveMessage(message, this.topicId);
	    } catch (error) {
	      this.#onError(error), this.scheduleTopicRefresh("invalid-message");
	      return;
	    }
	    if (normalized.kind === "ignore") return;
	    if (normalized.kind === "refresh-topic") {
	      this.scheduleTopicRefresh(normalized.reason);
	      return;
	    }
	    if (normalized.kind === "topic-stats") {
	      this.#pendingPosts.size === 0 && this.scheduleTopicRefresh("stats");
	      return;
	    }
	    if (this.#cancelStatsRefresh(), (normalized.kind === "boost-added" || normalized.kind === "boost-removed") && this.#commitBoostDelta(normalized)) return;
	    const postChange = normalized.kind === "boost-added" || normalized.kind === "boost-removed" ? Object.freeze({
	      kind: "post",
	      postId: normalized.postId,
	      created: !1
	    }) : normalized, current = this.#pendingPosts.get(postChange.postId);
	    if (current) {
	      if (postChange.kind === "post-delete")
	        current.operation = "delete", current.created = !1;
	      else {
	        const recoveringFromDelete = current.operation === "delete";
	        current.operation = "refresh", current.created = recoveringFromDelete ? postChange.created : current.created || postChange.created;
	      }
	      return;
	    }
	    const epoch = this.#activationEpoch, pending = {
	      postId: postChange.postId,
	      operation: postChange.kind === "post-delete" ? "delete" : "refresh",
	      created: postChange.kind === "post" && postChange.created,
	      timerId: this.#setTimer(() => {
	        this.#pendingPosts.delete(postChange.postId), this.#track(
	          pending.operation === "delete" ? this.#deletePost(postChange.postId, epoch) : this.#refreshPost(postChange.postId, pending.created, epoch)
	        );
	      }, this.#postDelayMs)
	    };
	    this.#pendingPosts.set(postChange.postId, pending);
	  }
	  #commitBoostDelta(message) {
	    const ingest = this.#session.ingestPosts, current = this.#session.postById(message.postId);
	    if (!ingest || !current) return !1;
	    const currentRecord = current, currentBoosts = Array.isArray(currentRecord.boosts) ? currentRecord.boosts.map((value) => objectRecord(value)).filter((value) => value !== null) : [];
	    let nextBoosts, ownBoostChanged = !1;
	    if (message.kind === "boost-added") {
	      const incomingIdentity = boostUser(message.boost);
	      let replaced = !1;
	      nextBoosts = Object.freeze(currentBoosts.map((boost) => {
	        const identity = boostUser(boost), sameBoost = positiveInteger(boost.id) === positiveInteger(message.boost.id), sameUser = incomingIdentity.id !== null && identity.id === incomingIdentity.id;
	        return !sameBoost && !sameUser ? boost : (replaced = !0, Object.freeze({ ...boost, ...message.boost }));
	      })), replaced || (nextBoosts = Object.freeze([
	        ...nextBoosts,
	        Object.freeze({ ...message.boost })
	      ])), ownBoostChanged = !!(this.#currentUsername && incomingIdentity.username === this.#currentUsername);
	    } else {
	      const removed = currentBoosts.find((boost) => positiveInteger(boost.id) === message.boostId);
	      nextBoosts = Object.freeze(currentBoosts.filter((boost) => positiveInteger(boost.id) !== message.boostId)), ownBoostChanged = !!(removed && this.#currentUsername && boostUser(removed).username === this.#currentUsername);
	    }
	    const next = Object.freeze({
	      ...currentRecord,
	      boosts: nextBoosts,
	      ...ownBoostChanged ? { can_boost: message.kind === "boost-removed" } : {}
	    });
	    try {
	      ingest.call(this.#session, [next], "message-bus");
	      const canonical = this.#session.postById(message.postId) ?? next;
	      return this.#emit(Object.freeze({
	        kind: "post",
	        postId: message.postId,
	        post: canonical,
	        created: !1,
	        wasKnown: !0
	      })), this.#track(this.#invalidate([
	        `topic:${this.topicId}`,
	        `post:${message.postId}`
	      ])), !0;
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	  }
	  async #deletePost(postId, epoch) {
	    if (this.#acceptsWork(epoch) && (await this.#invalidate([`topic:${this.topicId}`, `post:${postId}`]), !!this.#session.postById(postId)))
	      try {
	        const commit = this.#session.removePostById(postId, "message-bus");
	        if (!this.#acceptsWork(epoch)) return;
	        const postNumber = Number(commit.removedPostNumbers?.[0] ?? 0);
	        Number.isSafeInteger(postNumber) && postNumber > 0 && this.#emit(Object.freeze({
	          kind: "deleted",
	          postId,
	          postNumber
	        }));
	      } catch (error) {
	        this.#onError(error), this.#acceptsWork(epoch) && this.scheduleTopicRefresh("post-delete-failed");
	      }
	  }
	  async #refreshPost(postId, created, epoch) {
	    if (!this.#acceptsWork(epoch)) return;
	    await this.#invalidate([`topic:${this.topicId}`, `post:${postId}`]);
	    const wasKnown = this.#session.postById(postId) !== void 0;
	    try {
	      const post = await this.#session.loadPostById(postId, {
	        created: created && !wasKnown
	      });
	      if (!post || !this.#acceptsWork(epoch)) {
	        !post && this.#acceptsWork(epoch) && this.scheduleTopicRefresh("post-missing");
	        return;
	      }
	      this.#emit(Object.freeze({
	        kind: "post",
	        postId: (0, import_identifiers.discoursePostId)(postId),
	        post,
	        created: created && !wasKnown,
	        wasKnown
	      }));
	    } catch (error) {
	      this.#onError(error), this.#acceptsWork(epoch) && this.scheduleTopicRefresh("post-refresh-failed");
	    }
	  }
	  async #refreshTopic(epoch) {
	    if (!this.#acceptsWork(epoch) || this.#fullRefreshRunning) return;
	    this.#fullRefreshRunning = !0;
	    const reasons = Object.freeze([...this.#fullRefreshReasons].sort());
	    this.#fullRefreshReasons.clear();
	    try {
	      await this.#invalidate([`topic:${this.topicId}`]);
	      const topic = await this.#session.refresh({ background: !0 });
	      this.#acceptsWork(epoch) && this.#emit(Object.freeze({ kind: "topic", topic, reasons }));
	    } catch (error) {
	      this.#onError(error);
	    } finally {
	      this.#fullRefreshRunning = !1, this.#active && !this.#closed && this.#fullRefreshReasons.size && !this.#fullRefreshTimer && this.scheduleTopicRefresh("coalesced");
	    }
	  }
	  async #invalidate(tags) {
	    if (this.#cache)
	      try {
	        await this.#cache.invalidate({ tags });
	      } catch (error) {
	        this.#onError(error);
	      }
	  }
	  #emit(change) {
	    for (const error of this.changes.emit(change)) this.#onError(error);
	  }
	  #track(task) {
	    this.#tasks.add(task), task.finally(() => {
	      this.#tasks.delete(task);
	    }).catch(() => {
	    });
	  }
	  #cancelStatsRefresh() {
	    this.#fullRefreshReasons.delete("stats") && (this.#fullRefreshReasons.size > 0 || !this.#fullRefreshTimer || (this.#clearTimer(this.#fullRefreshTimer), this.#fullRefreshTimer = 0));
	  }
	  #deactivate() {
	    this.#activationEpoch += 1, this.#active = !1, this.#fullRefreshTimer && this.#clearTimer(this.#fullRefreshTimer), this.#fullRefreshTimer = 0, this.#fullRefreshReasons.clear();
	    for (const pending of this.#pendingPosts.values())
	      this.#clearTimer(pending.timerId);
	    this.#pendingPosts.clear();
	    for (const subscription of this.#subscriptions.splice(0).reverse())
	      try {
	        this.#messageBus.unsubscribe(subscription.channel, subscription.handler);
	      } catch (error) {
	        this.#onError(error);
	      }
	  }
	  #assertOpen() {
	    if (this.#closed) throw new Error("TopicLiveController 已销毁");
	  }
	  #acceptsWork(epoch) {
	    return this.#active && !this.#closed && epoch === this.#activationEpoch;
	  }
	}
}, "87237ccbaa9f3ffaeb3a0456dcd4c7991b588f66f5635a06971d99deded8dcd2");

/* Source: lite/src/network/browser-request-observation.ts */
runtime.register("src/network/browser-request-observation.js", function(module, exports, require) {
	var browser_request_observation_exports = {};
	__export(browser_request_observation_exports, {
	  BrowserResourceObservationAdapter: () => BrowserResourceObservationAdapter,
	  DiscourseNativeAjaxObservationAdapter: () => DiscourseNativeAjaxObservationAdapter
	});
	module.exports = __toCommonJS(browser_request_observation_exports);
	function jqueryModule(value) {
	  const candidate = value && typeof value == "object" && "default" in value ? value.default : value;
	  return typeof candidate == "function" ? candidate : null;
	}
	function ajaxResponseHeader(response, name) {
	  try {
	    return String(response.getResponseHeader?.(name) ?? "");
	  } catch {
	    return "";
	  }
	}
	function ajaxFinish(response) {
	  const status = Number(response.status), cloudflareMitigated = ajaxResponseHeader(response, "cf-mitigated").trim().toLowerCase() === "challenge";
	  return Object.freeze({
	    ...Number.isFinite(status) && status >= 0 ? { status } : {},
	    cloudflareMitigated,
	    rateLimitCode: ajaxResponseHeader(response, "Discourse-Rate-Limit-Error-Code") || ajaxResponseHeader(response, "X-Discourse-Rate-Limit-Error-Code"),
	    retryAfter: ajaxResponseHeader(response, "Retry-After"),
	    serverLimit: ajaxResponseHeader(response, "RateLimit-Limit") || ajaxResponseHeader(response, "X-RateLimit-Limit"),
	    serverRemaining: ajaxResponseHeader(response, "RateLimit-Remaining") || ajaxResponseHeader(response, "X-RateLimit-Remaining"),
	    serverReset: ajaxResponseHeader(response, "RateLimit-Reset") || ajaxResponseHeader(response, "X-RateLimit-Reset")
	  });
	}
	class DiscourseNativeAjaxObservationAdapter {
	  #observer;
	  #jqueryModule;
	  #document;
	  #namespace;
	  #hostRequestBudget;
	  constructor(options) {
	    this.#observer = options.observer, this.#jqueryModule = options.jqueryModule, this.#document = options.document ?? document, this.#hostRequestBudget = options.hostRequestBudget ?? null;
	    const namespace = String(options.namespace ?? "mianLiteRequestObserver").trim();
	    if (!/^[A-Za-z][A-Za-z0-9]*$/.test(namespace))
	      throw new Error("jQuery ajax observation namespace 非法");
	    this.#namespace = namespace;
	  }
	  install(scope) {
	    let module2 = null;
	    try {
	      module2 = jqueryModule(this.#jqueryModule);
	    } catch {
	      return !1;
	    }
	    if (!module2) return !1;
	    let target;
	    try {
	      target = module2(this.#document);
	    } catch {
	      return !1;
	    }
	    if (!target || typeof target.on != "function" || typeof target.off != "function")
	      return !1;
	    const active = /* @__PURE__ */ new Map(), borrowedIds = /* @__PURE__ */ new Set(), send = (_event, rawResponse, rawSettings) => {
	      if (!rawResponse || typeof rawResponse != "object") return;
	      const settings = rawSettings ?? {}, href = String(settings.url ?? "");
	      if (!href) return;
	      const method = String(settings.type ?? settings.method ?? "GET"), readerId = this.#observer.matchActive({
	        href,
	        method,
	        source: "reader",
	        excludedIds: borrowedIds
	      });
	      readerId !== null && borrowedIds.add(readerId);
	      const id = readerId ?? this.#observer.begin({
	        href,
	        method,
	        transport: "xmlhttprequest",
	        source: "host"
	      }), event = this.#observer.snapshot.events.find(
	        (candidate) => candidate.id === id
	      ), sharedApiResponse = !!(event?.sameOrigin && !["avatar", "media", "asset", "realtime", "presence"].includes(
	        event.type
	      )), countsAgainstSharedBudget = sharedApiResponse && event?.source === "host" && event.transport === "xmlhttprequest";
	      let hostLease = null;
	      if (countsAgainstSharedBudget && this.#hostRequestBudget && event)
	        try {
	          hostLease = this.#hostRequestBudget.recordHostStart({
	            startedAt: event.startedAt
	          });
	        } catch {
	        }
	      active.set(rawResponse, {
	        id,
	        borrowed: readerId !== null,
	        hostLease,
	        sharedResponse: sharedApiResponse && event ? Object.freeze({
	          source: event.source === "reader" ? "reader" : "host",
	          href: event.href,
	          recoveryProbe: event.recoveryProbe,
	          blockOnCloudflareChallenge: event.type !== "read"
	        }) : null
	      });
	    }, complete = (_event, rawResponse) => {
	      if (!rawResponse || typeof rawResponse != "object") return;
	      const current = active.get(rawResponse);
	      if (!current) return;
	      active.delete(rawResponse), current.borrowed && borrowedIds.delete(current.id), current.hostLease?.release();
	      const finish = ajaxFinish(rawResponse);
	      if (this.#observer.finish(
	        current.id,
	        finish
	      ), current.sharedResponse && this.#hostRequestBudget)
	        try {
	          this.#hostRequestBudget.noteObservedResponse({
	            ...current.sharedResponse,
	            status: finish.status ?? 0,
	            cloudflareMitigated: finish.cloudflareMitigated === !0,
	            retryAfter: finish.retryAfter ?? "",
	            rateLimitCode: finish.rateLimitCode ?? "",
	            serverLimit: finish.serverLimit ?? "",
	            serverRemaining: finish.serverRemaining ?? "",
	            serverReset: finish.serverReset ?? ""
	          });
	        } catch {
	        }
	    }, sendEvent = `ajaxSend.${this.#namespace}`, completeEvent = `ajaxComplete.${this.#namespace}`;
	    try {
	      target.on(sendEvent, send), target.on(completeEvent, complete);
	    } catch {
	      try {
	        target.off(sendEvent, send), target.off(completeEvent, complete);
	      } catch {
	      }
	      return !1;
	    }
	    return scope.add(() => {
	      target.off(sendEvent, send), target.off(completeEvent, complete);
	      for (const current of active.values())
	        current.hostLease?.release(), current.borrowed || this.#observer.finish(current.id, {
	          error: "observer-detached"
	        });
	      active.clear(), borrowedIds.clear();
	    }), !0;
	  }
	}
	class BrowserResourceObservationAdapter {
	  #observer;
	  #performance;
	  #createObserver;
	  constructor(options) {
	    this.#observer = options.observer, this.#performance = options.performance ?? performance, this.#createObserver = options.createObserver ?? ((callback) => new PerformanceObserver(callback));
	  }
	  install(scope) {
	    let nativeObserver = null;
	    try {
	      nativeObserver = this.#createObserver((list) => {
	        for (const entry of list.getEntries()) {
	          if (entry.entryType !== "resource") continue;
	          const resource = entry;
	          this.#observer.recordResource({
	            href: resource.name,
	            initiatorType: resource.initiatorType,
	            startedAt: this.#performance.timeOrigin + resource.startTime,
	            endedAt: this.#performance.timeOrigin + (resource.responseEnd || resource.startTime + resource.duration),
	            status: Number(resource.responseStatus) || 0,
	            size: Number(resource.transferSize || resource.encodedBodySize) || 0
	          });
	        }
	      }), nativeObserver.observe({ type: "resource", buffered: !0 });
	    } catch {
	      return nativeObserver?.disconnect(), !1;
	    }
	    const installedObserver = nativeObserver;
	    return scope.add(() => installedObserver.disconnect()), !0;
	  }
	}
}, "1944a5973784dd16ee42e192d771c5de36760feb89733e82c12d01e8efce138a");

/* Source: lite/src/network/browser-shared-request-permit.ts */
runtime.register("src/network/browser-shared-request-permit.js", function(module, exports, require) {
	var browser_shared_request_permit_exports = {};
	__export(browser_shared_request_permit_exports, {
	  BrowserSharedRequestPermit: () => BrowserSharedRequestPermit,
	  READER_CLOUDFLARE_CHALLENGE_WINDOW_NAME: () => READER_CLOUDFLARE_CHALLENGE_WINDOW_NAME,
	  READER_REQUEST_PERMIT_CHANNEL: () => READER_REQUEST_PERMIT_CHANNEL,
	  READER_REQUEST_PERMIT_LOCK: () => READER_REQUEST_PERMIT_LOCK,
	  READER_REQUEST_PERMIT_STORAGE_KEY: () => READER_REQUEST_PERMIT_STORAGE_KEY,
	  browserCloudflareChallengeFeatures: () => browserCloudflareChallengeFeatures,
	  browserCloudflareChallengeHref: () => browserCloudflareChallengeHref,
	  isReaderCloudflareChallengeWindow: () => isReaderCloudflareChallengeWindow
	});
	module.exports = __toCommonJS(browser_shared_request_permit_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	const READER_REQUEST_PERMIT_STORAGE_KEY = "linuxdo-enhanced-reader:request-permit:v1", READER_REQUEST_PERMIT_LOCK = "linuxdo-enhanced-reader:request-permit-lock:v1", READER_REQUEST_PERMIT_CHANNEL = "linuxdo-enhanced-reader:request-permit-channel:v1", READER_CLOUDFLARE_CHALLENGE_WINDOW_NAME = "ldp-cloudflare-challenge";
	function isReaderCloudflareChallengeWindow(window) {
	  return window.name === READER_CLOUDFLARE_CHALLENGE_WINDOW_NAME;
	}
	const PRIORITY_WEIGHT = Object.freeze({
	  critical: 0,
	  interactive: 1,
	  nested: 2,
	  visible: 3,
	  prefetch: 4,
	  background: 5
	});
	function positiveInteger(value, fallback, name) {
	  const normalized = Number(value ?? fallback);
	  if (!Number.isSafeInteger(normalized) || normalized < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return normalized;
	}
	function nonNegativeInteger(value, name) {
	  const normalized = Number(value);
	  if (!Number.isSafeInteger(normalized) || normalized < 0)
	    throw new RangeError(`${name} 必须是非负安全整数`);
	  return normalized;
	}
	function normalizedSourceId(value) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error("request permit sourceId 不能为空");
	  return normalized;
	}
	function emptyState() {
	  return {
	    schemaVersion: 1,
	    updatedAt: 0,
	    events: [],
	    intents: [],
	    active: [],
	    policies: [],
	    challenge: null
	  };
	}
	function normalizeState(raw, now, longWindowMs) {
	  const source = raw && typeof raw == "object" ? raw : {}, events = Array.isArray(source.events) ? source.events.map(Number).filter((at) => Number.isFinite(at) && at > now - longWindowMs && at <= now).sort((left, right) => left - right).slice(-1e3) : [], intents = Array.isArray(source.intents) ? source.intents.filter((intent) => !!intent && typeof intent == "object" && typeof intent.id == "string" && typeof intent.ownerId == "string" && intent.priority in PRIORITY_WEIGHT && Number.isFinite(intent.queuedAt) && Number(intent.expiresAt) > now).slice(-256) : [], active = Array.isArray(source.active) ? source.active.filter((permit) => !!permit && typeof permit == "object" && typeof permit.id == "string" && typeof permit.ownerId == "string" && Number(permit.expiresAt) > now).slice(-128) : [], policies = Array.isArray(source.policies) ? source.policies.filter((policy) => !!policy && typeof policy == "object" && typeof policy.ownerId == "string" && Number(policy.expiresAt) > now && Number.isSafeInteger(Number(policy.shortBudget)) && Number(policy.shortBudget) > 0 && Number.isSafeInteger(Number(policy.longBudget)) && Number(policy.longBudget) > 0 && Number.isSafeInteger(Number(policy.minIntervalMs)) && Number(policy.minIntervalMs) >= 0 && Number.isSafeInteger(Number(policy.maxConcurrent)) && Number(policy.maxConcurrent) > 0).map((policy) => Object.freeze({
	    ownerId: policy.ownerId,
	    shortBudget: Number(policy.shortBudget),
	    longBudget: Number(policy.longBudget),
	    minIntervalMs: Number(policy.minIntervalMs),
	    maxConcurrent: Number(policy.maxConcurrent),
	    expiresAt: Number(policy.expiresAt)
	  })).slice(-64) : [], challengeSource = source.challenge && typeof source.challenge == "object" ? source.challenge : null, challenge = challengeSource && typeof challengeSource.ownerId == "string" && ["required", "active", "passed"].includes(String(challengeSource.state)) && Number(challengeSource.expiresAt) > now ? Object.freeze({
	    ownerId: challengeSource.ownerId,
	    state: String(challengeSource.state) === "passed" ? "passed" : "active",
	    required: String(challengeSource.state) === "required" || String(challengeSource.state) === "active" && (challengeSource.required === !0 || challengeSource.ownerId === ""),
	    automaticAttempted: challengeSource.automaticAttempted === !0 || challengeSource.ownerId === "",
	    recoveryProbeAttempted: challengeSource.recoveryProbeAttempted === !0,
	    updatedAt: Math.max(
	      0,
	      Number(challengeSource.updatedAt) || 0
	    ),
	    expiresAt: Number(challengeSource.expiresAt)
	  }) : null;
	  return {
	    schemaVersion: 1,
	    updatedAt: Math.max(0, Number(source.updatedAt) || 0),
	    events,
	    intents,
	    active,
	    policies,
	    /* 旧 v1 的 cooldown/学习字段不会被复制,下一次写入即完成迁移。 */
	    challenge
	  };
	}
	function challengeOrigin(value) {
	  const url = new URL(String(value));
	  if (!["http:", "https:"].includes(url.protocol))
	    throw new Error("Cloudflare 验证 origin 必须是 HTTP(S)");
	  return url.origin;
	}
	function challengeHrefMatchesOrigin(href, origin) {
	  try {
	    return new URL(String(href), `${origin}/`).origin === origin;
	  } catch {
	    return !1;
	  }
	}
	function browserCloudflareChallengeHref(originValue, redirectHref) {
	  const origin = challengeOrigin(originValue);
	  let redirect = new URL("/", origin);
	  try {
	    const candidate = new URL(String(redirectHref ?? ""), `${origin}/`);
	    candidate.origin === origin && ["http:", "https:"].includes(candidate.protocol) && !/^\/challenge(?:\/|$)/i.test(candidate.pathname) && (candidate.username = "", candidate.password = "", redirect = candidate);
	  } catch {
	  }
	  const challenge = new URL(
	    new URL(origin).hostname.toLowerCase() === "linux.do" ? "/challenge" : "/",
	    origin
	  );
	  return challenge.pathname === "/challenge" && challenge.searchParams.set("redirect", redirect.href), challenge.href;
	}
	function inspectChallengeWindow(popup) {
	  try {
	    const document = popup.document;
	    if (!document || !!(document.querySelector(
	      'script[src*="/cdn-cgi/challenge-platform/"],#challenge-running,iframe[src*="challenges.cloudflare.com"],.cf-turnstile,input[name="cf-turnstile-response"]'
	    ) || /^(?:Just a moment|请稍候)/i.test(String(document.title ?? "")))) return "pending";
	    if (document.querySelector(
	      'meta[name="discourse-base-uri"],meta[name="generator"],#main-outlet,.d-header'
	    )) return "passed";
	    const href = String(popup.location?.href ?? ""), location = new URL(href);
	    return ["http:", "https:"].includes(location.protocol) && !/^\/challenge(?:\/|$)/i.test(location.pathname) && !/^\/cdn-cgi\/challenge-platform(?:\/|$)/i.test(location.pathname) && document.readyState !== "loading" && document.body ? "passed" : "pending";
	  } catch {
	    return "pending";
	  }
	}
	function browserCloudflareChallengeFeatures(screen) {
	  const availableWidth = Math.max(0, Number(screen?.availWidth) || 760), availableHeight = Math.max(0, Number(screen?.availHeight) || 720), width = Math.min(760, Math.max(420, availableWidth)), height = Math.min(720, Math.max(520, availableHeight)), left = Math.max(
	    0,
	    Math.round(
	      (Number(screen?.availLeft) || 0) + (availableWidth - width) / 2
	    )
	  ), top = Math.max(
	    0,
	    Math.round(
	      (Number(screen?.availTop) || 0) + (availableHeight - height) / 2
	    )
	  );
	  return `popup=yes,width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
	}
	class BrowserSharedRequestPermit {
	  coordinationMode;
	  scope;
	  #storage;
	  #sourceId;
	  #locks;
	  #shortWindowMs;
	  #longWindowMs;
	  #shortBudget;
	  #longBudget;
	  #minIntervalMs;
	  #maxConcurrent;
	  #intentTtlMs;
	  #permitTtlMs;
	  #policyTtlMs;
	  #now;
	  #createId;
	  #onError;
	  #challenge;
	  #challengeOrigin;
	  #challengeHref;
	  #challengeLeaseTtlMs;
	  #challengePassedTtlMs;
	  #challengePollIntervalMs;
	  #challengeVerifyIntervalMs;
	  #challengeMaxWaitMs;
	  #inspectChallenge;
	  #verifyChallenge;
	  #waiters = /* @__PURE__ */ new Set();
	  #channel;
	  #fallbackState = emptyState();
	  #localTransactionTail = Promise.resolve();
	  #sequence = 0;
	  #closed = !1;
	  #challengePromise = null;
	  #challengeController = null;
	  #challengeFocusRequested = !1;
	  #challengeWindow = null;
	  #challengeReconcilePromise = null;
	  constructor(options) {
	    if (this.#storage = options.storage, this.#sourceId = normalizedSourceId(options.sourceId), this.#locks = options.locks ?? null, this.coordinationMode = this.#locks ? "atomic" : "best-effort", this.#shortWindowMs = positiveInteger(options.shortWindowMs, 1e4, "shortWindowMs"), this.#longWindowMs = positiveInteger(options.longWindowMs, 6e4, "longWindowMs"), this.#longWindowMs < this.#shortWindowMs)
	      throw new RangeError("longWindowMs 不能小于 shortWindowMs");
	    this.#shortBudget = positiveInteger(options.shortBudget, 40, "shortBudget"), this.#longBudget = positiveInteger(options.longBudget, 160, "longBudget"), this.#minIntervalMs = nonNegativeInteger(options.minIntervalMs, "minIntervalMs"), this.#maxConcurrent = positiveInteger(options.maxConcurrent, 3, "maxConcurrent"), this.#intentTtlMs = positiveInteger(options.intentTtlMs, 15e3, "intentTtlMs"), this.#permitTtlMs = positiveInteger(options.permitTtlMs, 35e3, "permitTtlMs"), this.#policyTtlMs = positiveInteger(options.policyTtlMs, 3e4, "policyTtlMs"), this.#now = options.now ?? Date.now, this.#createId = options.createId ?? (() => `${this.#sourceId}:${this.#now().toString(36)}:${++this.#sequence}`), this.#onError = options.onError ?? (() => {
	    }), this.#challenge = options.challenge ?? null, this.#challengeOrigin = this.#challenge ? challengeOrigin(this.#challenge.origin) : "", this.#challengeHref = this.#challenge ? browserCloudflareChallengeHref(
	      this.#challengeOrigin,
	      this.#challenge.redirectHref
	    ) : "", this.#challengeLeaseTtlMs = positiveInteger(
	      this.#challenge?.leaseTtlMs,
	      15e3,
	      "challenge.leaseTtlMs"
	    ), this.#challengePassedTtlMs = positiveInteger(
	      this.#challenge?.passedTtlMs,
	      1e4,
	      "challenge.passedTtlMs"
	    ), this.#challengePollIntervalMs = positiveInteger(
	      this.#challenge?.pollIntervalMs,
	      250,
	      "challenge.pollIntervalMs"
	    ), this.#challengeVerifyIntervalMs = positiveInteger(
	      this.#challenge?.verifyIntervalMs,
	      1e3,
	      "challenge.verifyIntervalMs"
	    ), this.#challengeMaxWaitMs = positiveInteger(
	      this.#challenge?.maxWaitMs,
	      12e4,
	      "challenge.maxWaitMs"
	    ), this.#inspectChallenge = this.#challenge?.inspect ?? inspectChallengeWindow, this.#verifyChallenge = this.#challenge?.verify ?? null, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const factory = options.broadcastChannelFactory === void 0 ? typeof BroadcastChannel > "u" ? null : (name) => new BroadcastChannel(name) : options.broadcastChannelFactory;
	    let channel = null;
	    try {
	      channel = factory?.(READER_REQUEST_PERMIT_CHANNEL) ?? null, channel?.addEventListener("message", this.#onChannelMessage);
	    } catch (error) {
	      this.#onError(error);
	    }
	    this.#channel = channel, options.storageEvents && this.scope.listen(
	      options.storageEvents,
	      "storage",
	      this.#onStorage
	    ), this.scope.add(() => {
	      this.#channel?.removeEventListener("message", this.#onChannelMessage), this.#channel?.close();
	    }), this.scope.add(() => {
	      this.#closed = !0, this.#challengeController?.abort(
	        new DOMException("request permit 已销毁", "AbortError")
	      ), this.#challengeController = null;
	      try {
	        this.#challengeWindow?.close?.();
	      } catch {
	      }
	      this.#challengeWindow = null, this.#wake(), this.#removeOwnedState();
	    });
	  }
	  async acquire(input) {
	    if (this.#assertOpen(), input.signal.aborted) throw this.#abortReason(input.signal);
	    const intentId = `${this.#sourceId}:intent:${this.#createId()}`, queuedAt = this.#now();
	    let granted = !1, waitReason = "";
	    try {
	      for (; !this.#closed; ) {
	        if (input.signal.aborted) throw this.#abortReason(input.signal);
	        const decision = await this.#transact((state, now) => this.#tryGrant(state, now, intentId, queuedAt, input.priority));
	        if (decision.granted && decision.permitId)
	          return granted = !0, this.#permit(
	            decision.permitId,
	            decision.recoveryProbe === !0,
	            waitReason
	          );
	        waitReason = decision.reason || waitReason, await this.#wait(decision.waitMs, input.signal);
	      }
	      throw new Error("BrowserSharedRequestPermit 已销毁");
	    } finally {
	      granted || this.#transact((state) => {
	        state.intents = state.intents.filter((intent) => intent.id !== intentId);
	      }).catch(this.#onError);
	    }
	  }
	  async noteRateLimit(decision) {
	    this.#assertOpen();
	  }
	  noteObservedResponse(input) {
	    input.source !== "host" || input.cloudflareMitigated !== !0 || input.blockOnCloudflareChallenge === !1 || !input.href || this.noteCloudflareChallenge({ href: input.href }).catch(this.#onError);
	  }
	  async noteCloudflareChallenge(input) {
	    this.#assertOpen(), !(!this.#challenge || !challengeHrefMatchesOrigin(input.href, this.#challengeOrigin)) && await this.#transact((state, now) => {
	      state.challenge?.state === "passed" && input.force !== !0 || state.challenge?.state === "active" && !state.challenge.required || (state.challenge = Object.freeze({
	        ownerId: "",
	        state: "active",
	        required: !0,
	        automaticAttempted: state.challenge?.automaticAttempted === !0,
	        recoveryProbeAttempted: state.challenge?.recoveryProbeAttempted === !0,
	        updatedAt: now,
	        expiresAt: now + this.#challengeMaxWaitMs
	      }));
	    });
	  }
	  /**
	   * 页面重载可能销毁原验证 owner,却留下已完成验证的命名窗口与 required 状态。
	   * 每个 challenge 世代只允许一个新 context 做一次原生 session 探针;成功即解闸,
	   * 失败仍保留人工按钮。它不打开窗口、不重放业务请求,也不形成请求 cooldown。
	   */
	  reconcileCloudflareChallenge() {
	    if (this.#assertOpen(), !this.#verifyChallenge) return Promise.resolve(!1);
	    if (this.#challengeReconcilePromise) return this.#challengeReconcilePromise;
	    const controller = this.scope.abortController(
	      new DOMException("request permit 已销毁", "AbortError")
	    ), promise = this.#reconcileRequiredChallenge(controller.signal).finally(() => {
	      this.#challengeReconcilePromise === promise && (this.#challengeReconcilePromise = null);
	    });
	    return this.#challengeReconcilePromise = promise, promise;
	  }
	  recordHostStart(input) {
	    this.#assertOpen();
	    const activeId = `${this.#sourceId}:host:${this.#createId()}`, registration = this.#transact((state, now) => {
	      this.#rememberPolicy(state, now);
	      const startedAt = Math.max(
	        now - 5e3,
	        Math.min(now, Number(input.startedAt) || now)
	      );
	      state.events.push(startedAt), state.events.sort((left, right) => left - right), state.active.push(Object.freeze({
	        id: activeId,
	        ownerId: this.#sourceId,
	        expiresAt: now + this.#permitTtlMs
	      }));
	    });
	    let released = !1;
	    return Object.freeze({
	      release: (input2) => {
	        released || (released = !0, input2 && this.noteObservedResponse(input2), registration.then(() => this.#transact((state) => {
	          state.active = state.active.filter(
	            (permit) => permit.id !== activeId
	          );
	        })).catch(this.#onError));
	      }
	    });
	  }
	  async resolveCloudflareChallenge(input) {
	    if (this.#assertOpen(), !this.#challenge || !challengeHrefMatchesOrigin(input.href, this.#challengeOrigin))
	      return !1;
	    if (input.signal.aborted)
	      throw this.#abortReason(input.signal);
	    if (input.focus !== !0 && !this.#locks && (await this.noteCloudflareChallenge({ href: input.href }), await this.#transact((state) => {
	      state.challenge?.state === "active" && state.challenge.required && (state.challenge = Object.freeze({
	        ...state.challenge,
	        automaticAttempted: !0
	      }));
	    })), input.focus === !0 && (this.#challengeFocusRequested = !0, this.#focusChallengeWindow(), this.#postChannelMessage("challenge-focus"), this.#wake()), !this.#challengePromise) {
	      const controller = new AbortController();
	      this.#challengeController = controller;
	      const promise = this.#runChallenge(controller.signal).finally(() => {
	        this.#challengePromise === promise && (this.#challengePromise = null, this.#challengeController = null, this.#challengeFocusRequested = !1);
	      });
	      this.#challengePromise = promise;
	    }
	    const shared = this.#challengePromise;
	    let abort = () => {
	    };
	    const cancelled = new Promise((_resolve, reject) => {
	      abort = () => reject(this.#abortReason(input.signal)), input.signal.addEventListener("abort", abort, { once: !0 });
	    });
	    return Promise.race([shared, cancelled]).finally(() => {
	      input.signal.removeEventListener("abort", abort);
	    });
	  }
	  /** 兼容旧恢复入口;固定预防窗口必须原样保留,避免过盾后形成追赶突发。 */
	  resetRateLimits() {
	    return this.#assertOpen(), Promise.resolve();
	  }
	  async snapshot() {
	    const now = this.#now(), state = this.#read(now), policy = this.#effectivePolicy(state), blocking = this.#blockingState(state, now, policy), instances = /* @__PURE__ */ new Set([
	      this.#sourceId,
	      ...state.policies.map((entry) => entry.ownerId),
	      ...state.intents.map((entry) => entry.ownerId),
	      ...state.active.map((entry) => entry.ownerId)
	    ]);
	    return Object.freeze({
	      coordinationMode: this.coordinationMode,
	      shortBudget: policy.shortBudget,
	      longBudget: policy.longBudget,
	      minIntervalMs: policy.minIntervalMs,
	      maxConcurrent: policy.maxConcurrent,
	      instances: Math.max(1, instances.size),
	      queued: state.intents.length,
	      active: state.active.length,
	      shortCount: state.events.filter((at) => at > now - this.#shortWindowMs).length,
	      longCount: state.events.length,
	      challengeState: state.challenge ? state.challenge.state === "active" && state.challenge.required ? "required" : state.challenge.state : "idle",
	      challengeOwned: state.challenge?.state === "active" && !state.challenge.required && state.challenge.ownerId === this.#sourceId,
	      nextPermitDelay: blocking.waitMs,
	      blockingReason: blocking.reason || (state.intents.length ? "priority" : "")
	    });
	  }
	  /**
	   * 更新跨标签许可策略,但保留 intent、active lease 与固定窗口事件。
	   *
	   * 收紧后的策略只阻止后续 permit;不会中止已在执行的原站请求。
	   */
	  applyRuntimePolicy(policy) {
	    this.#closed || (this.#shortBudget = positiveInteger(
	      policy.shortBudget,
	      this.#shortBudget,
	      "shortBudget"
	    ), this.#longBudget = positiveInteger(
	      policy.longBudget,
	      this.#longBudget,
	      "longBudget"
	    ), this.#minIntervalMs = nonNegativeInteger(
	      policy.minIntervalMs,
	      "minIntervalMs"
	    ), this.#maxConcurrent = positiveInteger(
	      policy.maxConcurrent,
	      this.#maxConcurrent,
	      "maxConcurrent"
	    ), this.#transact((state, now) => {
	      this.#rememberPolicy(state, now);
	    }).catch(this.#onError));
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #runChallenge(signal) {
	    const startedAt = this.#now();
	    for (; !this.#closed && this.#now() - startedAt < this.#challengeMaxWaitMs; ) {
	      if (signal.aborted) throw this.#abortReason(signal);
	      const ownership = await this.#transact((state, now) => state.challenge?.state === "passed" ? "passed" : state.challenge?.state === "active" && state.challenge.required && state.challenge.automaticAttempted && !this.#challengeFocusRequested || state.challenge?.state === "active" && !state.challenge.required && state.challenge.ownerId !== this.#sourceId ? "waiting" : (state.challenge = Object.freeze({
	        ownerId: this.#sourceId,
	        state: "active",
	        required: !1,
	        automaticAttempted: !0,
	        updatedAt: now,
	        expiresAt: now + this.#challengeLeaseTtlMs
	      }), "owner"));
	      if (ownership === "passed") return !0;
	      if (ownership === "waiting") {
	        await this.#wait(this.#challengePollIntervalMs, signal);
	        continue;
	      }
	      try {
	        return await this.#challengePassesProbe(signal) ? (await this.#completeChallengeLease(), !0) : await this.#ownChallengeWindow(signal, startedAt);
	      } catch (error) {
	        try {
	          this.#challengeWindow?.close?.();
	        } catch {
	        }
	        throw this.#challengeWindow = null, await this.#releaseChallengeLease(), error;
	      }
	    }
	    return !1;
	  }
	  async #reconcileRequiredChallenge(signal) {
	    if (!await this.#transact((state, now) => state.challenge?.state !== "active" || !state.challenge.required || !state.challenge.automaticAttempted || state.challenge.recoveryProbeAttempted === !0 ? !1 : (state.challenge = Object.freeze({
	      ...state.challenge,
	      ownerId: this.#sourceId,
	      required: !1,
	      recoveryProbeAttempted: !0,
	      updatedAt: now,
	      expiresAt: now + this.#challengeLeaseTtlMs
	    }), !0))) return !1;
	    if (signal.aborted) throw this.#abortReason(signal);
	    let verified = !1;
	    try {
	      verified = await this.#verifyChallenge(signal);
	    } catch (error) {
	      if (signal.aborted) throw error;
	      this.#onError(error);
	    }
	    return verified ? (await this.#completeChallengeLease(), !0) : (await this.#releaseChallengeLease(), !1);
	  }
	  async #ownChallengeWindow(signal, startedAt) {
	    const challenge = this.#challenge;
	    if (!challenge) return !1;
	    if (!this.#challengeWindow || this.#challengeWindow.closed === !0)
	      try {
	        this.#challengeWindow = challenge.open(
	          this.#challengeHref,
	          READER_CLOUDFLARE_CHALLENGE_WINDOW_NAME,
	          browserCloudflareChallengeFeatures(challenge.screen)
	        );
	      } catch (error) {
	        this.#onError(error), this.#challengeWindow = null;
	      }
	    const popup = this.#challengeWindow;
	    if (!popup)
	      return await this.#releaseChallengeLease(), !1;
	    let popupLoaded = !1, verifyDelayMs = this.#challengeVerifyIntervalMs, nextVerifyAt = this.#now() + verifyDelayMs;
	    const onPopupLoad = () => {
	      popupLoaded = !0, this.#wake();
	    };
	    popup.addEventListener?.("load", onPopupLoad), this.#focusChallengeWindow();
	    try {
	      for (; !this.#closed && this.#now() - startedAt < this.#challengeMaxWaitMs; ) {
	        if (signal.aborted) throw this.#abortReason(signal);
	        if (popup.closed === !0)
	          return this.#challengeWindow = null, await this.#challengePassesProbe(signal) ? (await this.#completeChallengeLease(), !0) : (await this.#releaseChallengeLease(), !1);
	        const inspectedPassed = this.#inspectChallenge(popup) === "passed", probeDue = !!this.#verifyChallenge && (inspectedPassed || popupLoaded || this.#now() >= nextVerifyAt);
	        if (inspectedPassed && !this.#verifyChallenge) {
	          await this.#completeChallengeLease();
	          try {
	            popup.close?.();
	          } catch {
	          }
	          return this.#challengeWindow = null, !0;
	        }
	        if (probeDue) {
	          popupLoaded = !1;
	          const verified = await this.#challengePassesProbe(signal);
	          if (verifyDelayMs = Math.min(1e4, verifyDelayMs * 2), nextVerifyAt = this.#now() + verifyDelayMs, verified) {
	            await this.#completeChallengeLease();
	            try {
	              popup.close?.();
	            } catch {
	            }
	            return this.#challengeWindow = null, !0;
	          }
	        }
	        if (!await this.#transact((state, now) => state.challenge?.state !== "active" || state.challenge.ownerId !== this.#sourceId ? !1 : (state.challenge = Object.freeze({
	          ...state.challenge,
	          updatedAt: now,
	          expiresAt: now + this.#challengeLeaseTtlMs
	        }), !0))) {
	          try {
	            popup.close?.();
	          } catch {
	          }
	          return this.#challengeWindow = null, !1;
	        }
	        await this.#wait(this.#challengePollIntervalMs, signal);
	      }
	      if (await this.#challengePassesProbe(signal)) {
	        await this.#completeChallengeLease();
	        try {
	          popup.close?.();
	        } catch {
	        }
	        return this.#challengeWindow = null, !0;
	      }
	      try {
	        popup.close?.();
	      } catch {
	      }
	      return this.#challengeWindow = null, await this.#releaseChallengeLease(), !1;
	    } finally {
	      popup.removeEventListener?.("load", onPopupLoad);
	    }
	  }
	  async #challengePassesProbe(signal) {
	    if (!this.#verifyChallenge) return !1;
	    try {
	      return await this.#verifyChallenge(signal);
	    } catch (error) {
	      if (signal.aborted) throw error;
	      return this.#onError(error), !1;
	    }
	  }
	  #focusChallengeWindow() {
	    if (!(!this.#challengeFocusRequested || !this.#challengeWindow)) {
	      this.#challengeFocusRequested = !1;
	      try {
	        this.#challengeWindow.focus?.();
	      } catch {
	      }
	    }
	  }
	  async #completeChallengeLease() {
	    await this.#transact((state, now) => {
	      state.challenge?.state !== "active" || state.challenge.ownerId !== this.#sourceId || (state.challenge = Object.freeze({
	        ownerId: this.#sourceId,
	        state: "passed",
	        required: !1,
	        automaticAttempted: !0,
	        recoveryProbeAttempted: state.challenge.recoveryProbeAttempted === !0,
	        updatedAt: now,
	        expiresAt: now + this.#challengePassedTtlMs
	      }));
	    });
	  }
	  async #releaseChallengeLease() {
	    await this.#transact((state, now) => {
	      state.challenge?.state === "active" && state.challenge.ownerId === this.#sourceId && (state.challenge = Object.freeze({
	        ownerId: "",
	        state: "active",
	        required: !0,
	        automaticAttempted: !0,
	        recoveryProbeAttempted: state.challenge.recoveryProbeAttempted === !0,
	        updatedAt: now,
	        expiresAt: now + this.#challengeMaxWaitMs
	      }));
	    });
	  }
	  #tryGrant(state, now, intentId, queuedAt, priority) {
	    this.#rememberPolicy(state, now);
	    const policy = this.#effectivePolicy(state);
	    if (state.intents.find((intent) => intent.id === intentId) ? state.intents = state.intents.map((intent) => intent.id === intentId ? Object.freeze({ ...intent, expiresAt: now + this.#intentTtlMs }) : intent) : state.intents.push(Object.freeze({
	      id: intentId,
	      ownerId: this.#sourceId,
	      priority,
	      queuedAt,
	      expiresAt: now + this.#intentTtlMs
	    })), state.intents.sort((left, right) => PRIORITY_WEIGHT[left.priority] - PRIORITY_WEIGHT[right.priority] || left.queuedAt - right.queuedAt || left.id.localeCompare(right.id)), state.intents[0]?.id !== intentId)
	      return Object.freeze({
	        granted: !1,
	        waitMs: 80,
	        reason: "priority"
	      });
	    const blocking = this.#blockingState(state, now, policy), { waitMs } = blocking;
	    if (waitMs > 0)
	      return Object.freeze({
	        granted: !1,
	        waitMs,
	        reason: blocking.reason
	      });
	    state.intents.shift(), state.events.push(now);
	    const permitId = `${this.#sourceId}:permit:${this.#createId()}`;
	    return state.active.push(Object.freeze({
	      id: permitId,
	      ownerId: this.#sourceId,
	      expiresAt: now + this.#permitTtlMs
	    })), Object.freeze({
	      granted: !0,
	      permitId,
	      waitMs: 0,
	      reason: "",
	      recoveryProbe: !1
	    });
	  }
	  #rememberPolicy(state, now) {
	    const policy = Object.freeze({
	      ownerId: this.#sourceId,
	      shortBudget: this.#shortBudget,
	      longBudget: this.#longBudget,
	      minIntervalMs: this.#minIntervalMs,
	      maxConcurrent: this.#maxConcurrent,
	      expiresAt: now + this.#policyTtlMs
	    }), index = state.policies.findIndex(
	      (candidate) => candidate.ownerId === this.#sourceId
	    );
	    index >= 0 ? state.policies[index] = policy : state.policies.push(policy);
	  }
	  #effectivePolicy(state) {
	    return Object.freeze(this.#configuredPolicy(state));
	  }
	  #configuredPolicy(state) {
	    const effective = {
	      shortBudget: this.#shortBudget,
	      longBudget: this.#longBudget,
	      minIntervalMs: this.#minIntervalMs,
	      maxConcurrent: this.#maxConcurrent
	    };
	    for (const policy of state.policies)
	      policy.ownerId !== this.#sourceId && (effective.shortBudget = Math.min(
	        effective.shortBudget,
	        policy.shortBudget
	      ), effective.longBudget = Math.min(
	        effective.longBudget,
	        policy.longBudget
	      ), effective.minIntervalMs = Math.max(
	        effective.minIntervalMs,
	        policy.minIntervalMs
	      ), effective.maxConcurrent = Math.min(
	        effective.maxConcurrent,
	        policy.maxConcurrent
	      ));
	    return effective;
	  }
	  #blockingState(state, now, policy) {
	    if (state.challenge?.state === "active")
	      return Object.freeze({
	        waitMs: Math.max(25, state.challenge.expiresAt - now),
	        reason: "challenge",
	        recoveryProbe: !1
	      });
	    const activeDelay = state.active.length >= policy.maxConcurrent ? Math.max(
	      25,
	      Math.min(...state.active.map((permit) => permit.expiresAt - now))
	    ) : 0, shortEvents = state.events.filter(
	      (at) => at > now - this.#shortWindowMs
	    ), shortWindowDelay = this.#windowDelay(
	      shortEvents,
	      policy.shortBudget,
	      this.#shortWindowMs,
	      now
	    ), longWindowDelay = this.#windowDelay(
	      state.events,
	      policy.longBudget,
	      this.#longWindowMs,
	      now
	    ), latest = state.events.at(-1) ?? 0, intervalDelay = Math.max(
	      0,
	      latest + policy.minIntervalMs - now
	    ), candidates = [
	      ["concurrency", activeDelay],
	      ["interval", intervalDelay],
	      ["10s", shortWindowDelay],
	      ["60s", longWindowDelay]
	    ];
	    let reason = "", waitMs = 0;
	    for (const [candidateReason, delay] of candidates)
	      delay > waitMs && (reason = candidateReason, waitMs = delay);
	    return Object.freeze({ waitMs, reason, recoveryProbe: !1 });
	  }
	  #windowDelay(events, budget, windowMs, now) {
	    if (events.length < budget) return 0;
	    const boundary = events[events.length - budget];
	    return boundary === void 0 ? 0 : Math.max(0, boundary + windowMs - now + 1);
	  }
	  #permit(permitId, recoveryProbe, waitReason) {
	    let released = !1;
	    return Object.freeze({
	      recoveryProbe,
	      waitReason,
	      release: () => {
	        released || (released = !0, this.#transact((state) => {
	          state.active = state.active.filter((permit) => permit.id !== permitId);
	        }).catch(this.#onError));
	      }
	    });
	  }
	  async #transact(operation) {
	    const execute = async () => {
	      const now = this.#now(), state = this.#read(now), result = await operation(state, now);
	      return state.updatedAt = now, this.#write(state), this.#publish(), this.#wake(), result;
	    };
	    if (this.#locks)
	      return this.#locks.request(
	        READER_REQUEST_PERMIT_LOCK,
	        { mode: "exclusive" },
	        execute
	      );
	    const transaction = this.#localTransactionTail.catch(() => {
	    }).then(execute);
	    return this.#localTransactionTail = transaction.then(
	      () => {
	      },
	      () => {
	      }
	    ), transaction;
	  }
	  #read(now) {
	    try {
	      const stored = this.#storage.getItem(READER_REQUEST_PERMIT_STORAGE_KEY);
	      if (stored)
	        return normalizeState(JSON.parse(stored), now, this.#longWindowMs);
	    } catch (error) {
	      if (this.#onError(error), this.#locks) throw error;
	    }
	    return normalizeState(
	      this.#locks ? null : this.#fallbackState,
	      now,
	      this.#longWindowMs
	    );
	  }
	  #write(state) {
	    this.#locks || (this.#fallbackState = normalizeState(state, this.#now(), this.#longWindowMs));
	    try {
	      this.#storage.setItem(
	        READER_REQUEST_PERMIT_STORAGE_KEY,
	        JSON.stringify(state)
	      );
	    } catch (error) {
	      if (this.#onError(error), this.#locks) throw error;
	    }
	  }
	  #publish() {
	    this.#postChannelMessage("updated");
	  }
	  #postChannelMessage(type) {
	    try {
	      this.#channel?.postMessage(Object.freeze({
	        schemaVersion: 1,
	        sourceId: this.#sourceId,
	        type
	      }));
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  #wait(milliseconds, signal) {
	    return new Promise((resolve, reject) => {
	      let settled = !1;
	      const earliestWakeAt = this.#now() + 25, finish = (error) => {
	        settled || (settled = !0, clearTimeout(timer), this.#waiters.delete(wake), signal.removeEventListener("abort", abort), error !== void 0 ? reject(error) : resolve());
	      }, wake = () => {
	        const remaining = earliestWakeAt - this.#now();
	        if (remaining > 0) {
	          clearTimeout(timer), timer = setTimeout(wake, remaining);
	          return;
	        }
	        finish();
	      }, abort = () => finish(this.#abortReason(signal));
	      let timer = setTimeout(
	        wake,
	        Math.max(25, Math.min(1e3, milliseconds || 80))
	      );
	      this.#waiters.add(wake), signal.addEventListener("abort", abort, { once: !0 });
	    });
	  }
	  async #removeOwnedState() {
	    try {
	      await this.#transact((state, now) => {
	        state.intents = state.intents.filter(
	          (intent) => intent.ownerId !== this.#sourceId
	        ), state.active = state.active.filter(
	          (permit) => permit.ownerId !== this.#sourceId
	        ), state.policies = state.policies.filter(
	          (policy) => policy.ownerId !== this.#sourceId
	        ), state.challenge?.state === "active" && state.challenge.ownerId === this.#sourceId && (state.challenge = Object.freeze({
	          ownerId: "",
	          state: "active",
	          required: !0,
	          automaticAttempted: !0,
	          recoveryProbeAttempted: state.challenge.recoveryProbeAttempted === !0,
	          updatedAt: now,
	          expiresAt: now + this.#challengeMaxWaitMs
	        }));
	      });
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  #wake() {
	    for (const waiter of this.#waiters) waiter();
	    this.#waiters.clear();
	  }
	  #assertOpen() {
	    if (this.#closed || this.scope.destroyed)
	      throw new Error("BrowserSharedRequestPermit 已销毁");
	  }
	  #abortReason(signal) {
	    return signal.reason ?? new DOMException("Aborted", "AbortError");
	  }
	  #onChannelMessage = (event) => {
	    const message = event.data;
	    message?.schemaVersion === 1 && message.sourceId !== this.#sourceId && message.type === "challenge-focus" && this.#challengePromise && (this.#challengeFocusRequested = !0, this.#focusChallengeWindow()), this.#wake();
	  };
	  #onStorage = (event) => {
	    const key = event.key;
	    (key === null || key === READER_REQUEST_PERMIT_STORAGE_KEY) && this.#wake();
	  };
	}
}, "3d5947f9ebe73382f777cc139f097b0ce36c31d6d0f112f64c375e6e843ef93b");

/* Source: lite/src/network/coordinated-request-client.ts */
runtime.register("src/network/coordinated-request-client.js", function(module, exports, require) {
	var coordinated_request_client_exports = {};
	__export(coordinated_request_client_exports, {
	  CoordinatedRequestClient: () => CoordinatedRequestClient,
	  RequestChallengeWaitSuppressedError: () => RequestChallengeWaitSuppressedError,
	  RequestCloudflareChallengeError: () => RequestCloudflareChallengeError,
	  RequestRateLimitError: () => RequestRateLimitError,
	  RequestStatusError: () => RequestStatusError,
	  abortableDelay: () => abortableDelay,
	  requestFailureKind: () => requestFailureKind
	});
	module.exports = __toCommonJS(coordinated_request_client_exports);
	var import_request_scheduler = require("./request-scheduler.js");
	const PRIORITY_WEIGHT = Object.freeze({
	  critical: 0,
	  interactive: 1,
	  nested: 2,
	  visible: 3,
	  prefetch: 4,
	  background: 5
	});
	function requestFailureKind(status) {
	  return status === 400 || status === 422 ? "validation" : status === 401 ? "authentication" : status === 403 ? "forbidden" : status === 404 || status === 410 ? "not-found" : status === 408 ? "timeout" : status === 409 || status === 412 ? "conflict" : status === 429 ? "rate-limit" : status === 425 || status >= 500 && status <= 599 ? "server" : status >= 400 && status <= 499 ? "client" : "unknown";
	}
	class RequestStatusError extends Error {
	  status;
	  cloudflareMitigated;
	  kind;
	  constructor(status, options = {}) {
	    super(`HTTP ${status}`), this.name = "RequestStatusError", this.status = status, this.cloudflareMitigated = options.cloudflareMitigated === !0, this.kind = options.kind ?? requestFailureKind(status);
	  }
	}
	class RequestCloudflareChallengeError extends RequestStatusError {
	  href;
	  constructor(status, href) {
	    super(status, {
	      cloudflareMitigated: !0,
	      kind: "cloudflare"
	    }), this.name = "RequestCloudflareChallengeError", this.href = String(href);
	  }
	}
	class RequestChallengeWaitSuppressedError extends Error {
	  code = "challenge-superseded";
	  cloudflareMitigated = !0;
	  constructor() {
	    super("排队期间已进入 Cloudflare 验证,本次写入不在过盾后自动追发"), this.name = "RequestChallengeWaitSuppressedError";
	  }
	}
	class RequestRateLimitError extends RequestStatusError {
	  decision;
	  constructor(decision) {
	    super(429), this.name = "RequestRateLimitError", this.decision = decision;
	  }
	}
	function nonNegativeInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 0)
	    throw new RangeError(`${name} 必须是非负安全整数`);
	  return value;
	}
	function abortableDelay(milliseconds, signal) {
	  return new Promise((resolve, reject) => {
	    if (signal.aborted) {
	      reject(signal.reason);
	      return;
	    }
	    const onAbort = () => {
	      clearTimeout(timer), reject(signal.reason);
	    }, timer = setTimeout(() => {
	      signal.removeEventListener("abort", onAbort), resolve();
	    }, milliseconds);
	    signal.addEventListener("abort", onAbort, { once: !0 });
	  });
	}
	class CoordinatedRequestClient {
	  scheduler;
	  rateLimitPolicy;
	  permitPort;
	  #delay;
	  #defaultMax429Retries;
	  #defaultMaxChallengeRetries;
	  #observer;
	  #now;
	  #onCoordinationError;
	  #requests = /* @__PURE__ */ new Map();
	  #disposed = !1;
	  constructor(options) {
	    this.rateLimitPolicy = options.rateLimitPolicy, this.permitPort = options.permitPort, this.scheduler = new import_request_scheduler.RequestScheduler({
	      ...options.scheduler,
	      startGate: options.permitPort
	    }), this.#delay = options.delay ?? abortableDelay, this.#defaultMax429Retries = nonNegativeInteger(
	      options.defaultMax429Retries ?? 1,
	      "defaultMax429Retries"
	    ), this.#defaultMaxChallengeRetries = nonNegativeInteger(
	      options.defaultMaxChallengeRetries ?? 1,
	      "defaultMaxChallengeRetries"
	    ), this.#observer = options.observer ?? null, this.#now = options.now ?? Date.now, this.#onCoordinationError = options.onCoordinationError ?? (() => {
	    });
	  }
	  request(options, transport) {
	    const key = String(options.key).trim();
	    if (!key) return Promise.reject(new Error("request key 不能为空"));
	    const priority = options.priority ?? "visible";
	    if (this.#disposed)
	      return Promise.reject(new Error("request client 已销毁"));
	    if (options.signal?.aborted)
	      return Promise.reject(options.signal.reason);
	    const existing = this.#requests.get(key);
	    if (existing && !existing.controller.signal.aborted)
	      return this.#promoteLogical(existing, {
	        priority,
	        droppable: options.droppable === !0,
	        max429Retries: options.max429Retries ?? this.#defaultMax429Retries,
	        maxChallengeRetries: options.maxChallengeRetries ?? this.#defaultMaxChallengeRetries
	      }), this.#join(existing, options.signal);
	    const controller = new AbortController(), logical = {
	      promise: Promise.resolve(void 0),
	      controller,
	      consumers: /* @__PURE__ */ new Set(),
	      unabortableConsumer: !1,
	      settled: !1,
	      priority,
	      droppable: options.droppable === !0,
	      max429Retries: 0,
	      maxChallengeRetries: 0,
	      currentAttemptKey: ""
	    }, promise = this.#run(logical, options, transport).finally(() => {
	      logical.settled = !0, this.#requests.get(key) === logical && this.#requests.delete(key);
	    });
	    return logical.promise = promise, this.#requests.set(key, logical), this.#join(logical, options.signal);
	  }
	  promote(keyValue, promotion) {
	    const key = String(keyValue).trim();
	    if (!key || this.#disposed) return !1;
	    const logical = this.#requests.get(key);
	    return !logical || logical.controller.signal.aborted || logical.settled ? !1 : (this.#promoteLogical(logical, promotion), !0);
	  }
	  #join(logical, signal) {
	    if (!signal)
	      return logical.unabortableConsumer = !0, logical.promise;
	    if (signal.aborted) return Promise.reject(signal.reason);
	    const consumer = Symbol("request-consumer");
	    return logical.consumers.add(consumer), new Promise((resolve, reject) => {
	      let settled = !1;
	      const finish = (complete, value) => {
	        settled || (settled = !0, signal.removeEventListener("abort", onAbort), logical.consumers.delete(consumer), complete(value));
	      }, fail = (cause) => {
	        settled || (settled = !0, signal.removeEventListener("abort", onAbort), logical.consumers.delete(consumer), reject(cause));
	      }, onAbort = () => {
	        fail(signal.reason), !logical.settled && !logical.unabortableConsumer && logical.consumers.size === 0 && !logical.controller.signal.aborted && logical.controller.abort(signal.reason);
	      };
	      signal.addEventListener("abort", onAbort, { once: !0 }), logical.promise.then(
	        (value) => finish(resolve, value),
	        fail
	      );
	    });
	  }
	  applyRuntimePolicy(policy) {
	    this.scheduler.applyRuntimePolicy(policy);
	  }
	  /**
	   * 把中央 429 错误投影成长任务可等待的续传凭据。
	   *
	   * 使用 retryAt 计算剩余时间,避免错误沿调用栈返回后再次等待完整 Retry-After;
	   * 等待结束只允许长任务重新排队,绝不替代下一次请求的 scheduler/shared permit。
	   */
	  rateLimitResume(error) {
	    if (this.#disposed || !(error instanceof RequestRateLimitError)) return null;
	    const waitMs = Math.max(
	      0,
	      Math.ceil(error.decision.retryAt - this.#now())
	    );
	    return Object.freeze({
	      decision: error.decision,
	      waitMs,
	      wait: (signal) => this.#delay(waitMs, signal)
	    });
	  }
	  /**
	   * 把长任务中断统一投影为可取消恢复凭据。
	   *
	   * 普通 429 复用 Retry-After;Cloudflare 只等待共享 challenge lease,不在长任务
	   * 内重放探针、打开第二个窗口或绕过 scheduler。
	   */
	  requestResume(error) {
	    const rateLimit = this.rateLimitResume(error);
	    if (rateLimit)
	      return Object.freeze({
	        ...rateLimit,
	        kind: "rate-limit"
	      });
	    if (this.#disposed || !(error instanceof RequestCloudflareChallengeError) || !this.permitPort.resolveCloudflareChallenge) return null;
	    const resolve = this.permitPort.resolveCloudflareChallenge.bind(
	      this.permitPort
	    );
	    return Object.freeze({
	      kind: "cloudflare-challenge",
	      waitMs: 0,
	      wait: async (signal) => {
	        if (signal.aborted) throw signal.reason;
	        if (!await resolve({
	          href: error.href,
	          signal
	        })) throw error;
	      }
	    });
	  }
	  /** 清除本实例的 429 范围判定证据;共享固定窗口与已记录启动次数原样保留。 */
	  async resetRateLimits() {
	    this.rateLimitPolicy.reset(), await this.permitPort.resetRateLimits?.();
	  }
	  destroy() {
	    if (!this.#disposed) {
	      this.#disposed = !0;
	      for (const request of this.#requests.values())
	        request.controller.abort(new Error("request client 已销毁"));
	      this.scheduler.destroy();
	    }
	  }
	  async #run(logical, options, transport) {
	    const method = String(options.method ?? "GET").toUpperCase();
	    logical.max429Retries = nonNegativeInteger(
	      options.max429Retries ?? this.#defaultMax429Retries,
	      "max429Retries"
	    ), logical.maxChallengeRetries = nonNegativeInteger(
	      options.maxChallengeRetries ?? this.#defaultMaxChallengeRetries,
	      "maxChallengeRetries"
	    );
	    let rateLimitRetries = 0, challengeRetries = 0;
	    for (let attempt = 0; ; attempt += 1) {
	      logical.currentAttemptKey = `${options.key}:attempt:${attempt}`;
	      const queuedAt = this.#now();
	      let observationId = this.#beginQueuedObservation(
	        options,
	        method,
	        attempt,
	        logical.priority,
	        queuedAt
	      ), observationStarted = !1, attemptWaitReason = "", response;
	      try {
	        response = await this.scheduler.schedule(
	          {
	            key: logical.currentAttemptKey,
	            priority: logical.priority,
	            lane: options.lane ?? "standard",
	            ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
	            signal: logical.controller.signal,
	            droppable: logical.droppable,
	            onStart: (timing) => {
	              observationStarted = !0, attemptWaitReason = timing.waitReason, observationId = this.#markObservationStarted(
	                observationId,
	                options,
	                method,
	                attempt,
	                logical.priority,
	                timing
	              );
	            }
	          },
	          (signal) => {
	            if (options.suppressAfterChallengeWait === !0 && attemptWaitReason === "challenge")
	              throw new RequestChallengeWaitSuppressedError();
	            return transport({ signal, attempt });
	          }
	        );
	      } catch (error) {
	        logical.currentAttemptKey = "";
	        const reason = this.#errorCode(error);
	        throw observationId === null ? this.#recordControlled(
	          options,
	          method,
	          attempt,
	          reason,
	          queuedAt
	        ) : observationStarted ? this.#finishObservation(observationId, {
	          error: reason,
	          cloudflareMitigated: !!error && typeof error == "object" && "cloudflareMitigated" in error && error.cloudflareMitigated === !0
	        }) : this.#cancelObservation(observationId, reason), error;
	      }
	      if (logical.currentAttemptKey = "", this.#finishObservation(observationId, {
	        status: response.status,
	        cloudflareMitigated: response.cloudflareMitigated === !0,
	        ...response.retryAfter === void 0 ? {} : { retryAfter: String(response.retryAfter ?? "") },
	        ...response.rateLimitCode === void 0 ? {} : { rateLimitCode: String(response.rateLimitCode ?? "") },
	        ...response.serverLimit === void 0 ? {} : { serverLimit: String(response.serverLimit ?? "") },
	        ...response.serverRemaining === void 0 ? {} : { serverRemaining: String(response.serverRemaining ?? "") },
	        ...response.serverReset === void 0 ? {} : { serverReset: String(response.serverReset ?? "") }
	      }), !response.ok && response.cloudflareMitigated === !0) {
	        if (options.blockOnCloudflareChallenge === !1)
	          throw new RequestStatusError(response.status, {
	            cloudflareMitigated: !0
	          });
	        if (!this.permitPort.noteCloudflareChallenge && !this.permitPort.resolveCloudflareChallenge)
	          throw new RequestStatusError(response.status, {
	            cloudflareMitigated: !0,
	            kind: "cloudflare"
	          });
	        let passed = !1;
	        try {
	          this.permitPort.resolveCloudflareChallenge && challengeRetries < logical.maxChallengeRetries ? passed = await this.permitPort.resolveCloudflareChallenge({
	            href: String(options.input),
	            signal: logical.controller.signal
	          }) : await this.permitPort.noteCloudflareChallenge?.({
	            href: String(options.input),
	            force: challengeRetries > 0
	          });
	        } catch (error) {
	          if (logical.controller.signal.aborted) throw error;
	          this.#onCoordinationError(error);
	          try {
	            await this.permitPort.noteCloudflareChallenge?.({
	              href: String(options.input),
	              force: challengeRetries > 0
	            });
	          } catch (fallbackError) {
	            this.#onCoordinationError(fallbackError);
	          }
	        }
	        if (passed) {
	          this.rateLimitPolicy.reset(), challengeRetries += 1;
	          continue;
	        }
	        throw new RequestCloudflareChallengeError(
	          response.status,
	          String(options.input)
	        );
	      }
	      if (response.status === 429) {
	        const decision = this.rateLimitPolicy.noteRateLimit({
	          input: options.input,
	          method,
	          ...response.retryAfter === void 0 ? {} : { retryAfter: response.retryAfter },
	          knownGlobalWindow: response.knownGlobalRateLimitWindow === !0,
	          ...response.rateLimitWindow === void 0 ? {} : { globalWindow: response.rateLimitWindow }
	        });
	        if (decision.scope === "global")
	          try {
	            await this.permitPort.noteRateLimit(decision);
	          } catch (error) {
	            this.#onCoordinationError(error);
	          }
	        if (!([
	          "critical",
	          "interactive",
	          "nested",
	          "visible"
	        ].includes(logical.priority) && rateLimitRetries < logical.max429Retries)) throw new RequestRateLimitError(decision);
	        rateLimitRetries += 1, await this.#delay(decision.waitMs, logical.controller.signal);
	        continue;
	      }
	      if (!response.ok)
	        throw new RequestStatusError(response.status, {
	          cloudflareMitigated: response.cloudflareMitigated === !0
	        });
	      return response.value;
	    }
	  }
	  #promoteLogical(logical, promotion) {
	    logical.droppable = logical.droppable && promotion.droppable, promotion.max429Retries !== void 0 && (logical.max429Retries = Math.max(
	      logical.max429Retries,
	      nonNegativeInteger(promotion.max429Retries, "max429Retries")
	    )), promotion.maxChallengeRetries !== void 0 && (logical.maxChallengeRetries = Math.max(
	      logical.maxChallengeRetries,
	      nonNegativeInteger(
	        promotion.maxChallengeRetries,
	        "maxChallengeRetries"
	      )
	    )), PRIORITY_WEIGHT[promotion.priority] < PRIORITY_WEIGHT[logical.priority] && (logical.priority = promotion.priority), logical.currentAttemptKey && this.scheduler.promoteQueued(
	      logical.currentAttemptKey,
	      logical.priority,
	      logical.droppable
	    );
	  }
	  #beginQueuedObservation(options, method, attempt, priority, queuedAt) {
	    if (!this.#observer) return null;
	    try {
	      return this.#observer.begin({
	        href: String(options.input),
	        method,
	        transport: "scheduler",
	        source: "reader",
	        phase: "queued",
	        queuedAt,
	        priority,
	        attempt: attempt + 1,
	        recoveryProbe: attempt > 0,
	        callSite: options.callSite ?? ""
	      });
	    } catch (error) {
	      return this.#onCoordinationError(error), null;
	    }
	  }
	  #markObservationStarted(id, options, method, attempt, priority, timing) {
	    if (!this.#observer) return null;
	    try {
	      return id !== null && this.#observer.markStarted({
	        id,
	        queuedAt: timing.queuedAt,
	        permittedAt: timing.permittedAt,
	        startedAt: timing.startedAt,
	        priority,
	        recoveryProbe: timing.recoveryProbe,
	        waitReason: timing.waitReason || (timing.permittedAt - timing.queuedAt > 0.5 ? "scheduler" : "")
	      }) ? id : this.#observer.begin({
	        href: String(options.input),
	        method,
	        transport: "scheduler",
	        source: "reader",
	        phase: "running",
	        queuedAt: timing.queuedAt,
	        permittedAt: timing.permittedAt,
	        startedAt: timing.startedAt,
	        priority,
	        attempt: attempt + 1,
	        recoveryProbe: timing.recoveryProbe,
	        waitReason: timing.waitReason || (timing.permittedAt - timing.queuedAt > 0.5 ? "scheduler" : ""),
	        callSite: options.callSite ?? ""
	      });
	    } catch (error) {
	      return this.#onCoordinationError(error), null;
	    }
	  }
	  #cancelObservation(id, reason) {
	    if (this.#observer)
	      try {
	        this.#observer.cancel(id, { reason }) || this.#observer.finish(id, { error: reason });
	      } catch (error) {
	        this.#onCoordinationError(error);
	      }
	  }
	  #finishObservation(id, input) {
	    if (!(id === null || !this.#observer))
	      try {
	        this.#observer.finish(id, input);
	      } catch (error) {
	        this.#onCoordinationError(error);
	      }
	  }
	  #recordControlled(options, method, attempt, reason, queuedAt = this.#now()) {
	    if (this.#observer)
	      try {
	        this.#observer.begin({
	          href: String(options.input),
	          method,
	          transport: "scheduler",
	          source: "reader",
	          queuedAt,
	          startedAt: this.#now(),
	          priority: options.priority ?? "visible",
	          attempt: attempt + 1,
	          recoveryProbe: attempt > 0,
	          waitReason: reason,
	          callSite: options.callSite ?? "",
	          controlReason: reason
	        });
	      } catch (error) {
	        this.#onCoordinationError(error);
	      }
	  }
	  #errorCode(error) {
	    if (error && typeof error == "object") {
	      const message = "message" in error ? String(error.message ?? "") : "";
	      if (message.includes("滚出当前视口")) return "viewport-change";
	      if (message.includes("升级为可见快车道")) return "priority-upgrade";
	      if (message.includes("已切换") || message.includes("打开已被替代") || message.includes("新的打开事务")) return "topic-switch";
	      if (message.includes("已关闭") || message.includes("离开 Topic") || message.includes("读取链已结束") || message.includes("Topic session closed")) return "topic-close";
	      if (message.includes("request client 已销毁") || message.includes("Reader runtime 已销毁") || message.includes("Reader application 已销毁")) return "context-close";
	      const code = "code" in error && typeof error.code == "string" ? error.code : "";
	      if (code) return code.slice(0, 80);
	      const name = "name" in error ? String(error.name ?? "") : "";
	      if (name) return name.slice(0, 80);
	    }
	    return "request-failed";
	  }
	}
}, "885801a2627fe9cf4a9196ae4b18e4e26f79f102438e2983e9fad4c348da794a");

/* Source: lite/src/network/discourse-native-read-transport.ts */
runtime.register("src/network/discourse-native-read-transport.js", function(module, exports, require) {
	var discourse_native_read_transport_exports = {};
	__export(discourse_native_read_transport_exports, {
	  BrowserDiscourseNativeAjaxPort: () => BrowserDiscourseNativeAjaxPort,
	  BrowserDiscourseNativeMutationTransport: () => BrowserDiscourseNativeMutationTransport,
	  BrowserDiscourseNativeReadTransport: () => BrowserDiscourseNativeReadTransport,
	  discourseNativeAjaxAvailable: () => discourseNativeAjaxAvailable,
	  discourseNativeFailureResponse: () => discourseNativeFailureResponse
	});
	module.exports = __toCommonJS(discourse_native_read_transport_exports);
	var import_native_request_descriptors = require("../discourse/native-request-descriptors.js"), import_request_rate_limit_policy = require("./request-rate-limit-policy.js"), import_value_record = require("../kernel/value-record.js");
	const nativeReadTransportBrand = Symbol("DiscourseNativeReadTransport"), nativeMutationTransportBrand = Symbol("DiscourseNativeMutationTransport");
	function statusFromError(error) {
	  const record = (0, import_value_record.objectRecord)(error), response = (0, import_value_record.objectRecord)(record?.response), xhr = (0, import_value_record.objectRecord)(record?.jqXHR);
	  for (const value of [
	    record?.status,
	    record?.statusCode,
	    response?.status,
	    xhr?.status
	  ]) {
	    const status = Number(value);
	    if (Number.isSafeInteger(status) && status >= 100 && status <= 599)
	      return status;
	  }
	  return 0;
	}
	function responseHeader(error, name) {
	  const record = (0, import_value_record.objectRecord)(error), candidates = [error, record?.jqXHR, record?.response];
	  for (const candidate of candidates) {
	    const getResponseHeader = (0, import_value_record.objectRecord)(candidate)?.getResponseHeader;
	    if (typeof getResponseHeader == "function")
	      try {
	        const value = getResponseHeader.call(candidate, name);
	        if (value != null && String(value).trim())
	          return String(value);
	      } catch {
	      }
	  }
	  return null;
	}
	function retryAfterFromError(error) {
	  const record = (0, import_value_record.objectRecord)(error), response = (0, import_value_record.objectRecord)(record?.response);
	  for (const value of [
	    record?.retryAfter,
	    record?.retry_after,
	    response?.retryAfter,
	    response?.retry_after,
	    responseHeader(error, "Retry-After")
	  ])
	    if (value != null && String(value).trim())
	      return String(value);
	  return null;
	}
	function responseTextFromError(error) {
	  const record = (0, import_value_record.objectRecord)(error), candidates = [error, record?.jqXHR, record?.response];
	  for (const candidate of candidates) {
	    const response = (0, import_value_record.objectRecord)(candidate);
	    for (const value of [
	      response?.responseText,
	      response?.body,
	      response?.data
	    ])
	      if (typeof value == "string" && value.trim()) return value;
	  }
	  return "";
	}
	function cloudflareChallengeHtml(error) {
	  const body = responseTextFromError(error);
	  return body ? /<title>\s*(?:Just a moment(?:\.\.\.)?|请稍候(?:…|\.\.\.)?)\s*<\/title>/i.test(body) || /window\._cf_chl_opt\b/.test(body) && /\/cdn-cgi\/challenge-platform\//.test(body) : !1;
	}
	function cloudflareMitigatedFromError(error) {
	  const record = (0, import_value_record.objectRecord)(error), response = (0, import_value_record.objectRecord)(record?.response), xhr = (0, import_value_record.objectRecord)(record?.jqXHR);
	  return record?.cloudflareMitigated === !0 || response?.cloudflareMitigated === !0 || xhr?.cloudflareMitigated === !0 || responseHeader(error, "cf-mitigated")?.trim().toLowerCase() === "challenge" || cloudflareChallengeHtml(error);
	}
	function discourseNativeFailureResponse(error) {
	  const status = statusFromError(error);
	  if (!status) return null;
	  const rateLimitCode = responseHeader(error, "Discourse-Rate-Limit-Error-Code") ?? responseHeader(error, "X-Discourse-Rate-Limit-Error-Code") ?? "", rateLimitWindow = (0, import_request_rate_limit_policy.rateLimitWindowFromCode)(rateLimitCode);
	  return Object.freeze({
	    ok: !1,
	    status,
	    value: void 0,
	    retryAfter: retryAfterFromError(error),
	    rateLimitCode,
	    rateLimitWindow,
	    knownGlobalRateLimitWindow: rateLimitWindow !== "unknown",
	    cloudflareMitigated: cloudflareMitigatedFromError(error)
	  });
	}
	function normalizedOrigin(value) {
	  const normalized = String(value ?? "").trim();
	  return normalized ? new URL(normalized).origin : "";
	}
	function nativePath(value, origin) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error("Discourse 原生读取 path 不能为空");
	  if (/^[\\/]{2}/.test(normalized))
	    throw new Error("Discourse 原生读取拒绝跨源 URL");
	  if (normalized.startsWith("/")) return normalized;
	  let parsed;
	  try {
	    parsed = new URL(normalized);
	  } catch {
	    throw new Error("Discourse 原生读取只接受站内绝对路径");
	  }
	  if (!origin || parsed.origin !== origin)
	    throw new Error("Discourse 原生读取拒绝跨源 URL");
	  return `${parsed.pathname}${parsed.search}`;
	}
	function resolveNativeAjax(host) {
	  const loaded = host.lookupModule("discourse/lib/ajax"), module2 = (0, import_value_record.objectRecord)(loaded), defaultExport = (0, import_value_record.objectRecord)(module2?.default), owner = module2 && typeof module2.ajax == "function" ? module2 : defaultExport && typeof defaultExport.ajax == "function" ? defaultExport : null;
	  if (!owner)
	    throw new Error("Discourse 原生模块 discourse/lib/ajax#ajax 不可用");
	  return {
	    owner,
	    ajax: owner.ajax
	  };
	}
	function discourseNativeAjaxAvailable(host) {
	  try {
	    return resolveNativeAjax(host), !0;
	  } catch {
	    return !1;
	  }
	}
	async function executeNativeAjax(resolved, origin, input) {
	  if (input.signal.aborted) throw input.signal.reason;
	  const path = nativePath(input.path, origin), options = {
	    type: input.method,
	    ...input.headers && Object.keys(input.headers).length ? { headers: { ...input.headers } } : {},
	    ...input.data === void 0 ? {} : { data: { ...input.data } },
	    ...input.noStore ? { cache: !1 } : {}
	  };
	  let pending;
	  try {
	    pending = resolved.ajax.call(resolved.owner, path, options);
	  } catch (error) {
	    const failure = discourseNativeFailureResponse(error);
	    if (!failure) throw error;
	    return failure;
	  }
	  const abort = () => {
	    try {
	      pending.abort?.();
	    } catch {
	    }
	  };
	  input.signal.addEventListener("abort", abort, { once: !0 });
	  try {
	    const value = await pending;
	    if (input.signal.aborted) throw input.signal.reason;
	    return { ok: !0, status: 200, value };
	  } catch (error) {
	    if (input.signal.aborted) throw input.signal.reason;
	    const failure = discourseNativeFailureResponse(error);
	    if (!failure) throw error;
	    return failure;
	  } finally {
	    input.signal.removeEventListener("abort", abort);
	  }
	}
	class BrowserDiscourseNativeAjaxPort {
	  nativeBinding = "discourse/lib/ajax#ajax";
	  #host;
	  #origin;
	  #resolved = null;
	  constructor(host, options = {}) {
	    this.#host = host, this.#origin = normalizedOrigin(options.origin);
	  }
	  request(input) {
	    return this.#resolved ??= resolveNativeAjax(this.#host), executeNativeAjax(this.#resolved, this.#origin, input);
	  }
	}
	class BrowserDiscourseNativeReadTransport {
	  nativeBinding = "discourse/lib/ajax#ajax";
	  [nativeReadTransportBrand] = !0;
	  #ajax;
	  constructor(host, options = {}) {
	    this.#ajax = host instanceof BrowserDiscourseNativeAjaxPort ? host : new BrowserDiscourseNativeAjaxPort(host, options);
	  }
	  async request(input) {
	    return (0, import_native_request_descriptors.assertDiscourseNativeReadDescriptor)(input.descriptor), this.#ajax.request({
	      path: input.descriptor.path,
	      method: "GET",
	      signal: input.signal,
	      headers: input.descriptor.headers,
	      noStore: input.descriptor.browserCache === "no-store"
	    });
	  }
	}
	class BrowserDiscourseNativeMutationTransport {
	  nativeBinding = "discourse/lib/ajax#ajax";
	  [nativeMutationTransportBrand] = !0;
	  #ajax;
	  constructor(host, options = {}) {
	    this.#ajax = host instanceof BrowserDiscourseNativeAjaxPort ? host : new BrowserDiscourseNativeAjaxPort(host, options);
	  }
	  request(input) {
	    return (0, import_native_request_descriptors.assertDiscourseNativeMutationDescriptor)(input.descriptor), this.#ajax.request({
	      path: input.descriptor.path,
	      method: input.descriptor.method,
	      signal: input.signal,
	      headers: input.descriptor.headers,
	      data: input.descriptor.data,
	      noStore: !0
	    });
	  }
	}
}, "a7a4d027801b7432b77f7a2d3f4f92ad75d122130f406d8ebd37d141d829b3d5");

/* Source: lite/src/network/domain-request-gateway.ts */
runtime.register("src/network/domain-request-gateway.js", function(module, exports, require) {
	var domain_request_gateway_exports = {};
	__export(domain_request_gateway_exports, {
	  DomainRequestGateway: () => DomainRequestGateway
	});
	module.exports = __toCommonJS(domain_request_gateway_exports);
	var import_request_contract = require("./request-contract.js"), import_request_identities = require("./request-identities.js");
	const PRIORITY_WEIGHT = Object.freeze({
	  critical: 0,
	  interactive: 1,
	  nested: 2,
	  visible: 3,
	  prefetch: 4,
	  background: 5
	});
	function cachePolicy(contract, settings) {
	  return Object.freeze({
	    id: contract.cacheKey,
	    kind: settings.kind,
	    tags: Object.freeze([...new Set(settings.tags.map(String))].sort()),
	    freshForMs: settings.freshForMs,
	    retainForMs: settings.retainForMs,
	    persist: settings.persist
	  });
	}
	class DomainRequestGateway {
	  #client;
	  #responses;
	  #executions = /* @__PURE__ */ new Map();
	  constructor(client, responses) {
	    this.#client = client, this.#responses = responses;
	  }
	  loadTopicPosts(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "topic-visible",
	      lane: "topic-batch",
	      namespace: "topic-posts",
	      identity: (0, import_request_identities.topicPostsRequestIdentity)(input)
	    });
	  }
	  loadTopicTarget(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "topic-visible",
	      lane: "topic-batch",
	      namespace: "topic-target",
	      identity: (0, import_request_identities.topicRequestIdentity)(input)
	    });
	  }
	  loadNestedReplies(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "nested-visible",
	      lane: "nested-replies",
	      namespace: "topic-nested",
	      identity: (0, import_request_identities.nestedRequestIdentity)(input)
	    });
	  }
	  promoteTopicPosts(input) {
	    const contract = (0, import_request_contract.createRequestContract)(
	      input.profile ?? "topic-visible",
	      {
	        namespace: "topic-posts",
	        identity: (0, import_request_identities.topicPostsRequestIdentity)(input),
	        ...input.cacheMode === void 0 ? {} : { cacheMode: input.cacheMode }
	      }
	    );
	    return this.#promoteExecution(contract);
	  }
	  promoteNestedReplies(input) {
	    const contract = (0, import_request_contract.createRequestContract)(
	      input.profile ?? "nested-visible",
	      {
	        namespace: "topic-nested",
	        identity: (0, import_request_identities.nestedRequestIdentity)(input),
	        ...input.cacheMode === void 0 ? {} : { cacheMode: input.cacheMode }
	      }
	    );
	    return this.#promoteExecution(contract);
	  }
	  loadNotificationPage(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "notification-visible",
	      lane: "standard",
	      namespace: "notifications",
	      identity: (0, import_request_identities.notificationRequestIdentity)(input)
	    });
	  }
	  loadCollectionPage(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "collection-visible",
	      lane: "standard",
	      namespace: "reader-collection",
	      identity: (0, import_request_identities.collectionRequestIdentity)(input)
	    });
	  }
	  mutate(input) {
	    return this.#execute({
	      ...input,
	      cacheMode: "no-store",
	      profile: "action-critical",
	      lane: "control",
	      namespace: "reader-action",
	      identity: (0, import_request_identities.actionRequestIdentity)(input)
	    });
	  }
	  loadActionPermission(input) {
	    return this.#execute({
	      ...input,
	      cacheMode: "no-store",
	      profile: "action-permission",
	      lane: "control",
	      namespace: "reader-action-permission",
	      identity: (0, import_request_identities.actionRequestIdentity)(input)
	    });
	  }
	  submitReadState(input) {
	    return this.#execute({
	      ...input,
	      cacheMode: "no-store",
	      profile: "read-critical",
	      lane: "control",
	      namespace: "topic-read-state",
	      identity: (0, import_request_identities.readRequestIdentity)(input)
	    });
	  }
	  translate(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "translation-visible",
	      lane: "translation",
	      namespace: "reader-translation",
	      identity: (0, import_request_identities.translationRequestIdentity)(input)
	    });
	  }
	  loadResource(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "resource-visible",
	      lane: "standard",
	      namespace: "reader-resource",
	      identity: (0, import_request_identities.resourceRequestIdentity)(input)
	    });
	  }
	  loadUserResource(input) {
	    return this.#execute({
	      ...input,
	      profile: input.profile ?? "resource-visible",
	      lane: input.profile === "user-card-interactive" ? "user-card" : "standard",
	      namespace: "reader-user",
	      identity: (0, import_request_identities.userRequestIdentity)(input)
	    });
	  }
	  async cachedResource(input) {
	    const contract = this.#resourceContract(input), cached = await this.#responses.read(
	      cachePolicy(contract, input.cache)
	    );
	    return cached.state === "miss" ? null : cached.value;
	  }
	  async cachedTranslation(input) {
	    const contract = this.#translationCacheContract(input), cached = await this.#responses.read(
	      cachePolicy(contract, input.cache)
	    );
	    return cached.state === "miss" ? null : cached.value;
	  }
	  cacheTranslation(input, value) {
	    const contract = this.#translationCacheContract(input);
	    return this.#responses.write(cachePolicy(contract, input.cache), value);
	  }
	  invalidateResource(input) {
	    return this.#responses.invalidate({
	      ids: [this.#resourceContract(input).cacheKey]
	    });
	  }
	  invalidateResourceWithReport(input) {
	    return this.#responses.invalidateWithReport({
	      ids: [this.#resourceContract(input).cacheKey]
	    });
	  }
	  #resourceContract(input) {
	    return (0, import_request_contract.createRequestContract)("resource-visible", {
	      namespace: "reader-resource",
	      identity: (0, import_request_identities.resourceRequestIdentity)(input)
	    });
	  }
	  #translationCacheContract(input) {
	    return (0, import_request_contract.createRequestContract)("translation-visible", {
	      namespace: "reader-translation-section",
	      identity: (0, import_request_identities.translationRequestIdentity)(input)
	    });
	  }
	  #execute(input) {
	    if (input.signal.aborted) return Promise.reject(input.signal.reason);
	    const contract = (0, import_request_contract.createRequestContract)(input.profile, {
	      namespace: input.namespace,
	      identity: input.identity,
	      ...input.cacheMode === void 0 ? {} : { cacheMode: input.cacheMode },
	      ...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs }
	    });
	    if (contract.cacheMode !== "no-store" && !input.cache)
	      return Promise.reject(new Error(`${input.profile} 缺少 response cache settings`));
	    const execution = this.#acquireExecution(contract), network = async (signal) => {
	      if (input.beforeNetwork && execution.contract.priority === "background" && (await input.beforeNetwork(signal), signal.aborted))
	        throw signal.reason;
	      execution.started = !0;
	      const effective = execution.contract, requestOptions = {
	        key: effective.key,
	        input: input.input,
	        priority: effective.priority,
	        lane: input.lane,
	        timeoutMs: effective.timeoutMs,
	        droppable: effective.droppable,
	        max429Retries: effective.max429Retries,
	        maxChallengeRetries: effective.maxChallengeRetries,
	        blockOnCloudflareChallenge: effective.blockOnCloudflareChallenge !== !1,
	        suppressAfterChallengeWait: effective.suppressAfterChallengeWait === !0,
	        callSite: `${effective.profile} / ${input.namespace} / ${input.lane}`,
	        ...input.method === void 0 ? {} : { method: input.method }
	      };
	      return this.#client.request(
	        { ...requestOptions, signal },
	        input.transport
	      );
	    };
	    return (contract.cacheMode === "no-store" ? network(input.signal) : this.#responses.getOrLoad(
	      cachePolicy(contract, input.cache),
	      network,
	      {
	        cacheMode: contract.cacheMode,
	        signal: input.signal,
	        ...input.allowStaleOnError === void 0 ? {} : { allowStaleOnError: input.allowStaleOnError },
	        ...input.canFallback === void 0 ? {} : { canFallback: input.canFallback },
	        ...input.mapStaleFallback === void 0 ? {} : { mapStaleFallback: input.mapStaleFallback }
	      }
	    )).finally(() => this.#releaseExecution(contract.key, execution));
	  }
	  #acquireExecution(contract) {
	    const existing = this.#executions.get(contract.key);
	    if (existing)
	      return existing.consumers += 1, this.#upgradeExecution(existing, contract), existing;
	    const created = {
	      contract,
	      consumers: 1,
	      started: !1
	    };
	    return this.#executions.set(contract.key, created), created;
	  }
	  #promoteExecution(contract) {
	    const execution = this.#executions.get(contract.key);
	    return execution ? (this.#upgradeExecution(execution, contract), !0) : this.#client.promote?.(
	      contract.key,
	      this.#requestPromotion(contract)
	    ) ?? !1;
	  }
	  #upgradeExecution(execution, incoming) {
	    const current = execution.contract, winner = PRIORITY_WEIGHT[incoming.priority] < PRIORITY_WEIGHT[current.priority] ? incoming : current, merged = Object.freeze({
	      ...winner,
	      droppable: current.droppable && incoming.droppable,
	      max429Retries: Math.max(
	        current.max429Retries,
	        incoming.max429Retries
	      ),
	      maxChallengeRetries: Math.max(
	        current.maxChallengeRetries,
	        incoming.maxChallengeRetries
	      ),
	      timeoutMs: Math.min(current.timeoutMs, incoming.timeoutMs)
	    });
	    (merged.priority !== current.priority || merged.droppable !== current.droppable || merged.max429Retries !== current.max429Retries || merged.maxChallengeRetries !== current.maxChallengeRetries || merged.timeoutMs !== current.timeoutMs) && (execution.contract = merged, execution.started && this.#client.promote?.(
	      merged.key,
	      this.#requestPromotion(merged)
	    ));
	  }
	  #requestPromotion(contract) {
	    return Object.freeze({
	      priority: contract.priority,
	      droppable: contract.droppable,
	      max429Retries: contract.max429Retries,
	      maxChallengeRetries: contract.maxChallengeRetries
	    });
	  }
	  #releaseExecution(key, execution) {
	    execution.consumers = Math.max(0, execution.consumers - 1), execution.consumers === 0 && this.#executions.get(key) === execution && this.#executions.delete(key);
	  }
	}
}, "883c4462da5da97fbb25a7a89cd605f8aba5885aa50badc31d58612c6f73b60d");

/* Source: lite/src/network/public-resource-request-adapter.ts */
runtime.register("src/network/public-resource-request-adapter.js", function(module, exports, require) {
	var public_resource_request_adapter_exports = {};
	__export(public_resource_request_adapter_exports, {
	  BrowserPublicResourceHttpPort: () => BrowserPublicResourceHttpPort,
	  PublicResourceRequestAdapter: () => PublicResourceRequestAdapter
	});
	module.exports = __toCommonJS(public_resource_request_adapter_exports);
	var import_coordinated_request_client = require("./coordinated-request-client.js"), import_request_rate_limit_policy = require("./request-rate-limit-policy.js");
	const publicResourceDescriptorBrand = Symbol("PublicResourceHttpDescriptor"), publicResourceDescriptors = /* @__PURE__ */ new WeakSet();
	function normalizedSource(rawSource, baseUrl) {
	  const source = String(rawSource).trim();
	  if (!source) throw new Error("资源 URL 不能为空");
	  const url = new URL(source, baseUrl);
	  if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
	    throw new Error(`不支持的资源协议:${url.protocol}`);
	  return url.hash = "", url.href;
	}
	function descriptor(source) {
	  const value = Object.freeze({
	    url: source,
	    [publicResourceDescriptorBrand]: !0
	  });
	  return publicResourceDescriptors.add(value), value;
	}
	function assertDescriptor(input) {
	  if (!publicResourceDescriptors.has(input))
	    throw new Error("公共资源请求 descriptor 未登记");
	  const protocol = new URL(input.url).protocol;
	  if (!["http:", "https:", "blob:", "data:"].includes(protocol))
	    throw new Error(`公共资源请求拒绝协议 ${protocol}`);
	}
	class BrowserPublicResourceHttpPort {
	  #request;
	  constructor(options) {
	    this.#request = options.request;
	  }
	  async execute(input, request) {
	    if (assertDescriptor(input), request.signal.aborted) throw request.signal.reason;
	    const response = await this.#request(input.url, {
	      credentials: "omit",
	      cache: "force-cache",
	      signal: request.signal
	    }), contentType = String(response.headers.get("Content-Type") ?? "").trim().toLowerCase(), accepted = response.ok && (contentType === "" || contentType.startsWith("image/")), value = accepted ? await response.blob() : new Blob(), rateLimitCode = response.headers.get("Discourse-Rate-Limit-Error-Code") ?? response.headers.get("X-Discourse-Rate-Limit-Error-Code") ?? "", rateLimitWindow = (0, import_request_rate_limit_policy.rateLimitWindowFromCode)(rateLimitCode);
	    return {
	      ok: accepted,
	      status: response.ok && !accepted ? 415 : response.status,
	      value,
	      retryAfter: response.headers.get("Retry-After"),
	      rateLimitCode,
	      rateLimitWindow,
	      knownGlobalRateLimitWindow: rateLimitWindow !== "unknown",
	      serverLimit: response.headers.get("X-RateLimit-Limit"),
	      serverRemaining: response.headers.get("X-RateLimit-Remaining"),
	      serverReset: response.headers.get("X-RateLimit-Reset"),
	      cloudflareMitigated: response.headers.get("cf-mitigated")?.trim().toLowerCase() === "challenge"
	    };
	  }
	}
	class PublicResourceRequestAdapter {
	  #gateway;
	  #http;
	  #baseUrl;
	  #cache;
	  constructor(options) {
	    this.#gateway = options.gateway, this.#http = options.http, this.#baseUrl = options.baseUrl, this.#cache = options.cache;
	  }
	  async load(rawSource, options) {
	    const source = normalizedSource(rawSource, this.#baseUrl), requestDescriptor = descriptor(source);
	    if (source.startsWith("blob:") || source.startsWith("data:")) {
	      const response = await this.#http.execute(requestDescriptor, {
	        signal: options.signal,
	        attempt: 0
	      });
	      if (!response.ok) throw new import_coordinated_request_client.RequestStatusError(response.status);
	      return response.value;
	    }
	    return this.#gateway.loadResource({
	      resourceId: source,
	      variant: "blob",
	      input: source,
	      signal: options.signal,
	      cache: this.#cache,
	      ...options.cacheMode === void 0 ? {} : { cacheMode: options.cacheMode },
	      ...options.profile === void 0 ? {} : { profile: options.profile },
	      transport: (request) => this.#http.execute(requestDescriptor, request)
	    });
	  }
	  cached(rawSource) {
	    const source = normalizedSource(rawSource, this.#baseUrl);
	    return source.startsWith("blob:") || source.startsWith("data:") ? Promise.resolve(null) : this.#gateway.cachedResource({
	      resourceId: source,
	      variant: "blob",
	      cache: this.#cache
	    });
	  }
	  invalidate(rawSource) {
	    const source = normalizedSource(rawSource, this.#baseUrl);
	    return source.startsWith("blob:") || source.startsWith("data:") ? Promise.resolve() : this.#gateway.invalidateResource({
	      resourceId: source,
	      variant: "blob",
	      cache: this.#cache
	    });
	  }
	  invalidateWithReport(rawSource) {
	    const source = normalizedSource(rawSource, this.#baseUrl);
	    return source.startsWith("blob:") || source.startsWith("data:") ? Promise.resolve(Object.freeze({
	      memoryEntries: 0,
	      failures: Object.freeze([]),
	      complete: !0
	    })) : this.#gateway.invalidateResourceWithReport({
	      resourceId: source,
	      variant: "blob",
	      cache: this.#cache
	    });
	  }
	  normalize(rawSource) {
	    return normalizedSource(rawSource, this.#baseUrl);
	  }
	}
}, "1fb17f5061cd8ac997fdb42032378c0487557407203e1180d5fdcaeb925afe76");

/* Source: lite/src/network/request-contract.ts */
runtime.register("src/network/request-contract.js", function(module, exports, require) {
	var request_contract_exports = {};
	__export(request_contract_exports, {
	  createRequestContract: () => createRequestContract,
	  requestProfileContract: () => requestProfileContract
	});
	module.exports = __toCommonJS(request_contract_exports);
	function cacheModes(...values) {
	  return Object.freeze(values);
	}
	const PROFILES = Object.freeze({
	  "bootstrap-critical": Object.freeze({
	    priority: "critical",
	    lifecycle: "application",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 8e3,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "action-critical": Object.freeze({
	    priority: "critical",
	    lifecycle: "action",
	    droppable: !1,
	    defaultCacheMode: "no-store",
	    allowedCacheModes: cacheModes("no-store"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "action-permission": Object.freeze({
	    priority: "visible",
	    lifecycle: "action",
	    droppable: !1,
	    defaultCacheMode: "no-store",
	    allowedCacheModes: cacheModes("no-store"),
	    defaultTimeoutMs: 12e3,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "read-critical": Object.freeze({
	    priority: "critical",
	    lifecycle: "topic",
	    droppable: !1,
	    blockOnCloudflareChallenge: !1,
	    suppressAfterChallengeWait: !0,
	    defaultCacheMode: "no-store",
	    allowedCacheModes: cacheModes("no-store"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 1,
	    maxChallengeRetries: 0
	  }),
	  "topic-visible": Object.freeze({
	    priority: "visible",
	    lifecycle: "topic",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh", "no-store"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "nested-visible": Object.freeze({
	    priority: "nested",
	    lifecycle: "topic",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 12e3,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "user-card-interactive": Object.freeze({
	    priority: "interactive",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 12e3,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "translation-visible": Object.freeze({
	    priority: "visible",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "translation-access": Object.freeze({
	    priority: "interactive",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "no-store",
	    allowedCacheModes: cacheModes("no-store"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "translation-prefetch": Object.freeze({
	    priority: "prefetch",
	    lifecycle: "surface",
	    droppable: !0,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 3e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "notification-visible": Object.freeze({
	    priority: "visible",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "collection-visible": Object.freeze({
	    priority: "visible",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 1,
	    maxChallengeRetries: 1
	  }),
	  "resource-visible": Object.freeze({
	    priority: "visible",
	    lifecycle: "surface",
	    droppable: !1,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh", "no-store"),
	    defaultTimeoutMs: 3e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "surface-prefetch": Object.freeze({
	    priority: "prefetch",
	    lifecycle: "surface",
	    droppable: !0,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "user-prefetch": Object.freeze({
	    priority: "prefetch",
	    lifecycle: "surface",
	    droppable: !0,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 2e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "resource-prefetch": Object.freeze({
	    priority: "prefetch",
	    lifecycle: "surface",
	    droppable: !0,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 3e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  }),
	  "background-prefetch": Object.freeze({
	    priority: "background",
	    lifecycle: "topic",
	    droppable: !0,
	    defaultCacheMode: "default",
	    allowedCacheModes: cacheModes("default", "refresh"),
	    defaultTimeoutMs: 3e4,
	    max429Retries: 0,
	    maxChallengeRetries: 1
	  })
	});
	function nonEmptyToken(value, name) {
	  const token = String(value).trim();
	  if (!token) throw new Error(`${name} 不能为空`);
	  return token;
	}
	function encodedIdentity(identity) {
	  const entries = Object.entries(identity).sort(([left], [right]) => left.localeCompare(right));
	  if (!entries.length) throw new Error("request identity 不能为空");
	  return entries.map(([rawKey, rawValue]) => {
	    const key = nonEmptyToken(rawKey, "identity key"), value = typeof rawValue == "string" ? rawValue.trim() : String(rawValue);
	    if (!value) throw new Error(`identity ${key} 不能为空`);
	    return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
	  }).join("&");
	}
	function timeout(value, fallback) {
	  const resolved = value ?? fallback;
	  if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > 12e4)
	    throw new RangeError("timeoutMs 必须是 1..120000 的安全整数");
	  return resolved;
	}
	function requestProfileContract(profile) {
	  return PROFILES[profile];
	}
	function createRequestContract(profile, input) {
	  const contract = requestProfileContract(profile), cacheMode = input.cacheMode ?? contract.defaultCacheMode;
	  if (!contract.allowedCacheModes.includes(cacheMode))
	    throw new Error(`${profile} 不允许 cache mode ${cacheMode}`);
	  const cacheKey = `${nonEmptyToken(input.namespace, "request namespace")}?${encodedIdentity(input.identity)}`;
	  return Object.freeze({
	    ...contract,
	    profile,
	    cacheKey,
	    key: `${cacheKey}&cacheMode=${encodeURIComponent(cacheMode)}`,
	    cacheMode,
	    timeoutMs: timeout(input.timeoutMs, contract.defaultTimeoutMs)
	  });
	}
}, "3ba6a4107d7a3521502bd3f307e439c751961044cd9a64cbe5b7fbfbe92906fa");

/* Source: lite/src/network/request-identities.ts */
runtime.register("src/network/request-identities.js", function(module, exports, require) {
	var request_identities_exports = {};
	__export(request_identities_exports, {
	  actionRequestIdentity: () => actionRequestIdentity,
	  collectionRequestIdentity: () => collectionRequestIdentity,
	  nestedRequestIdentity: () => nestedRequestIdentity,
	  notificationRequestIdentity: () => notificationRequestIdentity,
	  readRequestIdentity: () => readRequestIdentity,
	  resourceRequestIdentity: () => resourceRequestIdentity,
	  topicPostsRequestIdentity: () => topicPostsRequestIdentity,
	  topicRequestIdentity: () => topicRequestIdentity,
	  translationRequestIdentity: () => translationRequestIdentity,
	  userRequestIdentity: () => userRequestIdentity
	});
	module.exports = __toCommonJS(request_identities_exports);
	var import_identifiers = require("../discourse/identifiers.js");
	function token(value, name) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error(`${name} 不能为空`);
	  return normalized;
	}
	function nonNegative(value, name) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 0)
	    throw new RangeError(`${name} 必须是非负安全整数`);
	  return numeric;
	}
	function topicRequestIdentity(input) {
	  const identity = {
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    topicId: (0, import_identifiers.discourseTopicId)(input.topicId),
	    operation: token(input.operation, "operation")
	  };
	  return input.postId !== void 0 && (identity.postId = (0, import_identifiers.discoursePostId)(input.postId)), input.postNumber !== void 0 && (identity.postNumber = (0, import_identifiers.discoursePostNumber)(input.postNumber)), input.cursor !== void 0 && (identity.cursor = (0, import_identifiers.discourseReplyCursor)(input.cursor)), Object.freeze(identity);
	}
	function topicPostsRequestIdentity(input) {
	  return Object.freeze({
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    topicId: (0, import_identifiers.discourseTopicId)(input.topicId),
	    postIds: (0, import_identifiers.discoursePostIds)(input.postIds).join(",")
	  });
	}
	function nestedRequestIdentity(input) {
	  const identity = {
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    topicId: (0, import_identifiers.discourseTopicId)(input.topicId),
	    parentPostNumber: (0, import_identifiers.discoursePostNumber)(input.parentPostNumber),
	    after: (0, import_identifiers.discourseReplyCursor)(input.after)
	  };
	  return input.parentPostId !== void 0 && (identity.parentPostId = (0, import_identifiers.discoursePostId)(input.parentPostId)), Object.freeze(identity);
	}
	function notificationRequestIdentity(input) {
	  return Object.freeze({
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    group: token(input.group, "group"),
	    page: nonNegative(input.page, "page")
	  });
	}
	function collectionRequestIdentity(input) {
	  const identity = {
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    collection: token(input.collection, "collection"),
	    page: nonNegative(input.page, "page")
	  };
	  return input.cursor !== void 0 && (identity.cursor = token(input.cursor, "cursor")), input.variant !== void 0 && (identity.variant = token(input.variant, "variant")), Object.freeze(identity);
	}
	function userRequestIdentity(input) {
	  const identity = {
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    username: token(input.username, "username").replace(/^@/, "").toLocaleLowerCase(),
	    resource: token(input.resource, "resource")
	  };
	  return input.page !== void 0 && (identity.page = nonNegative(input.page, "page")), Object.freeze(identity);
	}
	function resourceRequestIdentity(input) {
	  return Object.freeze({
	    resourceId: token(input.resourceId, "resourceId"),
	    variant: token(input.variant, "variant")
	  });
	}
	function translationRequestIdentity(input) {
	  return Object.freeze({
	    provider: token(input.provider, "provider"),
	    textFingerprint: token(input.textFingerprint, "textFingerprint"),
	    sourceLanguage: token(input.sourceLanguage, "sourceLanguage"),
	    targetLanguage: token(input.targetLanguage, "targetLanguage")
	  });
	}
	function actionRequestIdentity(input) {
	  const identity = {
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    operation: token(input.operation, "operation"),
	    targetType: token(input.targetType, "targetType"),
	    targetId: token(input.targetId, "targetId")
	  };
	  return input.variant !== void 0 && (identity.variant = token(input.variant, "variant")), Object.freeze(identity);
	}
	function readRequestIdentity(input) {
	  return Object.freeze({
	    authScope: (0, import_identifiers.discourseAuthScope)(input.authScope),
	    topicId: (0, import_identifiers.discourseTopicId)(input.topicId),
	    postNumbers: (0, import_identifiers.discoursePostNumbers)(input.postNumbers).join(",")
	  });
	}
}, "eae0dd4437edb39c183723fc1d72c9eb07ff1ad995fd92147c6f6fa6b474368c");

/* Source: lite/src/network/request-observer.ts */
runtime.register("src/network/request-observer.js", function(module, exports, require) {
	var request_observer_exports = {};
	__export(request_observer_exports, {
	  RequestObserver: () => RequestObserver,
	  requestObservationType: () => requestObservationType
	});
	module.exports = __toCommonJS(request_observer_exports);
	var import_signal = require("../kernel/signal.js");
	function nonNegative(value, fallback = 0) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) && numeric >= 0 ? numeric : fallback;
	}
	function positiveInteger(value, fallback) {
	  const numeric = Math.trunc(Number(value));
	  return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : fallback;
	}
	function requestUrl(href, baseHref) {
	  try {
	    return new URL(String(href), baseHref);
	  } catch {
	    return null;
	  }
	}
	function requestObservationHref(url) {
	  return url ? ["http:", "https:"].includes(url.protocol) ? `${url.origin}${url.pathname}`.slice(0, 512) : `${url.protocol.replace(":", "") || "本地"}资源` : "未知请求";
	}
	function requestObservationPath(url, baseOrigin) {
	  return url ? ["http:", "https:"].includes(url.protocol) ? `${url.origin === baseOrigin ? "" : url.host}${url.pathname || "/"}`.slice(0, 180) : `${url.protocol.replace(":", "") || "本地"}资源` : "未知请求";
	}
	function diagnosticText(value, maximum = 220) {
	  return String(value ?? "").replace(/[\r\n\t]+/g, " ").replace(/(?:https?|chrome-extension):\/\/[^\s)]+/g, (raw) => {
	    try {
	      const url = new URL(raw);
	      return `${url.origin}${url.pathname}`;
	    } catch {
	      return raw.split(/[?#]/, 1)[0] ?? "";
	    }
	  }).trim().slice(0, maximum);
	}
	function diagnosticCode(value, fallback = "") {
	  const normalized = diagnosticText(value, 80);
	  return /^[A-Za-z0-9_.:-]+$/.test(normalized) ? normalized : fallback;
	}
	function diagnosticError(value) {
	  const normalized = diagnosticText(value, 80);
	  return normalized ? /^[A-Za-z0-9_.:-]+$/.test(normalized) || /^(?:Failed to fetch|Load failed|NetworkError)$/i.test(normalized) ? normalized : "request-failed" : "";
	}
	const CANCELLATION_CODES = /* @__PURE__ */ new Set([
	  "AbortError",
	  "cancelled",
	  "context-close",
	  "context-closed",
	  "priority-upgrade",
	  "signal",
	  "topic-close",
	  "topic-switch",
	  "viewport-change"
	]);
	function isCancellationCode(value) {
	  return CANCELLATION_CODES.has(value);
	}
	function requestObservationType(href, options) {
	  const url = requestUrl(href, options.baseHref), path = String(url?.pathname ?? "").toLowerCase(), host = String(url?.hostname ?? "").toLowerCase(), initiator = String(options.initiatorType ?? "").toLowerCase(), method = String(options.method ?? "GET").toUpperCase();
	  return /\/posts\/\d+\/replies(?:\.json)?$/.test(path) ? "nested" : /\/user_avatar\/|\/letter_avatar\//.test(path) || /avatar/.test(host) ? "avatar" : /\/(?:uploads|optimized)\//.test(path) || /\.(?:avif|gif|jpe?g|png|webp|svg|mp4|webm|mp3|ogg)(?:$|\/)/.test(path) ? "media" : /\/bookmarks?(?:\/|\.|$)|remove_bookmarks/.test(path) ? "bookmark" : /\/notifications?(?:\/|\.|$)/.test(path) ? "notification" : /\/message-bus(?:\/|$)/.test(path) ? "realtime" : /\/presence\//.test(path) ? "presence" : /\/search(?:\/|\.|$)|\/filter(?:\/|\.|$)/.test(path) ? "search" : /topic-timings|\/timings(?:\/|\.|$)/.test(path) ? "read" : /\/u\/|\/directory_items|\/session\/current|\/category-experts\//.test(path) ? "user" : /post_actions|user_actions|discourse-reactions|\/boosts?(?:\/|\.|$)|\/emojis\.json$/.test(path) ? "reaction" : /\/t\/|\/posts(?:\/|\.|$)|\/posts\/by_number\//.test(path) ? "topic" : ["GET", "HEAD"].includes(method) ? ["img", "image", "video", "audio"].includes(initiator) ? "media" : ["css", "link", "script", "font"].includes(initiator) ? "asset" : "other" : "reaction";
	}
	class RequestObserver {
	  changes = new import_signal.Signal();
	  #baseHref;
	  #retentionMs;
	  #maxEntries;
	  #now;
	  #events = [];
	  #active = /* @__PURE__ */ new Map();
	  #sequence = 0;
	  #revision = 0;
	  constructor(options) {
	    const base = new URL(options.baseHref);
	    base.username = "", base.password = "", base.search = "", base.hash = "", this.#baseHref = base.href, this.#retentionMs = positiveInteger(options.retentionMs, 5 * 6e4), this.#maxEntries = positiveInteger(options.maxEntries, 500), this.#now = options.now ?? Date.now;
	  }
	  get snapshot() {
	    return this.#snapshot();
	  }
	  begin(input) {
	    const observedAt = this.#now(), requestedStart = nonNegative(input.startedAt, observedAt), requestedQueue = nonNegative(input.queuedAt, requestedStart), controlReason = diagnosticCode(input.controlReason), phase = controlReason ? "cancelled" : input.phase ?? "running", queuedAt = Math.min(requestedStart, requestedQueue), startedAt = phase === "queued" ? queuedAt : Math.max(queuedAt, requestedStart), permittedAt = phase === "queued" ? queuedAt : Math.min(
	      startedAt,
	      Math.max(queuedAt, nonNegative(input.permittedAt, startedAt))
	    ), method = String(input.method ?? "GET").trim().toUpperCase().slice(0, 16) || "GET", url = requestUrl(input.href, this.#baseHref), normalizedHref = requestObservationHref(url), baseOrigin = new URL(this.#baseHref).origin, event = Object.freeze({
	      id: ++this.#sequence,
	      href: normalizedHref,
	      path: requestObservationPath(url, baseOrigin),
	      method,
	      transport: input.transport,
	      source: input.source,
	      phase,
	      type: input.type ?? requestObservationType(input.href, {
	        baseHref: this.#baseHref,
	        method,
	        initiatorType: input.transport
	      }),
	      sameOrigin: !!(url && ["http:", "https:"].includes(url.protocol) && url.origin === baseOrigin),
	      queuedAt,
	      permittedAt,
	      startedAt,
	      endedAt: controlReason ? startedAt : 0,
	      permitWait: permittedAt - queuedAt,
	      dispatchDuration: startedAt - permittedAt,
	      duration: 0,
	      priority: input.priority ?? null,
	      attempt: positiveInteger(input.attempt, 1),
	      recoveryProbe: input.recoveryProbe === !0,
	      waitReason: diagnosticCode(input.waitReason),
	      callSite: diagnosticText(input.callSite),
	      controlReason,
	      pending: !controlReason,
	      status: controlReason ? 0 : null,
	      cloudflareMitigated: !1,
	      size: 0,
	      error: "",
	      rateLimitCode: "",
	      retryAfter: "",
	      serverLimit: "",
	      serverRemaining: "",
	      serverReset: "",
	      resourceTimed: !1
	    });
	    return this.#insert(event), event.pending && this.#active.set(event.id, event), this.#prune(startedAt), this.#publish(), event.id;
	  }
	  markStarted(input) {
	    const current = this.#active.get(input.id);
	    if (!current || current.phase !== "queued") return !1;
	    const startedAt = Math.max(
	      current.queuedAt,
	      nonNegative(input.startedAt, this.#now())
	    ), queuedAt = Math.min(
	      startedAt,
	      nonNegative(input.queuedAt, current.queuedAt)
	    ), permittedAt = Math.min(
	      startedAt,
	      Math.max(queuedAt, nonNegative(input.permittedAt, startedAt))
	    ), running = Object.freeze({
	      ...current,
	      phase: "running",
	      queuedAt,
	      permittedAt,
	      startedAt,
	      permitWait: permittedAt - queuedAt,
	      dispatchDuration: startedAt - permittedAt,
	      priority: input.priority === void 0 ? current.priority : input.priority,
	      recoveryProbe: input.recoveryProbe === !0,
	      waitReason: diagnosticCode(input.waitReason)
	    });
	    return this.#replace(current, running), this.#active.set(input.id, running), this.#prune(startedAt), this.#publish(), !0;
	  }
	  cancel(id, input) {
	    const current = this.#active.get(id);
	    if (!current || current.phase !== "queued") return !1;
	    const endedAt = Math.max(
	      current.queuedAt,
	      nonNegative(input.endedAt, this.#now())
	    ), reason = diagnosticCode(input.reason, "cancelled"), cancelled = Object.freeze({
	      ...current,
	      phase: "cancelled",
	      permittedAt: endedAt,
	      startedAt: endedAt,
	      endedAt,
	      permitWait: endedAt - current.queuedAt,
	      dispatchDuration: 0,
	      duration: 0,
	      waitReason: reason,
	      controlReason: reason,
	      pending: !1,
	      status: 0
	    });
	    return this.#replace(current, cancelled), this.#active.delete(id), this.#prune(endedAt), this.#publish(), !0;
	  }
	  matchActive(input) {
	    const href = requestObservationHref(requestUrl(input.href, this.#baseHref)), method = String(input.method ?? "GET").trim().toUpperCase() || "GET", startedAt = nonNegative(input.startedAt, this.#now()), toleranceMs = nonNegative(input.toleranceMs, 250);
	    return [...this.#active.values()].filter(
	      (event) => event.phase === "running" && event.href === href && event.method === method && !input.excludedIds?.has(event.id) && (input.source === void 0 || event.source === input.source) && Math.abs(event.startedAt - startedAt) <= toleranceMs
	    ).sort((left, right) => Math.abs(left.startedAt - startedAt) - Math.abs(right.startedAt - startedAt))[0]?.id ?? null;
	  }
	  finish(id, input = {}) {
	    const current = this.#active.get(id);
	    if (!current) return !1;
	    const endedAt = Math.max(current.startedAt, nonNegative(input.endedAt, this.#now())), error = diagnosticError(input.error), cancelled = isCancellationCode(error), controlledCancellation = cancelled && error !== "AbortError", completed = Object.freeze({
	      ...current,
	      phase: cancelled ? "cancelled" : "finished",
	      endedAt,
	      duration: endedAt - current.startedAt,
	      pending: !1,
	      status: cancelled ? 0 : input.status === void 0 ? current.status : Math.trunc(nonNegative(input.status)),
	      cloudflareMitigated: input.cloudflareMitigated === !0,
	      size: nonNegative(input.size, current.size),
	      error,
	      controlReason: controlledCancellation ? error : current.controlReason,
	      rateLimitCode: diagnosticCode(input.rateLimitCode),
	      retryAfter: diagnosticText(input.retryAfter, 80),
	      serverLimit: diagnosticText(input.serverLimit, 80),
	      serverRemaining: diagnosticText(input.serverRemaining, 80),
	      serverReset: diagnosticText(input.serverReset, 80)
	    }), index = this.#events.findIndex((event) => event.id === id);
	    return index >= 0 && (this.#events[index] = completed), this.#active.delete(id), this.#prune(endedAt), this.#publish(), !0;
	  }
	  recordResource(input) {
	    const initiator = String(input.initiatorType ?? "").toLowerCase(), resourceUrl = requestUrl(input.href, this.#baseHref);
	    if (!resourceUrl || !["http:", "https:"].includes(resourceUrl.protocol)) return 0;
	    const normalizedHref = requestObservationHref(
	      resourceUrl
	    ), resourceDuration = Math.max(0, input.endedAt - input.startedAt), repeated = this.#events.find(
	      (event) => event.resourceTimed && event.href === normalizedHref && Math.abs(event.startedAt - input.startedAt) <= 0.1 && Math.abs(event.duration - resourceDuration) <= 0.1
	    );
	    if (repeated) return repeated.id;
	    if (["fetch", "xmlhttprequest"].includes(initiator)) {
	      const match = this.#events.filter(
	        (event) => !event.resourceTimed && event.href === normalizedHref && (event.transport === initiator || initiator === "xmlhttprequest" && event.transport === "scheduler") && Math.abs(event.startedAt - input.startedAt) <= 100
	      ).sort((left, right) => Math.abs(left.startedAt - input.startedAt) - Math.abs(right.startedAt - input.startedAt))[0];
	      if (match) {
	        const enriched = Object.freeze({
	          ...match,
	          duration: resourceDuration,
	          size: nonNegative(input.size, match.size),
	          resourceTimed: !0
	        }), index2 = this.#events.findIndex((event) => event.id === match.id);
	        return index2 >= 0 && (this.#events[index2] = enriched), match.pending && this.#active.set(match.id, enriched), this.#publish(), match.id;
	      }
	    }
	    const id = this.begin({
	      href: input.href,
	      transport: "resource",
	      source: "browser",
	      startedAt: input.startedAt,
	      type: requestObservationType(input.href, {
	        baseHref: this.#baseHref,
	        ...input.initiatorType === void 0 ? {} : { initiatorType: input.initiatorType }
	      }),
	      callSite: input.initiatorType ? `${input.initiatorType} 资源加载` : "浏览器资源加载"
	    });
	    this.finish(id, input);
	    const index = this.#events.findIndex((event) => event.id === id), completed = this.#events[index];
	    return index >= 0 && completed && (this.#events[index] = Object.freeze({ ...completed, resourceTimed: !0 }), this.#publish()), id;
	  }
	  clearCompleted() {
	    for (let index = this.#events.length - 1; index >= 0; index -= 1)
	      this.#events[index].pending || this.#events.splice(index, 1);
	    this.#publish();
	  }
	  #insert(event) {
	    let low = 0, high = this.#events.length;
	    for (; low < high; ) {
	      const middle = Math.floor((low + high) / 2), current = this.#events[middle];
	      current.queuedAt < event.queuedAt || current.queuedAt === event.queuedAt && current.id <= event.id ? low = middle + 1 : high = middle;
	    }
	    this.#events.splice(low, 0, event);
	  }
	  #replace(current, next) {
	    const index = this.#events.findIndex((event) => event.id === current.id);
	    index < 0 || (this.#events.splice(index, 1), this.#insert(next));
	  }
	  #prune(at) {
	    const cutoff = at - this.#retentionMs;
	    for (let index = this.#events.length - 1; index >= 0; index -= 1) {
	      const event = this.#events[index], retainedAt = event.endedAt || event.startedAt;
	      !event.pending && retainedAt < cutoff && this.#events.splice(index, 1);
	    }
	    if (this.#events.length <= this.#maxEntries) return;
	    let overflow = this.#events.length - this.#maxEntries;
	    for (let index = 0; index < this.#events.length && overflow > 0; ) {
	      if (this.#events[index].pending) {
	        index += 1;
	        continue;
	      }
	      this.#events.splice(index, 1), overflow -= 1;
	    }
	  }
	  #publish() {
	    this.#revision += 1, this.changes.emit(this.#snapshot());
	  }
	  #snapshot() {
	    const queued = [...this.#active.values()].filter(
	      (event) => event.phase === "queued"
	    ).length, running = this.#active.size - queued;
	    return Object.freeze({
	      revision: this.#revision,
	      queued,
	      running,
	      active: running,
	      completed: this.#events.length - this.#active.size,
	      events: Object.freeze([...this.#events])
	    });
	  }
	}
}, "20627e321881321d564ba13d33fff77df98e51c9ddcde52774e8ecd105824471");

/* Source: lite/src/network/request-rate-limit-policy.ts */
runtime.register("src/network/request-rate-limit-policy.js", function(module, exports, require) {
	var request_rate_limit_policy_exports = {};
	__export(request_rate_limit_policy_exports, {
	  RequestRateLimitPolicy: () => RequestRateLimitPolicy,
	  endpointRequestIdentity: () => endpointRequestIdentity,
	  parseRetryAfterMs: () => parseRetryAfterMs,
	  rateLimitWindowFromCode: () => rateLimitWindowFromCode
	});
	module.exports = __toCommonJS(request_rate_limit_policy_exports);
	function rateLimitWindowFromCode(value) {
	  const short = /10[_-]?(?:secs?|seconds?)/i.test(String(value ?? "")), long = /60[_-]?(?:secs?|seconds?)|minute/i.test(String(value ?? ""));
	  return short && long ? "10s+60s" : short ? "10s" : long ? "60s" : "unknown";
	}
	function positiveFinite(value, name) {
	  if (!Number.isFinite(value) || value <= 0)
	    throw new RangeError(`${name} 必须是正有限数值`);
	  return value;
	}
	function positiveInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	function parseRetryAfterMs(value, options) {
	  const minMs = positiveFinite(options.minMs ?? 1e3, "minMs"), maxMs = positiveFinite(options.maxMs ?? 6e4, "maxMs");
	  if (maxMs < minMs) throw new RangeError("maxMs 不能小于 minMs");
	  const fallbackMs = positiveFinite(options.fallbackMs, "fallbackMs"), raw = String(value ?? "").trim();
	  let waitMs = 0;
	  if (raw) {
	    const seconds = Number(raw);
	    waitMs = Number.isFinite(seconds) ? seconds * 1e3 : Math.max(0, Date.parse(raw) - options.now);
	  }
	  return waitMs || (waitMs = fallbackMs), Math.max(minMs, Math.min(maxMs, waitMs));
	}
	function endpointRequestIdentity(input, method = "GET", baseUrl) {
	  let url;
	  try {
	    url = input instanceof URL ? new URL(input.href) : new URL(input, baseUrl);
	  } catch {
	    const invalid = `${String(method || "GET").toUpperCase()}:invalid:${String(input).slice(0, 160)}`;
	    return Object.freeze({ fingerprint: invalid, route: invalid });
	  }
	  const normalizedMethod = String(method || "GET").toUpperCase(), params = [...url.searchParams.entries()].filter(([key]) => key !== "_ldp_retry"), identity = (routeOnly) => {
	    const path = routeOnly ? url.pathname.replace(/\b\d+\b/g, ":id").replace(/[0-9a-f]{8}-[0-9a-f-]{27,}/gi, ":uuid") : url.pathname, sorted = params.map(([key, value]) => routeOnly ? [key, ""] : [key, value]).sort(([leftKey, leftValue], [rightKey, rightValue]) => leftKey.localeCompare(rightKey) || leftValue.localeCompare(rightValue)), query = routeOnly ? [...new Set(sorted.map(([key]) => encodeURIComponent(key)))].join("&") : sorted.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
	    return `${normalizedMethod}:${url.origin}${path}${query ? `?${query}` : ""}`;
	  };
	  return Object.freeze({ fingerprint: identity(!1), route: identity(!0) });
	}
	class RequestRateLimitPolicy {
	  #options;
	  #unknownEvidence = [];
	  constructor(options) {
	    const retryAfterMinMs = positiveFinite(options.retryAfterMinMs ?? 1e3, "retryAfterMinMs"), retryAfterMaxMs = positiveFinite(
	      options.retryAfterMaxMs ?? 6e4,
	      "retryAfterMaxMs"
	    );
	    if (retryAfterMaxMs < retryAfterMinMs)
	      throw new RangeError("retryAfterMaxMs 不能小于 retryAfterMinMs");
	    this.#options = {
	      evidenceWindowMs: positiveFinite(options.evidenceWindowMs, "evidenceWindowMs"),
	      maxEndpointEntries: positiveInteger(options.maxEndpointEntries, "maxEndpointEntries"),
	      retryAfterFallbackMs: positiveFinite(options.retryAfterFallbackMs, "retryAfterFallbackMs"),
	      retryAfterMinMs,
	      retryAfterMaxMs,
	      now: options.now ?? Date.now,
	      ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl }
	    };
	  }
	  noteRateLimit(observation) {
	    const at = this.#options.now();
	    this.#prune(at);
	    const identity = endpointRequestIdentity(
	      observation.input,
	      observation.method,
	      this.#options.baseUrl
	    ), retryAfterMs = parseRetryAfterMs(observation.retryAfter, {
	      now: at,
	      fallbackMs: this.#options.retryAfterFallbackMs,
	      minMs: this.#options.retryAfterMinMs,
	      maxMs: this.#options.retryAfterMaxMs
	    }), corroboratedGlobal = observation.knownGlobalWindow || this.#unknownEvidence.some(
	      (event) => event.route !== identity.route && event.at >= at - this.#options.evidenceWindowMs
	    );
	    return this.#unknownEvidence.push(Object.freeze({ at, route: identity.route })), this.#prune(at), this.#decision(
	      corroboratedGlobal ? "global" : "endpoint",
	      retryAfterMs,
	      at + retryAfterMs,
	      identity,
	      observation.globalWindow ?? "unknown"
	    );
	  }
	  reset() {
	    this.#unknownEvidence.length = 0;
	  }
	  #decision(scope, waitMs, retryAt, identity, window = "unknown") {
	    return Object.freeze({ scope, waitMs, retryAt, ...identity, window });
	  }
	  #prune(at) {
	    const evidenceCutoff = at - this.#options.evidenceWindowMs;
	    for (; this.#unknownEvidence.length && this.#unknownEvidence[0].at < evidenceCutoff; )
	      this.#unknownEvidence.shift();
	    this.#unknownEvidence.length > this.#options.maxEndpointEntries && this.#unknownEvidence.splice(
	      0,
	      this.#unknownEvidence.length - this.#options.maxEndpointEntries
	    );
	  }
	}
}, "e030bd7e0a3669833a435fc466c7d1dd25284f0e27da7838c54ecf4176ff3f4f");

/* Source: lite/src/network/request-scheduler.ts */
runtime.register("src/network/request-scheduler.js", function(module, exports, require) {
	var request_scheduler_exports = {};
	__export(request_scheduler_exports, {
	  RequestControlError: () => RequestControlError,
	  RequestScheduler: () => RequestScheduler,
	  RequestTimeoutError: () => RequestTimeoutError
	});
	module.exports = __toCommonJS(request_scheduler_exports);
	const REQUEST_LANES = Object.freeze([
	  "control",
	  "topic-batch",
	  "nested-replies",
	  "user-card",
	  "translation",
	  "standard"
	]), LANE_CONCURRENCY_CAP = Object.freeze({
	  control: 1,
	  /* post_ids[] 当前批次与一批可丢弃的预知批次可以并行。 */
	  "topic-batch": 2,
	  /* replies.json 最多并行两个父楼;全局 permit 仍统一约束启动间隔与额度。 */
	  "nested-replies": 2,
	  "user-card": 2,
	  /* 五路预加载之外为滚动到眼前的正文预留一路;共享 permit 仍限制启动。 */
	  translation: 6,
	  standard: 1
	}), PRIORITY_WEIGHT = Object.freeze({
	  critical: 0,
	  interactive: 1,
	  nested: 2,
	  visible: 3,
	  prefetch: 4,
	  background: 5
	});
	class RequestControlError extends Error {
	  code;
	  constructor(code) {
	    super(code), this.name = "AbortError", this.code = code;
	  }
	}
	class RequestTimeoutError extends Error {
	  constructor() {
	    super("请求超时"), this.name = "TimeoutError";
	  }
	}
	function positiveInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	function normalizedKey(value) {
	  const key = String(value).trim();
	  if (!key) throw new Error("request key 不能为空");
	  return key;
	}
	function abortReason(signal, fallback) {
	  return signal.reason ?? new RequestControlError(fallback);
	}
	class RequestScheduler {
	  #maxConcurrent;
	  #queueLimit;
	  #defaultTimeoutMs;
	  #now;
	  #startGate;
	  #onInternalError;
	  #queue = [];
	  #tasksByKey = /* @__PURE__ */ new Map();
	  #activeByLane = /* @__PURE__ */ new Map();
	  #activeCount = 0;
	  #sequence = 0;
	  #pumpQueued = !1;
	  #permitPending = !1;
	  #permitLane = null;
	  #permitTask = null;
	  #disposed = !1;
	  constructor(options) {
	    this.#maxConcurrent = positiveInteger(options.maxConcurrent, "maxConcurrent"), this.#queueLimit = positiveInteger(options.queueLimit, "queueLimit"), this.#defaultTimeoutMs = positiveInteger(options.defaultTimeoutMs, "defaultTimeoutMs"), this.#now = options.now ?? Date.now, this.#startGate = options.startGate, this.#onInternalError = options.onInternalError ?? (() => {
	    }), options.scope?.add(() => this.destroy());
	  }
	  schedule(options, operation) {
	    const key = normalizedKey(options.key), priority = options.priority ?? "visible", lane = options.lane ?? "standard";
	    if (this.#disposed)
	      return Promise.reject(new RequestControlError("context-closed"));
	    const existing = this.#tasksByKey.get(key);
	    if (existing && !existing.externalSignal?.aborted)
	      return options.droppable !== !0 && (existing.droppable = !1), this.#promoteTask(existing, priority), existing.promise;
	    if (options.signal?.aborted)
	      return Promise.reject(abortReason(options.signal, "signal"));
	    if (this.#waitingCount() >= this.#queueLimit) {
	      const evicted = options.droppable === !0 ? null : this.#evictLowerPriorityDroppable(priority);
	      if (!evicted && !this.#preemptDroppablePermit({
	        priority,
	        lane,
	        droppable: options.droppable === !0
	      }))
	        return Promise.reject(new RequestControlError("queue-limit"));
	      evicted && this.#finish(evicted, new RequestControlError("queue-limit"));
	    }
	    let resolve, reject;
	    const promise = new Promise((done, fail) => {
	      resolve = done, reject = fail;
	    }), task = {
	      key,
	      priority,
	      lane,
	      sequence: this.#sequence++,
	      queuedAt: this.#now(),
	      timeoutMs: positiveInteger(options.timeoutMs ?? this.#defaultTimeoutMs, "timeoutMs"),
	      externalSignal: options.signal,
	      operation,
	      onStart: options.onStart,
	      droppable: options.droppable === !0,
	      promise,
	      resolve,
	      reject,
	      state: "queued",
	      controller: null,
	      externalAbort: null,
	      permitRestart: !1
	    };
	    return task.externalSignal && (task.externalAbort = () => {
	      task.state === "queued" ? (this.#removeQueued(task), this.#finish(task, abortReason(task.externalSignal, "signal"))) : task.controller?.abort(abortReason(task.externalSignal, "signal"));
	    }, task.externalSignal.addEventListener("abort", task.externalAbort, { once: !0 })), this.#tasksByKey.set(key, task), this.#queue.push(task), this.#preemptDroppablePermit(task), this.#queuePump(), promise;
	  }
	  cancelQueued(key) {
	    const task = this.#tasksByKey.get(String(key));
	    return !task || task.state !== "queued" ? !1 : (this.#removeQueued(task), this.#finish(task, new RequestControlError("cancelled")), !0);
	  }
	  /**
	   * 原地更新只影响尚未启动的任务;已经取得 permit 的请求自然完成。
	   *
	   * 设置切换不得重建 scheduler,否则会丢失 single-flight、排队顺序和当前请求。
	   */
	  applyRuntimePolicy(policy) {
	    this.#disposed || (this.#maxConcurrent = positiveInteger(
	      policy.maxConcurrent,
	      "maxConcurrent"
	    ), this.#queuePump());
	  }
	  promoteQueued(key, priority, droppable) {
	    const task = this.#tasksByKey.get(String(key));
	    return task ? (droppable === !1 && (task.droppable = !1), this.#promoteTask(task, priority)) : !1;
	  }
	  snapshot() {
	    return this.#sortQueue(), Object.freeze({
	      active: this.#activeCount,
	      queued: this.#queue.length + (this.#permitPending ? 1 : 0),
	      maxConcurrent: this.#maxConcurrent,
	      queueLimit: this.#queueLimit,
	      disposed: this.#disposed,
	      queuedKeys: Object.freeze(this.#queue.map((task) => task.key)),
	      activeByLane: this.#laneCounts((lane) => this.#activeByLane.get(lane) ?? 0),
	      queuedByLane: this.#laneCounts((lane) => this.#queue.reduce(
	        (total, task) => total + (task.lane === lane ? 1 : 0),
	        this.#permitLane === lane ? 1 : 0
	      ))
	    });
	  }
	  destroy() {
	    if (!this.#disposed) {
	      this.#disposed = !0;
	      for (const task of this.#queue.splice(0))
	        this.#finish(task, new RequestControlError("context-closed"));
	      for (const task of this.#tasksByKey.values())
	        (task.state === "active" || task.state === "permit") && task.controller?.abort(new RequestControlError("context-closed"));
	    }
	  }
	  #queuePump() {
	    this.#pumpQueued || this.#disposed || (this.#pumpQueued = !0, queueMicrotask(() => {
	      this.#pumpQueued = !1, this.#pump();
	    }));
	  }
	  #pump() {
	    if (!(this.#disposed || this.#permitPending))
	      for (this.#sortQueue(); this.#activeCount < this.#maxConcurrent && this.#queue.length; ) {
	        const nextIndex = this.#queue.findIndex((candidate) => this.#taskCanStart(candidate));
	        if (nextIndex < 0) return;
	        const [task] = this.#queue.splice(nextIndex, 1);
	        if (!task) return;
	        if (task.externalSignal?.aborted) {
	          this.#finish(task, abortReason(task.externalSignal, "signal"));
	          continue;
	        }
	        const controller = new AbortController();
	        if (task.controller = controller, !this.#startGate) {
	          this.#run(task, controller, null, task.queuedAt);
	          continue;
	        }
	        task.state = "permit", this.#permitPending = !0, this.#permitLane = task.lane, this.#permitTask = task;
	        const permitPromise = Promise.resolve().then(() => this.#startGate.acquire({
	          key: task.key,
	          priority: task.priority,
	          lane: task.lane,
	          signal: controller.signal
	        })).then((permit) => {
	          if (!permit || typeof permit.release != "function")
	            throw new Error("start gate 返回了无效 permit");
	          if (controller.signal.aborted)
	            throw this.#releasePermit(permit), abortReason(controller.signal, "signal");
	          return permit;
	        }), permitAbort = new Promise((_resolve, reject) => {
	          controller.signal.addEventListener(
	            "abort",
	            () => reject(abortReason(controller.signal, "signal")),
	            { once: !0 }
	          );
	        });
	        Promise.race([permitPromise, permitAbort]).then((permit) => {
	          this.#clearPermitTask(task);
	          try {
	            if (this.#disposed || controller.signal.aborted)
	              throw this.#releasePermit(permit), abortReason(controller.signal, "context-closed");
	            this.#run(task, controller, permit, this.#now());
	          } catch (error) {
	            task.state !== "done" && this.#finish(task, error);
	          } finally {
	            this.#queuePump();
	          }
	        }, (error) => {
	          this.#clearPermitTask(task), task.permitRestart && !this.#disposed && !task.externalSignal?.aborted ? (task.permitRestart = !1, task.state = "queued", task.controller = null, this.#queue.push(task)) : task.state !== "done" && this.#finish(
	            task,
	            this.#disposed ? new RequestControlError("context-closed") : error
	          ), this.#queuePump();
	        });
	        break;
	      }
	  }
	  #sortQueue() {
	    this.#queue.sort(
	      (left, right) => PRIORITY_WEIGHT[left.priority] - PRIORITY_WEIGHT[right.priority] || left.sequence - right.sequence
	    );
	  }
	  #promoteTask(task, priority) {
	    return task.state !== "queued" && task.state !== "permit" || PRIORITY_WEIGHT[priority] >= PRIORITY_WEIGHT[task.priority] ? !1 : (task.priority = priority, task.state === "permit" ? (task.permitRestart = !0, task.controller?.abort(new RequestControlError("cancelled"))) : this.#queuePump(), !0);
	  }
	  #preemptDroppablePermit(incoming) {
	    const waiting = this.#permitTask, controller = waiting?.controller;
	    return incoming.droppable || !waiting || !waiting.droppable || waiting.state !== "permit" || !controller || controller.signal.aborted || this.#activeCount >= this.#maxConcurrent || !this.#taskCanStart(incoming) || PRIORITY_WEIGHT[incoming.priority] >= PRIORITY_WEIGHT[waiting.priority] ? !1 : (waiting.permitRestart = !1, controller.abort(new RequestControlError("cancelled")), !0);
	  }
	  #clearPermitTask(task) {
	    this.#permitTask === task && (this.#permitPending = !1, this.#permitLane = null, this.#permitTask = null);
	  }
	  #waitingCount() {
	    return this.#queue.length + (this.#permitPending ? 1 : 0);
	  }
	  #taskCanStart(task) {
	    let cap = Math.min(
	      this.#maxConcurrent,
	      LANE_CONCURRENCY_CAP[task.lane]
	    );
	    return task.lane === "topic-batch" && task.priority === "background" && (cap = Math.min(cap, 1)), (this.#activeByLane.get(task.lane) ?? 0) < cap;
	  }
	  #laneCounts(read) {
	    return Object.freeze(Object.fromEntries(
	      REQUEST_LANES.map((lane) => [lane, read(lane)])
	    ));
	  }
	  #evictLowerPriorityDroppable(incomingPriority) {
	    const incomingWeight = PRIORITY_WEIGHT[incomingPriority], candidate = this.#queue.filter((task) => task.droppable && PRIORITY_WEIGHT[task.priority] > incomingWeight).sort((left, right) => PRIORITY_WEIGHT[right.priority] - PRIORITY_WEIGHT[left.priority] || right.sequence - left.sequence)[0];
	    return candidate ? (this.#removeQueued(candidate), candidate) : null;
	  }
	  async #run(task, controller, permit, permittedAt) {
	    task.state = "active", task.controller = controller, task.permitRestart = !1, this.#activeCount += 1, this.#activeByLane.set(
	      task.lane,
	      (this.#activeByLane.get(task.lane) ?? 0) + 1
	    );
	    const startedAt = this.#now();
	    try {
	      task.onStart?.(Object.freeze({
	        queuedAt: task.queuedAt,
	        permittedAt: Math.max(task.queuedAt, permittedAt),
	        startedAt: Math.max(permittedAt, startedAt),
	        recoveryProbe: permit?.recoveryProbe === !0,
	        waitReason: String(permit?.waitReason ?? "")
	      }));
	    } catch (error) {
	      this.#onInternalError(error);
	    }
	    const timeoutError = new RequestTimeoutError();
	    let timedOut = !1;
	    const timeout = setTimeout(() => {
	      timedOut = !0, controller.abort(timeoutError);
	    }, task.timeoutMs), abortPromise = new Promise((_resolve, reject) => {
	      controller.signal.addEventListener(
	        "abort",
	        () => reject(abortReason(controller.signal, "signal")),
	        { once: !0 }
	      );
	    });
	    try {
	      const value = await Promise.race([task.operation(controller.signal), abortPromise]);
	      task.resolve(value);
	    } catch (error) {
	      task.reject(timedOut ? timeoutError : error);
	    } finally {
	      clearTimeout(timeout), this.#releasePermit(permit), this.#activeCount = Math.max(0, this.#activeCount - 1);
	      const laneActive = Math.max(
	        0,
	        (this.#activeByLane.get(task.lane) ?? 0) - 1
	      );
	      laneActive ? this.#activeByLane.set(task.lane, laneActive) : this.#activeByLane.delete(task.lane), this.#finish(task), this.#queuePump();
	    }
	  }
	  #releasePermit(permit) {
	    if (permit)
	      try {
	        permit.release();
	      } catch (error) {
	        this.#onInternalError(error);
	      }
	  }
	  #removeQueued(task) {
	    const index = this.#queue.indexOf(task);
	    index >= 0 && this.#queue.splice(index, 1);
	  }
	  #finish(task, error) {
	    task.state !== "done" && (task.state = "done", task.externalSignal && task.externalAbort && task.externalSignal.removeEventListener("abort", task.externalAbort), task.externalAbort = null, task.controller = null, task.permitRestart = !1, this.#tasksByKey.get(task.key) === task && this.#tasksByKey.delete(task.key), error !== void 0 && task.reject(error));
	  }
	}
}, "2355bf2f57a04b784116f47b363c09162e59f3c52542ecaa934150bd08a60c0a");

/* Source: lite/src/shell/embedded-host-appearance.ts */
runtime.register("src/shell/embedded-host-appearance.js", function(module, exports, require) {
	var embedded_host_appearance_exports = {};
	__export(embedded_host_appearance_exports, {
	  EmbeddedHostAppearanceController: () => EmbeddedHostAppearanceController,
	  measureEmbeddedHostTopicRowHeight: () => measureEmbeddedHostTopicRowHeight
	});
	module.exports = __toCommonJS(embedded_host_appearance_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_reader_workspace = require("./reader-workspace.js");
	const STYLE_PROPERTIES = Object.freeze([
	  "--ldp-reader-host-min-width",
	  "--ldp-reader-native-row-height"
	]), ROOT_STYLE_PROPERTIES = Object.freeze([
	  "--ldp-reader-list-zebra-color",
	  "--ldp-divider-line-color",
	  "--ldp-divider-line-width"
	]);
	function measureEmbeddedHostTopicRowHeight(documentPort, overlay) {
	  const heights = [...documentPort.querySelectorAll(
	    "tr.topic-list-item,.topic-list-item,.latest-topic-list-item"
	  )].filter((node) => !node.closest(".ldp-overlay") && !overlay.contains(node)).map((node) => {
	    const rect = node.getBoundingClientRect();
	    return rect.width > 0 && rect.height >= 48 && rect.height <= 320 ? Math.round(rect.height) : 0;
	  }).filter((height) => height > 0).sort((left, right) => left - right);
	  return heights.length ? heights[Math.floor((heights.length - 1) / 2)] : 0;
	}
	class EmbeddedHostAppearanceController {
	  scope;
	  #workspace;
	  #pageRoot;
	  #overlay;
	  #readAppearance;
	  #measureRowHeight;
	  #hostMinWidth;
	  #active = !1;
	  #rowHeight = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#workspace = options.workspace, this.#pageRoot = options.pageRoot, this.#overlay = options.overlay, this.#readAppearance = options.readAppearance, this.#measureRowHeight = options.measureRowHeight ?? (() => measureEmbeddedHostTopicRowHeight(
	      this.#pageRoot.ownerDocument,
	      this.#overlay
	    )), this.#hostMinWidth = Math.max(
	      1,
	      Math.round(options.hostMinWidth ?? import_reader_workspace.READER_HOST_MIN_WIDTH)
	    ), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#workspace.changes.subscribe(() => this.#syncActivation(), this.scope), options.appearanceChanges?.subscribe(() => {
	      this.#active && this.#applyAppearance();
	    }, this.scope), this.scope.add(() => this.#clear()), this.#syncActivation();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #syncActivation() {
	    if (this.#destroyed) return;
	    const active = this.#workspace.snapshot.presentation.embedded;
	    active && !this.#active ? (this.#active = !0, this.#rowHeight = Math.max(0, Math.round(this.#measureRowHeight())), this.#applyAppearance()) : !active && this.#active && (this.#active = !1, this.#clear());
	  }
	  #applyAppearance() {
	    const appearance = this.#readAppearance(), profile = appearance.profile, dark = appearance.theme === "dark", zebraColor = dark ? profile.listZebraColorDark : profile.listZebraColor, dividerColor = dark ? profile.dividerLineColorDark : profile.dividerLineColor;
	    for (const target of [this.#pageRoot, this.#overlay])
	      target.style.setProperty(
	        "--ldp-reader-host-min-width",
	        `${this.#hostMinWidth}px`
	      ), this.#rowHeight ? target.style.setProperty(
	        "--ldp-reader-native-row-height",
	        `${this.#rowHeight}px`
	      ) : target.style.removeProperty("--ldp-reader-native-row-height");
	    this.#pageRoot.style.setProperty("--ldp-reader-list-zebra-color", zebraColor), this.#pageRoot.classList.toggle(
	      "ldp-structure-colors-disabled",
	      !profile.structureColorsEnabled
	    ), !profile.structureColorsEnabled || dividerColor === appearance.defaultDividerLineColor ? this.#pageRoot.style.removeProperty("--ldp-divider-line-color") : this.#pageRoot.style.setProperty("--ldp-divider-line-color", dividerColor), profile.dividerLineWidth === appearance.defaultDividerLineWidth ? this.#pageRoot.style.removeProperty("--ldp-divider-line-width") : this.#pageRoot.style.setProperty(
	      "--ldp-divider-line-width",
	      `${profile.dividerLineWidth}px`
	    );
	  }
	  #clear() {
	    for (const target of [this.#pageRoot, this.#overlay])
	      for (const property of STYLE_PROPERTIES) target.style.removeProperty(property);
	    for (const property of ROOT_STYLE_PROPERTIES)
	      this.#pageRoot.style.removeProperty(property);
	    this.#pageRoot.classList.remove("ldp-structure-colors-disabled"), this.#rowHeight = 0;
	  }
	}
}, "551dfb96890751093acf088ff55afc564c71121e94330d07eb3be899bbdb95f4");

/* Source: lite/src/shell/embedded-host-root-controller.ts */
runtime.register("src/shell/embedded-host-root-controller.js", function(module, exports, require) {
	var embedded_host_root_controller_exports = {};
	__export(embedded_host_root_controller_exports, {
	  EmbeddedHostRootController: () => EmbeddedHostRootController
	});
	module.exports = __toCommonJS(embedded_host_root_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	const CARD_SELECTOR = "tr.topic-list-item,.topic-list-item,.latest-topic-list-item", ROOT_QUERIES = Object.freeze({
	  shell: "#ember-app,#main-outlet-wrapper,#main-outlet,.list-container,.topic-list,.navigation-container",
	  header: ".d-header,body > header",
	  sidebar: "#d-sidebar,.sidebar-wrapper,.sidebar-container"
	}), ROOT_RANK = Object.freeze({
	  sidebar: 1,
	  header: 2,
	  shell: 3
	});
	function elementFromNode(node) {
	  return node ? node.nodeType === 1 ? node : node.parentElement : null;
	}
	function topLevelBodyChild(documentPort, node) {
	  let current = elementFromNode(node);
	  for (; current?.parentElement && current.parentElement !== documentPort.body; )
	    current = current.parentElement;
	  return current?.parentElement === documentPort.body ? current : null;
	}
	class EmbeddedHostRootController {
	  scope;
	  #model;
	  #document;
	  #overlay;
	  #mutations;
	  #enhancements;
	  #requestFrame;
	  #cancelFrame;
	  #activeScope = null;
	  #roots = /* @__PURE__ */ new Map();
	  #changedCards = /* @__PURE__ */ new Set();
	  #activityCards = /* @__PURE__ */ new Set();
	  #rootFrame = 0;
	  #cardFrame = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#document = options.document, this.#overlay = options.overlay, this.#mutations = options.mutations, this.#enhancements = options.enhancements, this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#model.changes.subscribe(() => this.#syncActivation(), this.scope), this.scope.add(() => this.#deactivate()), this.#syncActivation();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #syncActivation() {
	    if (this.#destroyed) return;
	    const embedded = this.#model.snapshot.presentation.embedded;
	    if (embedded && !this.#activeScope) {
	      const activeScope = this.scope.child();
	      this.#activeScope = activeScope, this.#mutations.subscribe((batch) => this.#onMutations(batch), activeScope), this.#scheduleRootSync();
	    } else !embedded && this.#activeScope && this.#deactivate();
	  }
	  #deactivate() {
	    const activeScope = this.#activeScope;
	    this.#activeScope = null, activeScope?.destroy(), this.#rootFrame && this.#cancelFrame(this.#rootFrame), this.#cardFrame && this.#cancelFrame(this.#cardFrame), this.#rootFrame = 0, this.#cardFrame = 0, this.#changedCards.clear(), this.#activityCards.clear();
	    for (const root of this.#roots.keys())
	      root.removeAttribute("data-ldp-reader-host-root");
	    this.#roots.clear(), this.#enhancements.clear();
	  }
	  #onMutations(batch) {
	    if (!this.#activeScope) return;
	    const hostRootsMayHaveChanged = batch.records.some(
	      (record) => record.type === "childList" && (record.target === this.#document.body || !batch.root?.contains(record.target))
	    );
	    (batch.rootChanged || hostRootsMayHaveChanged) && this.#scheduleRootSync();
	    for (const record of batch.records) this.#collectRecord(record);
	    this.#scheduleCardSync();
	  }
	  #collectRecord(record) {
	    if (record.type === "childList") {
	      const target = elementFromNode(record.target), activityTarget = target?.matches(".relative-date") && target.closest("td.activity"), changedNodes = [...record.addedNodes, ...record.removedNodes];
	      if (activityTarget && record.addedNodes.length > 0 && record.removedNodes.length > 0 && changedNodes.every((node) => node.nodeType === 3)) {
	        const card = activityTarget.closest(CARD_SELECTOR);
	        card && this.#activityCards.add(card);
	        return;
	      }
	      changedNodes.length > 0 && changedNodes.every((node) => {
	        const element = elementFromNode(node);
	        return !!(element?.matches(".ldp-topic-stats-component") || element?.closest(".ldp-topic-stats-component"));
	      }) || this.#collectNearestCard(record.target);
	      for (const node of record.addedNodes) this.#collectAddedCards(node);
	    } else
	      this.#collectNearestCard(record.target);
	  }
	  #collectNearestCard(node) {
	    const element = elementFromNode(node);
	    if (!element || element.closest(".ldp-overlay,.ldp-topic-stats-component")) return;
	    const card = element.matches(CARD_SELECTOR) ? element : element.closest(CARD_SELECTOR);
	    card && this.#changedCards.add(card);
	  }
	  #collectAddedCards(node) {
	    const element = elementFromNode(node);
	    if (!element || element.closest(".ldp-overlay,.ldp-topic-stats-component")) return;
	    const nearest = element.matches(CARD_SELECTOR) ? element : element.closest(CARD_SELECTOR);
	    if (nearest) {
	      this.#changedCards.add(nearest);
	      return;
	    }
	    for (const card of element.querySelectorAll(CARD_SELECTOR))
	      this.#changedCards.add(card);
	  }
	  #scheduleRootSync() {
	    this.#rootFrame || !this.#activeScope || (this.#rootFrame = this.#requestFrame(() => this.#syncRoots()));
	  }
	  #syncRoots() {
	    if (this.#rootFrame = 0, !this.#activeScope || !this.#document.body) return;
	    const next = /* @__PURE__ */ new Map(), mark = (node, role) => {
	      const root = topLevelBodyChild(this.#document, node);
	      if (!root || root === this.#overlay || root.contains(this.#overlay)) return;
	      const previous = next.get(root);
	      (!previous || ROOT_RANK[role] > ROOT_RANK[previous]) && next.set(root, role);
	    };
	    for (const role of ["shell", "header", "sidebar"])
	      for (const node of this.#document.querySelectorAll(ROOT_QUERIES[role]))
	        mark(node, role);
	    for (const [root, previousRole] of this.#roots) {
	      const nextRole = next.get(root);
	      previousRole === "shell" && nextRole !== "shell" && this.#enhancements.releaseRoot(root), nextRole || root.removeAttribute("data-ldp-reader-host-root");
	    }
	    for (const [root, role] of next)
	      root.setAttribute("data-ldp-reader-host-root", role), role === "shell" && this.#enhancements.syncRoot(root);
	    this.#roots = next;
	  }
	  #scheduleCardSync() {
	    this.#cardFrame || !this.#activeScope || !this.#changedCards.size && !this.#activityCards.size || (this.#cardFrame = this.#requestFrame(() => this.#flushCards()));
	  }
	  #flushCards() {
	    if (this.#cardFrame = 0, !this.#activeScope) return;
	    for (const card of this.#activityCards)
	      card.isConnected && !this.#changedCards.has(card) && !this.#enhancements.syncActivity(card) && this.#changedCards.add(card);
	    this.#activityCards.clear();
	    const cards = [...this.#changedCards].filter((card) => card.isConnected);
	    this.#changedCards.clear(), cards.length && this.#enhancements.syncCards(Object.freeze(cards));
	  }
	}
}, "a8b7bd4a80d4d85a55f76d0a2fdcf88a198fa8379f7e8fc6b9e4a88baf6d6a84");

/* Source: lite/src/shell/embedded-host-scrollbar.ts */
runtime.register("src/shell/embedded-host-scrollbar.js", function(module, exports, require) {
	var embedded_host_scrollbar_exports = {};
	__export(embedded_host_scrollbar_exports, {
	  EmbeddedHostScrollbarController: () => EmbeddedHostScrollbarController,
	  EmbeddedHostScrollbarModel: () => EmbeddedHostScrollbarModel
	});
	module.exports = __toCommonJS(embedded_host_scrollbar_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	const MIN_THUMB_HEIGHT = 24, KEY_LINE_STEP = 40, KEY_PAGE_RATIO = 0.9;
	function finite(value, fallback) {
	  return Number.isFinite(value) ? value : fallback;
	}
	function clamp(value, minimum, maximum) {
	  return Math.max(minimum, Math.min(maximum, value));
	}
	function roundedCssPixels(value) {
	  return `${Math.round(value * 100) / 100}px`;
	}
	function emptySnapshot() {
	  return Object.freeze({
	    viewportHeight: 1,
	    scrollHeight: 1,
	    scrollTop: 0,
	    maxScroll: 0,
	    trackHeight: 1,
	    thumbHeight: 1,
	    maxThumbTop: 0,
	    thumbTop: 0,
	    inactive: !0
	  });
	}
	class EmbeddedHostScrollbarModel {
	  changes = new import_signal.Signal();
	  #snapshot = emptySnapshot();
	  get snapshot() {
	    return this.#snapshot;
	  }
	  update(rawMetrics, rawTrackHeight) {
	    const viewportHeight = Math.max(1, finite(rawMetrics.viewportHeight, 1)), scrollHeight = Math.max(
	      viewportHeight,
	      finite(rawMetrics.scrollHeight, viewportHeight)
	    ), maxScroll = Math.max(0, scrollHeight - viewportHeight), scrollTop = clamp(finite(rawMetrics.scrollTop, 0), 0, maxScroll), trackHeight = Math.max(1, finite(rawTrackHeight, 1)), thumbHeight = maxScroll > 0 ? Math.min(
	      trackHeight,
	      Math.max(MIN_THUMB_HEIGHT, trackHeight * viewportHeight / scrollHeight)
	    ) : trackHeight, maxThumbTop = Math.max(0, trackHeight - thumbHeight), thumbTop = maxScroll > 0 ? maxThumbTop * scrollTop / maxScroll : 0, next = Object.freeze({
	      viewportHeight,
	      scrollHeight,
	      scrollTop,
	      maxScroll,
	      trackHeight,
	      thumbHeight,
	      maxThumbTop,
	      thumbTop,
	      inactive: maxScroll <= 0
	    }), previous = this.#snapshot;
	    return next.viewportHeight === previous.viewportHeight && next.scrollHeight === previous.scrollHeight && next.scrollTop === previous.scrollTop && next.trackHeight === previous.trackHeight ? previous : (this.#snapshot = next, this.changes.emit(next), next);
	  }
	  scrollTopForPointer(clientY, trackTop, pointerOffsetY) {
	    const snapshot = this.#snapshot;
	    if (snapshot.maxScroll <= 0 || snapshot.maxThumbTop <= 0) return 0;
	    const thumbTop = clamp(
	      clientY - trackTop - pointerOffsetY,
	      0,
	      snapshot.maxThumbTop
	    );
	    return snapshot.maxScroll * thumbTop / snapshot.maxThumbTop;
	  }
	  scrollTopForKey(key) {
	    const snapshot = this.#snapshot;
	    let next = snapshot.scrollTop;
	    if (key === "ArrowUp") next -= KEY_LINE_STEP;
	    else if (key === "ArrowDown") next += KEY_LINE_STEP;
	    else if (key === "PageUp") next -= snapshot.viewportHeight * KEY_PAGE_RATIO;
	    else if (key === "PageDown") next += snapshot.viewportHeight * KEY_PAGE_RATIO;
	    else if (key === "Home") next = 0;
	    else if (key === "End") next = snapshot.maxScroll;
	    else return null;
	    return clamp(next, 0, snapshot.maxScroll);
	  }
	  reset() {
	    const next = emptySnapshot();
	    return this.#snapshot = next, this.changes.emit(next), next;
	  }
	}
	function pointerEventTarget(event) {
	  return event.target;
	}
	class EmbeddedHostScrollbarController {
	  scope;
	  model = new EmbeddedHostScrollbarModel();
	  #workspace;
	  #track;
	  #thumb;
	  #scroll;
	  #readTrack;
	  #requestFrame;
	  #cancelFrame;
	  #createResizeObserver;
	  #resizeTargets;
	  #resizeObserver = null;
	  #frame = 0;
	  #pointer = null;
	  #active = !1;
	  #geometryDirty = !0;
	  #trackGeometry = { top: 0, height: 1 };
	  #destroyed = !1;
	  constructor(options) {
	    this.#workspace = options.workspace, this.#track = options.track, this.#thumb = options.thumb, this.#scroll = options.scroll, this.#resizeTargets = Object.freeze([
	      ...options.resizeTargets ?? [],
	      options.track
	    ]), this.#createResizeObserver = options.createResizeObserver, this.#readTrack = options.readTrack ?? (() => ({ top: this.#track.getBoundingClientRect().top, height: Math.max(1, this.#track.clientHeight) })), this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#workspace.changes.subscribe(() => this.#syncActivation(), this.scope), this.scope.listen(options.scrollTarget, "scroll", () => this.#schedule(), {
	      passive: !0
	    }), this.scope.listen(this.#track, "pointerdown", (event) => {
	      this.#onPointerDown(event);
	    }), this.scope.listen(this.#track, "pointermove", (event) => {
	      this.#onPointerMove(event);
	    });
	    for (const type of ["pointerup", "pointercancel", "lostpointercapture"])
	      this.scope.listen(this.#track, type, (event) => {
	        this.#stopPointer(event);
	      });
	    this.scope.listen(this.#track, "keydown", (event) => {
	      this.#onKeyDown(event);
	    }), this.scope.add(() => this.#deactivate()), this.#syncActivation();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  invalidateGeometry() {
	    this.#geometryDirty = !0, this.#schedule();
	  }
	  #syncActivation() {
	    if (this.#destroyed) return;
	    const active = this.#workspace.snapshot.presentation.embedded;
	    active !== this.#active && (active ? this.#activate() : this.#deactivate());
	  }
	  #activate() {
	    if (this.#active = !0, this.#geometryDirty = !0, !this.#resizeObserver && this.#createResizeObserver) {
	      this.#resizeObserver = this.#createResizeObserver(() => this.invalidateGeometry());
	      for (const target of this.#resizeTargets) this.#resizeObserver.observe(target);
	    }
	    this.#schedule();
	  }
	  #deactivate() {
	    this.#active = !1, this.#frame && this.#cancelFrame(this.#frame), this.#frame = 0, this.#resizeObserver?.disconnect(), this.#resizeObserver = null, this.#stopPointer(), this.#geometryDirty = !0, this.#trackGeometry = { top: 0, height: 1 }, this.model.reset(), this.#clearDom();
	  }
	  #schedule() {
	    !this.#active || this.#frame || (this.#frame = this.#requestFrame(() => this.#sync()));
	  }
	  #sync() {
	    if (this.#frame = 0, !this.#active) return;
	    if (this.#geometryDirty) {
	      const geometry = this.#readTrack();
	      this.#trackGeometry = {
	        top: finite(geometry.top, 0),
	        height: Math.max(1, finite(geometry.height, 1))
	      }, this.#geometryDirty = !1;
	    }
	    const snapshot = this.model.update(this.#scroll.read(), this.#trackGeometry.height);
	    this.#apply(snapshot);
	  }
	  #apply(snapshot) {
	    this.#track.classList.toggle(
	      "ldp-reader-host-scrollbar-inactive",
	      snapshot.inactive
	    ), this.#track.setAttribute("aria-disabled", String(snapshot.inactive)), this.#track.setAttribute("aria-valuemin", "0"), this.#track.setAttribute("aria-valuemax", String(Math.round(snapshot.maxScroll))), this.#track.setAttribute("aria-valuenow", String(Math.round(snapshot.scrollTop))), this.#thumb.style.height = roundedCssPixels(snapshot.thumbHeight), this.#thumb.style.transform = `translateY(${roundedCssPixels(snapshot.thumbTop)})`;
	  }
	  #clearDom() {
	    this.#track.classList.remove("ldp-reader-host-scrollbar-dragging"), this.#track.classList.add("ldp-reader-host-scrollbar-inactive"), this.#track.setAttribute("aria-disabled", "true"), this.#track.setAttribute("aria-valuemin", "0"), this.#track.setAttribute("aria-valuemax", "0"), this.#track.setAttribute("aria-valuenow", "0"), this.#thumb.style.removeProperty("height"), this.#thumb.style.removeProperty("transform");
	  }
	  #onPointerDown(event) {
	    if (event.button !== 0 || !this.#active) return;
	    this.#frame && this.#cancelFrame(this.#frame), this.#frame = 0, this.#geometryDirty = !0, this.#sync();
	    const snapshot = this.model.snapshot;
	    if (snapshot.inactive) return;
	    event.preventDefault(), event.stopPropagation();
	    const onThumb = pointerEventTarget(event) === this.#thumb, thumbRect = this.#thumb.getBoundingClientRect();
	    this.#pointer = {
	      pointerId: event.pointerId,
	      offsetY: onThumb ? event.clientY - thumbRect.top : snapshot.thumbHeight / 2
	    }, this.#track.classList.add("ldp-reader-host-scrollbar-dragging");
	    try {
	      this.#track.setPointerCapture(event.pointerId);
	    } catch {
	    }
	    try {
	      this.#track.focus({ preventScroll: !0 });
	    } catch {
	      this.#track.focus();
	    }
	    this.#scrollFromPointer(event.clientY);
	  }
	  #onPointerMove(event) {
	    !this.#pointer || event.pointerId !== this.#pointer.pointerId || (event.preventDefault(), this.#scrollFromPointer(event.clientY));
	  }
	  #scrollFromPointer(clientY) {
	    this.#pointer && this.#scroll.scrollTo(this.model.scrollTopForPointer(
	      clientY,
	      this.#trackGeometry.top,
	      this.#pointer.offsetY
	    ));
	  }
	  #stopPointer(event) {
	    if (!this.#pointer || event && event.pointerId !== this.#pointer.pointerId)
	      return;
	    const pointerId = this.#pointer.pointerId;
	    this.#pointer = null, this.#track.classList.remove("ldp-reader-host-scrollbar-dragging");
	    try {
	      this.#track.hasPointerCapture(pointerId) && this.#track.releasePointerCapture(pointerId);
	    } catch {
	    }
	  }
	  #onKeyDown(event) {
	    if (!this.#active) return;
	    this.#sync();
	    const top = this.model.scrollTopForKey(event.key);
	    top !== null && (event.preventDefault(), event.stopPropagation(), this.#scroll.scrollTo(top));
	  }
	}
}, "286dc64ee043a9481bac9e51bb448ef56ab04fc07dfc33ed8126ef39427e43d9");

/* Source: lite/src/shell/embedded-host-top-shortcut.ts */
runtime.register("src/shell/embedded-host-top-shortcut.js", function(module, exports, require) {
	var embedded_host_top_shortcut_exports = {};
	__export(embedded_host_top_shortcut_exports, {
	  EmbeddedHostTopShortcutController: () => EmbeddedHostTopShortcutController
	});
	module.exports = __toCommonJS(embedded_host_top_shortcut_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	const TOP_EDGE = 8, BUTTON_SIZE = 30, POINTER_GAP = 12, UPWARD_DISTANCE = 240, UPWARD_WINDOW_MS = 450, UPWARD_BREAK_MS = 240, SHOW_DURATION_MS = 3e3;
	class EmbeddedHostTopShortcutController {
	  scope;
	  #workspace;
	  #button;
	  #readScrollTop;
	  #readViewportHeight;
	  #scrollToTop;
	  #now;
	  #setTimeout;
	  #clearTimeout;
	  #setInterval;
	  #clearInterval;
	  #requestFrame;
	  #cancelFrame;
	  #hideTimer = 0;
	  #countdownTimer = 0;
	  #jumpFrame = 0;
	  #scrollTop = 0;
	  #upwardDistance = 0;
	  #upwardStartedAt = 0;
	  #lastScrollAt = 0;
	  #pointerX = Number.NaN;
	  #pointerY = Number.NaN;
	  #jumping = !1;
	  #active = !1;
	  #destroyed = !1;
	  constructor(options) {
	    this.#workspace = options.workspace, this.#button = options.button, this.#readScrollTop = options.readScrollTop, this.#readViewportHeight = options.readViewportHeight, this.#scrollToTop = options.scrollToTop, this.#now = options.now ?? (() => performance.now()), this.#setTimeout = options.setTimeout ?? ((callback, milliseconds) => window.setTimeout(callback, milliseconds)), this.#clearTimeout = options.clearTimeout ?? ((id) => window.clearTimeout(id)), this.#setInterval = options.setInterval ?? ((callback, milliseconds) => window.setInterval(callback, milliseconds)), this.#clearInterval = options.clearInterval ?? ((id) => window.clearInterval(id)), this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#button.hidden = !0, this.#workspace.changes.subscribe(() => this.#syncActivation(), this.scope);
	    for (const type of ["pointermove", "wheel"])
	      this.scope.listen(options.pointerTarget, type, (event) => {
	        this.#rememberPointer(event);
	      }, { passive: !0 });
	    this.scope.listen(options.scrollTarget, "scroll", () => this.#onScroll(), {
	      passive: !0
	    }), this.scope.listen(this.#button, "click", (event) => this.#onClick(event)), this.scope.add(() => this.#deactivate()), this.#syncActivation();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #syncActivation() {
	    if (this.#destroyed) return;
	    const active = this.#workspace.snapshot.presentation.embedded;
	    active !== this.#active && (active ? (this.#active = !0, this.#resetUpward(this.#safeScrollTop())) : this.#deactivate());
	  }
	  #deactivate() {
	    this.#active = !1, this.#hide(), this.#jumpFrame && this.#cancelFrame(this.#jumpFrame), this.#jumpFrame = 0, this.#jumping = !1, this.#pointerX = Number.NaN, this.#pointerY = Number.NaN, this.#resetUpward(0);
	  }
	  #safeScrollTop() {
	    return Math.max(0, Number(this.#readScrollTop()) || 0);
	  }
	  #hostBounds() {
	    const snapshot = this.#workspace.snapshot;
	    return snapshot.presentation.side === "left" ? { left: snapshot.embedWidth, right: snapshot.viewportWidth } : { left: 0, right: snapshot.viewportWidth - snapshot.embedWidth };
	  }
	  #rememberPointer(event) {
	    if (!this.#active || !Number.isFinite(event.clientX) || !Number.isFinite(event.clientY))
	      return;
	    const bounds = this.#hostBounds();
	    event.clientX < bounds.left || event.clientX > bounds.right || (this.#pointerX = event.clientX, this.#pointerY = event.clientY);
	  }
	  #onScroll() {
	    if (!this.#active) return;
	    const scrollTop = this.#safeScrollTop();
	    if (this.#jumping) {
	      this.#resetUpward(scrollTop);
	      return;
	    }
	    const now = this.#now(), upwardDelta = this.#scrollTop - scrollTop;
	    if (this.#scrollTop = scrollTop, scrollTop <= TOP_EDGE && this.#hide(), upwardDelta <= 0) {
	      this.#upwardDistance = 0, this.#upwardStartedAt = 0, this.#lastScrollAt = now;
	      return;
	    }
	    (!this.#upwardStartedAt || now - this.#lastScrollAt > UPWARD_BREAK_MS) && (this.#upwardStartedAt = now, this.#upwardDistance = 0), this.#lastScrollAt = now, this.#upwardDistance += upwardDelta, this.#upwardDistance >= UPWARD_DISTANCE && now - this.#upwardStartedAt <= UPWARD_WINDOW_MS && (this.#show(), this.#upwardStartedAt = now, this.#upwardDistance = 0);
	  }
	  #show() {
	    if (!this.#active || !Number.isFinite(this.#pointerX) || !Number.isFinite(this.#pointerY))
	      return;
	    if (this.#safeScrollTop() <= TOP_EDGE) {
	      this.#hide();
	      return;
	    }
	    if (this.#button.hidden) {
	      const bounds = this.#hostBounds(), left = Math.max(
	        bounds.left + TOP_EDGE,
	        Math.min(
	          bounds.right - BUTTON_SIZE - TOP_EDGE,
	          this.#pointerX + POINTER_GAP
	        )
	      ), top = Math.max(
	        TOP_EDGE,
	        Math.min(
	          this.#readViewportHeight() - BUTTON_SIZE - TOP_EDGE,
	          this.#pointerY - BUTTON_SIZE / 2
	        )
	      );
	      this.#button.style.left = `${Math.round(left)}px`, this.#button.style.top = `${Math.round(top)}px`, this.#button.hidden = !1;
	    }
	    this.#clearTimers();
	    const countdown = this.#button.querySelector(
	      ".ldp-reader-host-top-countdown,[data-reader-host-top-countdown]"
	    ), hideAt = this.#now() + SHOW_DURATION_MS, updateCountdown = () => {
	      countdown && (countdown.textContent = String(Math.max(
	        1,
	        Math.ceil((hideAt - this.#now()) / 1e3)
	      )));
	    };
	    updateCountdown(), this.#countdownTimer = this.#setInterval(updateCountdown, 200), this.#hideTimer = this.#setTimeout(() => this.#hide(), SHOW_DURATION_MS);
	  }
	  #hide() {
	    this.#clearTimers(), this.#button.hidden = !0;
	  }
	  #clearTimers() {
	    this.#hideTimer && this.#clearTimeout(this.#hideTimer), this.#countdownTimer && this.#clearInterval(this.#countdownTimer), this.#hideTimer = 0, this.#countdownTimer = 0;
	  }
	  #resetUpward(scrollTop) {
	    this.#scrollTop = Math.max(0, scrollTop), this.#upwardDistance = 0, this.#upwardStartedAt = 0, this.#lastScrollAt = 0;
	  }
	  #onClick(event) {
	    this.#active && (event.preventDefault(), event.stopPropagation(), this.#hide(), this.#jumping = !0, this.#scrollToTop(), this.#jumpFrame && this.#cancelFrame(this.#jumpFrame), this.#jumpFrame = this.#requestFrame(() => {
	      this.#jumpFrame = 0, this.#jumping = !1, this.#resetUpward(0);
	    }));
	  }
	}
}, "e5f43df781c7a5f30f18d8048664857a0badfe6b1f7a8e1121dbfcb17dd5c62f");

/* Source: lite/src/shell/embedded-host-topic-card-enhancement.ts */
runtime.register("src/shell/embedded-host-topic-card-enhancement.js", function(module, exports, require) {
	var embedded_host_topic_card_enhancement_exports = {};
	__export(embedded_host_topic_card_enhancement_exports, {
	  EmbeddedHostTopicCardEnhancement: () => EmbeddedHostTopicCardEnhancement
	});
	module.exports = __toCommonJS(embedded_host_topic_card_enhancement_exports);
	var import_native_topic_notification_action = require("../discourse/native-topic-notification-action.js"), import_value_record = require("../kernel/value-record.js");
	const CARD_SELECTOR = "tr.topic-list-item,.topic-list-item,.latest-topic-list-item", TOPIC_LINK_SELECTOR = 'a.raw-topic-link[href*="/t/"],a.title[href*="/t/"],a[href*="/t/"]';
	function modelValue(value, key) {
	  const source = (0, import_value_record.valueRecord)(value);
	  if (!source) return;
	  const getter = source.get;
	  if (typeof getter == "function")
	    try {
	      const result = getter.call(value, key);
	      if (result !== void 0) return result;
	    } catch {
	    }
	  return source[key];
	}
	function modelArray(value) {
	  if (Array.isArray(value)) return value;
	  const toArray = (0, import_value_record.valueRecord)(value)?.toArray;
	  if (typeof toArray != "function") return Object.freeze([]);
	  try {
	    const result = toArray.call(value);
	    return Array.isArray(result) ? result : Object.freeze([]);
	  } catch {
	    return Object.freeze([]);
	  }
	}
	function setModelValue(value, key, next) {
	  const source = (0, import_value_record.valueRecord)(value), setter = source?.set;
	  typeof setter == "function" ? setter.call(value, key, next) : source && (source[key] = next);
	}
	function reactionCountTotal(value) {
	  const reactions = modelValue(value, "reactions"), source = (0, import_value_record.valueRecord)(reactions);
	  return !Array.isArray(reactions) && typeof source?.toArray != "function" ? null : modelArray(reactions).reduce((total, reaction) => {
	    const count = Number(modelValue(reaction, "count"));
	    return Number.isFinite(count) && count > 0 ? total + Math.trunc(count) : total;
	  }, 0);
	}
	function markup(node) {
	  return node.nodeType === 1 ? node.outerHTML : String(node.textContent ?? "");
	}
	function sourceNodes(cell, owned) {
	  return Object.freeze(
	    [...cell.childNodes].filter((node) => node !== owned).map((node) => node.cloneNode(!0))
	  );
	}
	function directChild(line, node) {
	  let current = node;
	  for (; current && current.parentElement !== line; )
	    current = current.parentElement;
	  return current?.parentElement === line ? current : null;
	}
	function normalizedLabel(value) {
	  return String(value ?? "").replace(/\s+/g, " ").trim();
	}
	function nativeDndLabel(node) {
	  return [
	    node.textContent,
	    node.getAttribute("aria-label"),
	    node.getAttribute("title"),
	    node.getAttribute("data-tooltip"),
	    node.getAttribute("data-tippy-content")
	  ].map(normalizedLabel).filter(Boolean).some(
	    (label) => label.includes("免打扰") || label.includes("静音") || /\b(?:mute|muted|unmute)\b/i.test(label)
	  );
	}
	class EmbeddedHostTopicCardEnhancement {
	  #document;
	  #host;
	  #notifications;
	  #notify;
	  #onError;
	  #roots = /* @__PURE__ */ new Set();
	  constructor(document, host, options = {}) {
	    this.#document = document, this.#host = host, this.#notifications = options.notifications ?? new import_native_topic_notification_action.BrowserDiscourseTopicNotificationLevelMutationPort(host), this.#notify = options.notify ?? (() => {
	    }), this.#onError = options.onError ?? (() => {
	    });
	  }
	  syncRoot(root) {
	    this.#roots.add(root), this.syncCards(
	      Object.freeze([...root.querySelectorAll(CARD_SELECTOR)])
	    );
	  }
	  releaseRoot(root) {
	    this.#roots.has(root) && (this.#clearRoot(root), this.#roots.delete(root));
	  }
	  syncActivity(card) {
	    const source = card.querySelector(":scope > td.activity .relative-date"), component = card.querySelector(
	      ":scope > td.posts > .ldp-topic-stats-component"
	    ), target = component?.querySelector(
	      ".ldp-topic-stat--activity .relative-date"
	    );
	    if (!source || !component || !target) return !1;
	    target.textContent !== source.textContent && (target.textContent = source.textContent);
	    const parts = String(
	      component.dataset.ldpSourceSignature ?? ""
	    ).split(""), activity = card.querySelector(":scope > td.activity");
	    return parts.length === 5 && activity && (parts[2] = [...activity.childNodes].map(markup).join(""), component.dataset.ldpSourceSignature = parts.join("")), !0;
	  }
	  syncCards(cards) {
	    const topicModels = this.#topicModels(), reactions = this.#reactionCounts(topicModels);
	    for (const card of cards)
	      !card.matches(CARD_SELECTOR) || card.closest(".ldp-overlay") || (this.#markDateCells(card), this.#groupTitleTools(
	        card,
	        this.#topicInput(card, topicModels)
	      ), this.#syncStats(card, reactions));
	  }
	  clear() {
	    for (const root of this.#roots)
	      this.#clearRoot(root);
	    this.#roots.clear();
	  }
	  #clearRoot(root) {
	    for (const component of root.querySelectorAll(
	      ".ldp-topic-stats-component"
	    )) component.remove();
	    for (const button of root.querySelectorAll(
	      "[data-ldp-owned-native-dnd]"
	    )) button.remove();
	    for (const group of root.querySelectorAll(
	      ".ldp-native-topic-title-tools"
	    )) group.replaceWith(...group.childNodes);
	    for (const node of root.querySelectorAll(
	      "[data-ldp-native-dnd]"
	    )) node.removeAttribute("data-ldp-native-dnd");
	    for (const node of root.querySelectorAll(
	      "[data-ldp-native-topic-date]"
	    )) node.removeAttribute("data-ldp-native-topic-date");
	    for (const node of root.querySelectorAll(
	      "[data-ldp-native-old-topic]"
	    )) node.removeAttribute("data-ldp-native-old-topic");
	  }
	  #syncStats(card, reactions) {
	    const posts = card.querySelector(":scope > td.posts"), views = card.querySelector(":scope > td.views"), activity = card.querySelector(":scope > td.activity");
	    if (!posts || !views || !activity) return;
	    let component = posts.querySelector(
	      ":scope > .ldp-topic-stats-component"
	    );
	    const topicId = this.#cardTopicId(card), responseCount = topicId && reactions.has(topicId) ? reactions.get(topicId) : null, sourceSignature = [
	      [...posts.childNodes].filter((node) => node !== component).map(markup).join(""),
	      [...views.childNodes].map(markup).join(""),
	      [...activity.childNodes].map(markup).join(""),
	      views.dataset.ldpNativeOldTopic === "true" ? "old" : "recent",
	      responseCount ?? ""
	    ].join("");
	    if (component?.dataset.ldpSourceSignature === sourceSignature && component.querySelectorAll(":scope > .ldp-topic-stats-row").length === 2) return;
	    component || (component = this.#document.createElement("div"), component.className = "ldp-topic-stats-component", posts.append(component));
	    const stat = (kind, label, nodes) => {
	      const item = this.#document.createElement("span");
	      item.className = `ldp-topic-stat ldp-topic-stat--${kind}`;
	      const caption = this.#document.createElement("span");
	      caption.className = "ldp-topic-stat-label", caption.textContent = label;
	      const value = this.#document.createElement("span");
	      return value.className = "ldp-topic-stat-value", value.append(...nodes), item.append(caption, value), item;
	    }, row = (...items) => {
	      const value = this.#document.createElement("span");
	      return value.className = "ldp-topic-stats-row", value.append(...items), value;
	    }, oldTopic = views.dataset.ldpNativeOldTopic === "true";
	    component.replaceChildren(
	      row(
	        stat("reply", "回复", sourceNodes(posts, component)),
	        stat(
	          oldTopic ? "old" : "date",
	          oldTopic ? "旧帖" : "最近回复",
	          sourceNodes(views, null)
	        )
	      ),
	      row(
	        stat("activity", "活跃", sourceNodes(activity, null)),
	        stat(
	          "response",
	          "回应",
	          [this.#document.createTextNode(
	            responseCount == null ? "—" : String(responseCount)
	          )]
	        )
	      )
	    ), component.dataset.ldpSourceSignature = sourceSignature;
	  }
	  #markDateCells(card) {
	    const cells = [...card.querySelectorAll(":scope > td")], dateCell = card.querySelector(":scope > td.views") ?? cells[3] ?? null;
	    for (const cell of cells) {
	      const text = cell === dateCell ? String(cell.textContent ?? "").replace(/\s+/g, " ").trim() : "", date = /\b(?:\d{4}[/-])?\d{1,2}[/-]\d{1,2}\s+\d{1,2}:\d{2}\b/.test(text), old = /\b\d{4}[/-]\d{1,2}[/-]\d{1,2}\s+\d{1,2}:\d{2}\b/.test(text);
	      cell.toggleAttribute("data-ldp-native-topic-date", date), cell.toggleAttribute("data-ldp-native-old-topic", old);
	    }
	  }
	  #groupTitleTools(card, topic) {
	    const line = card.querySelector(".link-top-line");
	    if (!line) return;
	    const direct = [...line.querySelectorAll(
	      'a,button,[role="button"]'
	    )].find(
	      (node) => !node.matches(TOPIC_LINK_SELECTOR) && nativeDndLabel(node)
	    ), fallback = direct ? null : [...line.querySelectorAll("span")].find(
	      (node) => !node.closest(TOPIC_LINK_SELECTOR) && nativeDndLabel(node)
	    );
	    let dnd = direct ?? fallback?.closest(
	      'a,button,[role="button"]'
	    ) ?? fallback;
	    if (!dnd && topic && this.#currentUsername() && (dnd = this.#createDndButton(line, topic)), !dnd) return;
	    dnd.dataset.ldpNativeDnd = "true";
	    const expert = [...line.querySelectorAll("*")].find((node) => normalizedLabel(node.textContent) === "专家回应");
	    if (!expert) return;
	    const startNode = directChild(line, expert), endNode = directChild(line, dnd);
	    if (!startNode || !endNode || startNode === endNode && startNode.classList.contains("ldp-native-topic-title-tools")) return;
	    const children = [...line.children], start = children.indexOf(startNode), end = children.indexOf(endNode);
	    if (start < 0 || end < start) return;
	    const group = this.#document.createElement("span");
	    group.className = "ldp-native-topic-title-tools", startNode.before(group), group.append(...children.slice(start, end + 1));
	  }
	  #createDndButton(line, topic) {
	    const button = this.#document.createElement("button");
	    return button.type = "button", button.dataset.ldpOwnedNativeDnd = "true", button.dataset.ldpNativeDnd = "true", button.setAttribute("aria-label", "将此话题设为免打扰"), button.setAttribute("aria-pressed", "false"), button.title = "免打扰", button.addEventListener("click", (event) => {
	      event.preventDefault(), event.stopPropagation(), this.#muteTopic(button, topic);
	    }), line.append(button), button;
	  }
	  async #muteTopic(button, topic) {
	    if (!(button.dataset.ldpNativeDndPending === "true" || button.getAttribute("aria-pressed") === "true")) {
	      button.dataset.ldpNativeDndPending = "true", button.disabled = !0, button.setAttribute("aria-busy", "true");
	      try {
	        await this.#notifications.setLevel(topic, 0), setModelValue(topic, "notification_level", 0);
	        const details = modelValue(topic, "details");
	        details && setModelValue(details, "notification_level", 0), button.dataset.ldpNativeDndActive = "true", button.setAttribute("aria-pressed", "true"), button.setAttribute("aria-label", "此话题已设为免打扰"), button.title = "已设为免打扰", this.#notify("已将话题设为免打扰");
	      } catch (cause) {
	        this.#onError(cause), this.#notify("设置免打扰失败,请稍后重试");
	      } finally {
	        delete button.dataset.ldpNativeDndPending, button.disabled = !1, button.removeAttribute("aria-busy");
	      }
	    }
	  }
	  #currentUsername() {
	    return normalizedLabel(modelValue(
	      this.#host.lookup("service:current-user"),
	      "username"
	    ));
	  }
	  #topicModels() {
	    const result = /* @__PURE__ */ new Map(), router = this.#host.lookup("service:router"), routeName = String(
	      modelValue(router, "currentRouteName") ?? ""
	    ).trim(), controllerNames = [
	      routeName ? `controller:${routeName}` : "",
	      routeName ? `controller:${routeName.replaceAll(".", "/")}` : "",
	      "controller:discovery/list",
	      "controller:tag/show",
	      "controller:tags/intersection"
	    ].filter(Boolean), seen = /* @__PURE__ */ new Set();
	    for (const name of controllerNames) {
	      const controller = this.#host.lookup(name);
	      if (!controller || seen.has(controller)) continue;
	      seen.add(controller);
	      const model = modelValue(controller, "model"), list = modelValue(model, "list") ?? modelValue(controller, "list") ?? model, topics = modelArray(
	        modelValue(list, "topics") ?? modelValue(list, "content")
	      );
	      for (const topic of topics) {
	        const topicId = String(modelValue(topic, "id") ?? "").trim();
	        !topicId || result.has(topicId) || result.set(topicId, topic);
	      }
	    }
	    return result;
	  }
	  #reactionCounts(topics) {
	    const counts = /* @__PURE__ */ new Map();
	    for (const [topicId, topic] of topics) {
	      const reactions = modelValue(topic, "op_reactions_data") ?? modelValue(topic, "opReactionsData"), total = reactionCountTotal(reactions), raw = reactions ? total ?? modelValue(reactions, "reaction_users_count") ?? modelValue(reactions, "reactionUsersCount") : modelValue(topic, "op_like_count") ?? modelValue(topic, "opLikeCount"), numeric = Number(raw);
	      counts.set(
	        topicId,
	        Number.isFinite(numeric) && numeric >= 0 ? Math.trunc(numeric) : null
	      );
	    }
	    return counts;
	  }
	  #topicInput(card, topics) {
	    const topicId = this.#cardTopicId(card);
	    if (!topicId) return null;
	    const model = topics.get(topicId);
	    if (model) return model;
	    const numericId = Number(topicId);
	    if (!Number.isSafeInteger(numericId) || numericId < 1) return null;
	    const link = card.querySelector(TOPIC_LINK_SELECTOR);
	    return Object.freeze({
	      id: numericId,
	      title: normalizedLabel(link?.textContent),
	      slug: "topic",
	      notification_level: 1
	    });
	  }
	  #cardTopicId(card) {
	    return String(
	      card.dataset.topicId ?? card.getAttribute("data-topic-id") ?? this.#topicId(card.querySelector(TOPIC_LINK_SELECTOR))
	    ).trim();
	  }
	  #topicId(link) {
	    return (link?.getAttribute("href") ?? "").match(/\/t\/(?:[^/]+\/)?(\d+)(?:\/|$)/)?.[1] ?? "";
	  }
	}
}, "78cf133c3be18786587ae7156e2ce9ddea07ef0fac934c049b8723f2f1f1728c");

/* Source: lite/src/shell/main-outlet-mutation-hub.ts */
runtime.register("src/shell/main-outlet-mutation-hub.js", function(module, exports, require) {
	var main_outlet_mutation_hub_exports = {};
	__export(main_outlet_mutation_hub_exports, {
	  MainOutletMutationHub: () => MainOutletMutationHub
	});
	module.exports = __toCommonJS(main_outlet_mutation_hub_exports);
	const MAIN_OUTLET_SELECTORS = Object.freeze([
	  "#main-outlet",
	  ".list-container,.topic-list,.latest-topic-list",
	  "#ember-app"
	]);
	function mainOutlet(documentPort) {
	  for (const selector of MAIN_OUTLET_SELECTORS) {
	    const root = documentPort.querySelector(selector);
	    if (root) return root;
	  }
	  return null;
	}
	class MainOutletMutationHub {
	  #document;
	  #createObserver;
	  #onListenerError;
	  #listeners = /* @__PURE__ */ new Set();
	  #observer = null;
	  #root = null;
	  #destroyed = !1;
	  constructor(options) {
	    this.#document = options.document, this.#createObserver = options.createObserver ?? ((callback) => new MutationObserver(callback)), this.#onListenerError = options.onListenerError ?? (() => {
	    });
	  }
	  get currentRoot() {
	    return this.#root;
	  }
	  subscribe(listener, scope) {
	    if (this.#destroyed) throw new Error("MainOutletMutationHub 已销毁");
	    this.#listeners.add(listener), this.#listeners.size === 1 && this.#start(), this.#notifyOne(listener, Object.freeze({
	      root: this.#root,
	      rootChanged: !0,
	      records: Object.freeze([])
	    }));
	    let active = !0;
	    const unsubscribe = () => {
	      active && (active = !1, this.#listeners.delete(listener), this.#listeners.size || this.#stop());
	    };
	    return scope?.add(unsubscribe), unsubscribe;
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.#listeners.clear(), this.#stop());
	  }
	  #start() {
	    this.#observer || !this.#document.body || (this.#observer = this.#createObserver((records) => this.#receive(records)), this.#retarget());
	  }
	  #stop() {
	    this.#observer?.disconnect(), this.#observer = null, this.#root = null;
	  }
	  #retarget() {
	    const observer = this.#observer, body = this.#document.body;
	    if (!observer || !body) return !1;
	    const nextRoot = mainOutlet(this.#document);
	    if (this.#root === nextRoot && nextRoot?.isConnected) return !1;
	    if (observer.disconnect(), observer.observe(body, { childList: !0 }), nextRoot) {
	      let ancestor = nextRoot.parentElement;
	      for (; ancestor && ancestor !== body; )
	        observer.observe(ancestor, { childList: !0 }), ancestor = ancestor.parentElement;
	      observer.observe(nextRoot, {
	        childList: !0,
	        subtree: !0,
	        characterData: !0
	      });
	    }
	    return this.#root = nextRoot, !0;
	  }
	  #receive(records) {
	    if (this.#destroyed || !this.#listeners.size) return;
	    const body = this.#document.body, rootChanged = records.some(
	      (record) => record.target === body || !this.#root?.isConnected || !this.#root.contains(record.target)
	    ) ? this.#retarget() : !1, batch = Object.freeze({
	      root: this.#root,
	      rootChanged,
	      records: Object.freeze([...records])
	    });
	    for (const listener of [...this.#listeners]) this.#notifyOne(listener, batch);
	  }
	  #notifyOne(listener, batch) {
	    try {
	      listener(batch);
	    } catch (error) {
	      this.#onListenerError(error);
	    }
	  }
	}
}, "1b77cf81270ca6322e13b2672695a5a2fcb0a16f057ebe5dc6b8817a3c54763c");

/* Source: lite/src/shell/reader-action-form-support.ts */
runtime.register("src/shell/reader-action-form-support.js", function(module, exports, require) {
	var reader_action_form_support_exports = {};
	__export(reader_action_form_support_exports, {
	  ReaderActionFormSession: () => ReaderActionFormSession,
	  ReaderActionFormSurfaceHost: () => ReaderActionFormSurfaceHost,
	  ReaderActionFormTiming: () => ReaderActionFormTiming,
	  createReaderActionFormFrame: () => createReaderActionFormFrame,
	  handleReaderActionDialogKeydown: () => handleReaderActionDialogKeydown,
	  renderReaderActionIcon: () => renderReaderActionIcon,
	  setReaderActionFormBusy: () => setReaderActionFormBusy
	});
	module.exports = __toCommonJS(reader_action_form_support_exports);
	var import_event_target = require("../dom/event-target.js"), import_floating_surface_wheel = require("../dom/floating-surface-wheel.js"), import_reader_icon = require("../components/reader-icon.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_reader_action_surface_coordinator = require("./reader-action-surface-coordinator.js");
	function renderReaderActionIcon(document, name, renderIcon) {
	  return (0, import_reader_icon.renderReaderIcon)(document, name, renderIcon);
	}
	function createReaderActionFormFrame(options) {
	  const { document } = options, layer = document.createElement("div");
	  layer.className = "ldp-reader-action-layer";
	  const dialog = document.createElement("section");
	  dialog.className = "ldp-reader-action-dialog", dialog.setAttribute("role", "dialog"), dialog.setAttribute("aria-modal", "true"), dialog.setAttribute("aria-labelledby", options.titleId);
	  const head = document.createElement("div");
	  head.className = "ldp-reader-action-head";
	  const title = document.createElement("strong");
	  title.id = options.titleId, title.textContent = options.title;
	  const close = document.createElement("button");
	  close.type = "button", close.className = "ldp-reader-action-close", close.setAttribute(options.closeDataAttribute, ""), close.setAttribute("aria-label", "关闭"), close.append(renderReaderActionIcon(document, "x", options.renderIcon)), head.append(title, close);
	  const form = document.createElement("form");
	  form.className = "ldp-reader-action-form";
	  const body = document.createElement("div");
	  if (body.className = "ldp-reader-action-body", options.intro) {
	    const intro = document.createElement("p");
	    intro.className = "ldp-reader-action-intro", intro.textContent = options.intro, body.append(intro);
	  }
	  const status = document.createElement("p");
	  status.className = "ldp-reader-action-status", status.setAttribute("role", "status"), status.setAttribute("aria-live", "polite"), body.append(status);
	  const footer = document.createElement("div");
	  footer.className = "ldp-reader-action-footer";
	  const cancel = document.createElement("button");
	  cancel.type = "button", cancel.className = "ldp-reader-action-cancel", cancel.setAttribute(options.cancelDataAttribute, ""), cancel.textContent = "取消";
	  const submit = document.createElement("button");
	  return submit.type = "submit", submit.className = "ldp-reader-action-submit", submit.textContent = options.submitLabel, footer.append(cancel, submit), form.append(body, footer), dialog.append(head, form), layer.append(dialog), { layer, dialog, form, body, status, cancel, submit };
	}
	class ReaderActionFormTiming {
	  #document;
	  #successDelayMs;
	  #schedule;
	  #cancel;
	  #focusSoon;
	  #closeTimer = null;
	  constructor(document, options) {
	    if (this.#document = document, this.#successDelayMs = Number(options.successDelayMs ?? 650), !Number.isFinite(this.#successDelayMs) || this.#successDelayMs < 0)
	      throw new RangeError("successDelayMs 必须是非负有限数值");
	    this.#schedule = options.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs)), this.#cancel = options.cancel ?? ((handle) => clearTimeout(
	      handle
	    )), this.#focusSoon = options.focusSoon ?? ((callback) => {
	      const view = this.#document.defaultView;
	      view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : queueMicrotask(callback);
	    });
	  }
	  focus(callback) {
	    this.#focusSoon(callback);
	  }
	  restore(previousFocus) {
	    this.focus(() => {
	      previousFocus?.isConnected && typeof previousFocus.focus == "function" && previousFocus.focus({ preventScroll: !0 });
	    });
	  }
	  scheduleClose(callback) {
	    this.clear(), this.#closeTimer = this.#schedule(callback, this.#successDelayMs);
	  }
	  clear() {
	    this.#closeTimer !== null && (this.#cancel(this.#closeTimer), this.#closeTimer = null);
	  }
	}
	function setReaderActionFormBusy(form, submit, busy, busyLabel, idleLabel) {
	  for (const control of form.querySelectorAll("input,textarea,select,button"))
	    control.disabled = busy;
	  submit.textContent = busy ? busyLabel : idleLabel;
	}
	function handleReaderActionDialogKeydown(event, document, dialog, busy, close) {
	  if (event.key === "Escape" && !busy) {
	    event.preventDefault(), close();
	    return;
	  }
	  if (event.key !== "Tab") return;
	  const controls = [
	    ...dialog.querySelectorAll(
	      "input:not(:disabled),textarea:not(:disabled),select:not(:disabled),button:not(:disabled)"
	    )
	  ];
	  if (!controls.length) return;
	  const first = controls[0], last = controls.at(-1), active = (0, import_event_target.deepActiveElement)(document);
	  event.shiftKey && active === first ? (event.preventDefault(), last.focus()) : !event.shiftKey && active === last && (event.preventDefault(), first.focus());
	}
	class ReaderActionFormSurfaceHost {
	  scope;
	  document;
	  renderIcon;
	  #root;
	  #label;
	  #timing;
	  #coordinator;
	  #active = null;
	  #id = 0;
	  constructor(options) {
	    this.document = options.document, this.#root = options.root, this.#label = options.label, this.renderIcon = options.renderIcon, this.#timing = new ReaderActionFormTiming(options.document, options), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#coordinator = options.coordinator ?? new import_reader_action_surface_coordinator.ReaderActionSurfaceCoordinator({ parentScope: this.scope }), this.scope.add(() => this.close(!1));
	  }
	  prepare() {
	    this.#assertActive();
	    const previousFocus = this.#active?.previousFocus ?? (0, import_event_target.deepActiveElement)(this.document);
	    return this.close(!1), Object.freeze({
	      id: ++this.#id,
	      previousFocus
	    });
	  }
	  start(options) {
	    this.#assertActive();
	    let session;
	    return session = new ReaderActionFormSession({
	      document: this.document,
	      root: this.#root,
	      frame: options.frame,
	      timing: this.#timing,
	      coordinator: this.#coordinator,
	      previousFocus: options.previousFocus,
	      closeSelector: options.closeSelector,
	      cancelSelector: options.cancelSelector,
	      signal: options.signal,
	      onSettled: () => {
	        this.#active === session && (this.#active = null), options.onSettled?.();
	      }
	    }), this.#active = session, session;
	  }
	  close(submitted = !1) {
	    this.#active?.settle(submitted);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error(`${this.#label} 已销毁`);
	  }
	}
	class ReaderActionFormSession {
	  previousFocus;
	  result;
	  #options;
	  #resolve;
	  #releaseAction = () => {
	  };
	  #removeAbortListener = () => {
	  };
	  #busy = !1;
	  #mounted = !1;
	  #settled = !1;
	  constructor(options) {
	    this.#options = options, this.previousFocus = options.previousFocus, this.result = new Promise((resolve) => {
	      this.#resolve = resolve;
	    });
	  }
	  get active() {
	    return !this.#settled;
	  }
	  get busy() {
	    return this.#busy;
	  }
	  setBusy(busy) {
	    this.#settled || (this.#busy = busy);
	  }
	  mount(focus) {
	    if (this.#mounted || this.#settled) return;
	    this.#mounted = !0;
	    const { document, root, frame, coordinator, signal } = this.#options;
	    if (this.#releaseAction = coordinator.claim(() => this.settle(!1)), frame.layer.addEventListener("click", (event) => {
	      const target = event.target;
	      target?.closest(this.#options.closeSelector) ? this.settle(!1) : !this.#busy && (event.target === frame.layer || target?.closest(this.#options.cancelSelector)) && this.settle(!1);
	    }), frame.layer.addEventListener("keydown", (event) => {
	      handleReaderActionDialogKeydown(
	        event,
	        document,
	        frame.dialog,
	        this.#busy,
	        () => this.settle(!1)
	      );
	    }), frame.layer.addEventListener("wheel", (event) => (0, import_floating_surface_wheel.containFloatingSurfaceWheel)(frame.layer, event), {
	      passive: !1
	    }), root.append(frame.layer), signal) {
	      const onAbort = () => this.settle(!1);
	      signal.addEventListener("abort", onAbort, { once: !0 }), this.#removeAbortListener = () => signal.removeEventListener("abort", onAbort), signal.aborted && this.settle(!1);
	    }
	    this.#options.timing.focus(() => {
	      frame.layer.isConnected && focus();
	    });
	  }
	  resetStatus() {
	    this.#options.frame.status.classList.remove("success"), this.#options.frame.status.textContent = "";
	  }
	  submit(options) {
	    if (this.#busy || this.#settled) return;
	    this.setBusy(!0), options.setBusy(!0);
	    let pending;
	    try {
	      pending = options.execute();
	    } catch (cause) {
	      this.#rejectSubmission(cause, options);
	      return;
	    }
	    Promise.resolve(pending).then((value) => {
	      if (this.#settled) return;
	      const status = this.#options.frame.status;
	      status.classList.add("success"), status.textContent = options.successMessage(value), this.#options.timing.scheduleClose(() => this.settle(!0));
	    }).catch((cause) => this.#rejectSubmission(cause, options));
	  }
	  settle(submitted) {
	    this.#settled || (this.#settled = !0, this.#releaseAction(), this.#removeAbortListener(), this.#options.timing.clear(), this.#options.frame.layer.remove(), this.#options.onSettled?.(), this.#options.timing.restore(this.previousFocus), this.#resolve(submitted));
	  }
	  #rejectSubmission(cause, options) {
	    this.#settled || (this.#options.frame.status.textContent = options.failureMessage(cause), this.setBusy(!1), options.setBusy(!1));
	  }
	}
}, "6d9b160295ad415aa13e84b6cf19abf62ff4ef8e5b7ba6e4f83396376b87290e");

/* Source: lite/src/shell/reader-action-surface-coordinator.ts */
runtime.register("src/shell/reader-action-surface-coordinator.js", function(module, exports, require) {
	var reader_action_surface_coordinator_exports = {};
	__export(reader_action_surface_coordinator_exports, {
	  ReaderActionSurfaceCoordinator: () => ReaderActionSurfaceCoordinator
	});
	module.exports = __toCommonJS(reader_action_surface_coordinator_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderActionSurfaceCoordinator {
	  scope;
	  #active = null;
	  constructor(options = {}) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => {
	      this.closeActive();
	    });
	  }
	  get active() {
	    return this.#active !== null;
	  }
	  claim(close) {
	    if (this.scope.destroyed)
	      throw new Error("ReaderActionSurfaceCoordinator 已销毁");
	    const previous = this.#active;
	    this.#active = null, previous?.close();
	    const token = Symbol("reader-action-surface");
	    this.#active = { token, close };
	    let released = !1;
	    return () => {
	      released || (released = !0, this.#active?.token === token && (this.#active = null));
	    };
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  closeActive() {
	    const active = this.#active;
	    return this.#active = null, active?.close(), active !== null;
	  }
	}
}, "fe11b3d151a62bd97b52adbcfe525c6e29c3e75aa7df95756ea3520b0442456c");

/* Source: lite/src/shell/reader-assignment-form-surface.ts */
runtime.register("src/shell/reader-assignment-form-surface.js", function(module, exports, require) {
	var reader_assignment_form_surface_exports = {};
	__export(reader_assignment_form_surface_exports, {
	  ReaderAssignmentFormSurface: () => ReaderAssignmentFormSurface
	});
	module.exports = __toCommonJS(reader_assignment_form_surface_exports);
	var import_reader_action_form_support = require("./reader-action-form-support.js");
	class ReaderAssignmentFormSurface {
	  scope;
	  #host;
	  constructor(options) {
	    this.#host = new import_reader_action_form_support.ReaderActionFormSurfaceHost({
	      ...options,
	      label: "ReaderAssignmentFormSurface"
	    }), this.scope = this.#host.scope;
	  }
	  open(request) {
	    if (request.signal?.aborted) return Promise.resolve(!1);
	    const { id, previousFocus } = this.#host.prepare(), document = this.#host.document, titleId = `ldp-reader-assignment-title-${id}`, frame = (0, import_reader_action_form_support.createReaderActionFormFrame)({
	      document,
	      titleId,
	      title: request.title,
	      intro: request.intro,
	      closeDataAttribute: "data-assignment-close",
	      cancelDataAttribute: "data-assignment-cancel",
	      submitLabel: "确认指定",
	      renderIcon: this.#host.renderIcon
	    }), { form, status, submit } = frame, usernameField = document.createElement("label");
	    usernameField.className = "ldp-reader-action-field";
	    const usernameLabel = document.createElement("span");
	    usernameLabel.textContent = "用户名";
	    const username = document.createElement("input");
	    username.name = "username", username.type = "text", username.maxLength = 100, username.autocomplete = "off", username.placeholder = "不含 @", username.required = !0, username.value = String(request.initialUsername ?? "").replace(/^@+/, ""), usernameField.append(usernameLabel, username);
	    const noteField = document.createElement("label");
	    noteField.className = "ldp-reader-action-field";
	    const noteLabel = document.createElement("span");
	    noteLabel.textContent = "备注(选填)";
	    const note = document.createElement("textarea");
	    note.name = "note", note.maxLength = 1e3, note.placeholder = "说明这次指定的原因或任务", noteField.append(noteLabel, note), status.before(usernameField, noteField);
	    const session = this.#host.start({
	      frame,
	      previousFocus,
	      closeSelector: "[data-assignment-close]",
	      cancelSelector: "[data-assignment-cancel]",
	      signal: request.signal
	    });
	    return form.addEventListener("submit", (event) => {
	      if (event.preventDefault(), session.busy) return;
	      const normalizedUsername = username.value.trim().replace(/^@+/, ""), normalizedNote = note.value.trim();
	      if (session.resetStatus(), !normalizedUsername) {
	        status.textContent = "请输入要指定的用户名", username.focus();
	        return;
	      }
	      session.submit({
	        execute: () => request.submit(Object.freeze({
	          username: normalizedUsername,
	          note: normalizedNote
	        })),
	        setBusy: (busy) => (0, import_reader_action_form_support.setReaderActionFormBusy)(
	          form,
	          submit,
	          busy,
	          "指定中…",
	          "确认指定"
	        ),
	        successMessage: (message) => message || "指定已更新",
	        failureMessage: (cause) => cause instanceof Error ? cause.message : "指定失败,请重试"
	      });
	    }), session.mount(() => username.focus({ preventScroll: !0 })), session.result;
	  }
	  destroy() {
	    this.#host.destroy();
	  }
	}
}, "d3cfc6ae2bfd3be19338f0fa6631051f17f122abad4b9b95e5a299b5d35f9d5e");

/* Source: lite/src/shell/reader-choice-form-surface.ts */
runtime.register("src/shell/reader-choice-form-surface.js", function(module, exports, require) {
	var reader_choice_form_surface_exports = {};
	__export(reader_choice_form_surface_exports, {
	  ReaderChoiceFormSurface: () => ReaderChoiceFormSurface
	});
	module.exports = __toCommonJS(reader_choice_form_surface_exports);
	var import_reader_action_form_support = require("./reader-action-form-support.js");
	class ReaderChoiceFormSurface {
	  scope;
	  #host;
	  constructor(options) {
	    this.#host = new import_reader_action_form_support.ReaderActionFormSurfaceHost({
	      ...options,
	      label: "ReaderChoiceFormSurface"
	    }), this.scope = this.#host.scope;
	  }
	  open(request) {
	    if (request.signal?.aborted) return Promise.resolve(!1);
	    const options = request.options.filter(
	      (option) => String(option.value).trim() && String(option.label).trim()
	    );
	    if (!options.length)
	      return Promise.reject(new Error("当前没有可用选项"));
	    const { id, previousFocus } = this.#host.prepare(), document = this.#host.document, titleId = `ldp-reader-choice-title-${id}`, submitLabel = request.submitLabel ?? "提交", frame = (0, import_reader_action_form_support.createReaderActionFormFrame)({
	      document,
	      titleId,
	      title: request.title,
	      intro: request.intro,
	      closeDataAttribute: "data-choice-close",
	      cancelDataAttribute: "data-choice-cancel",
	      submitLabel,
	      renderIcon: this.#host.renderIcon
	    }), { form, status, submit } = frame;
	    let firstChoice = null;
	    if (request.mode === "select") {
	      const field = document.createElement("label");
	      if (field.className = "ldp-reader-action-field", request.fieldLabel) {
	        const label = document.createElement("span");
	        label.textContent = request.fieldLabel, field.append(label);
	      }
	      const select = document.createElement("select");
	      select.className = "ldp-reader-select", select.name = `reader-choice-${id}`;
	      for (const option of options) {
	        const item = document.createElement("option");
	        item.value = option.value, item.textContent = option.label, item.selected = option.selected === !0, item.disabled = option.disabled === !0, select.append(item);
	      }
	      field.append(select), status.before(field), firstChoice = select;
	    } else {
	      const choices = document.createElement("div");
	      choices.className = "ldp-reader-action-options";
	      for (const option of options) {
	        const label = document.createElement("label");
	        label.className = "ldp-reader-action-option";
	        const input = document.createElement("input");
	        input.type = "checkbox", input.name = `reader-choice-${id}`, input.value = option.value, input.checked = option.selected === !0, input.disabled = option.disabled === !0;
	        const copy = document.createElement("span");
	        copy.className = "ldp-reader-action-option-copy";
	        const name = document.createElement("strong");
	        if (name.textContent = option.label, copy.append(name), option.description) {
	          const description = document.createElement("small");
	          description.textContent = option.description, copy.append(description);
	        }
	        label.append(input, copy), choices.append(label), !firstChoice && !input.disabled && (firstChoice = input);
	      }
	      status.before(choices);
	    }
	    const disabled = /* @__PURE__ */ new Map(), session = this.#host.start({
	      frame,
	      previousFocus,
	      closeSelector: "[data-choice-close]",
	      cancelSelector: "[data-choice-cancel]",
	      signal: request.signal
	    }), setBusy = (busy) => {
	      for (const control of form.querySelectorAll("input,select,button"))
	        busy && disabled.set(control, control.disabled), control.disabled = busy || disabled.get(control) === !0;
	      submit.textContent = busy ? request.busyLabel ?? "提交中…" : submitLabel;
	    };
	    return form.addEventListener("submit", (event) => {
	      if (event.preventDefault(), session.busy) return;
	      const values = [...form.querySelectorAll(
	        `[name="reader-choice-${id}"]`
	      )].flatMap((control) => {
	        if (control.tagName === "SELECT")
	          return control.value ? [control.value] : [];
	        const input = control;
	        return input.checked && !input.disabled ? [input.value] : [];
	      });
	      if (session.resetStatus(), !values.length) {
	        status.textContent = request.emptySelectionError ?? "请选择至少一个选项", firstChoice?.focus();
	        return;
	      }
	      session.submit({
	        execute: () => request.submit(Object.freeze(values)),
	        setBusy,
	        successMessage: (message) => message ?? request.successMessage ?? "操作已完成",
	        failureMessage: (cause) => cause instanceof Error ? cause.message : "操作失败,请重试"
	      });
	    }), session.mount(() => firstChoice?.focus({ preventScroll: !0 })), session.result;
	  }
	  destroy() {
	    this.#host.destroy();
	  }
	}
}, "723b9450b79cc805e438b79f39d532588d39337494ea98895e5324ea4bf12ebb");

/* Source: lite/src/shell/reader-embed-resize-controller.ts */
runtime.register("src/shell/reader-embed-resize-controller.js", function(module, exports, require) {
	var reader_embed_resize_controller_exports = {};
	__export(reader_embed_resize_controller_exports, {
	  ReaderEmbedResizeController: () => ReaderEmbedResizeController
	});
	module.exports = __toCommonJS(reader_embed_resize_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderEmbedResizeController {
	  scope;
	  #model;
	  #pageRoot;
	  #overlay;
	  #handle;
	  #readViewportWidth;
	  #onPersist;
	  #requestFrame;
	  #cancelFrame;
	  #pointer = null;
	  #frame = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#pageRoot = options.pageRoot, this.#overlay = options.overlay, this.#handle = options.handle, this.#readViewportWidth = options.readViewportWidth, this.#onPersist = options.onPersist, this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.listen(this.#handle, "pointerdown", (event) => {
	      this.#onPointerDown(event);
	    }), this.scope.listen(this.#handle, "pointermove", (event) => {
	      this.#onPointerMove(event);
	    });
	    for (const type of ["pointerup", "pointercancel", "lostpointercapture"])
	      this.scope.listen(this.#handle, type, (event) => {
	        this.#finish(event, !0);
	      });
	    this.scope.listen(options.viewportTarget, "resize", () => {
	      this.#finish(void 0, !1), this.#model.resizeViewport(this.#readViewportWidth());
	    }), this.#model.changes.subscribe((snapshot) => {
	      snapshot.presentation.embedded || this.#finish(void 0, !1);
	    }, this.scope), this.scope.add(() => this.#finish(void 0, !1));
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #onPointerDown(event) {
	    if (!(this.#destroyed || event.button !== 0 || !this.#model.snapshot.presentation.embedded)) {
	      event.preventDefault(), event.stopPropagation(), this.#pointer = {
	        pointerId: event.pointerId,
	        startWidth: this.#model.snapshot.embedWidth,
	        clientX: event.clientX
	      }, this.#setResizing(!0);
	      try {
	        this.#handle.setPointerCapture(event.pointerId);
	      } catch {
	      }
	    }
	  }
	  #onPointerMove(event) {
	    !this.#pointer || event.pointerId !== this.#pointer.pointerId || (this.#pointer.clientX = event.clientX, this.#frame || (this.#frame = this.#requestFrame(() => this.#render())));
	  }
	  #render() {
	    this.#frame = 0;
	    const pointer = this.#pointer, presentation = this.#model.snapshot.presentation;
	    if (!pointer || !presentation.embedded) return;
	    const viewportWidth = this.#readViewportWidth(), requestedWidth = presentation.side === "left" ? pointer.clientX : viewportWidth - pointer.clientX;
	    this.#model.setEmbedWidth(requestedWidth);
	  }
	  #finish(event, persist = !1) {
	    const pointer = this.#pointer;
	    if (!pointer || event && event.pointerId !== pointer.pointerId) {
	      pointer || this.#setResizing(!1);
	      return;
	    }
	    this.#frame && (this.#cancelFrame(this.#frame), this.#frame = 0, persist && this.#render()), this.#pointer = null, persist || this.#model.setEmbedWidth(pointer.startWidth), this.#setResizing(!1);
	    try {
	      this.#handle.hasPointerCapture(pointer.pointerId) && this.#handle.releasePointerCapture(pointer.pointerId);
	    } catch {
	    }
	    persist && this.#model.snapshot.presentation.embedded && this.#onPersist?.(this.#model.snapshot.embedWidth);
	  }
	  #setResizing(active) {
	    this.#pageRoot.classList.toggle("ldp-reader-embed-resizing", active), this.#overlay.classList.toggle("ldp-reader-embed-resizing", active);
	  }
	}
}, "7667c373d4473cbe1d0054cde36715186d2fe45400e95cc5028541c45db701a2");

/* Source: lite/src/shell/reader-escape-surface.ts */
runtime.register("src/shell/reader-escape-surface.js", function(module, exports, require) {
	var reader_escape_surface_exports = {};
	__export(reader_escape_surface_exports, {
	  readerEscapeOwnedBy: () => readerEscapeOwnedBy,
	  readerFrontmostEscapeSurface: () => readerFrontmostEscapeSurface,
	  readerSurfaceQuery: () => readerSurfaceQuery,
	  readerSurfaceQueryAll: () => readerSurfaceQueryAll
	});
	module.exports = __toCommonJS(reader_escape_surface_exports);
	const ESCAPE_SURFACE_SELECTOR = [
	  ".ldp-reader-action-layer:not([hidden])",
	  ".ldp-avatar-viewer",
	  ".ldp-native-boost-menu:not([hidden])",
	  '[data-identifier="ldp-native-boost-emoji-picker"]',
	  ".emoji-picker",
	  ".ldp-settings-popover:not([hidden])",
	  ".ldp-color-picker-popover:not([hidden])",
	  ".ldp-notifications-popover:not([hidden])",
	  ".ldp-history-popover:not([hidden])",
	  ".ldp-bookmarks-popover:not([hidden])",
	  ".ldp-lightbox",
	  ".ldp-lb-batch-overlay:not([hidden])",
	  ".ldp-descendant-replies-layer:not([hidden])",
	  ".ldp-code-preview-layer",
	  ".ldp-user-card-fallback.open",
	  ".ldp-user-card-follow-panel:not([hidden])",
	  ".ldp-user-card-follow-preview:not([hidden])",
	  ".ldp-reaction-picker:not([hidden])",
	  ".ldp-selection-toolbar:not([hidden])"
	].join(",");
	function visible(document, element) {
	  if (!element.isConnected || element.hidden || element.getAttribute("aria-hidden") === "true") return !1;
	  const viewport = document.defaultView;
	  if (!viewport?.getComputedStyle) return !0;
	  const style = viewport.getComputedStyle(element);
	  return style.display !== "none" && style.visibility !== "hidden";
	}
	function declaredSurfaceZIndex(element) {
	  for (const [selector, zIndex] of [
	    [".ldp-reader-action-layer", 2147483612],
	    [".ldp-user-card-follow-preview", 2147483611],
	    [".ldp-user-card-follow-panel", 2147483610],
	    [".ldp-color-picker-popover", 2147483610],
	    ['[data-identifier="ldp-native-boost-emoji-picker"]', 2147483610],
	    [".ldp-native-boost-emoji-picker", 2147483610],
	    [".ldp-selection-toolbar", 2147483610],
	    [".ldp-avatar-viewer", 2147483609],
	    [".ldp-native-boost-menu", 2147483608],
	    [".ldp-user-card-fallback", 2147483608],
	    [".ldp-settings-popover", 2147483606],
	    [".ldp-notifications-popover", 2147483606],
	    [".ldp-history-popover", 2147483606],
	    [".ldp-bookmarks-popover", 2147483606],
	    [".ldp-lightbox", 2147483600],
	    [".ldp-descendant-replies-layer-centered", 2147483590],
	    [".ldp-descendant-replies-layer", 30],
	    [".ldp-lb-batch-overlay", 12],
	    [".ldp-reaction-picker", 3]
	  ])
	    if (element.matches(selector)) return zIndex;
	  return null;
	}
	function stackingVector(document, element) {
	  const values = [], viewport = document.defaultView;
	  let current = element;
	  for (; current; ) {
	    const raw = viewport?.getComputedStyle ? viewport.getComputedStyle(current).zIndex : current.style.zIndex, normalized = String(raw ?? "").trim(), computed = normalized && normalized !== "auto" ? Number(normalized) : Number.NaN, value = Number.isFinite(computed) ? computed : declaredSurfaceZIndex(current);
	    value !== null && values.unshift(value), current = current.parentElement;
	  }
	  return values;
	}
	function compareVector(left, right) {
	  const length = Math.max(left.length, right.length);
	  for (let index = 0; index < length; index += 1) {
	    const difference = (left[index] ?? 0) - (right[index] ?? 0);
	    if (difference) return difference;
	  }
	  return left.length - right.length;
	}
	function escapeSurfaceRoots(document) {
	  const roots = [document];
	  for (const host of document.querySelectorAll(
	    "[data-ldp-reader-portal]"
	  ))
	    host.shadowRoot && roots.push(host.shadowRoot);
	  return roots;
	}
	function readerSurfaceQuery(document, selector) {
	  for (const root of escapeSurfaceRoots(document)) {
	    const surface = root.querySelector(selector);
	    if (surface) return surface;
	  }
	  return null;
	}
	function readerSurfaceQueryAll(document, selector) {
	  return Object.freeze(escapeSurfaceRoots(document).flatMap(
	    (root) => [...root.querySelectorAll(selector)]
	  ));
	}
	function readerFrontmostEscapeSurface(document) {
	  const candidates = escapeSurfaceRoots(document).flatMap(
	    (root) => [...root.querySelectorAll(ESCAPE_SURFACE_SELECTOR)]
	  ).filter((candidate) => visible(document, candidate));
	  let frontmost = null, frontmostVector = Object.freeze([]);
	  for (const candidate of candidates) {
	    const vector = stackingVector(document, candidate), comparison = compareVector(vector, frontmostVector), order = frontmost?.compareDocumentPosition(candidate) ?? 0;
	    (!frontmost || comparison > 0 || comparison === 0 && order & 4) && (frontmost = candidate, frontmostVector = vector);
	  }
	  return frontmost;
	}
	function readerEscapeOwnedBy(document, owners) {
	  const frontmost = readerFrontmostEscapeSurface(document);
	  return frontmost ? (Array.isArray(owners) ? owners : [owners]).some((owner) => owner === frontmost) : !0;
	}
}, "8c4834e1dbdb5692dccee2a5d2291c648ef55f8e0143e47635aad83c06cbc64b");

/* Source: lite/src/shell/reader-exclusive-panel-coordinator.ts */
runtime.register("src/shell/reader-exclusive-panel-coordinator.js", function(module, exports, require) {
	var reader_exclusive_panel_coordinator_exports = {};
	__export(reader_exclusive_panel_coordinator_exports, {
	  ReaderExclusivePanelCoordinator: () => ReaderExclusivePanelCoordinator
	});
	module.exports = __toCommonJS(reader_exclusive_panel_coordinator_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderExclusivePanelCoordinator {
	  scope;
	  #entries;
	  #onError;
	  #epoch = 0;
	  constructor(options) {
	    this.#entries = Object.freeze([...options.entries]), this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const triggers = /* @__PURE__ */ new Set();
	    for (const entry of this.#entries) {
	      if (!entry.id || triggers.has(entry.trigger))
	        throw new Error("Header 面板互斥项必须拥有唯一 id 与 trigger");
	      triggers.add(entry.trigger), this.scope.listen(entry.trigger, "click", (event) => {
	        event.preventDefault(), event.stopImmediatePropagation(), this.#activate(entry);
	      }, !0);
	    }
	    this.scope.add(() => {
	      this.#epoch += 1;
	      for (const entry of this.#entries)
	        entry.trigger.removeAttribute("aria-busy");
	    });
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #activate(target) {
	    const epoch = ++this.#epoch;
	    target.trigger.setAttribute("aria-busy", "true");
	    try {
	      if (target.isOpen()) {
	        await target.close();
	        return;
	      }
	      for (const entry of this.#entries) {
	        if (entry === target || !entry.isOpen()) continue;
	        if (await entry.close() === !1 || epoch !== this.#epoch || this.scope.destroyed)
	          return;
	      }
	      if (epoch !== this.#epoch || this.scope.destroyed) return;
	      await target.open();
	    } catch (cause) {
	      !this.scope.destroyed && epoch === this.#epoch && this.#onError(cause);
	    } finally {
	      epoch === this.#epoch && target.trigger.removeAttribute("aria-busy");
	    }
	  }
	}
}, "17b7aa46b869bc5efc5ec15ca4b95a016cc456893a13253ecdf8757e3ac560fc");

/* Source: lite/src/shell/reader-feedback-surface.ts */
runtime.register("src/shell/reader-feedback-surface.js", function(module, exports, require) {
	var reader_feedback_surface_exports = {};
	__export(reader_feedback_surface_exports, {
	  ReaderFeedbackSurface: () => ReaderFeedbackSurface
	});
	module.exports = __toCommonJS(reader_feedback_surface_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_event_target = require("../dom/event-target.js"), import_floating_surface_wheel = require("../dom/floating-surface-wheel.js"), import_reader_action_form_support = require("./reader-action-form-support.js"), import_reader_action_surface_coordinator = require("./reader-action-surface-coordinator.js");
	class ReaderFeedbackSurface {
	  scope;
	  #document;
	  #root;
	  #toastLifetimeMs;
	  #schedule;
	  #cancel;
	  #focusSoon;
	  #renderIcon;
	  #coordinator;
	  #confirmation = null;
	  #toast = null;
	  #toastTimer = null;
	  #id = 0;
	  constructor(options) {
	    if (this.#document = options.document, this.#root = options.root, this.#toastLifetimeMs = Number(options.toastLifetimeMs ?? 1800), !Number.isFinite(this.#toastLifetimeMs) || this.#toastLifetimeMs < 0)
	      throw new RangeError("toastLifetimeMs 必须是非负有限数值");
	    this.#schedule = options.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs)), this.#cancel = options.cancel ?? ((handle) => clearTimeout(
	      handle
	    )), this.#renderIcon = options.renderIcon, this.#focusSoon = options.focusSoon ?? ((callback) => {
	      const view = this.#document.defaultView;
	      view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : queueMicrotask(callback);
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#coordinator = options.coordinator ?? new import_reader_action_surface_coordinator.ReaderActionSurfaceCoordinator({ parentScope: this.scope }), this.scope.add(() => {
	      this.#closeConfirmation("cancel"), this.#clearToast();
	    });
	  }
	  confirm(request) {
	    return this.choose(request).then((choice) => choice === "confirm");
	  }
	  choose(request, root = this.#root) {
	    this.#assertActive();
	    const previousFocus = this.#confirmation?.previousFocus ?? (0, import_event_target.deepActiveElement)(this.#document);
	    this.#closeConfirmation("cancel");
	    const id = ++this.#id, layer = this.#document.createElement("div");
	    layer.className = "ldp-reader-action-layer ldp-reader-confirm-layer";
	    const dialog = this.#document.createElement("section");
	    dialog.className = "ldp-reader-action-dialog ldp-reader-confirm-dialog" + (request.tone === "primary" ? " is-primary" : ""), dialog.setAttribute("role", "alertdialog"), dialog.setAttribute("aria-modal", "true");
	    const titleId = `ldp-reader-confirm-title-${id}`, descriptionId = `ldp-reader-confirm-description-${id}`, detailsId = `ldp-reader-confirm-details-${id}`, noteId = `ldp-reader-confirm-note-${id}`, details = (request.details ?? []).filter(
	      (detail) => String(detail.label).trim()
	    ), noteText = String(request.note ?? "").trim();
	    dialog.setAttribute("aria-labelledby", titleId), dialog.setAttribute(
	      "aria-describedby",
	      [
	        descriptionId,
	        details.length ? detailsId : "",
	        noteText ? noteId : ""
	      ].filter(Boolean).join(" ")
	    );
	    const content = this.#document.createElement("div");
	    content.className = "ldp-reader-confirm-content";
	    const icon = this.#document.createElement("span");
	    icon.className = "ldp-reader-confirm-icon", icon.setAttribute("aria-hidden", "true"), icon.append((0, import_reader_action_form_support.renderReaderActionIcon)(
	      this.#document,
	      request.icon ?? "alertTriangle",
	      this.#renderIcon
	    ));
	    const copy = this.#document.createElement("div");
	    copy.className = "ldp-reader-confirm-copy";
	    const title = this.#document.createElement("strong");
	    title.id = titleId, title.textContent = String(request.title || "确认操作");
	    const message = this.#document.createElement("p");
	    if (message.id = descriptionId, message.textContent = String(request.message ?? ""), copy.append(title, message), details.length) {
	      const list = this.#document.createElement("ul");
	      list.className = "ldp-reader-confirm-details", list.id = detailsId;
	      for (const detail of details) {
	        const item = this.#document.createElement("li"), label = this.#document.createElement("span");
	        label.textContent = detail.label;
	        const value = this.#document.createElement("strong");
	        value.textContent = detail.value ?? "", item.append(label, value), list.append(item);
	      }
	      copy.append(list);
	    }
	    if (noteText) {
	      const note = this.#document.createElement("small");
	      note.id = noteId, note.textContent = noteText, copy.append(note);
	    }
	    content.append(icon, copy);
	    const footer = this.#document.createElement("div");
	    footer.className = "ldp-reader-action-footer";
	    const cancel = this.#document.createElement("button");
	    cancel.type = "button", cancel.className = "ldp-reader-action-cancel", cancel.textContent = request.cancelLabel ?? "取消";
	    const secondary = request.secondaryLabel ? this.#document.createElement("button") : null;
	    secondary && (secondary.type = "button", secondary.className = "ldp-reader-action-secondary", secondary.textContent = request.secondaryLabel ?? "");
	    const submit = this.#document.createElement("button");
	    return submit.type = "button", submit.className = "ldp-reader-action-submit", submit.textContent = request.confirmLabel ?? "确认", footer.append(cancel), secondary && footer.append(secondary), footer.append(submit), dialog.append(content, footer), layer.append(dialog), new Promise((resolve) => {
	      let settled = !1, releaseAction = () => {
	      };
	      const settle = (value) => {
	        settled || (settled = !0, releaseAction(), layer.remove(), this.#confirmation?.layer === layer && (this.#confirmation = null), this.#focusSoon(() => {
	          previousFocus?.isConnected && typeof previousFocus.focus == "function" && previousFocus.focus({ preventScroll: !0 });
	        }), resolve(value));
	      };
	      this.#confirmation = { layer, previousFocus, settle }, releaseAction = this.#coordinator.claim(() => settle("cancel")), layer.addEventListener("click", (event) => {
	        const target = event.target;
	        target?.closest(".ldp-reader-action-secondary") ? settle("secondary") : event.target === layer || target?.closest(".ldp-reader-action-cancel") ? settle("cancel") : target?.closest(".ldp-reader-action-submit") && settle("confirm");
	      }), layer.addEventListener("keydown", (event) => {
	        const keyboard = event;
	        if (keyboard.key === "Escape") {
	          keyboard.preventDefault(), settle("cancel");
	          return;
	        }
	        if (keyboard.key !== "Tab") return;
	        const buttons = [cancel, secondary, submit].filter(
	          (button) => !!button
	        ).filter(
	          (button) => !button.disabled
	        );
	        if (!buttons.length) return;
	        const first = buttons[0], last = buttons.at(-1), active = (0, import_event_target.deepActiveElement)(this.#document);
	        keyboard.shiftKey && active === first ? (keyboard.preventDefault(), last.focus()) : !keyboard.shiftKey && active === last && (keyboard.preventDefault(), first.focus());
	      }), layer.addEventListener("wheel", (event) => (0, import_floating_surface_wheel.containFloatingSurfaceWheel)(layer, event), {
	        passive: !1
	      }), root.append(layer), this.#focusSoon(() => {
	        layer.isConnected && cancel.focus({ preventScroll: !0 });
	      });
	    });
	  }
	  show(message) {
	    this.#assertActive(), this.#clearToast();
	    const toast = this.#document.createElement("div");
	    toast.className = "ldp-selection-toast", toast.setAttribute("role", "status"), toast.setAttribute("aria-live", "polite"), toast.textContent = String(message), this.#root.append(toast), this.#toast = toast, this.#toastTimer = this.#schedule(() => {
	      this.#toast === toast && (this.#toast = null, this.#toastTimer = null), toast.remove();
	    }, this.#toastLifetimeMs);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #closeConfirmation(value) {
	    const active = this.#confirmation;
	    this.#confirmation = null, active?.settle(value);
	  }
	  #clearToast() {
	    this.#toastTimer !== null && (this.#cancel(this.#toastTimer), this.#toastTimer = null), this.#toast?.remove(), this.#toast = null;
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("ReaderFeedbackSurface 已销毁");
	  }
	}
}, "294a7bce2ea83ebcf2c371e4ce45567a795ad0cc232d4c66ecbd9c08d19e0279");

/* Source: lite/src/shell/reader-rate-limit-notice.ts */
runtime.register("src/shell/reader-rate-limit-notice.js", function(module, exports, require) {
	var reader_rate_limit_notice_exports = {};
	__export(reader_rate_limit_notice_exports, {
	  ReaderRateLimitNotice: () => ReaderRateLimitNotice
	});
	module.exports = __toCommonJS(reader_rate_limit_notice_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderRateLimitNotice {
	  scope;
	  #document;
	  #elements;
	  #snapshot;
	  #intervalMs;
	  #timer = null;
	  #epoch = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#document = options.document, this.#elements = options.elements, this.#snapshot = options.snapshot, this.#intervalMs = Math.max(250, options.intervalMs ?? 1e3), options.challengeHref ? (this.#elements.challenge.href = options.challengeHref, this.#elements.challenge.hidden = !1) : (this.#elements.challenge.removeAttribute("href"), this.#elements.challenge.hidden = !0), this.scope.listen(this.#document, "visibilitychange", () => {
	      this.#syncPolling();
	    }), this.scope.add(() => {
	      this.#epoch += 1, this.#stopPolling(), this.#hide();
	    }), this.#syncPolling();
	  }
	  async refresh() {
	    if (this.scope.destroyed) return;
	    const epoch = ++this.#epoch;
	    try {
	      const snapshot = await this.#snapshot();
	      if (this.scope.destroyed || epoch !== this.#epoch) return;
	      if (snapshot.challengeState === "required") {
	        this.#elements.detail.textContent = "关键 Reader 请求遇到 Cloudflare 验证,已暂停后续请求;点击右侧按钮只打开一个人工验证浮窗。", this.#show();
	        return;
	      }
	      if (snapshot.challengeState === "active") {
	        this.#elements.detail.textContent = snapshot.challengeOwned ? "本页过盾浮窗已打开;若未显示,点击右侧按钮唤起。验证完成后请求自动恢复。" : "其他标签页已有唯一过盾浮窗;点击右侧按钮可唤起。验证完成后请求自动恢复。", this.#show();
	        return;
	      }
	      this.#hide();
	    } catch {
	      !this.scope.destroyed && epoch === this.#epoch && this.#hide();
	    }
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #show() {
	    delete this.#elements.root.dataset.cooldownSeconds, this.#elements.root.hidden = !1;
	  }
	  #hide() {
	    this.#elements.root.hidden = !0, delete this.#elements.root.dataset.cooldownSeconds;
	  }
	  #syncPolling() {
	    this.#stopPolling(), !(this.scope.destroyed || this.#document.visibilityState === "hidden") && (this.refresh(), this.#timer = setInterval(() => {
	      this.refresh();
	    }, this.#intervalMs));
	  }
	  #stopPolling() {
	    this.#timer !== null && (clearInterval(this.#timer), this.#timer = null);
	  }
	}
}, "105e5418a41d0b484798de92b32252448685ee89a0148d676fd36f049f53a13b");

/* Source: lite/src/shell/reader-report-form-surface.ts */
runtime.register("src/shell/reader-report-form-surface.js", function(module, exports, require) {
	var reader_report_form_surface_exports = {};
	__export(reader_report_form_surface_exports, {
	  ReaderReportFormSurface: () => ReaderReportFormSurface
	});
	module.exports = __toCommonJS(reader_report_form_surface_exports);
	var import_reader_action_form_support = require("./reader-action-form-support.js");
	class ReaderReportFormSurface {
	  scope;
	  #host;
	  constructor(options) {
	    this.#host = new import_reader_action_form_support.ReaderActionFormSurfaceHost({
	      ...options,
	      label: "ReaderReportFormSurface"
	    }), this.scope = this.#host.scope;
	  }
	  open(request) {
	    const options = request.options.filter(
	      (option) => Number.isSafeInteger(option.id) && option.id > 0 && String(option.label).trim()
	    );
	    if (!options.length)
	      return Promise.reject(new Error("当前没有可用的举报类型"));
	    const messageMaxLength = Number(request.messageMaxLength);
	    if (!Number.isSafeInteger(messageMaxLength) || messageMaxLength <= 0)
	      return Promise.reject(new Error("举报说明长度限制无效"));
	    const { id, previousFocus } = this.#host.prepare(), document = this.#host.document, titleId = `ldp-reader-report-title-${id}`, frame = (0, import_reader_action_form_support.createReaderActionFormFrame)({
	      document,
	      titleId,
	      title: request.title,
	      intro: request.intro,
	      closeDataAttribute: "data-report-close",
	      cancelDataAttribute: "data-report-cancel",
	      submitLabel: "提交举报",
	      renderIcon: this.#host.renderIcon
	    }), { form, status, submit } = frame, optionList = document.createElement("div");
	    optionList.className = "ldp-reader-action-options";
	    for (const [index, option] of options.entries()) {
	      const label = document.createElement("label");
	      label.className = "ldp-reader-action-option";
	      const radio = document.createElement("input");
	      radio.type = "radio", radio.name = `reader-report-type-${id}`, radio.value = String(option.id), radio.checked = index === 0;
	      const copy = document.createElement("span");
	      copy.className = "ldp-reader-action-option-copy";
	      const name = document.createElement("strong");
	      name.textContent = option.label;
	      const description = document.createElement("small");
	      description.textContent = option.description, copy.append(name, description), label.append(radio, copy), optionList.append(label);
	    }
	    const field = document.createElement("label");
	    field.className = "ldp-reader-action-field";
	    const fieldLabel = document.createElement("span");
	    fieldLabel.textContent = "补充说明";
	    const message = document.createElement("textarea");
	    message.maxLength = messageMaxLength, message.placeholder = request.placeholder ?? "选填;所选类型要求说明时必须填写", field.append(fieldLabel, message), status.before(optionList, field);
	    const session = this.#host.start({
	      frame,
	      previousFocus,
	      closeSelector: "[data-report-close]",
	      cancelSelector: "[data-report-cancel]"
	    });
	    return form.addEventListener("submit", (event) => {
	      if (event.preventDefault(), session.busy) return;
	      const selected = form.querySelector(
	        `input[name="reader-report-type-${id}"]:checked`
	      ), optionId = Number(selected?.value), option = options.find((entry) => entry.id === optionId), normalizedMessage = message.value.trim();
	      if (session.resetStatus(), !option) {
	        status.textContent = "请选择举报类型";
	        return;
	      }
	      if (option.requireMessage && !normalizedMessage) {
	        status.textContent = request.requiredMessageError ?? `${option.label}需要填写具体原因`, message.focus();
	        return;
	      }
	      session.submit({
	        execute: () => request.submit(Object.freeze({
	          optionId,
	          message: normalizedMessage
	        })),
	        setBusy: (busy) => (0, import_reader_action_form_support.setReaderActionFormBusy)(
	          form,
	          submit,
	          busy,
	          "提交中…",
	          "提交举报"
	        ),
	        successMessage: (message2) => message2 || "举报已提交",
	        failureMessage: (cause) => cause instanceof Error ? cause.message : "举报失败,请重试"
	      });
	    }), session.mount(() => {
	      optionList.querySelector(
	        'input[type="radio"]'
	      )?.focus({ preventScroll: !0 });
	    }), session.result;
	  }
	  destroy() {
	    this.#host.destroy();
	  }
	}
}, "f720bdf9c906620c34ce48021bcaadad9788f96c026a8fccd340dd307d3e51b3");

/* Source: lite/src/shell/reader-select-surface.ts */
runtime.register("src/shell/reader-select-surface.js", function(module, exports, require) {
	var reader_select_surface_exports = {};
	__export(reader_select_surface_exports, {
	  ReaderSelectSurface: () => ReaderSelectSurface
	});
	module.exports = __toCommonJS(reader_select_surface_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_event_target = require("../dom/event-target.js");
	const SELECTOR = [
	  "select.ldp-reader-select",
	  "select.ldp-cache-select",
	  "select.ldp-collection-scope",
	  "select.ldp-font-weight-select"
	].join(",");
	function eventElement(event) {
	  const target = event.target;
	  return target && typeof target.closest == "function" ? target : null;
	}
	class ReaderSelectSurface {
	  scope;
	  #document;
	  #root;
	  #states = /* @__PURE__ */ new Map();
	  #openSelect = null;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#document = options.document, this.#root = options.root, this.#scan(options.root);
	    const Observer = options.document.defaultView?.MutationObserver ?? globalThis.MutationObserver;
	    if (typeof Observer == "function") {
	      const observer = new Observer((mutations) => {
	        for (const mutation of mutations)
	          for (const added of mutation.addedNodes) {
	            const element = added;
	            added.nodeType === 1 && this.#scan(element);
	          }
	        this.#prune();
	      });
	      this.scope.observe(observer, options.root, {
	        childList: !0,
	        subtree: !0
	      });
	    }
	    this.scope.listen(options.root, "pointerdown", (event) => this.#pointerDown(event)), this.scope.listen(options.root, "mousedown", (event) => this.#mouseDown(event)), this.scope.listen(options.root, "click", (event) => this.#click(event)), this.scope.listen(options.root, "keydown", (event) => this.#keydown(event)), this.scope.listen(options.root, "change", (event) => {
	      const select = eventElement(event)?.closest(SELECTOR);
	      select && this.#sync(select);
	    }), this.scope.listen(options.document, "pointerdown", (event) => {
	      const select = this.#openSelect;
	      if (!select) return;
	      const wrapper = this.#states.get(select);
	      (0, import_event_target.eventPathIncludes)(event, wrapper ?? null) || this.#close();
	    }, !0);
	    const viewport = options.document.defaultView;
	    viewport && this.scope.listen(viewport, "resize", () => this.#positionOpenMenu()), this.scope.add(() => {
	      this.#close();
	      for (const [select, wrapper] of this.#states)
	        select.removeAttribute("aria-expanded"), select.removeAttribute("aria-haspopup"), select.removeAttribute("data-reader-select-enhanced"), wrapper.replaceWith(select);
	      this.#states.clear();
	    });
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #scan(root) {
	    root.matches(SELECTOR) && this.#enhance(root);
	    for (const select of root.querySelectorAll(SELECTOR))
	      this.#enhance(select);
	  }
	  #enhance(select) {
	    if (select.multiple || select.dataset.readerSelectEnhanced || !select.parentNode) return;
	    const wrapper = this.#document.createElement("span");
	    wrapper.className = "ldp-select-surface";
	    const indicator = this.#document.createElement("span");
	    indicator.className = "ldp-select-indicator", indicator.setAttribute("aria-hidden", "true");
	    const menu = this.#document.createElement("span");
	    menu.className = "ldp-select-menu ldp-picker-options", menu.setAttribute("role", "presentation"), menu.hidden = !0, select.parentNode.insertBefore(wrapper, select), wrapper.append(select, indicator, menu), select.dataset.readerSelectEnhanced = "1", select.setAttribute("aria-haspopup", "listbox"), select.setAttribute("aria-expanded", "false"), this.#states.set(select, wrapper);
	  }
	  #prune() {
	    for (const [select] of this.#states)
	      select.isConnected || (this.#openSelect === select && (this.#openSelect = null), this.#states.delete(select));
	  }
	  #pointerDown(event) {
	    if (event.button !== 0) return;
	    const select = eventElement(event)?.closest(SELECTOR);
	    !select || !this.#root.contains(select) || select.disabled || (event.preventDefault(), select.focus({ preventScroll: !0 }), this.#openSelect === select ? this.#close() : this.#open(select));
	  }
	  #mouseDown(event) {
	    const select = eventElement(event)?.closest(SELECTOR);
	    select && this.#states.has(select) && event.preventDefault();
	  }
	  #click(event) {
	    const target = eventElement(event), option = target?.closest("[data-reader-select-value]");
	    if (option) {
	      const select2 = this.#openSelect;
	      if (!select2 || !this.#states.get(select2)?.contains(option) || (event.preventDefault(), option.disabled)) return;
	      const value = option.dataset.readerSelectValue ?? "", changed = select2.value !== value;
	      let matched = !1;
	      for (const nativeOption of select2.options) {
	        const selected = !matched && nativeOption.value === value;
	        nativeOption.selected = selected, selected && (matched = !0);
	      }
	      if (this.#sync(select2), this.#close(), select2.focus({ preventScroll: !0 }), changed) {
	        const EventConstructor = this.#document.defaultView?.Event ?? Event;
	        select2.dispatchEvent(new EventConstructor("input", { bubbles: !0 })), select2.dispatchEvent(new EventConstructor("change", { bubbles: !0 }));
	      }
	      return;
	    }
	    const select = target?.closest(SELECTOR);
	    select && this.#states.has(select) && event.preventDefault();
	  }
	  #keydown(event) {
	    const target = eventElement(event);
	    if (target?.closest(".ldp-select-search") && this.#openSelect) {
	      if (event.key === "Escape") {
	        event.preventDefault(), this.#close(!0);
	        return;
	      }
	      event.key === "ArrowDown" && (event.preventDefault(), this.#enabledOptions(this.#openSelect)[0]?.focus());
	      return;
	    }
	    const option = target?.closest("[data-reader-select-value]");
	    if (option && this.#openSelect) {
	      const buttons = this.#enabledOptions(this.#openSelect), index = buttons.indexOf(option);
	      if (event.key === "Escape") {
	        event.preventDefault(), this.#close(!0);
	        return;
	      }
	      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
	        event.preventDefault();
	        const delta = event.key === "ArrowDown" ? 1 : -1;
	        buttons[(index + delta + buttons.length) % buttons.length]?.focus();
	      }
	      return;
	    }
	    const select = target?.closest(SELECTOR);
	    if (!(!select || !this.#states.has(select) || select.disabled)) {
	      if (event.key === "Escape" && this.#openSelect === select) {
	        event.preventDefault(), this.#close(!0);
	        return;
	      }
	      (["Enter", " ", "F4"].includes(event.key) || event.altKey && ["ArrowDown", "ArrowUp"].includes(event.key)) && (event.preventDefault(), this.#openSelect === select ? this.#close() : this.#open(select, !0));
	    }
	  }
	  #open(select, focusSelected = !1) {
	    this.#close();
	    const wrapper = this.#states.get(select), menu = wrapper?.querySelector(".ldp-select-menu");
	    if (!wrapper || !menu) return;
	    const options = this.#document.createElement("span");
	    if (options.className = "ldp-select-options", options.setAttribute("role", "listbox"), options.setAttribute(
	      "aria-label",
	      select.getAttribute("aria-label") || "下拉选项"
	    ), options.replaceChildren(...[...select.options].map((nativeOption) => {
	      const option = this.#document.createElement("button");
	      return option.type = "button", option.className = "ldp-select-option ldp-picker-option", option.dataset.readerSelectValue = nativeOption.value, option.textContent = nativeOption.label || nativeOption.textContent || "", option.disabled = nativeOption.disabled, option.setAttribute("role", "option"), option.setAttribute("aria-selected", String(nativeOption.selected)), option;
	    })), select.dataset.readerSelectSearchable === "true") {
	      const search2 = this.#document.createElement("input");
	      search2.type = "search", search2.className = "ldp-select-search", search2.placeholder = "搜索字体", search2.setAttribute("aria-label", "搜索字体");
	      const empty = this.#document.createElement("span");
	      empty.className = "ldp-select-empty", empty.textContent = "没有匹配的字体", empty.hidden = !0, search2.addEventListener("input", () => {
	        const query = search2.value.trim().toLocaleLowerCase();
	        let visible = 0;
	        for (const option of options.querySelectorAll(
	          "[data-reader-select-value]"
	        )) {
	          const matches = !query || (option.textContent ?? "").toLocaleLowerCase().includes(query);
	          option.hidden = !matches, matches && (visible += 1);
	        }
	        empty.hidden = visible > 0;
	      }), menu.replaceChildren(search2, options, empty);
	    } else
	      menu.replaceChildren(options);
	    this.#openSelect = select, select.setAttribute("aria-expanded", "true"), menu.hidden = !1, this.#positionOpenMenu();
	    const search = menu.querySelector(".ldp-select-search");
	    search ? search.focus({ preventScroll: !0 }) : focusSelected && this.#enabledOptions(select).find((option) => option.dataset.readerSelectValue === select.value)?.focus();
	  }
	  #positionOpenMenu() {
	    const select = this.#openSelect, wrapper = select ? this.#states.get(select) : null, menu = wrapper?.querySelector(".ldp-select-menu"), viewport = this.#document.defaultView;
	    if (!select || !wrapper || !menu || menu.hidden || !viewport) return;
	    menu.style.removeProperty("left"), menu.style.removeProperty("max-height"), menu.style.removeProperty("max-width"), wrapper.classList.remove("is-menu-above");
	    const selectRect = select.getBoundingClientRect(), menuRect = menu.getBoundingClientRect(), menuWidth = menuRect.width || menu.offsetWidth, menuHeight = menuRect.height || menu.offsetHeight, margin = 12, gap = 6, settingsRect = select.closest(
	      ".ldp-settings-popover"
	    )?.getBoundingClientRect(), bounds = Object.freeze({
	      left: Math.max(margin, (settingsRect?.left ?? 0) + margin),
	      right: Math.min(
	        viewport.innerWidth - margin,
	        (settingsRect?.right ?? viewport.innerWidth) - margin
	      ),
	      top: Math.max(margin, (settingsRect?.top ?? 0) + margin),
	      bottom: Math.min(
	        viewport.innerHeight - margin,
	        (settingsRect?.bottom ?? viewport.innerHeight) - margin
	      )
	    });
	    if (menuWidth > 0) {
	      const availableWidth = Math.max(1, bounds.right - bounds.left), positionedWidth = Math.min(menuWidth, availableWidth);
	      menuWidth > availableWidth && (menu.style.maxWidth = `${Math.floor(availableWidth)}px`);
	      const maxLeft = Math.max(bounds.left, bounds.right - positionedWidth), left = Math.max(
	        bounds.left,
	        Math.min(maxLeft, menuRect.left)
	      );
	      menu.style.left = `${Math.round(left - menuRect.left)}px`;
	    }
	    const spaceBelow = Math.max(
	      0,
	      bounds.bottom - selectRect.bottom - gap
	    ), spaceAbove = Math.max(
	      0,
	      selectRect.top - bounds.top - gap
	    ), menuAbove = menuHeight > spaceBelow && spaceAbove > spaceBelow;
	    wrapper.classList.toggle("is-menu-above", menuAbove);
	    const availableHeight = menuAbove ? spaceAbove : spaceBelow;
	    menuHeight > availableHeight && (menu.style.maxHeight = `${Math.max(1, Math.floor(availableHeight))}px`);
	  }
	  #sync(select) {
	    const wrapper = this.#states.get(select);
	    wrapper?.classList.toggle("is-disabled", select.disabled);
	    for (const option of wrapper?.querySelectorAll(
	      "[data-reader-select-value]"
	    ) ?? [])
	      option.setAttribute(
	        "aria-selected",
	        String(option.dataset.readerSelectValue === select.value)
	      );
	  }
	  #enabledOptions(select) {
	    return [...this.#states.get(select)?.querySelectorAll(
	      "[data-reader-select-value]:not(:disabled)"
	    ) ?? []].filter((option) => !option.hidden);
	  }
	  #close(restoreFocus = !1) {
	    const select = this.#openSelect;
	    if (!select) return;
	    this.#openSelect = null, select.setAttribute("aria-expanded", "false");
	    const wrapper = this.#states.get(select);
	    wrapper?.classList.remove("is-menu-above");
	    const menu = wrapper?.querySelector(".ldp-select-menu");
	    menu && (menu.hidden = !0, menu.style.removeProperty("left"), menu.style.removeProperty("max-height"), menu.style.removeProperty("max-width")), restoreFocus && select.focus({ preventScroll: !0 });
	  }
	}
}, "d241bb583b736323434e178cff20af100ef6c08ba053be687ca9299c06217543");

/* Source: lite/src/shell/reader-shell-recovery-view.ts */
runtime.register("src/shell/reader-shell-recovery-view.js", function(module, exports, require) {
	var reader_shell_recovery_view_exports = {};
	__export(reader_shell_recovery_view_exports, {
	  ReaderShellRecoveryView: () => ReaderShellRecoveryView
	});
	module.exports = __toCommonJS(reader_shell_recovery_view_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderShellRecoveryView {
	  scope;
	  #host;
	  #root;
	  #message;
	  #detail;
	  #retry;
	  #challenge;
	  #onRetry;
	  #onClose;
	  #busy = !1;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#host = options.host, this.#onRetry = options.onRetry, this.#onClose = options.onClose;
	    const document = options.document;
	    this.#root = document.createElement("div"), this.#root.className = "ldp-error", this.#root.role = "alert", this.#root.hidden = !0, this.#message = document.createElement("div"), this.#message.className = "ldp-error-message", this.#detail = document.createElement("small"), this.#detail.className = "ldp-error-message ldp-error-detail";
	    const actions = document.createElement("div");
	    actions.className = "ldp-error-actions", this.#retry = document.createElement("button"), this.#retry.className = "ldp-error-retry", this.#retry.type = "button", this.#retry.textContent = "重新加载", this.#challenge = document.createElement("a"), this.#challenge.className = "ldp-error-challenge", this.#challenge.target = "_blank", this.#challenge.rel = "noopener noreferrer", this.#challenge.textContent = "手动完成 Cloudflare 验证", this.#challenge.hidden = !0;
	    const close = document.createElement("button");
	    close.className = "ldp-error-close", close.type = "button", close.textContent = "关闭阅读器", actions.append(this.#retry, this.#challenge, close), this.#root.append(this.#message, this.#detail, actions), this.scope.listen(this.#retry, "click", () => void this.#retryNow()), this.scope.listen(close, "click", () => void this.#close()), this.scope.add(() => this.#root.remove());
	  }
	  get visible() {
	    return !this.#root.hidden && this.#root.isConnected;
	  }
	  show(failure) {
	    this.scope.destroyed || (this.#message.textContent = failure.message, this.#detail.textContent = failure.detail, this.#root.dataset.failureKind = failure.kind, failure.kind === "cloudflare" && failure.challengeHref ? (this.#challenge.href = failure.challengeHref, this.#challenge.hidden = !1) : (this.#challenge.removeAttribute("href"), this.#challenge.hidden = !0), this.#busy = !1, this.#retry.disabled = !1, this.#retry.textContent = "重新加载", this.#root.hidden = !1, this.#root.parentNode !== this.#host && this.#host.append(this.#root));
	  }
	  clear() {
	    this.#busy = !1, this.#retry.disabled = !1, this.#retry.textContent = "重新加载", this.#root.hidden = !0, this.#root.remove();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #retryNow() {
	    if (!(this.#busy || this.scope.destroyed)) {
	      this.#busy = !0, this.#retry.disabled = !0, this.#retry.textContent = "正在重新加载…";
	      try {
	        await this.#onRetry() && this.clear();
	      } catch {
	        this.visible && (this.#detail.textContent = "重新加载仍然失败;当前状态已保留,请稍后再试。");
	      } finally {
	        this.#busy = !1, this.visible && (this.#retry.disabled = !1, this.#retry.textContent = "重新加载");
	      }
	    }
	  }
	  async #close() {
	    this.scope.destroyed || (this.clear(), await this.#onClose());
	  }
	}
}, "3cdf02d3208fe62efdafc48ed1193199225cf205f36b1a83022857526825921c");

/* Source: lite/src/shell/reader-shell-template.ts */
runtime.register("src/shell/reader-shell-template.js", function(module, exports, require) {
	var reader_shell_template_exports = {};
	__export(reader_shell_template_exports, {
	  createReaderShellTemplate: () => createReaderShellTemplate
	});
	module.exports = __toCommonJS(reader_shell_template_exports);
	var import_reader_notification_model = require("../notification/reader-notification-model.js"), import_reader_icon = require("../components/reader-icon.js"), import_reader_image_fallback = require("../components/reader-image-fallback.js"), import_html_element = require("../dom/html-element.js");
	function nonEmpty(value, name) {
	  const normalized = String(value).trim();
	  if (!normalized) throw new Error(`${name} 不能为空`);
	  return normalized;
	}
	function icon(options, name) {
	  return (0, import_reader_icon.renderReaderIcon)(options.document, name, options.renderIcon);
	}
	function button(options, className, label, iconName) {
	  const node = (0, import_html_element.htmlElement)(options.document, "button", className);
	  return node.type = "button", node.setAttribute("aria-label", label), node.append(icon(options, iconName)), node;
	}
	function popoverSearch(options, name, placeholder, label, clearLabel) {
	  const root = (0, import_html_element.htmlElement)(options.document, "label", "ldp-popover-search");
	  root.append(icon(options, "search"));
	  const input = (0, import_html_element.htmlElement)(
	    options.document,
	    "input",
	    `ldp-popover-search-input ldp-${name}-search`
	  );
	  input.type = "search", input.autocomplete = "off", input.spellcheck = !1, input.placeholder = placeholder, input.setAttribute("aria-label", label);
	  const clear = button(
	    options,
	    `ldp-popover-search-clear ldp-${name}-search-clear`,
	    clearLabel,
	    "x"
	  );
	  return clear.hidden = !0, root.append(input, clear), { root, input, clear };
	}
	function popoverPager(options, name, infoText) {
	  const root = (0, import_html_element.htmlElement)(
	    options.document,
	    "div",
	    "ldp-notification-pager"
	  ), prefix = name === "notification" ? "" : `ldp-${name}-page-`, previous = button(
	    options,
	    `${prefix ? `${prefix}prev ` : ""}ldp-notification-page-prev`,
	    "上一页",
	    "chevron-left"
	  );
	  previous.disabled = !0;
	  const info = (0, import_html_element.htmlElement)(
	    options.document,
	    "span",
	    `${prefix ? `${prefix}info ` : ""}ldp-notification-page-info`
	  );
	  info.textContent = infoText;
	  const next = button(
	    options,
	    `${prefix ? `${prefix}next ` : ""}ldp-notification-page-next`,
	    "下一页",
	    "chevron-right"
	  );
	  return next.disabled = !0, root.append(previous, info, next), { root, previous, info, next };
	}
	function collectionBulkActions(options, name, noun, deleteLabel) {
	  const root = (0, import_html_element.htmlElement)(
	    options.document,
	    "div",
	    `ldp-collection-title-actions ldp-${name}-bulk-actions`
	  );
	  root.hidden = !0;
	  const scope = (0, import_html_element.htmlElement)(
	    options.document,
	    "select",
	    `ldp-reader-select ldp-collection-scope ldp-${name}-select-scope`
	  );
	  scope.setAttribute(
	    "aria-label",
	    name === "history" ? "历史全选范围" : "收藏全选范围"
	  );
	  for (const [value, label] of [
	    ["page", "本页"],
	    ["all", "全部页"]
	  ]) {
	    const option = (0, import_html_element.htmlElement)(options.document, "option", "");
	    option.value = value, option.textContent = label, scope.append(option);
	  }
	  const select = button(
	    options,
	    `ldp-collection-action ldp-${name}-select-toggle`,
	    `全选本页${noun}`,
	    "square"
	  );
	  select.setAttribute("aria-pressed", "false");
	  const remove = button(
	    options,
	    `ldp-collection-action danger ldp-${name}-delete-selected`,
	    deleteLabel,
	    "trash"
	  );
	  remove.disabled = !0;
	  const count = (0, import_html_element.htmlElement)(
	    options.document,
	    "b",
	    `ldp-collection-count ldp-${name}-delete-selected-label`
	  );
	  count.hidden = !0, count.textContent = "0", remove.append(count);
	  const done = button(
	    options,
	    `ldp-collection-action ldp-${name}-multi-done`,
	    "退出多选",
	    "x"
	  );
	  return root.append(scope, select, remove, done), { root, scope, select, remove, count, done };
	}
	function popoverList(document, className, emptyText) {
	  const list = (0, import_html_element.htmlElement)(document, "div", className), empty = (0, import_html_element.htmlElement)(document, "div", "ldp-notification-empty");
	  return empty.textContent = emptyText, list.append(empty), list;
	}
	function createReaderShellTemplate(options) {
	  const { document } = options, siteName = nonEmpty(options.siteName, "siteName"), homeUrl = nonEmpty(options.homeUrl, "homeUrl"), root = (0, import_html_element.htmlElement)(document, "div", "ldp-overlay");
	  root.dataset.readerWorkspaceMode = "floating", root.dataset.readerListModeAllowed = String(options.listModeAllowed);
	  const capsule = (0, import_html_element.htmlElement)(document, "div", "ldp-reader-window-capsule"), pinButton = button(
	    options,
	    "ldp-reader-pin-button",
	    "点击外部时保持显示",
	    "pin"
	  );
	  pinButton.setAttribute("aria-pressed", "false");
	  const lockButton = (0, import_html_element.htmlElement)(document, "button", "ldp-reader-lock-button");
	  lockButton.type = "button", lockButton.setAttribute("aria-label", "锁定浮窗");
	  const unlockedIcon = (0, import_html_element.htmlElement)(document, "span", "ldp-reader-lock-icon");
	  unlockedIcon.dataset.readerLockIcon = "unlocked", unlockedIcon.append(icon(options, "unlock"));
	  const lockedIcon = (0, import_html_element.htmlElement)(document, "span", "ldp-reader-lock-icon");
	  lockedIcon.dataset.readerLockIcon = "locked", lockedIcon.hidden = !0, lockedIcon.append(icon(options, "lock")), lockButton.append(unlockedIcon, lockedIcon), lockButton.setAttribute("aria-pressed", "false");
	  const placementControl = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reader-placement-control"
	  ), placementDivider = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-reader-placement-divider"
	  );
	  placementDivider.setAttribute("aria-hidden", "true");
	  const placementStrip = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reader-placement-strip"
	  );
	  placementStrip.setAttribute("role", "group"), placementStrip.setAttribute("aria-label", "切换阅读器显示方式");
	  const placementOptions = Object.freeze([
	    ["embed-left", "嵌入左侧", "panel-left"],
	    ["embed-right", "嵌入右侧", "panel-right"],
	    ["floating", "浮窗阅读器", "floating-window"],
	    ["fullpage", "全屏阅读器", "maximize-2"]
	  ].map(([mode, label, iconName]) => {
	    const option = button(
	      options,
	      "ldp-reader-placement-option",
	      label,
	      iconName
	    );
	    return option.dataset.readerPlacement = mode, option.dataset.tooltip = "", option.setAttribute("aria-pressed", "false"), placementStrip.append(option), option;
	  }));
	  placementControl.append(placementDivider, placementStrip), capsule.append(pinButton, lockButton, placementControl);
	  const hostScrollbar = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reader-host-scrollbar ldp-reader-host-scrollbar-inactive"
	  );
	  hostScrollbar.setAttribute("role", "scrollbar"), hostScrollbar.setAttribute("aria-label", "原站主题列表滚动条"), hostScrollbar.setAttribute("aria-orientation", "vertical"), hostScrollbar.setAttribute("aria-valuemin", "0"), hostScrollbar.setAttribute("aria-valuemax", "0"), hostScrollbar.setAttribute("aria-valuenow", "0"), hostScrollbar.setAttribute("aria-disabled", "true"), hostScrollbar.tabIndex = 0;
	  const hostScrollbarThumb = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-reader-host-scrollbar-thumb"
	  );
	  hostScrollbarThumb.setAttribute("aria-hidden", "true"), hostScrollbar.append(hostScrollbarThumb);
	  const hostTopButton = button(
	    options,
	    "ldp-reader-host-top",
	    "回到原站页面顶部",
	    "arrow-up"
	  );
	  hostTopButton.title = "回到原站页面顶部", hostTopButton.hidden = !0;
	  const countdown = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-reader-host-top-countdown"
	  );
	  countdown.textContent = "3", countdown.setAttribute("aria-hidden", "true"), hostTopButton.append(countdown);
	  const modal = (0, import_html_element.htmlElement)(document, "div", "ldp-modal"), embedResizeHandle = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-reader-embed-resize"
	  );
	  embedResizeHandle.setAttribute("aria-hidden", "true"), modal.append(embedResizeHandle);
	  for (const direction of ["n", "s", "e", "w", "nw", "ne", "sw", "se"]) {
	    const handle = (0, import_html_element.htmlElement)(document, "span", "ldp-reader-resize-handle");
	    handle.dataset.readerResize = direction, handle.setAttribute("aria-hidden", "true"), modal.append(handle);
	  }
	  const header = (0, import_html_element.htmlElement)(document, "div", "ldp-header"), home = (0, import_html_element.htmlElement)(document, "a", "ldp-home-logo");
	  if (home.href = homeUrl, home.setAttribute("aria-label", `回到 ${siteName} 首页`), options.logoUrl) {
	    const logo = (0, import_html_element.htmlElement)(document, "img", "ldp-logo");
	    (0, import_reader_image_fallback.installReaderSiteLogoFallback)(logo, options.logoUrl), logo.alt = "", logo.loading = "lazy", logo.decoding = "async", logo.dataset.ldpSiteLogo = "", home.append(logo);
	  }
	  const titleWrap = (0, import_html_element.htmlElement)(document, "div", "ldp-title-wrap"), title = (0, import_html_element.htmlElement)(document, "h2", "ldp-title"), titleJump = (0, import_html_element.htmlElement)(document, "span", "ldp-title-jump");
	  titleJump.textContent = options.loadingTitle ?? "正在载入主题…", titleJump.setAttribute("role", "button"), titleJump.tabIndex = 0, titleJump.setAttribute("aria-label", "跳到 #1"), title.append(titleJump);
	  const titleSubline = (0, import_html_element.htmlElement)(document, "div", "ldp-title-subline"), metaRow = (0, import_html_element.htmlElement)(document, "div", "ldp-meta-row"), meta = (0, import_html_element.htmlElement)(document, "div", "ldp-meta"), metaStats = (0, import_html_element.htmlElement)(document, "span", "ldp-meta-stats");
	  metaStats.textContent = "正在读取主题信息…";
	  const metaOwner = (0, import_html_element.htmlElement)(document, "span", "ldp-meta-owner");
	  metaOwner.hidden = !0;
	  const metaOwnerCopy = (0, import_html_element.htmlElement)(document, "span", "ldp-meta-owner-copy");
	  metaOwnerCopy.append("楼主 ");
	  const metaOwnerValue = (0, import_html_element.htmlElement)(
	    document,
	    "a",
	    "ldp-user-link ldp-topic-owner-link ldp-meta-owner-value"
	  );
	  metaOwnerCopy.append(metaOwnerValue);
	  const onlyOpToggle = (0, import_html_element.htmlElement)(
	    document,
	    "button",
	    "ldp-only-op-toggle"
	  );
	  onlyOpToggle.type = "button", onlyOpToggle.disabled = !0, onlyOpToggle.setAttribute("aria-label", "只看楼主"), onlyOpToggle.setAttribute("aria-pressed", "false"), onlyOpToggle.append(icon(options, "user-round")), metaOwner.append(metaOwnerCopy, onlyOpToggle);
	  const onlyOpProgress = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-only-op-progress"
	  );
	  onlyOpProgress.hidden = !0, onlyOpProgress.setAttribute("role", "status"), onlyOpProgress.setAttribute("aria-live", "polite");
	  const onlyOpProgressTrack = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-only-op-progress-track"
	  ), onlyOpProgressFill = (0, import_html_element.htmlElement)(
	    document,
	    "i",
	    "ldp-only-op-progress-fill"
	  );
	  onlyOpProgressTrack.append(onlyOpProgressFill);
	  const onlyOpProgressValue = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-only-op-progress-value"
	  );
	  onlyOpProgress.append(onlyOpProgressTrack, onlyOpProgressValue), meta.append(metaStats, metaOwner, onlyOpProgress), metaRow.append(meta);
	  const topicIdentityHost = (0, import_html_element.htmlElement)(document, "div", "ldp-title-topic-row");
	  titleSubline.append(metaRow, topicIdentityHost), titleWrap.append(title, titleSubline);
	  const headerActions = (0, import_html_element.htmlElement)(document, "div", "ldp-head-btns"), layoutToggle = button(
	    options,
	    "ldp-layout-toggle",
	    "切换阅读器布局",
	    "maximize-2"
	  ), notificationsToggle = button(
	    options,
	    "ldp-notifications-toggle",
	    "消息",
	    "bell"
	  );
	  notificationsToggle.setAttribute("aria-expanded", "false");
	  const notificationsToggleLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  notificationsToggleLabel.textContent = "消息";
	  const notificationUnreadBadge = (0, import_html_element.htmlElement)(
	    document,
	    "b",
	    "ldp-notification-unread-badge"
	  );
	  notificationUnreadBadge.hidden = !0, notificationsToggle.append(
	    notificationsToggleLabel,
	    notificationUnreadBadge
	  );
	  const notificationsPopover = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-notifications-popover"
	  );
	  notificationsPopover.hidden = !0;
	  const notificationModeTabsHost = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-notification-mode-tabs"
	  );
	  notificationModeTabsHost.setAttribute("role", "tablist"), notificationModeTabsHost.setAttribute("aria-label", "消息来源");
	  const notificationModeTabs = [];
	  for (const [mode, label, iconName] of [
	    ["notifications", "通知", "bell"],
	    ["messages", "私信", "mail"]
	  ]) {
	    const tab = button(
	      options,
	      "ldp-notification-mode-tab",
	      label,
	      iconName
	    );
	    tab.dataset.notificationMode = mode, tab.setAttribute("role", "tab");
	    const active = mode === "notifications";
	    tab.classList.toggle("active", active), tab.setAttribute("aria-selected", String(active));
	    const copy = (0, import_html_element.htmlElement)(document, "span", "");
	    copy.textContent = label, tab.append(copy), notificationModeTabs.push(tab), notificationModeTabsHost.append(tab);
	  }
	  const notificationGroupPanels = [], notificationGroupTabs = [];
	  for (const mode of ["notifications", "messages"]) {
	    const keys = import_reader_notification_model.READER_NOTIFICATION_GROUP_ORDER.filter((key) => import_reader_notification_model.READER_NOTIFICATION_GROUPS[key].mode === mode), panel = (0, import_html_element.htmlElement)(document, "div", "ldp-notification-tabs");
	    panel.dataset.notificationModePanel = mode, panel.setAttribute("role", "tablist"), panel.setAttribute("aria-label", mode === "notifications" ? "通知分类" : "私信分类"), panel.style.setProperty("--ldp-notification-tab-count", String(keys.length)), panel.hidden = mode === "messages";
	    for (const key of keys) {
	      const group = import_reader_notification_model.READER_NOTIFICATION_GROUPS[key], tab = button(
	        options,
	        "ldp-notification-tab",
	        group.label,
	        group.icon
	      );
	      tab.dataset.notificationGroup = key, tab.setAttribute("role", "tab");
	      const active = key === "all";
	      tab.classList.toggle("active", active), tab.setAttribute("aria-selected", String(active));
	      const copy = (0, import_html_element.htmlElement)(document, "span", "");
	      copy.textContent = group.label, tab.append(copy), notificationGroupTabs.push(tab), panel.append(tab);
	    }
	    notificationGroupPanels.push(panel);
	  }
	  const notificationToolbar = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-notification-toolbar"
	  ), notificationUnreadStatus = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-notification-unread-status"
	  );
	  notificationUnreadStatus.textContent = "没有未读消息";
	  const notificationMarkAll = button(
	    options,
	    "ldp-notification-mark-all",
	    "全部已读",
	    "check-square"
	  );
	  notificationMarkAll.disabled = !0;
	  const notificationMarkAllLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  notificationMarkAllLabel.textContent = "全部已读", notificationMarkAll.append(notificationMarkAllLabel);
	  const notificationNewMessage = (0, import_html_element.htmlElement)(
	    document,
	    "a",
	    "ldp-notification-new-message"
	  );
	  notificationNewMessage.href = `${homeUrl.replace(/\/+$/, "")}/new-message` || "/new-message", notificationNewMessage.hidden = !0, notificationNewMessage.append(icon(options, "mail"));
	  const notificationNewMessageLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  notificationNewMessageLabel.textContent = "新消息", notificationNewMessage.append(notificationNewMessageLabel), notificationToolbar.append(
	    notificationUnreadStatus,
	    notificationMarkAll,
	    notificationNewMessage
	  );
	  const {
	    root: notificationSearchWrap,
	    input: notificationSearch,
	    clear: notificationSearchClear
	  } = popoverSearch(
	    options,
	    "notification",
	    "搜索用户、标题、内容或拼音",
	    "搜索消息",
	    "清空消息搜索"
	  ), notificationList = popoverList(
	    document,
	    "ldp-notification-list",
	    "正在加载消息…"
	  ), {
	    root: notificationPager,
	    previous: notificationPagePrevious,
	    info: notificationPageInfo,
	    next: notificationPageNext
	  } = popoverPager(
	    options,
	    "notification",
	    "第 1 页"
	  );
	  notificationsPopover.append(
	    notificationModeTabsHost,
	    ...notificationGroupPanels,
	    notificationToolbar,
	    notificationSearchWrap,
	    notificationList,
	    notificationPager
	  );
	  const historyToggle = button(
	    options,
	    "ldp-history-toggle",
	    "浏览历史",
	    "history"
	  );
	  historyToggle.setAttribute("aria-expanded", "false");
	  const historyPopover = (0, import_html_element.htmlElement)(document, "div", "ldp-history-popover");
	  historyPopover.hidden = !0;
	  const historyTitle = (0, import_html_element.htmlElement)(document, "div", "ldp-collection-title"), historyTitleLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  historyTitleLabel.textContent = "浏览历史";
	  const historyDefaultActions = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-collection-title-actions ldp-history-default-actions"
	  ), historySortToggle = button(
	    options,
	    "ldp-collection-action ldp-history-sort-toggle",
	    "切换浏览历史排序",
	    "history"
	  ), historyMultiButton = button(
	    options,
	    "ldp-collection-action ldp-history-multi",
	    "多选浏览历史",
	    "list-checks"
	  ), historyClearButton = button(
	    options,
	    "ldp-collection-action danger ldp-history-clear",
	    "清空全部浏览历史",
	    "trash"
	  );
	  historyClearButton.disabled = !0, historyDefaultActions.append(
	    historySortToggle,
	    historyMultiButton,
	    historyClearButton
	  );
	  const {
	    root: historyBulkActions,
	    scope: historySelectScope,
	    select: historySelectToggle,
	    remove: historyDeleteSelected,
	    count: historyDeleteSelectedLabel,
	    done: historyMultiDone
	  } = collectionBulkActions(
	    options,
	    "history",
	    "浏览历史",
	    "删除所选浏览历史"
	  );
	  historyTitle.append(
	    historyTitleLabel,
	    historyDefaultActions,
	    historyBulkActions
	  );
	  const {
	    root: historySearchWrap,
	    input: historySearch,
	    clear: historySearchClear
	  } = popoverSearch(
	    options,
	    "history",
	    "搜索标题或拼音",
	    "搜索浏览历史",
	    "清空历史搜索"
	  ), historyList = popoverList(
	    document,
	    "ldp-history-list ldp-notification-list",
	    "暂无浏览历史"
	  ), {
	    root: historyPager,
	    previous: historyPagePrevious,
	    info: historyPageInfo,
	    next: historyPageNext
	  } = popoverPager(
	    options,
	    "history",
	    "暂无记录"
	  );
	  historyPopover.append(
	    historyTitle,
	    historySearchWrap,
	    historyList,
	    historyPager
	  );
	  const bookmarksToggle = button(
	    options,
	    "ldp-bookmarks-toggle",
	    "收藏与回应",
	    "bookmark"
	  );
	  bookmarksToggle.setAttribute("aria-expanded", "false");
	  const bookmarksToggleLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  bookmarksToggleLabel.textContent = "收藏", bookmarksToggle.append(bookmarksToggleLabel);
	  const bookmarksPopover = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-bookmarks-popover"
	  );
	  bookmarksPopover.hidden = !0;
	  const bookmarksTitle = (0, import_html_element.htmlElement)(document, "div", "ldp-collection-title"), bookmarksTitleLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  bookmarksTitleLabel.textContent = "收藏与回应";
	  const bookmarksDefaultActions = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-collection-title-actions ldp-bookmarks-default-actions"
	  ), bookmarksMultiButton = button(
	    options,
	    "ldp-collection-action ldp-bookmarks-multi",
	    "多选收藏",
	    "list-checks"
	  );
	  bookmarksDefaultActions.append(bookmarksMultiButton);
	  const {
	    root: bookmarksBulkActions,
	    scope: bookmarksSelectScope,
	    select: bookmarksSelectToggle,
	    remove: bookmarksDeleteSelected,
	    count: bookmarksDeleteSelectedLabel,
	    done: bookmarksMultiDone
	  } = collectionBulkActions(
	    options,
	    "bookmarks",
	    "收藏",
	    "取消所选收藏"
	  );
	  bookmarksTitle.append(
	    bookmarksTitleLabel,
	    bookmarksDefaultActions,
	    bookmarksBulkActions
	  );
	  const bookmarkTabsHost = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-bookmark-tabs"
	  );
	  bookmarkTabsHost.setAttribute("role", "tablist"), bookmarkTabsHost.setAttribute("aria-label", "收藏与回应类型");
	  const bookmarkTabs = [];
	  for (const [type, label] of [
	    ["Reaction", "回应"],
	    ["Topic", "帖子"],
	    ["Post", "楼层"]
	  ]) {
	    const tab = button(
	      options,
	      "ldp-bookmark-tab",
	      `${label};拖动排序,首项默认`,
	      "bookmark"
	    );
	    tab.dataset.bookmarkType = type, tab.setAttribute("role", "tab"), tab.setAttribute("aria-selected", String(type === "Reaction")), tab.classList.toggle("active", type === "Reaction"), tab.replaceChildren(), tab.textContent = label, bookmarkTabs.push(tab), bookmarkTabsHost.append(tab);
	  }
	  const {
	    root: bookmarksSearchWrap,
	    input: bookmarksSearch,
	    clear: bookmarksSearchClear
	  } = popoverSearch(
	    options,
	    "bookmarks",
	    "搜索收藏标题、内容或拼音",
	    "搜索收藏",
	    "清空收藏搜索"
	  ), bookmarkReactionFilters = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reaction-filters"
	  );
	  bookmarkReactionFilters.setAttribute("role", "group"), bookmarkReactionFilters.setAttribute("aria-label", "按回应表情筛选"), bookmarkReactionFilters.hidden = !0;
	  const bookmarksList = popoverList(
	    document,
	    "ldp-bookmarks-list ldp-notification-list",
	    "正在加载收藏…"
	  ), {
	    root: bookmarksPager,
	    previous: bookmarksPagePrevious,
	    info: bookmarksPageInfo,
	    next: bookmarksPageNext
	  } = popoverPager(
	    options,
	    "bookmarks",
	    "暂无记录"
	  );
	  bookmarksPopover.append(
	    bookmarksTitle,
	    bookmarkTabsHost,
	    bookmarksSearchWrap,
	    bookmarkReactionFilters,
	    bookmarksList,
	    bookmarksPager
	  );
	  const openNative = (0, import_html_element.htmlElement)(document, "a", "ldp-open ldp-icon-btn");
	  openNative.target = "_blank", openNative.rel = "noopener noreferrer", openNative.setAttribute("aria-label", "打开原生主题页面"), openNative.title = "打开原生主题页面", openNative.hidden = !0, openNative.append(icon(options, "external-link")), headerActions.append(
	    layoutToggle,
	    notificationsToggle,
	    notificationsPopover,
	    historyToggle,
	    historyPopover,
	    bookmarksToggle,
	    bookmarksPopover
	  );
	  const titleActions = (0, import_html_element.htmlElement)(document, "div", "ldp-title-actions"), topicEditTrigger = button(
	    options,
	    "ldp-topic-edit-trigger",
	    "编辑帖子标题、类别和 label",
	    "pencil"
	  );
	  topicEditTrigger.hidden = !0, topicEditTrigger.setAttribute("aria-haspopup", "dialog"), topicEditTrigger.setAttribute("aria-expanded", "false");
	  const refreshTopic = button(
	    options,
	    "ldp-reader-refresh ldp-icon-btn",
	    "清除当前帖子缓存并刷新",
	    "rotate-ccw"
	  );
	  refreshTopic.disabled = !0;
	  const closeReader = button(
	    options,
	    "ldp-close ldp-icon-btn",
	    "关闭阅读器",
	    "x"
	  );
	  titleActions.append(
	    topicEditTrigger,
	    refreshTopic,
	    openNative,
	    closeReader
	  ), header.append(home, titleWrap, headerActions, titleActions);
	  const readerMain = (0, import_html_element.htmlElement)(document, "div", "ldp-reader-main"), rateLimitNotice = (0, import_html_element.htmlElement)(document, "div", "ldp-rate-limit-notice");
	  rateLimitNotice.hidden = !0, rateLimitNotice.setAttribute("role", "status"), rateLimitNotice.setAttribute("aria-live", "polite"), rateLimitNotice.setAttribute("aria-atomic", "true");
	  const rateLimitIcon = (0, import_html_element.htmlElement)(document, "span", "ldp-rate-limit-icon");
	  rateLimitIcon.setAttribute("aria-hidden", "true"), rateLimitIcon.append(icon(options, "alert-triangle"));
	  const rateLimitCopy = (0, import_html_element.htmlElement)(document, "span", "ldp-rate-limit-copy"), rateLimitTitle = (0, import_html_element.htmlElement)(document, "strong", "");
	  rateLimitTitle.textContent = "Cloudflare 验证";
	  const rateLimitDetail = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-rate-limit-detail"
	  );
	  rateLimitDetail.textContent = "关键 Reader 请求遇到 Cloudflare 验证时暂停新请求,最多只打开一个独立过盾浮窗。", rateLimitCopy.append(rateLimitTitle, rateLimitDetail);
	  const rateLimitChallenge = (0, import_html_element.htmlElement)(
	    document,
	    "a",
	    "ldp-rate-limit-challenge"
	  );
	  rateLimitChallenge.target = "_blank", rateLimitChallenge.rel = "noopener", rateLimitChallenge.setAttribute(
	    "aria-label",
	    "在独立浮窗打开 LINUX DO 官网完成 Cloudflare 验证"
	  ), rateLimitChallenge.append(icon(options, "external-link"));
	  const rateLimitChallengeLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  rateLimitChallengeLabel.textContent = "打开过盾浮窗", rateLimitChallenge.append(rateLimitChallengeLabel), rateLimitNotice.append(
	    rateLimitIcon,
	    rateLimitCopy,
	    rateLimitChallenge
	  );
	  const body = (0, import_html_element.htmlElement)(document, "div", "ldp-body"), topicTimeline = (0, import_html_element.htmlElement)(document, "aside", "ldp-topic-timeline");
	  topicTimeline.hidden = !0, topicTimeline.setAttribute("aria-label", "帖子时间轴");
	  const topicTimelineDate = (0, import_html_element.htmlElement)(
	    document,
	    "button",
	    "ldp-topic-timeline-date"
	  );
	  topicTimelineDate.type = "button", topicTimelineDate.setAttribute("aria-label", "跳到首帖");
	  const topicTimelineTrack = (0, import_html_element.htmlElement)(
	    document,
	    "button",
	    "ldp-topic-timeline-track"
	  );
	  topicTimelineTrack.type = "button", topicTimelineTrack.setAttribute("role", "slider"), topicTimelineTrack.setAttribute("aria-label", "跳转楼层"), topicTimelineTrack.setAttribute("aria-orientation", "vertical");
	  const topicTimelineCursor = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-cursor ldp-timeline-lens-composited"
	  );
	  topicTimelineCursor.setAttribute("aria-hidden", "true");
	  const topicTimelineThumb = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-thumb"
	  ), topicTimelineCount = (0, import_html_element.htmlElement)(
	    document,
	    "strong",
	    "ldp-topic-timeline-count"
	  ), topicTimelineCurrent = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-current"
	  );
	  topicTimelineCurrent.textContent = "1";
	  const topicTimelineDivider = (0, import_html_element.htmlElement)(document, "span", "");
	  topicTimelineDivider.textContent = "/";
	  const topicTimelineTotal = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-total"
	  );
	  topicTimelineTotal.textContent = "1", topicTimelineCount.append(
	    topicTimelineCurrent,
	    topicTimelineDivider,
	    topicTimelineTotal
	  );
	  const topicTimelinePreview = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-preview"
	  );
	  topicTimelinePreview.setAttribute("aria-hidden", "true"), topicTimelineTrack.append(
	    topicTimelineCursor,
	    topicTimelineThumb,
	    topicTimelineCount,
	    topicTimelinePreview
	  );
	  const topicTimelineFooter = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-topic-timeline-footer"
	  ), topicTimelineRelative = (0, import_html_element.htmlElement)(
	    document,
	    "button",
	    "ldp-topic-timeline-relative"
	  );
	  topicTimelineRelative.type = "button", topicTimelineRelative.setAttribute("aria-label", "跳到末尾可见楼层");
	  const topicTimelineJump = (0, import_html_element.htmlElement)(
	    document,
	    "button",
	    "ldp-topic-timeline-jump"
	  );
	  topicTimelineJump.type = "button", topicTimelineJump.textContent = "#", topicTimelineJump.setAttribute("aria-label", "跳到指定楼层"), topicTimelineJump.setAttribute("aria-haspopup", "dialog"), topicTimelineJump.setAttribute("aria-expanded", "false");
	  const topicTimelineTop = button(
	    options,
	    "ldp-topic-timeline-top",
	    "回到顶部,第 1 楼",
	    "arrow-up"
	  );
	  topicTimelineFooter.append(
	    topicTimelineRelative,
	    topicTimelineJump,
	    topicTimelineTop
	  );
	  const topicTimelineJumpForm = (0, import_html_element.htmlElement)(
	    document,
	    "form",
	    "ldp-topic-timeline-jump-form"
	  );
	  topicTimelineJumpForm.hidden = !0, topicTimelineJumpForm.setAttribute("role", "dialog"), topicTimelineJumpForm.setAttribute("aria-label", "跳到指定楼层");
	  const topicTimelineJumpField = (0, import_html_element.htmlElement)(
	    document,
	    "label",
	    "ldp-topic-timeline-jump-field"
	  ), topicTimelineJumpPrefix = (0, import_html_element.htmlElement)(document, "span", "");
	  topicTimelineJumpPrefix.textContent = "#", topicTimelineJumpPrefix.setAttribute("aria-hidden", "true");
	  const topicTimelineJumpInput = (0, import_html_element.htmlElement)(
	    document,
	    "input",
	    "ldp-topic-timeline-jump-input"
	  );
	  topicTimelineJumpInput.type = "text", topicTimelineJumpInput.inputMode = "numeric", topicTimelineJumpInput.maxLength = 2, topicTimelineJumpInput.autocomplete = "off", topicTimelineJumpInput.spellcheck = !1, topicTimelineJumpInput.setAttribute("enterkeyhint", "go"), topicTimelineJumpInput.setAttribute("aria-label", "楼层号"), topicTimelineJumpInput.setAttribute("aria-invalid", "false");
	  const topicTimelineJumpHint = (0, import_html_element.htmlElement)(
	    document,
	    "span",
	    "ldp-topic-timeline-jump-hint"
	  );
	  topicTimelineJumpHint.id = "ldp-topic-timeline-jump-hint", topicTimelineJumpHint.setAttribute("role", "status"), topicTimelineJumpHint.setAttribute("aria-live", "polite"), topicTimelineJumpHint.setAttribute("aria-atomic", "true"), topicTimelineJumpInput.setAttribute(
	    "aria-describedby",
	    topicTimelineJumpHint.id
	  ), topicTimelineJumpField.append(
	    topicTimelineJumpPrefix,
	    topicTimelineJumpInput
	  );
	  const topicTimelineJumpSubmit = button(
	    options,
	    "ldp-topic-timeline-jump-submit",
	    "跳转",
	    "chevron-right"
	  );
	  topicTimelineJumpSubmit.type = "submit", topicTimelineJumpSubmit.disabled = !0, topicTimelineJumpForm.append(
	    topicTimelineJumpField,
	    topicTimelineJumpSubmit,
	    topicTimelineJumpHint
	  ), topicTimeline.append(
	    topicTimelineDate,
	    topicTimelineTrack,
	    topicTimelineFooter,
	    topicTimelineJumpForm
	  ), readerMain.append(rateLimitNotice, body, topicTimeline);
	  const historyBackEdge = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reader-history-edge ldp-reader-history-edge-back"
	  );
	  historyBackEdge.hidden = !0;
	  const historyBackButton = button(
	    options,
	    "ldp-reader-history-nav ldp-reader-history-back",
	    "上一条阅读历史",
	    "chevron-left"
	  );
	  historyBackButton.hidden = !0, historyBackEdge.append(historyBackButton);
	  const historyForwardEdge = (0, import_html_element.htmlElement)(
	    document,
	    "div",
	    "ldp-reader-history-edge ldp-reader-history-edge-forward"
	  );
	  historyForwardEdge.hidden = !0;
	  const historyForwardButton = button(
	    options,
	    "ldp-reader-history-nav ldp-reader-history-forward",
	    "下一条阅读历史",
	    "chevron-right"
	  );
	  historyForwardButton.hidden = !0, historyForwardEdge.append(historyForwardButton);
	  const liveUpdate = (0, import_html_element.htmlElement)(document, "div", "ldp-live-update");
	  liveUpdate.hidden = !0, liveUpdate.setAttribute("aria-live", "polite");
	  const liveUpdateJump = button(
	    options,
	    "ldp-live-update-jump",
	    "查看新回复",
	    "message-square"
	  ), liveUpdateLabel = (0, import_html_element.htmlElement)(document, "span", "");
	  liveUpdateJump.append(liveUpdateLabel);
	  const liveUpdateDismiss = button(
	    options,
	    "ldp-live-update-dismiss",
	    "暂时关闭新消息提示",
	    "x"
	  );
	  liveUpdate.append(liveUpdateJump, liveUpdateDismiss), modal.append(
	    header,
	    historyBackEdge,
	    readerMain,
	    historyForwardEdge,
	    liveUpdate
	  ), root.append(capsule, hostScrollbar, hostTopButton, modal), options.mount.append(root);
	  const view = Object.freeze({
	    root,
	    modal,
	    body,
	    topicHost: body,
	    surfaceHost: root
	  }), workspaceElements = Object.freeze({
	    pageRoot: document.documentElement,
	    overlay: root,
	    modal,
	    header,
	    titleActions,
	    headButtons: headerActions,
	    windowCapsule: capsule,
	    windowLockButton: lockButton,
	    windowPinButton: pinButton,
	    windowPlacementControl: placementControl,
	    windowPlacementStrip: placementStrip,
	    windowPlacementOptions: placementOptions,
	    embedResizeHandle,
	    hostScrollbar,
	    hostScrollbarThumb,
	    hostTopButton
	  });
	  return Object.freeze({
	    view,
	    workspaceElements,
	    titleJump,
	    metaHost: meta,
	    metaStats,
	    metaOwner,
	    metaOwnerValue,
	    onlyOpToggle,
	    onlyOpProgress,
	    onlyOpProgressValue,
	    topicIdentityHost,
	    headerActions,
	    layoutToggle,
	    titleActions,
	    topicEditTrigger,
	    refreshTopic,
	    openNative,
	    closeReader,
	    rateLimitNotice,
	    rateLimitDetail,
	    rateLimitChallenge,
	    notificationsToggle,
	    notificationUnreadBadge,
	    notificationsPopover,
	    notificationModeTabs: Object.freeze(notificationModeTabs),
	    notificationGroupPanels: Object.freeze(notificationGroupPanels),
	    notificationGroupTabs: Object.freeze(notificationGroupTabs),
	    notificationToolbar,
	    notificationUnreadStatus,
	    notificationMarkAll,
	    notificationNewMessage,
	    notificationSearch,
	    notificationSearchClear,
	    notificationList,
	    notificationPagePrevious,
	    notificationPageInfo,
	    notificationPageNext,
	    historyBackEdge,
	    historyForwardEdge,
	    historyBackButton,
	    historyForwardButton,
	    historyToggle,
	    historyPopover,
	    historySortToggle,
	    historyMultiButton,
	    historyClearButton,
	    historyDefaultActions,
	    historyBulkActions,
	    historySelectScope,
	    historySelectToggle,
	    historyDeleteSelected,
	    historyDeleteSelectedLabel,
	    historyMultiDone,
	    historySearch,
	    historySearchClear,
	    historyList,
	    historyPagePrevious,
	    historyPageInfo,
	    historyPageNext,
	    bookmarksToggle,
	    bookmarksPopover,
	    bookmarkTabs: Object.freeze(bookmarkTabs),
	    bookmarksDefaultActions,
	    bookmarksMultiButton,
	    bookmarksBulkActions,
	    bookmarksSelectScope,
	    bookmarksSelectToggle,
	    bookmarksDeleteSelected,
	    bookmarksDeleteSelectedLabel,
	    bookmarksMultiDone,
	    bookmarksSearch,
	    bookmarksSearchClear,
	    bookmarkReactionFilters,
	    bookmarksList,
	    bookmarksPagePrevious,
	    bookmarksPageInfo,
	    bookmarksPageNext,
	    topicTimeline,
	    topicTimelineDate,
	    topicTimelineTrack,
	    topicTimelineCursor,
	    topicTimelineCurrent,
	    topicTimelineTotal,
	    topicTimelinePreview,
	    topicTimelineRelative,
	    topicTimelineJump,
	    topicTimelineTop,
	    topicTimelineJumpForm,
	    topicTimelineJumpInput,
	    topicTimelineJumpSubmit,
	    topicTimelineJumpHint,
	    liveUpdate,
	    liveUpdateJump,
	    liveUpdateLabel,
	    liveUpdateDismiss
	  });
	}
}, "4a7c2d5e47275aa24ac709fab31ecfa031acf3d9f40d4ffef041dc0245b0421f");

/* Source: lite/src/shell/reader-shell.ts */
runtime.register("src/shell/reader-shell.js", function(module, exports, require) {
	var reader_shell_exports = {};
	__export(reader_shell_exports, {
	  ReaderShell: () => ReaderShell,
	  ReaderSurfaceManager: () => ReaderSurfaceManager,
	  createReaderShellStage: () => createReaderShellStage
	});
	module.exports = __toCommonJS(reader_shell_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function compatibilityKey(value) {
	  const key = String(value).trim();
	  if (!key) throw new Error("Reader Shell compatibilityKey 不能为空");
	  return key;
	}
	function abortReason(topicId) {
	  return new DOMException(`Topic ${topicId} 打开已被替代`, "AbortError");
	}
	function validateView(view) {
	  for (const [name, element] of Object.entries(view)) {
	    if (!element || element.nodeType !== 1)
	      throw new TypeError(`Reader Shell ${name} 必须是 Element`);
	    if (name !== "root" && element !== view.root && !view.root.contains(element))
	      throw new Error(`Reader Shell ${name} 必须属于 root`);
	  }
	  return Object.freeze({ ...view });
	}
	class ReaderSurfaceManager {
	  parking;
	  #root;
	  #defaultHost;
	  #surfaces = /* @__PURE__ */ new Set();
	  #destroyed = !1;
	  constructor(root, defaultHost, parentScope) {
	    if (defaultHost !== root && !root.contains(defaultHost))
	      throw new Error("surface defaultHost 必须属于 Shell root");
	    this.#root = root, this.#defaultHost = defaultHost, this.parking = root.ownerDocument.createDocumentFragment(), parentScope?.add(() => this.destroy());
	  }
	  get size() {
	    return this.#surfaces.size;
	  }
	  mount(surface, host = this.#defaultHost) {
	    if (this.#assertActive(), this.#assertHost(host), surface === this.#root) throw new Error("Shell root 不能登记为 surface");
	    return this.#surfaces.add(surface), surface.parentNode !== host && host.append(surface), surface;
	  }
	  park(surface) {
	    if (this.#assertActive(), !this.#surfaces.has(surface))
	      throw new Error("未登记 surface 不能进入 Shell parking");
	    return surface.parentNode !== this.parking && this.parking.append(surface), surface;
	  }
	  destroy() {
	    if (!this.#destroyed) {
	      this.#destroyed = !0;
	      for (const surface of this.#surfaces) surface.remove();
	      this.#surfaces.clear(), this.parking.replaceChildren();
	    }
	  }
	  #assertHost(host) {
	    if (host !== this.#root && !this.#root.contains(host))
	      throw new Error("surface host 必须属于当前 Shell root");
	  }
	  #assertActive() {
	    if (this.#destroyed) throw new Error("ReaderSurfaceManager 已销毁");
	  }
	}
	class ReaderShell {
	  changes = new import_signal.Signal();
	  diagnostics = new import_signal.Signal();
	  compatibilityKey;
	  view;
	  scope;
	  surfaces;
	  #state = "idle";
	  #epoch = 0;
	  #active = null;
	  #opening = null;
	  #deactivation = null;
	  constructor(compatibility, view, parentScope) {
	    this.compatibilityKey = compatibilityKey(compatibility), this.view = validateView(view), this.scope = import_lifecycle.LifecycleScope.ownedBy(parentScope), this.surfaces = new ReaderSurfaceManager(
	      this.view.root,
	      this.view.surfaceHost,
	      this.scope
	    ), this.#syncRootVisibility();
	  }
	  get state() {
	    return this.#state;
	  }
	  get activeTopicId() {
	    return this.#active?.topicId ?? null;
	  }
	  get activeValue() {
	    return this.#active?.value ?? null;
	  }
	  canReuse(compatibility) {
	    return this.#state !== "destroyed" && this.compatibilityKey === compatibilityKey(compatibility);
	  }
	  open(topicIdValue, factory) {
	    if (this.#state === "destroyed")
	      return Promise.resolve({
	        status: "failed",
	        topicId: (0, import_identifiers.discourseTopicId)(topicIdValue),
	        cause: new Error("Reader Shell 已销毁")
	      });
	    const topicId = (0, import_identifiers.discourseTopicId)(topicIdValue);
	    if (this.#opening?.topicId === topicId) return this.#opening.promise;
	    if (!this.#opening && this.#active?.topicId === topicId)
	      return Promise.resolve(Object.freeze({
	        status: "reused",
	        topicId,
	        value: this.#active.value
	      }));
	    this.#epoch += 1, this.#cancelOpening();
	    const controller = new AbortController(), scope = this.scope.child();
	    scope.add(() => {
	      controller.signal.aborted || controller.abort(abortReason(topicId));
	    });
	    const opening = {
	      topicId,
	      epoch: this.#epoch,
	      controller,
	      scope,
	      promise: Promise.resolve({ status: "superseded", topicId })
	    };
	    return this.#opening = opening, this.#setState(this.#active ? "switching" : "opening"), opening.promise = this.#runOpen(opening, factory), opening.promise;
	  }
	  async closeTopic() {
	    if (this.#isDestroyed()) return !0;
	    const epoch = ++this.#epoch;
	    this.#cancelOpening(), this.#setState("closed");
	    const closed = await this.#deactivateActive("close");
	    return this.#isDestroyed() ? !0 : (this.#epoch === epoch && this.#setState("closed"), closed);
	  }
	  destroy() {
	    if (this.#state !== "destroyed") {
	      this.#epoch += 1, this.#cancelOpening(), this.#active = null, this.#setState("destroyed");
	      try {
	        this.scope.destroy();
	      } catch (cause) {
	        this.diagnostics.emit(Object.freeze({
	          phase: "shell-cleanup",
	          topicId: null,
	          cause
	        }));
	      } finally {
	        this.view.root.remove();
	      }
	    }
	  }
	  async #runOpen(opening, factory) {
	    if (!await this.#deactivateActive("switch")) {
	      const cause = new Error("当前 Topic prepareClose 失败"), current = this.#isCurrent(opening);
	      return this.#destroyTopicScope(opening.scope, opening.topicId), current ? (this.#opening = null, this.#setState("running"), Object.freeze({ status: "failed", topicId: opening.topicId, cause })) : Object.freeze({ status: "superseded", topicId: opening.topicId });
	    }
	    if (!this.#isCurrent(opening))
	      return this.#destroyTopicScope(opening.scope, opening.topicId), Object.freeze({ status: "superseded", topicId: opening.topicId });
	    try {
	      const result = await factory(Object.freeze({
	        topicId: opening.topicId,
	        scope: opening.scope,
	        signal: opening.controller.signal,
	        mount: (node) => (this.view.topicHost.append(node), opening.scope.add(() => node.parentNode?.removeChild(node)))
	      }));
	      return typeof result.cleanup == "function" && opening.scope.add(result.cleanup), this.#isCurrent(opening) ? (this.#active = Object.freeze({
	        topicId: opening.topicId,
	        value: result.value,
	        scope: opening.scope,
	        ...result.prepareClose ? { prepareClose: result.prepareClose } : {}
	      }), this.#opening = null, this.#setState("running"), Object.freeze({
	        status: "opened",
	        topicId: opening.topicId,
	        value: result.value
	      })) : (this.#destroyTopicScope(opening.scope, opening.topicId), Object.freeze({ status: "superseded", topicId: opening.topicId }));
	    } catch (cause) {
	      const current = this.#isCurrent(opening);
	      return this.#destroyTopicScope(opening.scope, opening.topicId), current ? (this.#opening = null, this.#setState("failed"), this.diagnostics.emit(Object.freeze({
	        phase: "topic-open",
	        topicId: opening.topicId,
	        cause
	      })), Object.freeze({ status: "failed", topicId: opening.topicId, cause })) : Object.freeze({ status: "superseded", topicId: opening.topicId });
	    }
	  }
	  #cancelOpening() {
	    const opening = this.#opening;
	    opening && (this.#opening = null, opening.controller.signal.aborted || opening.controller.abort(abortReason(opening.topicId)), this.#destroyTopicScope(opening.scope, opening.topicId));
	  }
	  #isCurrent(opening) {
	    return this.#state !== "destroyed" && this.#opening === opening && this.#epoch === opening.epoch && !opening.controller.signal.aborted;
	  }
	  #deactivateActive(reason) {
	    if (!this.#active) return Promise.resolve(!0);
	    if (this.#deactivation) {
	      const deactivation2 = this.#deactivation;
	      return reason === "switch" ? deactivation2 : deactivation2.then((closed) => closed || !this.#active ? closed : this.#deactivateActive("close"));
	    }
	    const active = this.#active, deactivation = (async () => {
	      let prepared = !0;
	      try {
	        await active.prepareClose?.(reason);
	      } catch (cause) {
	        if (prepared = !1, this.diagnostics.emit(Object.freeze({
	          phase: "prepare-close",
	          topicId: active.topicId,
	          cause
	        })), reason === "switch") return !1;
	      }
	      return this.#active === active && (this.#active = null), this.#destroyTopicScope(active.scope, active.topicId), prepared;
	    })().finally(() => {
	      this.#deactivation === deactivation && (this.#deactivation = null);
	    });
	    return this.#deactivation = deactivation, this.#deactivation;
	  }
	  #destroyTopicScope(scope, topicId) {
	    try {
	      scope.destroy();
	    } catch (cause) {
	      this.diagnostics.emit(Object.freeze({
	        phase: "topic-cleanup",
	        topicId,
	        cause
	      }));
	    }
	  }
	  #setState(state) {
	    this.#state !== state && (this.#state = state, this.#syncRootVisibility(), this.changes.emit(state));
	  }
	  #syncRootVisibility() {
	    this.view.root.hidden = this.#state === "idle" || this.#state === "closed" || this.#state === "destroyed";
	  }
	  #isDestroyed() {
	    return this.#state === "destroyed";
	  }
	}
	function createReaderShellStage(options) {
	  return Object.freeze({
	    name: String(options.name ?? "reader-shell").trim() || "reader-shell",
	    required: !0,
	    setup: (scope, context) => {
	      const shell = new ReaderShell(
	        options.compatibilityKey(context),
	        options.createView(context),
	        scope
	      );
	      let readyCleanup;
	      try {
	        readyCleanup = options.onReady?.(shell, context) || void 0;
	      } catch (cause) {
	        throw shell.destroy(), cause;
	      }
	      return () => {
	        try {
	          readyCleanup?.();
	        } finally {
	          shell.destroy();
	        }
	      };
	    }
	  });
	}
}, "aa4bb2cf57a3e3ac5ee0cf9690c2dd7c56caf9ae6d71e1f8e454ea58f8836d20");

/* Source: lite/src/shell/reader-shortcut-controller.ts */
runtime.register("src/shell/reader-shortcut-controller.js", function(module, exports, require) {
	var reader_shortcut_controller_exports = {};
	__export(reader_shortcut_controller_exports, {
	  READER_SHORTCUT_GROUPS: () => READER_SHORTCUT_GROUPS,
	  ReaderShortcutController: () => ReaderShortcutController,
	  readerPreferencesShortcutAdapter: () => readerPreferencesShortcutAdapter,
	  readerShortcutBindingFromEvent: () => readerShortcutBindingFromEvent,
	  readerShortcutBindingIssue: () => readerShortcutBindingIssue,
	  readerShortcutBindingLabel: () => readerShortcutBindingLabel
	});
	module.exports = __toCommonJS(reader_shortcut_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_event_target = require("../dom/event-target.js"), import_signal = require("../kernel/signal.js"), import_reader_preferences_schema = require("../state/reader-preferences-schema.js");
	const readerPreferencesShortcutAdapter = Object.freeze({
	  read: (preferences) => preferences.readerShortcutBindings,
	  createPatch: (readerShortcutBindings) => ({
	    readerShortcutBindings
	  })
	}), definitions = Object.freeze({
	  historyBack: Object.freeze({
	    group: "navigation",
	    label: "上一条阅读历史",
	    description: "切换到上一条帖子并保存当前位置。"
	  }),
	  historyForward: Object.freeze({
	    group: "navigation",
	    label: "下一条阅读历史",
	    description: "切换到下一条帖子并恢复阅读位置。"
	  }),
	  topicTop: Object.freeze({
	    group: "navigation",
	    label: "回到帖子开头",
	    description: "跳到主楼。"
	  }),
	  topicBottom: Object.freeze({
	    group: "navigation",
	    label: "跳到帖子末尾",
	    description: "跳到当前帖子最后可见楼层。"
	  }),
	  floorJump: Object.freeze({
	    group: "navigation",
	    label: "跳到指定楼层",
	    description: "打开楼层号输入框。"
	  }),
	  discussionHorizontalScroll: Object.freeze({
	    group: "navigation",
	    label: "完整讨论横向滚动",
	    description: "树状层级超出宽度时,按滚轮方向左右查看。"
	  }),
	  onlyAuthor: Object.freeze({
	    group: "reading",
	    label: "只看楼主",
	    description: "切换只看楼主模式。"
	  }),
	  translate: Object.freeze({
	    group: "reading",
	    label: "切换正文翻译",
	    description: "在原文、双语和译文之间切换。"
	  }),
	  refreshTopic: Object.freeze({
	    group: "reading",
	    label: "刷新当前帖子",
	    description: "清除当前帖子缓存并重新加载。"
	  }),
	  refreshHost: Object.freeze({
	    group: "reading",
	    label: "刷新嵌入原站",
	    description: "嵌入阅读时刷新后方的原站列表。"
	  }),
	  openOriginal: Object.freeze({
	    group: "reading",
	    label: "打开原帖",
	    description: "在新标签页打开原站帖子。"
	  }),
	  settings: Object.freeze({
	    group: "panels",
	    label: "设置",
	    description: "打开设置面板。"
	  }),
	  notifications: Object.freeze({
	    group: "panels",
	    label: "消息",
	    description: "打开或关闭通知与私信面板。"
	  }),
	  historyPanel: Object.freeze({
	    group: "panels",
	    label: "浏览历史面板",
	    description: "打开或关闭浏览历史列表。"
	  }),
	  bookmarksPanel: Object.freeze({
	    group: "panels",
	    label: "收藏与回应面板",
	    description: "打开或关闭收藏与回应列表。"
	  }),
	  likeTopic: Object.freeze({
	    group: "topicActions",
	    label: "点赞主帖",
	    description: "点赞或取消点赞当前主帖。"
	  }),
	  replyTopic: Object.freeze({
	    group: "topicActions",
	    label: "回复主题",
	    description: "打开当前主题的回复编辑器。"
	  }),
	  bookmarkTopic: Object.freeze({
	    group: "topicActions",
	    label: "收藏主题",
	    description: "收藏或编辑当前主题收藏。"
	  }),
	  toggleFullscreen: Object.freeze({
	    group: "window",
	    label: "全屏/浮窗切换",
	    description: "在全屏阅读与浮窗阅读之间切换。"
	  }),
	  toggleQueue: Object.freeze({
	    group: "window",
	    label: "阅读队列",
	    description: "展开或收起阅读队列。"
	  }),
	  closeReader: Object.freeze({
	    group: "window",
	    label: "关闭阅读器",
	    description: "遵循“连续两次关闭”安全设置退出阅读器。"
	  })
	}), groupCopy = Object.freeze({
	  navigation: Object.freeze({
	    title: "浏览导航",
	    description: "在阅读历史和当前帖子的关键位置之间移动。"
	  }),
	  reading: Object.freeze({
	    title: "阅读工具",
	    description: "切换阅读过滤、翻译和内容刷新。"
	  }),
	  panels: Object.freeze({
	    title: "界面面板",
	    description: "快速打开阅读器里的常用信息面板。"
	  }),
	  topicActions: Object.freeze({
	    title: "帖子操作",
	    description: "复用主帖操作列,不复制点赞、回复或收藏逻辑。"
	  }),
	  window: Object.freeze({
	    title: "窗口与队列",
	    description: "控制阅读器窗口、阅读队列与退出。"
	  })
	}), READER_SHORTCUT_GROUPS = Object.freeze(
	  Object.keys(groupCopy).map(
	    (group) => Object.freeze({
	      id: group,
	      ...groupCopy[group],
	      actions: Object.freeze(import_reader_preferences_schema.READER_SHORTCUT_ACTIONS.filter((id) => definitions[id].group === group).map((id) => Object.freeze({
	        id,
	        label: definitions[id].label,
	        description: definitions[id].description
	      })))
	    })
	  )
	), reserved = /* @__PURE__ */ new Set([
	  "Ctrl+KeyD",
	  "Ctrl+KeyF",
	  "Ctrl+KeyH",
	  "Ctrl+KeyJ",
	  "Ctrl+KeyL",
	  "Ctrl+KeyN",
	  "Ctrl+KeyO",
	  "Ctrl+KeyP",
	  "Ctrl+KeyR",
	  "Ctrl+KeyS",
	  "Ctrl+KeyT",
	  "Ctrl+KeyW",
	  "Ctrl+Tab",
	  "Ctrl+Shift+KeyN",
	  "Ctrl+Shift+KeyT",
	  "Ctrl+Shift+Tab",
	  "Alt+ArrowLeft",
	  "Alt+ArrowRight",
	  "Alt+F4",
	  "Alt+Home",
	  "Meta+Comma",
	  "Meta+KeyF",
	  "Meta+KeyL",
	  "Meta+KeyN",
	  "Meta+KeyP",
	  "Meta+KeyQ",
	  "Meta+KeyR",
	  "Meta+KeyS",
	  "Meta+KeyT",
	  "Meta+KeyW",
	  "Shift+Meta+KeyT",
	  "F11",
	  "F12"
	]);
	function readerShortcutBindingLabel(binding) {
	  const labels = Object.freeze({
	    Ctrl: "Ctrl",
	    Alt: "Alt",
	    Shift: "Shift",
	    Meta: "Meta",
	    ArrowLeft: "←",
	    ArrowRight: "→",
	    ArrowUp: "↑",
	    ArrowDown: "↓",
	    Home: "Home",
	    End: "End",
	    PageUp: "Page Up",
	    PageDown: "Page Down",
	    Space: "空格",
	    Escape: "Esc",
	    Enter: "Enter",
	    Tab: "Tab",
	    Comma: ",",
	    Period: ".",
	    Slash: "/",
	    Semicolon: ";",
	    Quote: "'",
	    BracketLeft: "[",
	    BracketRight: "]",
	    Backslash: "\\",
	    Minus: "-",
	    Equal: "=",
	    Backquote: "`",
	    Mouse1: "鼠标中键",
	    Mouse3: "鼠标后退键",
	    Mouse4: "鼠标前进键",
	    Wheel: "滚轮"
	  });
	  return binding.split("+").map(
	    (part) => labels[part] ?? (/^Key[A-Z]$/.test(part) ? part.slice(3) : /^Digit\d$/.test(part) ? part.slice(5) : /^Mouse\d+$/.test(part) ? `鼠标键 ${Number(part.slice(5)) + 1}` : part)
	  ).join(" + ");
	}
	function readerShortcutBindingFromEvent(event) {
	  const source = event, wheel = event.type === "wheel", mouse = /^(?:mouse|auxclick)/.test(event.type);
	  if (wheel && !source.ctrlKey && !source.altKey && !source.shiftKey && !source.metaKey) return "";
	  const code = wheel ? "Wheel" : mouse ? source.button === 1 || Number(source.button) >= 3 ? `Mouse${source.button}` : "" : String(source.code ?? "");
	  return !code || !mouse && !wheel && /^(?:Control|Alt|Shift|Meta)(?:Left|Right)?$/.test(code) ? "" : (0, import_reader_preferences_schema.normalizeReaderShortcutBinding)([
	    source.ctrlKey && "Ctrl",
	    source.altKey && "Alt",
	    source.shiftKey && "Shift",
	    source.metaKey && "Meta",
	    code
	  ].filter(Boolean).join("+"));
	}
	function readerShortcutBindingIssue(bindings, binding, exceptAction) {
	  const normalized = (0, import_reader_preferences_schema.normalizeReaderShortcutBinding)(binding);
	  if (!normalized) return "这个按键组合无法识别,请换一个。";
	  const owner = import_reader_preferences_schema.READER_SHORTCUT_ACTIONS.find(
	    (action) => action !== exceptAction && bindings[action].includes(normalized)
	  );
	  if (owner)
	    return `${readerShortcutBindingLabel(normalized)} 已绑定“${definitions[owner].label}”,请先移除或改用其他组合。`;
	  if (reserved.has(normalized))
	    return `${readerShortcutBindingLabel(normalized)} 通常由浏览器占用,无法保证生效,请换一个组合。`;
	  const parts = normalized.split("+"), code = parts.at(-1) ?? "";
	  return parts.length === 1 && /^(?:Key[A-Z]|Digit\d)$/.test(code) ? "单个字母或数字容易与论坛快捷键冲突,请至少加入 Ctrl、Alt、Shift 或 Meta。" : "";
	}
	function editableTarget(event) {
	  const target = (0, import_event_target.eventElement)(event);
	  if (!target) return !1;
	  const element = target;
	  return element.closest(
	    'input,textarea,select,[contenteditable="true"],[contenteditable=""]'
	  ) ? !0 : event.type !== "keydown" ? !1 : !!element.closest(
	    'button,a[href],[role="button"],[role="slider"],[role="tab"],[role="menuitem"],[role="dialog"]'
	  );
	}
	class ReaderShortcutController {
	  scope;
	  changes = new import_signal.Signal();
	  captures = new import_signal.Signal();
	  #adapter;
	  #persist;
	  #execute;
	  #canExecute;
	  #onUnavailable;
	  #onError;
	  #bindings;
	  #recording = null;
	  #mouseGuard = null;
	  constructor(options) {
	    this.#adapter = options.preferences, this.#persist = options.persist, this.#execute = options.execute, this.#canExecute = options.canExecute ?? (() => !0), this.#onUnavailable = options.onUnavailable ?? (() => {
	    }), this.#onError = options.onError ?? (() => {
	    }), this.#bindings = (0, import_reader_preferences_schema.normalizeReaderShortcutBindings)(
	      this.#adapter.read(options.readPreferences())
	    ), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), options.preferenceChanges.subscribe((preferences) => {
	      this.#bindings = (0, import_reader_preferences_schema.normalizeReaderShortcutBindings)(
	        this.#adapter.read(preferences)
	      ), this.#publish();
	    }, this.scope), this.scope.listen(options.target, "keydown", (event) => {
	      this.#handle(event);
	    }, !1), this.scope.listen(options.target, "wheel", (event) => {
	      this.#handle(event);
	    }, { capture: !0, passive: !1 }), this.scope.listen(options.target, "mousedown", (event) => {
	      this.#handle(event);
	    }, !0), this.scope.listen(options.target, "mouseup", (event) => {
	      this.#handle(event);
	    }, !0), this.scope.listen(options.target, "auxclick", (event) => {
	      this.#handle(event);
	    }, !0), this.scope.add(() => {
	      this.changes.clear(), this.captures.clear();
	    });
	  }
	  get snapshot() {
	    return Object.freeze({
	      bindings: this.#bindings,
	      recording: this.#recording
	    });
	  }
	  startRecording(action) {
	    if (!import_reader_preferences_schema.READER_SHORTCUT_ACTIONS.includes(action))
	      throw new RangeError(`未知快捷动作:${action}`);
	    this.#recording = this.#recording === action ? null : action, this.#publish();
	  }
	  cancelRecording() {
	    this.#recording !== null && (this.#recording = null, this.#publish());
	  }
	  remove(action, binding) {
	    this.#replace(action, this.#bindings[action].filter(
	      (candidate) => candidate !== binding
	    ));
	  }
	  clear(action) {
	    this.#replace(action, Object.freeze([]));
	  }
	  reset(action) {
	    const defaults = import_reader_preferences_schema.READER_SHORTCUT_DEFAULTS[action], issue = defaults.map((binding) => readerShortcutBindingIssue(this.#bindings, binding, action)).find(Boolean);
	    return issue || (this.#replace(action, defaults), "");
	  }
	  resetAll() {
	    this.#write((0, import_reader_preferences_schema.normalizeReaderShortcutBindings)(import_reader_preferences_schema.READER_SHORTCUT_DEFAULTS));
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #handle(event) {
	    const keyboard = event, mouse = event;
	    if (event.type !== "mousedown" && this.#mouseGuard && mouse.button === this.#mouseGuard.button && Date.now() < this.#mouseGuard.until) {
	      event.preventDefault(), event.stopImmediatePropagation();
	      return;
	    }
	    if (event.defaultPrevented || keyboard.isComposing || keyboard.repeat) return;
	    const binding = readerShortcutBindingFromEvent(event);
	    if (!binding) return;
	    if (this.#recording) {
	      this.#consume(event, mouse);
	      const action2 = this.#recording, issue = readerShortcutBindingIssue(
	        this.#bindings,
	        binding,
	        action2
	      );
	      if (!issue && this.#bindings[action2].length >= 3) {
	        this.captures.emit(Object.freeze({
	          action: action2,
	          binding,
	          accepted: !1,
	          message: "每项最多保留 3 个快捷方式。"
	        }));
	        return;
	      }
	      if (issue) {
	        this.captures.emit(Object.freeze({
	          action: action2,
	          binding,
	          accepted: !1,
	          message: issue
	        }));
	        return;
	      }
	      this.#replace(action2, Object.freeze([
	        ...this.#bindings[action2],
	        binding
	      ])), this.#recording = null, this.captures.emit(Object.freeze({
	        action: action2,
	        binding,
	        accepted: !0,
	        message: `已绑定 ${readerShortcutBindingLabel(binding)}`
	      })), this.#publish();
	      return;
	    }
	    const targetBlocked = editableTarget(event), action = import_reader_preferences_schema.READER_SHORTCUT_ACTIONS.find(
	      (candidate) => this.#bindings[candidate].includes(binding)
	    );
	    if (action) {
	      if (targetBlocked || !this.#canExecute(action, event)) {
	        event.type !== "keydown" && this.#consume(event, mouse);
	        return;
	      }
	      try {
	        const result = this.#execute(action, event);
	        if (result === !1 && action === "discussionHorizontalScroll")
	          return;
	        if (this.#consume(event, mouse), result === !1) {
	          this.#onUnavailable(action, definitions[action].label);
	          return;
	        }
	        result && typeof result.then == "function" && Promise.resolve(result).then((value) => {
	          value === !1 && this.#onUnavailable(action, definitions[action].label);
	        }).catch(this.#onError);
	      } catch (cause) {
	        this.#onError(cause);
	      }
	    }
	  }
	  #consume(event, mouse) {
	    event.preventDefault(), event.stopImmediatePropagation(), event.type === "mousedown" && (this.#mouseGuard = Object.freeze({
	      button: Number(mouse.button),
	      until: Date.now() + 800
	    }));
	  }
	  #replace(action, bindings) {
	    this.#write((0, import_reader_preferences_schema.normalizeReaderShortcutBindings)({
	      ...this.#bindings,
	      [action]: bindings
	    }));
	  }
	  #write(bindings) {
	    const persisted = this.#persist(this.#adapter.createPatch(bindings));
	    this.#bindings = (0, import_reader_preferences_schema.normalizeReaderShortcutBindings)(
	      this.#adapter.read(persisted)
	    ), this.#publish();
	  }
	  #publish() {
	    this.scope.destroyed || this.changes.emit(this.snapshot);
	  }
	}
}, "d1011e417cc7ef5f2c25a5891952a9160942452deac0ca4e31f5453b92853545");

/* Source: lite/src/shell/reader-surface-portal.ts */
runtime.register("src/shell/reader-surface-portal.js", function(module, exports, require) {
	var reader_surface_portal_exports = {};
	__export(reader_surface_portal_exports, {
	  ReaderSurfacePortal: () => ReaderSurfacePortal
	});
	module.exports = __toCommonJS(reader_surface_portal_exports);
	const PORTAL_ID = "ldp-mian-lite-portal";
	class ReaderSurfacePortal {
	  host;
	  root;
	  style;
	  #destroyed = !1;
	  constructor(document, stylesheet) {
	    if (!document.documentElement)
	      throw new Error("Reader Surface Portal 缺少 documentElement");
	    if (document.getElementById(PORTAL_ID))
	      throw new Error("Reader Surface Portal 已存在");
	    const host = document.createElement("div");
	    host.id = PORTAL_ID, host.className = "ldp-reader-portal-host sciapp-ldp-owned", host.dataset.ldpReaderPortal = "mian-lite";
	    const root = host.attachShadow({ mode: "open" }), style = document.createElement("style");
	    style.dataset.ldpReaderShadow = "mian-lite", style.textContent = stylesheet, root.append(style), document.documentElement.append(host), this.host = host, this.root = root, this.style = style;
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.host.remove());
	  }
	}
}, "fa2ac6ce26a1fa3db363281f1f16bf64a67328c388b03d2ee73e2b0fa3ccc621");

/* Source: lite/src/shell/reader-topic-edit-form-surface.ts */
runtime.register("src/shell/reader-topic-edit-form-surface.js", function(module, exports, require) {
	var reader_topic_edit_form_surface_exports = {};
	__export(reader_topic_edit_form_surface_exports, {
	  ReaderTopicEditFormSurface: () => ReaderTopicEditFormSurface
	});
	module.exports = __toCommonJS(reader_topic_edit_form_surface_exports);
	var import_html_element = require("../dom/html-element.js"), import_reader_action_form_support = require("./reader-action-form-support.js");
	function tagKey(value) {
	  return String(value ?? "").trim().toLocaleLowerCase();
	}
	function categorySearchText(category, byId) {
	  return `${(category.parentCategoryId ? byId.get(category.parentCategoryId) : null)?.name ?? ""} ${category.name}`.trim().toLocaleLowerCase();
	}
	class ReaderTopicEditFormSurface {
	  scope;
	  #host;
	  constructor(options) {
	    this.#host = new import_reader_action_form_support.ReaderActionFormSurfaceHost({
	      ...options,
	      label: "ReaderTopicEditFormSurface"
	    }), this.scope = this.#host.scope;
	  }
	  get #document() {
	    return this.#host.document;
	  }
	  open(request) {
	    if (request.signal?.aborted) return Promise.resolve(!1);
	    const categories = request.categories.filter((category) => Number.isSafeInteger(category.id) && category.id > 0 && String(category.name).trim());
	    if (!categories.length)
	      return Promise.reject(new Error("原站分类数据尚未就绪,请稍后重试"));
	    const categoryById = new Map(categories.map((category) => [
	      category.id,
	      category
	    ]));
	    let selectedCategoryId = categoryById.has(request.categoryId) ? request.categoryId : categories[0].id;
	    const availableTags = /* @__PURE__ */ new Map(), selectedTagKeys = /* @__PURE__ */ new Set();
	    for (const tag of request.tags) {
	      const key = tagKey(tag.name);
	      key && (availableTags.set(key, tag), selectedTagKeys.add(key));
	    }
	    const { id, previousFocus } = this.#host.prepare(), layer = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-reader-action-layer ldp-topic-edit-layer"
	    ), dialog = (0, import_html_element.htmlElement)(
	      this.#document,
	      "section",
	      "ldp-reader-action-dialog ldp-topic-edit-dialog"
	    );
	    dialog.setAttribute("role", "dialog"), dialog.setAttribute("aria-modal", "true");
	    const titleId = `ldp-topic-edit-dialog-title-${id}`;
	    dialog.setAttribute("aria-labelledby", titleId);
	    const head = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-reader-action-head"), heading = (0, import_html_element.htmlElement)(this.#document, "strong", "", "编辑帖子信息");
	    heading.id = titleId;
	    const close = this.#iconButton(
	      "ldp-reader-action-close ldp-topic-edit-close",
	      "关闭编辑弹层",
	      "x"
	    );
	    close.dataset.topicEditClose = "", head.append(heading, close);
	    const form = (0, import_html_element.htmlElement)(
	      this.#document,
	      "form",
	      "ldp-reader-action-form ldp-topic-edit-form"
	    ), body = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-reader-action-body ldp-topic-edit-body"
	    ), titleField = (0, import_html_element.htmlElement)(
	      this.#document,
	      "label",
	      "ldp-topic-edit-field ldp-topic-edit-title"
	    ), titleLabel = (0, import_html_element.htmlElement)(this.#document, "span", "", "标题"), titleInput = this.#document.createElement("input");
	    titleInput.name = "title", titleInput.type = "text", titleInput.maxLength = 255, titleInput.required = !0, titleInput.value = String(request.title ?? "").trim(), titleField.append(titleLabel, titleInput);
	    const categoryField = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-topic-edit-field"
	    ), categoryLabel = (0, import_html_element.htmlElement)(this.#document, "span", "", "类别");
	    categoryLabel.id = `ldp-topic-edit-category-label-${id}`;
	    const categoryRoot = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-topic-edit-category"
	    ), categoryTrigger = (0, import_html_element.htmlElement)(
	      this.#document,
	      "button",
	      "ldp-topic-edit-category-trigger ldp-picker-trigger"
	    );
	    categoryTrigger.type = "button", categoryTrigger.setAttribute("aria-haspopup", "listbox"), categoryTrigger.setAttribute("aria-expanded", "false"), categoryTrigger.setAttribute("aria-labelledby", categoryLabel.id);
	    const categoryValue = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-topic-edit-category-value"
	    );
	    categoryTrigger.append(categoryValue, this.#icon("chevron-right"));
	    const categoryMenu = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-topic-edit-category-menu"
	    );
	    categoryMenu.hidden = !0;
	    const categorySearchLabel = (0, import_html_element.htmlElement)(
	      this.#document,
	      "label",
	      "ldp-topic-edit-category-search ldp-picker-search"
	    );
	    categorySearchLabel.append(this.#icon("search"));
	    const categorySearch = this.#document.createElement("input");
	    categorySearch.name = "category-search", categorySearch.type = "search", categorySearch.autocomplete = "off", categorySearch.placeholder = "搜索类别", categorySearch.setAttribute("aria-label", "搜索类别"), categorySearchLabel.append(categorySearch);
	    const categoryOptions = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-topic-edit-category-options ldp-picker-options"
	    );
	    categoryOptions.setAttribute("role", "listbox"), categoryOptions.setAttribute("aria-labelledby", categoryLabel.id), categoryMenu.append(categorySearchLabel, categoryOptions), categoryRoot.append(categoryTrigger, categoryMenu), categoryField.append(categoryLabel, categoryRoot);
	    const tagField = (0, import_html_element.htmlElement)(this.#document, "label", "ldp-topic-edit-field"), tagLabel = (0, import_html_element.htmlElement)(this.#document, "span", "", "label"), tagControl = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-topic-edit-label-control"
	    ), tagValues = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-topic-edit-label-values"
	    ), tagInput = (0, import_html_element.htmlElement)(
	      this.#document,
	      "input",
	      "ldp-topic-edit-label-input"
	    );
	    tagInput.name = "tag-search", tagInput.type = "text", tagInput.autocomplete = "off", tagInput.placeholder = "搜索 label", tagInput.setAttribute("aria-label", "添加 label");
	    const tagOptionsId = `ldp-topic-edit-label-options-${id}`;
	    tagInput.setAttribute("list", tagOptionsId);
	    const tagOptions = (0, import_html_element.htmlElement)(
	      this.#document,
	      "datalist",
	      "ldp-topic-edit-label-options"
	    );
	    tagOptions.id = tagOptionsId, tagControl.append(tagValues, tagInput), tagField.append(tagLabel, tagControl, tagOptions);
	    const status = (0, import_html_element.htmlElement)(this.#document, "p", "ldp-topic-edit-status");
	    status.setAttribute("role", "status"), status.setAttribute("aria-live", "polite"), body.append(titleField, categoryField, tagField, status);
	    const footer = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-reader-action-footer ldp-topic-edit-actions"
	    ), submit = this.#iconButton(
	      "ldp-topic-edit-save",
	      "保存帖子信息",
	      "check"
	    );
	    submit.type = "submit";
	    const cancel = this.#iconButton(
	      "ldp-topic-edit-cancel",
	      "取消编辑",
	      "x"
	    );
	    cancel.dataset.topicEditCancel = "", footer.append(submit, cancel), form.append(body, footer), dialog.append(head, form), layer.append(dialog);
	    {
	      let busy = !1, searchTimer = null, searchSequence = 0;
	      const selectedTags = () => Object.freeze([...selectedTagKeys].map((key) => availableTags.get(key)).filter((tag) => !!tag)), clearSearch = () => {
	        searchSequence += 1, searchTimer !== null && clearTimeout(searchTimer), searchTimer = null;
	      }, frame = {
	        layer,
	        dialog,
	        form,
	        body,
	        status,
	        cancel,
	        submit
	      }, session = this.#host.start({
	        frame,
	        previousFocus,
	        closeSelector: "[data-topic-edit-close]",
	        cancelSelector: "[data-topic-edit-cancel]",
	        signal: request.signal,
	        onSettled: clearSearch
	      }), closeCategoryMenu = (restoreFocus = !1) => {
	        categoryMenu.hidden = !0, categoryTrigger.setAttribute("aria-expanded", "false"), restoreFocus && !categoryTrigger.disabled && categoryTrigger.focus({ preventScroll: !0 });
	      }, setBusy = (value) => {
	        busy = value, session.setBusy(value), titleInput.disabled = value, categoryTrigger.disabled = value, categorySearch.disabled = value, tagInput.disabled = value, submit.disabled = value, cancel.disabled = value, close.disabled = value, form.setAttribute("aria-busy", String(value)), value && closeCategoryMenu();
	      }, syncCategory = () => {
	        const selected = categoryById.get(selectedCategoryId), parent = selected?.parentCategoryId ? categoryById.get(selected.parentCategoryId) : null;
	        categoryValue.textContent = selected ? `${parent ? `${parent.name} / ` : ""}${selected.name}` : "请选择类别";
	        for (const option of categoryOptions.querySelectorAll(
	          ".ldp-topic-edit-category-option"
	        ))
	          option.setAttribute(
	            "aria-selected",
	            String(Number(option.dataset.categoryId) === selectedCategoryId)
	          );
	      }, renderCategories = () => {
	        const fragment = this.#document.createDocumentFragment();
	        for (const category of categories) {
	          const parent = category.parentCategoryId ? categoryById.get(category.parentCategoryId) : null, option = (0, import_html_element.htmlElement)(
	            this.#document,
	            "button",
	            "ldp-topic-edit-category-option ldp-picker-option"
	          );
	          option.type = "button", option.dataset.categoryId = String(category.id), option.dataset.searchText = categorySearchText(
	            category,
	            categoryById
	          ), option.setAttribute("role", "option");
	          const dot = (0, import_html_element.htmlElement)(
	            this.#document,
	            "span",
	            "ldp-topic-edit-category-dot"
	          );
	          /^[0-9a-f]{6}$/i.test(category.color) && dot.style.setProperty(
	            "--ldp-category-color",
	            `#${category.color}`
	          );
	          const copy = (0, import_html_element.htmlElement)(
	            this.#document,
	            "span",
	            "ldp-topic-edit-category-copy ldp-picker-option-copy"
	          ), name = (0, import_html_element.htmlElement)(
	            this.#document,
	            "strong",
	            "",
	            category.name
	          ), level = (0, import_html_element.htmlElement)(
	            this.#document,
	            "small",
	            "",
	            parent?.name ?? "一级类别"
	          );
	          copy.append(name, level), option.append(dot, copy), fragment.append(option);
	        }
	        categoryOptions.replaceChildren(fragment), syncCategory();
	      }, scheduleTagSearch = () => {
	        clearSearch();
	        const sequence = searchSequence;
	        searchTimer = setTimeout(() => {
	          searchTimer = null, request.searchTags({
	            query: tagInput.value.trim(),
	            categoryId: selectedCategoryId,
	            selected: selectedTags()
	          }).then((tags) => {
	            if (!(!session.active || sequence !== searchSequence)) {
	              for (const tag of tags) {
	                const key = tagKey(tag.name);
	                key && availableTags.set(key, tag);
	              }
	              tagOptions.replaceChildren(...tags.map((tag) => {
	                const option = (0, import_html_element.htmlElement)(this.#document, "option");
	                return option.value = tag.name, option;
	              })), status.textContent = "";
	            }
	          }).catch((cause) => {
	            !session.active || sequence !== searchSequence || (status.textContent = cause instanceof Error ? cause.message : "标签搜索失败,请重试");
	          });
	        }, 300);
	      }, renderTags = () => {
	        tagValues.replaceChildren(...selectedTags().map((tag) => {
	          const chip = (0, import_html_element.htmlElement)(
	            this.#document,
	            "span",
	            "ldp-topic-edit-label-chip"
	          ), copy = (0, import_html_element.htmlElement)(this.#document, "span", "", tag.name), remove = this.#iconButton(
	            "ldp-topic-edit-label-remove",
	            `移除 label ${tag.name}`,
	            "x"
	          );
	          return remove.dataset.labelKey = tagKey(tag.name), chip.append(copy, remove), chip;
	        }));
	      }, commitTagInput = (showError = !0) => {
	        const raw = tagInput.value.replace(/[,,]\s*$/, "").trim();
	        if (!raw)
	          return tagInput.value = "", !0;
	        const key = tagKey(raw);
	        return availableTags.get(key) ? (selectedTagKeys.add(key), tagInput.value = "", status.textContent = "", renderTags(), scheduleTagSearch(), !0) : (showError && (status.textContent = `没有找到 label“${raw}”`), !1);
	      };
	      return layer.addEventListener("click", (event) => {
	        const target = event.target;
	        target && (target.closest(".ldp-topic-edit-category") || closeCategoryMenu());
	      }), layer.addEventListener("keydown", (event) => {
	        const keyboard = event;
	        if (keyboard.key === "Escape" && !categoryMenu.hidden && !busy) {
	          keyboard.preventDefault(), keyboard.stopImmediatePropagation(), closeCategoryMenu(!0);
	          return;
	        }
	        keyboard.key === "Escape" && !busy && keyboard.stopPropagation();
	      }), categoryTrigger.addEventListener("click", () => {
	        if (!busy) {
	          if (!categoryMenu.hidden) {
	            closeCategoryMenu(!0);
	            return;
	          }
	          categoryMenu.hidden = !1, categoryTrigger.setAttribute("aria-expanded", "true"), categorySearch.value = "";
	          for (const option of categoryOptions.children)
	            option.hidden = !1;
	          categorySearch.focus({ preventScroll: !0 });
	        }
	      }), categoryTrigger.addEventListener("keydown", (event) => {
	        event.key === "ArrowDown" && (event.preventDefault(), categoryTrigger.click());
	      }), categorySearch.addEventListener("input", () => {
	        const query = categorySearch.value.trim().toLocaleLowerCase();
	        for (const option of categoryOptions.querySelectorAll(
	          ".ldp-topic-edit-category-option"
	        ))
	          option.hidden = !!(query && !String(option.dataset.searchText).includes(query));
	      }), categoryOptions.addEventListener("click", (event) => {
	        const option = event.target?.closest(
	          ".ldp-topic-edit-category-option"
	        );
	        !option || busy || (selectedCategoryId = Number(option.dataset.categoryId), syncCategory(), closeCategoryMenu(!0), scheduleTagSearch());
	      }), tagInput.addEventListener("input", scheduleTagSearch), tagInput.addEventListener("change", () => commitTagInput(!1)), tagInput.addEventListener("keydown", (event) => {
	        const key = event.key;
	        ["Enter", ",", ","].includes(key) && (event.preventDefault(), commitTagInput());
	      }), tagValues.addEventListener("click", (event) => {
	        const remove = event.target?.closest(
	          ".ldp-topic-edit-label-remove"
	        );
	        !remove || busy || (selectedTagKeys.delete(String(remove.dataset.labelKey)), renderTags(), scheduleTagSearch(), tagInput.focus({ preventScroll: !0 }));
	      }), form.addEventListener("submit", (event) => {
	        if (event.preventDefault(), busy || !commitTagInput()) return;
	        const nextTitle = titleInput.value.trim(), category = categoryById.get(selectedCategoryId);
	        if (!nextTitle) {
	          status.textContent = "标题不能为空", titleInput.focus({ preventScroll: !0 });
	          return;
	        }
	        if (!category) {
	          status.textContent = "请选择类别", categoryTrigger.focus({ preventScroll: !0 });
	          return;
	        }
	        setBusy(!0), status.textContent = "正在保存…", new Promise((resolve) => {
	          resolve(request.submit(Object.freeze({
	            title: nextTitle,
	            category,
	            tags: selectedTags()
	          })));
	        }).then(() => {
	          session.active && session.settle(!0);
	        }).catch((cause) => {
	          session.active && (status.textContent = cause instanceof Error ? cause.message : "保存失败,请重试", setBusy(!1));
	        });
	      }), renderCategories(), renderTags(), session.mount(() => {
	        titleInput.focus({ preventScroll: !0 }), titleInput.select();
	      }), scheduleTagSearch(), session.result;
	    }
	  }
	  destroy() {
	    this.#host.destroy();
	  }
	  #icon(name) {
	    return (0, import_reader_action_form_support.renderReaderActionIcon)(
	      this.#document,
	      name,
	      this.#host.renderIcon
	    );
	  }
	  #iconButton(className, label, icon) {
	    const button = (0, import_html_element.htmlElement)(this.#document, "button", className);
	    return button.type = "button", button.setAttribute("aria-label", label), button.append(this.#icon(icon)), button;
	  }
	}
}, "964d8ccccee41bb4adf4de61bc618d25ece0686372c1447f235cf320c47e7195");

/* Source: lite/src/shell/reader-workspace-coordinator.ts */
runtime.register("src/shell/reader-workspace-coordinator.js", function(module, exports, require) {
	var reader_workspace_coordinator_exports = {};
	__export(reader_workspace_coordinator_exports, {
	  ReaderHeaderAlignmentController: () => ReaderHeaderAlignmentController,
	  ReaderHostTakeoverController: () => ReaderHostTakeoverController,
	  ReaderWorkspaceCoordinator: () => ReaderWorkspaceCoordinator,
	  createReaderShellWorkspaceStage: () => createReaderShellWorkspaceStage
	});
	module.exports = __toCommonJS(reader_workspace_coordinator_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_embedded_host_appearance = require("./embedded-host-appearance.js"), import_embedded_host_root_controller = require("./embedded-host-root-controller.js"), import_embedded_host_scrollbar = require("./embedded-host-scrollbar.js"), import_embedded_host_top_shortcut = require("./embedded-host-top-shortcut.js"), import_main_outlet_mutation_hub = require("./main-outlet-mutation-hub.js"), import_reader_embed_resize_controller = require("./reader-embed-resize-controller.js"), import_reader_shell = require("./reader-shell.js"), import_reader_workspace = require("./reader-workspace.js");
	const READER_VISIBLE_STATES = /* @__PURE__ */ new Set([
	  "opening",
	  "switching",
	  "running",
	  "failed"
	]);
	class ReaderHostTakeoverController {
	  scope;
	  #shell;
	  #workspace;
	  #pageRoot;
	  #routeKind;
	  #destroyed = !1;
	  constructor(options) {
	    this.#shell = options.shell, this.#workspace = options.workspace, this.#pageRoot = options.pageRoot, this.#routeKind = options.routeKind, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#shell.changes.subscribe(() => this.#sync(), this.scope), this.#workspace.changes.subscribe(() => this.#sync(), this.scope), this.scope.add(() => this.#clear()), this.#sync();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #sync() {
	    if (this.#destroyed) return;
	    const visible = READER_VISIBLE_STATES.has(this.#shell.state), embedded = this.#workspace.snapshot.presentation.embedded;
	    this.#pageRoot.classList.toggle("ldp-reader-open", visible), this.#pageRoot.classList.toggle(
	      "ldp-scroll-lock",
	      visible && !embedded
	    ), this.#pageRoot.classList.toggle(
	      "ldp-route-takeover",
	      visible && this.#routeKind === "direct-topic"
	    );
	  }
	  #clear() {
	    this.#pageRoot.classList.remove(
	      "ldp-reader-open",
	      "ldp-scroll-lock",
	      "ldp-route-takeover"
	    );
	  }
	}
	class ReaderHeaderAlignmentController {
	  scope;
	  #shell;
	  #workspace;
	  #elements;
	  #requestFrame;
	  #cancelFrame;
	  #resizeObserver;
	  #mutationObserver;
	  #observedSizes = /* @__PURE__ */ new WeakMap();
	  #content = null;
	  #frame = 0;
	  #titleFrame = 0;
	  #titleDirty = !0;
	  #lastAlignment = null;
	  #destroyed = !1;
	  constructor(options) {
	    this.#shell = options.shell, this.#workspace = options.workspace, this.#elements = options.elements, this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#resizeObserver = options.createResizeObserver?.(
	      (entries) => this.#onResize(entries)
	    ) ?? null, this.#resizeObserver?.observe(this.#elements.modal), this.#resizeObserver?.observe(this.#elements.header), this.#mutationObserver = options.createMutationObserver?.((records) => {
	      records.some(
	        (record) => record.target === this.#shell.view.root || this.#elements.header.contains(record.target)
	      ) && this.#schedule(!0);
	    }) ?? null, this.#mutationObserver?.observe(this.#shell.view.root, {
	      attributes: !0,
	      attributeFilter: ["style"]
	    }), this.#mutationObserver?.observe(this.#elements.header, {
	      attributes: !0,
	      attributeFilter: ["hidden"],
	      childList: !0,
	      characterData: !0,
	      subtree: !0
	    }), this.#shell.changes.subscribe(() => this.#schedule(!0), this.scope), this.#workspace.changes.subscribe(() => this.#schedule(!0), this.scope), this.scope.add(() => {
	      this.#resizeObserver?.disconnect(), this.#mutationObserver?.disconnect(), this.#frame && this.#cancelFrame(this.#frame), this.#titleFrame && this.#cancelFrame(this.#titleFrame), this.#clear();
	    }), this.#schedule(!0);
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #schedule(titleDirty = !0) {
	    titleDirty && (this.#titleDirty = !0), !(this.#destroyed || this.#frame) && (this.#frame = this.#requestFrame(() => {
	      this.#frame = 0, this.#sync();
	    }));
	  }
	  #onResize(entries) {
	    if (!entries.length) {
	      this.#schedule(!1);
	      return;
	    }
	    let sizeChanged = !1;
	    for (const entry of entries) {
	      const borderSize = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize, width = borderSize?.inlineSize ?? entry.contentRect.width, height = borderSize?.blockSize ?? entry.contentRect.height;
	      if (!Number.isFinite(width) || width < 0 || !Number.isFinite(height) || height < 0) continue;
	      const size = entry.target === this.#elements.modal || entry.target === this.#elements.header ? `${Math.round(width * 2)}:${Math.round(height * 2)}` : `${Math.round(width * 2)}`, previous = this.#observedSizes.get(entry.target);
	      this.#observedSizes.set(entry.target, size), previous !== size && (entry.target === this.#elements.header && (this.#titleDirty = !0), sizeChanged = !0);
	    }
	    sizeChanged && this.#schedule(!1);
	  }
	  #sync() {
	    if (this.#destroyed) return;
	    this.#elements.modal.classList.toggle(
	      "ldp-reader-surface-short",
	      this.#elements.modal.clientHeight <= 560
	    );
	    const content = this.#elements.overlay.querySelector(".ldp-topic-runtime") ?? Array.from(
	      this.#elements.overlay.querySelectorAll(".ldp-comments")
	    ).find((candidate) => candidate.getBoundingClientRect().width > 0) ?? null;
	    if (content !== this.#content && (this.#resizeObserver?.disconnect(), this.#resizeObserver?.observe(this.#elements.modal), this.#resizeObserver?.observe(this.#elements.header), content && this.#resizeObserver?.observe(content), this.#content = content, this.#lastAlignment = null, this.#titleDirty = !0), !this.#workspace.snapshot.presentation.fullPage || !content) {
	      this.#clearGeometry(), this.#scheduleTitleActions();
	      return;
	    }
	    const modalRect = this.#elements.modal.getBoundingClientRect(), contentRect = content.getBoundingClientRect();
	    if (!(modalRect.width > 0) || !(contentRect.width > 0)) return;
	    const left = Math.max(0, Math.round(contentRect.left - modalRect.left)), right = Math.max(0, Math.round(modalRect.right - contentRect.right)), alignment = `${left}:${right}`;
	    if (alignment !== this.#lastAlignment) {
	      this.#lastAlignment = alignment, this.#titleDirty = !0;
	      const header = this.#elements.header;
	      header.style.setProperty("--ldp-header-logo-inset", `${left}px`), header.style.paddingLeft = `${left}px`, header.style.paddingRight = `${right}px`, this.#elements.titleActions.style.right = `${right}px`, this.#elements.headButtons.classList.add("is-content-aligned");
	    }
	    this.#scheduleTitleActions();
	  }
	  #scheduleTitleActions() {
	    this.#destroyed || !this.#titleDirty || this.#titleFrame || (this.#titleFrame = this.#requestFrame(() => {
	      this.#titleFrame = 0, !(this.#destroyed || !this.#titleDirty) && (this.#titleDirty = !1, this.#syncTitleActions());
	    }));
	  }
	  #syncTitleActions() {
	    const { header, titleActions } = this.#elements, titleJump = header.querySelector(".ldp-title-jump");
	    if (!titleJump) return;
	    const titleRange = titleActions.ownerDocument.createRange();
	    titleRange.selectNodeContents(titleJump);
	    const titleRects = Array.from(titleRange.getClientRects()).filter(
	      (rect) => rect.width > 0 && rect.height > 0
	    ), firstTop = titleRects[0]?.top, singleLine = firstTop !== void 0 && titleRects.every((rect) => Math.abs(rect.top - firstTop) < 1), singleLineChanged = header.classList.contains("ldp-title-single-line") !== singleLine, view = titleActions.ownerDocument.defaultView, actionIcon = Array.from(titleActions.children).find(
	      (control) => control instanceof HTMLElement && !control.hidden && view?.getComputedStyle(control).display !== "none"
	    )?.querySelector(".ldp-icon");
	    if (!actionIcon) {
	      header.classList.toggle("ldp-title-single-line", singleLine), titleActions.style.setProperty("--ldp-title-actions-align-y", "0px"), singleLineChanged && (this.#titleDirty = !0, this.#scheduleTitleActions());
	      return;
	    }
	    const titleTextRect = titleRects[0], graphicTop = Array.from(actionIcon.querySelectorAll(
	      ":is(path,circle,ellipse,line,polyline,polygon,rect,use)"
	    )).reduce((top, graphic) => {
	      const rect = graphic.getBoundingClientRect();
	      return rect.width > 0 && rect.height > 0 ? Math.min(top, rect.top) : top;
	    }, 1 / 0), titleTop = titleTextRect?.top ?? titleJump.getBoundingClientRect().top, actionTop = Number.isFinite(graphicTop) ? graphicTop : actionIcon.getBoundingClientRect().top, shift = (Number.parseFloat(
	      titleActions.style.getPropertyValue("--ldp-title-actions-align-y")
	    ) || 0) + titleTop - actionTop;
	    header.classList.toggle("ldp-title-single-line", singleLine), titleActions.style.setProperty(
	      "--ldp-title-actions-align-y",
	      `${Math.round(shift * 2) / 2}px`
	    ), singleLineChanged && (this.#titleDirty = !0, this.#scheduleTitleActions());
	  }
	  #clearGeometry() {
	    if (this.#lastAlignment === null) return;
	    this.#lastAlignment = null, this.#titleDirty = !0;
	    const { header, titleActions, headButtons } = this.#elements;
	    header.style.removeProperty("--ldp-header-logo-inset"), header.style.removeProperty("padding-left"), header.style.removeProperty("padding-right"), titleActions.style.removeProperty("right"), headButtons.classList.remove("is-content-aligned");
	  }
	  #clear() {
	    this.#clearGeometry(), this.#elements.modal.classList.remove("ldp-reader-surface-short"), this.#elements.titleActions.style.removeProperty(
	      "--ldp-title-actions-align-y"
	    );
	  }
	}
	class ReaderWorkspaceCoordinator {
	  scope;
	  workspace;
	  window;
	  mutations;
	  #onPersistMode;
	  #onPersistWindow;
	  #destroyed = !1;
	  constructor(options) {
	    const viewport = options.readViewport(), requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id));
	    this.#onPersistMode = options.onPersistMode, this.#onPersistWindow = options.onPersistWindow, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    try {
	      this.workspace = new import_reader_workspace.ReaderWorkspaceModel({
	        routeKind: options.routeKind,
	        requestedMode: options.requestedMode,
	        embedWidth: options.embedWidth,
	        viewportWidth: viewport.width,
	        ...options.active === void 0 ? {} : { active: options.active }
	      }), this.window = new import_reader_workspace.ReaderWindowGeometryModel({
	        preferences: options.windowPreferences,
	        viewportWidth: viewport.width,
	        viewportHeight: viewport.height,
	        mode: this.workspace.snapshot.presentation.mode
	      }), this.workspace.changes.subscribe((snapshot) => {
	        this.window.setMode(snapshot.presentation.mode);
	      }, this.scope), new import_reader_workspace.ReaderWorkspaceDomAdapter({
	        model: this.workspace,
	        pageRoot: options.elements.pageRoot,
	        overlay: options.elements.overlay,
	        parentScope: this.scope
	      }), options.elements.windowCapsule && options.elements.windowPlacementControl && options.elements.windowPlacementStrip && options.elements.windowPlacementOptions && new import_reader_workspace.ReaderWorkspacePlacementController({
	        model: this.workspace,
	        routeKind: options.routeKind,
	        capsule: options.elements.windowCapsule,
	        control: options.elements.windowPlacementControl,
	        strip: options.elements.windowPlacementStrip,
	        options: options.elements.windowPlacementOptions,
	        onSelect: (mode) => this.setMode(mode),
	        parentScope: this.scope
	      }), new import_reader_workspace.ReaderWindowDomAdapter({
	        model: this.window,
	        overlay: options.elements.overlay,
	        modal: options.elements.modal,
	        header: options.elements.header,
	        ...options.elements.windowLockButton ? { lockButton: options.elements.windowLockButton } : {},
	        ...options.elements.windowPinButton ? { pinButton: options.elements.windowPinButton } : {},
	        parentScope: this.scope
	      }), new import_reader_workspace.ReaderWindowPointerController({
	        model: this.window,
	        overlay: options.elements.overlay,
	        modal: options.elements.modal,
	        header: options.elements.header,
	        ...options.elements.windowLockButton ? { lockButton: options.elements.windowLockButton } : {},
	        ...options.elements.windowPinButton ? { pinButton: options.elements.windowPinButton } : {},
	        viewportTarget: options.viewportTarget,
	        readViewport: options.readViewport,
	        ...options.onPersistWindow ? { onPersist: options.onPersistWindow } : {},
	        requestFrame,
	        cancelFrame,
	        parentScope: this.scope
	      }), new import_reader_embed_resize_controller.ReaderEmbedResizeController({
	        model: this.workspace,
	        pageRoot: options.elements.pageRoot,
	        overlay: options.elements.overlay,
	        handle: options.elements.embedResizeHandle,
	        viewportTarget: options.viewportTarget,
	        readViewportWidth: () => options.readViewport().width,
	        ...options.onPersistEmbedWidth ? { onPersist: options.onPersistEmbedWidth } : {},
	        requestFrame,
	        cancelFrame,
	        parentScope: this.scope
	      }), this.mutations = new import_main_outlet_mutation_hub.MainOutletMutationHub({
	        document: options.document,
	        ...options.createMutationObserver ? { createObserver: options.createMutationObserver } : {}
	      }), this.scope.add(() => this.mutations.destroy()), new import_embedded_host_root_controller.EmbeddedHostRootController({
	        model: this.workspace,
	        document: options.document,
	        overlay: options.elements.overlay,
	        mutations: this.mutations,
	        enhancements: options.enhancements,
	        requestFrame,
	        cancelFrame,
	        parentScope: this.scope
	      }), new import_embedded_host_scrollbar.EmbeddedHostScrollbarController({
	        workspace: this.workspace,
	        track: options.elements.hostScrollbar,
	        thumb: options.elements.hostScrollbarThumb,
	        scrollTarget: options.scrollTarget,
	        scroll: options.hostScroll,
	        resizeTargets: Object.freeze([
	          options.elements.pageRoot,
	          ...options.document.body ? [options.document.body] : []
	        ]),
	        ...options.createResizeObserver ? { createResizeObserver: options.createResizeObserver } : {},
	        ...options.readHostScrollbarTrack ? { readTrack: options.readHostScrollbarTrack } : {},
	        requestFrame,
	        cancelFrame,
	        parentScope: this.scope
	      }), new import_embedded_host_top_shortcut.EmbeddedHostTopShortcutController({
	        workspace: this.workspace,
	        button: options.elements.hostTopButton,
	        pointerTarget: options.pointerTarget,
	        scrollTarget: options.scrollTarget,
	        readScrollTop: () => options.hostScroll.read().scrollTop,
	        readViewportHeight: () => options.readViewport().height,
	        scrollToTop: () => options.hostScroll.scrollTo(0),
	        requestFrame,
	        cancelFrame,
	        parentScope: this.scope
	      }), new import_embedded_host_appearance.EmbeddedHostAppearanceController({
	        workspace: this.workspace,
	        pageRoot: options.elements.pageRoot,
	        overlay: options.elements.overlay,
	        readAppearance: options.readAppearance,
	        ...options.appearanceChanges ? { appearanceChanges: options.appearanceChanges } : {},
	        ...options.measureHostRowHeight ? { measureRowHeight: options.measureHostRowHeight } : {},
	        parentScope: this.scope
	      });
	    } catch (error) {
	      throw this.scope.destroy(), error;
	    }
	  }
	  setMode(mode) {
	    const accepted = this.workspace.setRequestedMode(mode);
	    return accepted && this.#onPersistMode?.(mode), accepted;
	  }
	  setEmbedWidth(width) {
	    return this.workspace.setEmbedWidth(width);
	  }
	  setWindowGeometry(width, height, left, top) {
	    const previous = this.window.snapshot;
	    this.window.setGeometry(width, height, left, top) !== previous && this.#persistWindow();
	  }
	  setWindowLocked(locked) {
	    const previous = this.window.snapshot;
	    this.window.setLocked(locked) !== previous && this.#persistWindow();
	  }
	  setWindowPinned(pinned) {
	    const previous = this.window.snapshot;
	    this.window.setPinned(pinned) !== previous && this.#persistWindow();
	  }
	  resetWindow() {
	    const previous = this.window.snapshot;
	    this.window.reset() !== previous && this.#persistWindow();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #persistWindow() {
	    this.#onPersistWindow?.(this.window.preferencePatch());
	  }
	}
	function createReaderShellWorkspaceStage(options) {
	  return (0, import_reader_shell.createReaderShellStage)({
	    name: options.name ?? "reader-shell-workspace",
	    compatibilityKey: options.compatibilityKey,
	    createView: options.createView,
	    onReady(shell, context) {
	      const workspaceOptions = options.createWorkspaceOptions(shell, context), workspace = new ReaderWorkspaceCoordinator({
	        ...workspaceOptions,
	        active: READER_VISIBLE_STATES.has(shell.state),
	        parentScope: shell.scope
	      });
	      shell.changes.subscribe((state) => {
	        workspace.workspace.setActive(READER_VISIBLE_STATES.has(state));
	      }, workspace.scope);
	      const takeover = new ReaderHostTakeoverController({
	        shell,
	        workspace: workspace.workspace,
	        pageRoot: workspaceOptions.elements.pageRoot,
	        routeKind: workspaceOptions.routeKind,
	        parentScope: shell.scope
	      }), headerAlignment = new ReaderHeaderAlignmentController({
	        shell,
	        workspace: workspace.workspace,
	        elements: workspaceOptions.elements,
	        ...workspaceOptions.createMutationObserver ? {
	          createMutationObserver: workspaceOptions.createMutationObserver
	        } : {},
	        ...workspaceOptions.createResizeObserver ? {
	          createResizeObserver: workspaceOptions.createResizeObserver
	        } : {},
	        ...workspaceOptions.requestFrame ? { requestFrame: workspaceOptions.requestFrame } : {},
	        ...workspaceOptions.cancelFrame ? { cancelFrame: workspaceOptions.cancelFrame } : {},
	        parentScope: shell.scope
	      });
	      let readyCleanup;
	      try {
	        readyCleanup = options.onReady?.(shell, workspace, context) || void 0;
	      } catch (error) {
	        throw headerAlignment.destroy(), takeover.destroy(), workspace.destroy(), error;
	      }
	      return () => {
	        try {
	          readyCleanup?.();
	        } finally {
	          try {
	            headerAlignment.destroy();
	          } finally {
	            try {
	              takeover.destroy();
	            } finally {
	              workspace.destroy();
	            }
	          }
	        }
	      };
	    }
	  });
	}
}, "222333071d8542f27f46257f47efac93e24938eaacf9653f5d372881e18aae0e");

/* Source: lite/src/shell/reader-workspace.ts */
runtime.register("src/shell/reader-workspace.js", function(module, exports, require) {
	var reader_workspace_exports = {};
	__export(reader_workspace_exports, {
	  READER_COMPACT_MAX_WIDTH: () => READER_COMPACT_MAX_WIDTH,
	  READER_EMBED_MIN_WIDTH: () => READER_EMBED_MIN_WIDTH,
	  READER_HOST_MIN_WIDTH: () => READER_HOST_MIN_WIDTH,
	  READER_WINDOW_MARGIN: () => READER_WINDOW_MARGIN,
	  READER_WINDOW_MIN_HEIGHT: () => READER_WINDOW_MIN_HEIGHT,
	  READER_WINDOW_MIN_WIDTH: () => READER_WINDOW_MIN_WIDTH,
	  ReaderWindowDomAdapter: () => ReaderWindowDomAdapter,
	  ReaderWindowGeometryModel: () => ReaderWindowGeometryModel,
	  ReaderWindowPointerController: () => ReaderWindowPointerController,
	  ReaderWorkspaceDomAdapter: () => ReaderWorkspaceDomAdapter,
	  ReaderWorkspaceModel: () => ReaderWorkspaceModel,
	  ReaderWorkspacePlacementController: () => ReaderWorkspacePlacementController,
	  readerWorkspacePositionMode: () => readerWorkspacePositionMode
	});
	module.exports = __toCommonJS(reader_workspace_exports);
	var import_event_target = require("../dom/event-target.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function readerWorkspacePositionMode(mode) {
	  return mode === "fullpage" ? "fullpage" : mode === "embed-left" || mode === "embed-right" ? "embedded" : "floating";
	}
	const READER_EMBED_MIN_WIDTH = 360, READER_HOST_MIN_WIDTH = 680, READER_WINDOW_MARGIN = 8, READER_WINDOW_MIN_WIDTH = 360, READER_WINDOW_MIN_HEIGHT = 320, READER_COMPACT_MAX_WIDTH = 700, WINDOW_DEFAULT_WIDTH = 1080, WINDOW_DEFAULT_VIEWPORT_WIDTH = 0.94, WINDOW_DEFAULT_VIEWPORT_HEIGHT = 0.86, WINDOW_HANDLE_DOCK_THRESHOLD_PX = 2, DEFAULT_WINDOW_GEOMETRY_POLICY = Object.freeze({
	  margin: READER_WINDOW_MARGIN,
	  minWidth: READER_WINDOW_MIN_WIDTH,
	  minHeight: READER_WINDOW_MIN_HEIGHT,
	  compactWidth: READER_COMPACT_MAX_WIDTH,
	  defaultWidth: WINDOW_DEFAULT_WIDTH,
	  defaultHeight: null,
	  defaultViewportWidth: WINDOW_DEFAULT_VIEWPORT_WIDTH,
	  defaultViewportHeight: WINDOW_DEFAULT_VIEWPORT_HEIGHT
	});
	function projectReaderWindowPlacement(overlay, geometry) {
	  if (overlay.classList.toggle(
	    "ldp-window-handle-docked",
	    !!(geometry && geometry.top <= WINDOW_HANDLE_DOCK_THRESHOLD_PX)
	  ), !geometry) {
	    overlay.style.removeProperty("--ldp-reader-window-center-x"), overlay.style.removeProperty("--ldp-reader-window-top");
	    return;
	  }
	  overlay.style.setProperty(
	    "--ldp-reader-window-center-x",
	    `${geometry.left + geometry.width / 2}px`
	  ), overlay.style.setProperty("--ldp-reader-window-top", `${geometry.top}px`);
	}
	const PRESENTATIONS = Object.freeze({
	  floating: Object.freeze({
	    mode: "floating",
	    floating: !0,
	    fullPage: !1,
	    embedded: !1,
	    side: ""
	  }),
	  fullpage: Object.freeze({
	    mode: "fullpage",
	    floating: !1,
	    fullPage: !0,
	    embedded: !1,
	    side: ""
	  }),
	  "embed-left": Object.freeze({
	    mode: "embed-left",
	    floating: !1,
	    fullPage: !1,
	    embedded: !0,
	    side: "left"
	  }),
	  "embed-right": Object.freeze({
	    mode: "embed-right",
	    floating: !1,
	    fullPage: !1,
	    embedded: !0,
	    side: "right"
	  })
	}), LIST_MODES = /* @__PURE__ */ new Set([
	  "floating",
	  "fullpage",
	  "embed-left",
	  "embed-right"
	]), DIRECT_TOPIC_MODES = /* @__PURE__ */ new Set(["floating", "fullpage"]);
	function finiteViewport(value, name) {
	  if (!Number.isFinite(value) || value < 1)
	    throw new RangeError(`${name} 必须是正有限数`);
	  return value;
	}
	function finiteNumber(value, fallback) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? numeric : fallback;
	}
	function windowGeometryPolicy(value) {
	  const source = {
	    ...DEFAULT_WINDOW_GEOMETRY_POLICY,
	    ...value
	  }, positive = (candidate, name) => {
	    const numeric = Number(candidate);
	    if (!Number.isFinite(numeric) || numeric <= 0)
	      throw new RangeError(`${name} 必须是正有限数`);
	    return numeric;
	  }, compactWidth = Number(source.compactWidth);
	  if (!Number.isFinite(compactWidth) || compactWidth < 0)
	    throw new RangeError("compactWidth 必须是非负有限数");
	  const defaultHeight = source.defaultHeight === null ? null : positive(source.defaultHeight, "defaultHeight");
	  return Object.freeze({
	    margin: positive(source.margin, "margin"),
	    minWidth: positive(source.minWidth, "minWidth"),
	    minHeight: positive(source.minHeight, "minHeight"),
	    compactWidth,
	    defaultWidth: positive(source.defaultWidth, "defaultWidth"),
	    defaultHeight,
	    defaultViewportWidth: positive(
	      source.defaultViewportWidth,
	      "defaultViewportWidth"
	    ),
	    defaultViewportHeight: positive(
	      source.defaultViewportHeight,
	      "defaultViewportHeight"
	    )
	  });
	}
	function workspacePresentation(mode) {
	  return PRESENTATIONS[mode];
	}
	function workspaceModeAllowed(mode, routeKind) {
	  return (routeKind === "direct-topic" ? DIRECT_TOPIC_MODES : LIST_MODES).has(mode);
	}
	function dispatchCompatibilityEvent(target, name) {
	  const event = target.ownerDocument.createEvent("Event");
	  event.initEvent(name, !1, !1), target.dispatchEvent(event);
	}
	class ReaderWorkspaceModel {
	  changes = new import_signal.Signal();
	  #routeKind;
	  #requestedMode;
	  #viewportWidth;
	  #requestedEmbedWidth;
	  #active;
	  #snapshot;
	  constructor(options) {
	    this.#routeKind = options.routeKind, this.#viewportWidth = finiteViewport(options.viewportWidth, "viewportWidth"), this.#active = options.active !== !1, this.#requestedMode = workspaceModeAllowed(options.requestedMode, this.#routeKind) ? options.requestedMode : this.#fallbackMode(), this.#requestedEmbedWidth = Math.max(
	      READER_EMBED_MIN_WIDTH,
	      Math.round(finiteNumber(options.embedWidth, READER_EMBED_MIN_WIDTH))
	    ), this.#snapshot = this.#derive();
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  setRequestedMode(mode) {
	    return workspaceModeAllowed(mode, this.#routeKind) ? (this.#requestedMode = mode, this.#commit(), !0) : !1;
	  }
	  /**
	   * Shell 关闭时只撤销 presentation,requestedMode 仍作为下次打开的偏好。
	   */
	  setActive(active) {
	    return this.#active = active, this.#commit(), this.#snapshot;
	  }
	  setEmbedWidth(width) {
	    return this.#requestedEmbedWidth = this.#clampEmbedWidth(width), this.#commit(), this.#snapshot.embedWidth;
	  }
	  resizeViewport(width) {
	    return this.#viewportWidth = finiteViewport(width, "viewportWidth"), this.#commit(), this.#snapshot;
	  }
	  #fallbackMode() {
	    return this.#routeKind === "direct-topic" ? "fullpage" : "floating";
	  }
	  #canEmbed() {
	    return this.#viewportWidth >= READER_EMBED_MIN_WIDTH + READER_HOST_MIN_WIDTH;
	  }
	  #maximumEmbedWidth() {
	    return Math.max(
	      READER_EMBED_MIN_WIDTH,
	      Math.floor(this.#viewportWidth - READER_HOST_MIN_WIDTH)
	    );
	  }
	  #clampEmbedWidth(value) {
	    return Math.min(
	      this.#maximumEmbedWidth(),
	      Math.max(
	        READER_EMBED_MIN_WIDTH,
	        Math.round(finiteNumber(value, READER_EMBED_MIN_WIDTH))
	      )
	    );
	  }
	  #derive() {
	    const requested = workspaceModeAllowed(this.#requestedMode, this.#routeKind) ? this.#requestedMode : this.#fallbackMode(), effective = this.#active ? workspacePresentation(requested).embedded && !this.#canEmbed() ? "floating" : requested : this.#fallbackMode();
	    return Object.freeze({
	      requestedMode: requested,
	      presentation: workspacePresentation(effective),
	      viewportWidth: this.#viewportWidth,
	      canEmbed: this.#canEmbed(),
	      embedWidth: this.#clampEmbedWidth(this.#requestedEmbedWidth)
	    });
	  }
	  #commit() {
	    const next = this.#derive();
	    next.requestedMode === this.#snapshot.requestedMode && next.presentation.mode === this.#snapshot.presentation.mode && next.viewportWidth === this.#snapshot.viewportWidth && next.embedWidth === this.#snapshot.embedWidth || (this.#snapshot = next, this.changes.emit(next));
	  }
	}
	class ReaderWorkspaceDomAdapter {
	  scope;
	  #model;
	  #pageRoot;
	  #overlay;
	  #dispatchEvents;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#pageRoot = options.pageRoot, this.#overlay = options.overlay, this.#dispatchEvents = options.dispatchEvents !== !1, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#model.changes.subscribe((snapshot) => this.#apply(snapshot, !0), this.scope), this.scope.add(() => this.#clear()), this.#apply(this.#model.snapshot, !1);
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #apply(snapshot, changed) {
	    if (this.#destroyed) return;
	    const presentation = snapshot.presentation;
	    if (this.#overlay.dataset.readerWorkspaceMode = presentation.mode, this.#overlay.classList.toggle("ldp-fullpage", presentation.fullPage), this.#overlay.classList.toggle("ldp-reader-embedded", presentation.embedded), this.#overlay.classList.toggle(
	      "ldp-reader-embedded-left",
	      presentation.side === "left"
	    ), this.#overlay.classList.toggle(
	      "ldp-reader-embedded-right",
	      presentation.side === "right"
	    ), this.#pageRoot.classList.toggle("ldp-reader-workspace", presentation.embedded), this.#pageRoot.classList.toggle(
	      "ldp-reader-embedded-left",
	      presentation.side === "left"
	    ), this.#pageRoot.classList.toggle(
	      "ldp-reader-embedded-right",
	      presentation.side === "right"
	    ), presentation.embedded) {
	      const width = `${snapshot.embedWidth}px`;
	      this.#overlay.dataset.readerWorkspaceWidth = String(snapshot.embedWidth), this.#overlay.style.setProperty("--ldp-reader-workspace-width", width), this.#pageRoot.style.setProperty("--ldp-reader-workspace-width", width);
	    } else {
	      delete this.#overlay.dataset.readerWorkspaceWidth;
	      for (const target of [this.#overlay, this.#pageRoot])
	        target.style.removeProperty("--ldp-reader-workspace-width");
	    }
	    changed && this.#dispatchEvents && dispatchCompatibilityEvent(this.#overlay, "ldp-reader-workspace-change");
	  }
	  #clear() {
	    for (const className of [
	      "ldp-fullpage",
	      "ldp-reader-embedded",
	      "ldp-reader-embedded-left",
	      "ldp-reader-embedded-right"
	    ])
	      this.#overlay.classList.remove(className);
	    this.#pageRoot.classList.remove(
	      "ldp-reader-workspace",
	      "ldp-reader-embedded-left",
	      "ldp-reader-embedded-right"
	    ), delete this.#overlay.dataset.readerWorkspaceMode, delete this.#overlay.dataset.readerWorkspaceWidth;
	    for (const target of [this.#overlay, this.#pageRoot])
	      target.style.removeProperty("--ldp-reader-workspace-width");
	  }
	}
	class ReaderWorkspacePlacementController {
	  scope;
	  #model;
	  #capsule;
	  #control;
	  #strip;
	  #options;
	  #allowedModes;
	  #onSelect;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#capsule = options.capsule, this.#control = options.control, this.#strip = options.strip, this.#options = options.options, this.#allowedModes = options.routeKind === "direct-topic" ? DIRECT_TOPIC_MODES : LIST_MODES, this.#onSelect = options.onSelect, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    for (const option of this.#options)
	      this.scope.listen(option, "click", (event) => {
	        event.preventDefault(), event.stopPropagation();
	        const mode = option.dataset.readerPlacement;
	        !mode || !this.#allowedModes.has(mode) || this.#onSelect(mode);
	      });
	    this.#model.changes.subscribe(() => this.#sync(), this.scope), this.scope.add(() => this.#clear()), this.#sync();
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #sync() {
	    if (this.#destroyed) return;
	    const available = this.#allowedModes.size > 1;
	    this.#control.hidden = !available, this.#strip.hidden = !available, this.#capsule.classList.toggle("ldp-reader-placement-available", available);
	    const activeMode = this.#model.snapshot.presentation.mode;
	    for (const option of this.#options) {
	      const mode = option.dataset.readerPlacement, allowed = !!(mode && this.#allowedModes.has(mode)), active = allowed && mode === activeMode;
	      option.hidden = !allowed, option.disabled = !allowed, option.classList.toggle("active", active), option.setAttribute("aria-pressed", String(active));
	    }
	  }
	  #clear() {
	    this.#capsule.classList.remove("ldp-reader-placement-available"), this.#control.hidden = !0, this.#strip.hidden = !0;
	    for (const option of this.#options)
	      option.classList.remove("active"), option.removeAttribute("aria-pressed");
	  }
	}
	class ReaderWindowGeometryModel {
	  changes = new import_signal.Signal();
	  #policy;
	  #viewportWidth;
	  #viewportHeight;
	  #presentation;
	  #geometry;
	  #geometryPersisted;
	  #locked;
	  #pinned;
	  #snapshot;
	  constructor(options) {
	    this.#policy = windowGeometryPolicy(options.policy), this.#viewportWidth = finiteViewport(options.viewportWidth, "viewportWidth"), this.#viewportHeight = finiteViewport(options.viewportHeight, "viewportHeight"), this.#presentation = workspacePresentation(options.mode), this.#locked = options.preferences.readerWindowLocked, this.#pinned = options.preferences.readerWindowPinned, this.#geometryPersisted = !!(options.preferences.readerWindowWidth || options.preferences.readerWindowHeight || options.preferences.readerWindowX || options.preferences.readerWindowY), this.#geometry = this.#preferredGeometry(options.preferences), this.#snapshot = this.#derive();
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  setMode(mode) {
	    return this.#presentation = workspacePresentation(mode), this.#commit(), this.#snapshot;
	  }
	  resizeViewport(width, height) {
	    return this.#viewportWidth = finiteViewport(width, "viewportWidth"), this.#viewportHeight = finiteViewport(height, "viewportHeight"), this.#geometryPersisted || (this.#geometry = this.#centeredGeometry()), this.#commit(), this.#snapshot;
	  }
	  setGeometry(width, height, left, top) {
	    if (this.#viewportWidth <= this.#policy.compactWidth) return this.#snapshot;
	    const current = this.#geometry;
	    return this.#geometry = this.#clampGeometry({
	      width: finiteNumber(width, current.width),
	      height: finiteNumber(height, current.height),
	      left: finiteNumber(left, current.left),
	      top: finiteNumber(top, current.top)
	    }), this.#geometryPersisted = !0, this.#commit(), this.#snapshot;
	  }
	  setLocked(locked) {
	    return this.#viewportWidth <= this.#policy.compactWidth ? this.#snapshot : (this.#locked = !!locked, this.#commit(), this.#snapshot);
	  }
	  setPinned(pinned) {
	    return this.#viewportWidth <= this.#policy.compactWidth ? this.#snapshot : (this.#pinned = !!pinned, this.#commit(), this.#snapshot);
	  }
	  reset() {
	    return this.#viewportWidth <= this.#policy.compactWidth ? this.#snapshot : (this.#locked = !1, this.#pinned = !1, this.#geometryPersisted = !1, this.#geometry = this.#centeredGeometry(), this.#commit(), this.#snapshot);
	  }
	  preferencePatch() {
	    return Object.freeze({
	      readerWindowWidth: this.#geometryPersisted ? Math.round(this.#geometry.width) : 0,
	      readerWindowHeight: this.#geometryPersisted ? Math.round(this.#geometry.height) : 0,
	      readerWindowX: this.#geometryPersisted ? Math.round(this.#geometry.left) : 0,
	      readerWindowY: this.#geometryPersisted ? Math.round(this.#geometry.top) : 0,
	      readerWindowLocked: this.#locked,
	      readerWindowPinned: this.#pinned
	    });
	  }
	  previewGeometry(width, height, left, top) {
	    return this.#clampGeometry({ width, height, left, top });
	  }
	  previewResize(start, direction, deltaX, deltaY) {
	    let left = start.left, top = start.top, right = start.left + start.width, bottom = start.top + start.height;
	    const bounds = this.#viewportBounds(), minWidth = Math.min(this.#policy.minWidth, bounds.maxWidth), minHeight = Math.min(this.#policy.minHeight, bounds.maxHeight);
	    return direction.includes("w") && (left = Math.min(
	      right - minWidth,
	      Math.max(this.#policy.margin, start.left + deltaX)
	    )), direction.includes("e") && (right = Math.max(
	      left + minWidth,
	      Math.min(
	        this.#viewportWidth - this.#policy.margin,
	        right + deltaX
	      )
	    )), direction.includes("n") && (top = Math.min(
	      bottom - minHeight,
	      Math.max(this.#policy.margin, start.top + deltaY)
	    )), direction.includes("s") && (bottom = Math.max(
	      top + minHeight,
	      Math.min(
	        this.#viewportHeight - this.#policy.margin,
	        bottom + deltaY
	      )
	    )), this.#clampGeometry({
	      left,
	      top,
	      width: right - left,
	      height: bottom - top
	    });
	  }
	  #managed() {
	    return this.#presentation.floating && this.#viewportWidth > this.#policy.compactWidth;
	  }
	  #viewportBounds() {
	    return {
	      maxWidth: Math.max(
	        1,
	        this.#viewportWidth - this.#policy.margin * 2
	      ),
	      maxHeight: Math.max(
	        1,
	        this.#viewportHeight - this.#policy.margin * 2
	      )
	    };
	  }
	  #clampSize(width, height) {
	    const bounds = this.#viewportBounds();
	    return {
	      width: Math.min(
	        bounds.maxWidth,
	        Math.max(
	          Math.min(this.#policy.minWidth, bounds.maxWidth),
	          Math.round(width)
	        )
	      ),
	      height: Math.min(
	        bounds.maxHeight,
	        Math.max(
	          Math.min(this.#policy.minHeight, bounds.maxHeight),
	          Math.round(height)
	        )
	      )
	    };
	  }
	  #centeredGeometry() {
	    const size = this.#clampSize(
	      Math.min(
	        this.#policy.defaultWidth,
	        this.#viewportWidth * this.#policy.defaultViewportWidth
	      ),
	      Math.min(
	        this.#policy.defaultHeight ?? Number.POSITIVE_INFINITY,
	        this.#viewportHeight * this.#policy.defaultViewportHeight
	      )
	    );
	    return this.#clampGeometry({
	      ...size,
	      left: Math.round((this.#viewportWidth - size.width) / 2),
	      top: Math.round((this.#viewportHeight - size.height) / 2)
	    });
	  }
	  #preferredGeometry(preferences) {
	    const fallback = this.#centeredGeometry(), size = this.#clampSize(
	      preferences.readerWindowWidth || fallback.width,
	      preferences.readerWindowHeight || fallback.height
	    );
	    return this.#clampGeometry({
	      ...size,
	      left: preferences.readerWindowX || Math.round(
	        (this.#viewportWidth - size.width) / 2
	      ),
	      top: preferences.readerWindowY || Math.round(
	        (this.#viewportHeight - size.height) / 2
	      )
	    });
	  }
	  #clampGeometry(value) {
	    const size = this.#clampSize(value.width, value.height);
	    return Object.freeze({
	      ...size,
	      left: Math.min(
	        this.#viewportWidth - this.#policy.margin - size.width,
	        Math.max(this.#policy.margin, Math.round(value.left))
	      ),
	      top: Math.min(
	        this.#viewportHeight - this.#policy.margin - size.height,
	        Math.max(this.#policy.margin, Math.round(value.top))
	      )
	    });
	  }
	  #derive() {
	    return Object.freeze({
	      geometry: this.#clampGeometry(this.#geometry),
	      viewportWidth: this.#viewportWidth,
	      viewportHeight: this.#viewportHeight,
	      presentation: this.#presentation,
	      managed: this.#managed(),
	      locked: this.#locked,
	      pinned: this.#pinned,
	      isDefault: !this.#geometryPersisted && !this.#locked && !this.#pinned
	    });
	  }
	  #commit() {
	    const next = this.#derive(), previous = this.#snapshot;
	    next.geometry.left === previous.geometry.left && next.geometry.top === previous.geometry.top && next.geometry.width === previous.geometry.width && next.geometry.height === previous.geometry.height && next.viewportWidth === previous.viewportWidth && next.viewportHeight === previous.viewportHeight && next.presentation.mode === previous.presentation.mode && next.locked === previous.locked && next.pinned === previous.pinned && next.isDefault === previous.isDefault || (this.#snapshot = next, this.changes.emit(next));
	  }
	}
	class ReaderWindowDomAdapter {
	  scope;
	  #model;
	  #overlay;
	  #modal;
	  #header;
	  #lockButton;
	  #pinButton;
	  #dispatchEvents;
	  #previousLocked = null;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#overlay = options.overlay, this.#modal = options.modal, this.#header = options.header, this.#lockButton = options.lockButton, this.#pinButton = options.pinButton, this.#dispatchEvents = options.dispatchEvents !== !1, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#lockButton && this.scope.listen(this.#lockButton, "animationend", () => {
	      this.#lockButton?.classList.remove("ldp-lock-state-changing");
	    }), this.#model.changes.subscribe((snapshot) => this.#apply(snapshot, !0), this.scope), this.scope.add(() => this.#clear()), this.#apply(this.#model.snapshot, !1);
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #apply(snapshot, changed) {
	    if (!this.#destroyed) {
	      if (this.#overlay.classList.toggle("ldp-window-locked", snapshot.locked), this.#overlay.classList.toggle("ldp-window-pinned", snapshot.pinned), this.#overlay.classList.toggle("ldp-window-managed", snapshot.managed), this.#header?.toggleAttribute(
	        "data-ldp-reader-drag-surface",
	        snapshot.managed && !snapshot.locked
	      ), this.#header && (snapshot.managed && !snapshot.locked ? this.#header.dataset.ldpTooltipLabel = "按住空白处可拖动" : delete this.#header.dataset.ldpTooltipLabel), this.#syncWindowButtons(snapshot), snapshot.managed) {
	        const geometry = snapshot.geometry;
	        this.#modal.style.setProperty("left", `${geometry.left}px`), this.#modal.style.setProperty("top", `${geometry.top}px`), this.#modal.style.setProperty("width", `${geometry.width}px`), this.#modal.style.setProperty("height", `${geometry.height}px`), projectReaderWindowPlacement(this.#overlay, geometry);
	      } else
	        this.#clearGeometry();
	      changed && this.#dispatchEvents && dispatchCompatibilityEvent(this.#overlay, "ldp-reader-window-change");
	    }
	  }
	  #syncWindowButtons(snapshot) {
	    if (this.#lockButton) {
	      const label = snapshot.locked ? "解锁浮窗" : "锁定浮窗";
	      this.#lockButton.classList.toggle("active", snapshot.locked), this.#lockButton.setAttribute("aria-pressed", String(snapshot.locked)), this.#lockButton.setAttribute("aria-label", label), this.#lockButton.title = label, this.#lockButton.dataset.locked = String(snapshot.locked);
	      for (const icon of this.#lockButton.querySelectorAll(
	        "[data-reader-lock-icon]"
	      ))
	        icon.hidden = icon.dataset.readerLockIcon !== (snapshot.locked ? "locked" : "unlocked");
	      this.#previousLocked !== null && this.#previousLocked !== snapshot.locked && (this.#lockButton.classList.remove("ldp-lock-state-changing"), this.#lockButton.offsetWidth, this.#lockButton.classList.add("ldp-lock-state-changing"));
	    }
	    if (this.#previousLocked = snapshot.locked, this.#pinButton) {
	      const label = snapshot.pinned ? "恢复点击外部关闭" : "点击外部时保持显示";
	      this.#pinButton.classList.toggle("active", snapshot.pinned), this.#pinButton.setAttribute("aria-pressed", String(snapshot.pinned)), this.#pinButton.setAttribute("aria-label", label), this.#pinButton.title = label;
	    }
	  }
	  #clearGeometry() {
	    projectReaderWindowPlacement(this.#overlay, null);
	    for (const property of ["left", "top", "width", "height"])
	      this.#modal.style.removeProperty(property);
	  }
	  #clear() {
	    this.#overlay.classList.remove(
	      "ldp-window-locked",
	      "ldp-window-pinned",
	      "ldp-window-managed"
	    ), this.#header?.removeAttribute("data-ldp-reader-drag-surface"), this.#header && delete this.#header.dataset.ldpTooltipLabel, this.#lockButton?.classList.remove("active", "ldp-lock-state-changing"), this.#pinButton?.classList.remove("active"), this.#clearGeometry();
	  }
	}
	const WINDOW_DRAG_BLOCKED_SELECTOR = [
	  "a",
	  "button",
	  "input",
	  "select",
	  "textarea",
	  "label",
	  "summary",
	  '[role="button"]',
	  '[contenteditable="true"]',
	  ".ldp-title-jump",
	  ".ldp-meta-row",
	  ".ldp-title-topic-row",
	  ".ldp-notifications-popover",
	  ".ldp-history-popover",
	  ".ldp-bookmarks-popover",
	  ".ldp-settings-popover",
	  ".ldp-topic-edit-layer"
	].join(",");
	class ReaderWindowPointerController {
	  scope;
	  #model;
	  #overlay;
	  #modal;
	  #header;
	  #onPersist;
	  #requestFrame;
	  #cancelFrame;
	  #dragSurfaceSelector;
	  #blockedSelector;
	  #interactingClassName;
	  #restingTransform;
	  #projectPlacement;
	  #interaction = null;
	  #frame = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#model = options.model, this.#overlay = options.overlay, this.#modal = options.modal, this.#header = options.header, this.#onPersist = options.onPersist, this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.#dragSurfaceSelector = options.dragSurfaceSelector ?? ".ldp-header[data-ldp-reader-drag-surface]", this.#blockedSelector = options.blockedSelector ?? WINDOW_DRAG_BLOCKED_SELECTOR, this.#interactingClassName = options.interactingClassName ?? "ldp-window-interacting", this.#restingTransform = options.restingTransform, this.#projectPlacement = options.projectPlacement ?? projectReaderWindowPlacement, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.listen(this.#overlay, "pointerdown", (event) => this.#onPointerDown(
	      event
	    )), this.scope.listen(this.#overlay, "pointermove", (event) => this.#onPointerMove(
	      event
	    ));
	    for (const type of ["pointerup", "pointercancel"])
	      this.scope.listen(this.#overlay, type, (event) => this.#onPointerEnd(
	        event
	      ));
	    options.lockButton && this.scope.listen(options.lockButton, "click", (event) => {
	      event.preventDefault(), event.stopPropagation(), this.#stopInteraction(), this.#model.setLocked(!this.#model.snapshot.locked), this.#persist();
	    }), options.pinButton && this.scope.listen(options.pinButton, "click", (event) => {
	      event.preventDefault(), event.stopPropagation(), this.#model.setPinned(!this.#model.snapshot.pinned), this.#persist();
	    }), options.viewportTarget && options.readViewport && this.scope.listen(options.viewportTarget, "resize", () => {
	      this.#stopInteraction();
	      const viewport = options.readViewport();
	      this.#model.resizeViewport(viewport.width, viewport.height);
	    }), this.scope.add(() => this.#stopInteraction());
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #onPointerDown(event) {
	    if (this.#destroyed || event.button !== 0 || !this.#model.snapshot.managed || this.#model.snapshot.locked)
	      return;
	    const target = (0, import_event_target.eventElement)(event);
	    if (!target) return;
	    const resizeHandle = target.closest("[data-reader-resize]");
	    let mode = null, handle = null;
	    if (resizeHandle && this.#overlay.contains(resizeHandle)) {
	      const direction = resizeHandle.dataset.readerResize;
	      (direction === "n" || direction === "s" || direction === "e" || direction === "w" || direction === "nw" || direction === "ne" || direction === "sw" || direction === "se") && (mode = direction, handle = resizeHandle);
	    } else {
	      const dragSurface = target.closest(
	        this.#dragSurfaceSelector
	      );
	      dragSurface === this.#header && !target.closest(this.#blockedSelector) && (mode = "drag", handle = dragSurface);
	    }
	    if (!mode || !handle) return;
	    event.preventDefault(), event.stopPropagation();
	    const start = this.#model.snapshot.geometry;
	    this.#interaction = {
	      pointerId: event.pointerId,
	      target: handle,
	      mode,
	      startX: event.clientX,
	      startY: event.clientY,
	      x: event.clientX,
	      y: event.clientY,
	      start,
	      preview: null
	    }, this.#overlay.classList.add(this.#interactingClassName);
	    const pointerTarget = handle;
	    try {
	      pointerTarget.setPointerCapture?.(event.pointerId);
	    } catch {
	    }
	  }
	  #onPointerMove(event) {
	    !this.#interaction || event.pointerId !== this.#interaction.pointerId || (this.#interaction.x = event.clientX, this.#interaction.y = event.clientY, this.#frame || (this.#frame = this.#requestFrame(() => {
	      this.#frame = 0, this.#renderInteraction();
	    })));
	  }
	  #onPointerEnd(event) {
	    if (!this.#interaction || event.pointerId !== this.#interaction.pointerId) return;
	    this.#interaction.x = event.clientX, this.#interaction.y = event.clientY, this.#frame && (this.#cancelFrame(this.#frame), this.#frame = 0), this.#renderInteraction();
	    const interaction = this.#interaction;
	    if (interaction?.mode === "drag" && interaction.preview) {
	      const preview = interaction.preview;
	      this.#model.setGeometry(
	        preview.width,
	        preview.height,
	        preview.left,
	        preview.top
	      );
	    }
	    this.#stopInteraction(), this.#persist();
	  }
	  #renderInteraction() {
	    const interaction = this.#interaction;
	    if (!interaction || !this.#model.snapshot.managed) return;
	    const deltaX = interaction.x - interaction.startX, deltaY = interaction.y - interaction.startY;
	    if (interaction.mode === "drag") {
	      const preview = this.#model.previewGeometry(
	        interaction.start.width,
	        interaction.start.height,
	        interaction.start.left + deltaX,
	        interaction.start.top + deltaY
	      );
	      interaction.preview = preview, this.#modal.style.transform = `translate3d(${preview.left - interaction.start.left}px,${preview.top - interaction.start.top}px,0)`, this.#projectPlacement(this.#overlay, preview);
	      return;
	    }
	    const raw = this.#model.previewResize(
	      interaction.start,
	      interaction.mode,
	      deltaX,
	      deltaY
	    );
	    this.#model.setGeometry(raw.width, raw.height, raw.left, raw.top);
	  }
	  #stopInteraction() {
	    this.#frame && this.#cancelFrame(this.#frame), this.#frame = 0;
	    const interaction = this.#interaction;
	    if (this.#interaction = null, this.#overlay.classList.remove(this.#interactingClassName), this.#restingTransform === void 0 ? this.#modal.style.removeProperty("transform") : this.#modal.style.setProperty("transform", this.#restingTransform), !interaction) return;
	    const snapshot = this.#model.snapshot;
	    this.#projectPlacement(
	      this.#overlay,
	      snapshot.managed ? snapshot.geometry : null
	    );
	    const pointerTarget = interaction.target;
	    try {
	      pointerTarget.hasPointerCapture?.(interaction.pointerId) && pointerTarget.releasePointerCapture?.(interaction.pointerId);
	    } catch {
	    }
	  }
	  #persist() {
	    this.#onPersist?.(this.#model.preferencePatch());
	  }
	}
}, "a917af8306e469bd60d9eca1ff0eeefd329c34961904fce6270a4b138a8290e8");

/* Source: lite/src/state/preferences-config-codec.ts */
runtime.register("src/state/preferences-config-codec.js", function(module, exports, require) {
	var preferences_config_codec_exports = {};
	__export(preferences_config_codec_exports, {
	  PreferencesConfigCodec: () => PreferencesConfigCodec
	});
	module.exports = __toCommonJS(preferences_config_codec_exports);
	function nonEmpty(value, name) {
	  const normalized = String(value ?? "").trim();
	  if (!normalized) throw new Error(`${name} 不能为空`);
	  return normalized;
	}
	function plainRecord(value, name) {
	  if (!value || typeof value != "object" || Array.isArray(value))
	    throw new Error(`${name} 必须是对象`);
	  return value;
	}
	class PreferencesConfigCodec {
	  #format;
	  #schemaVersion;
	  #scriptVersion;
	  #defaults;
	  #normalize;
	  #legacyRules;
	  #settingKeys;
	  #settingKeySet;
	  #now;
	  constructor(options) {
	    if (this.#format = nonEmpty(options.format, "config format"), !Number.isSafeInteger(options.schemaVersion) || options.schemaVersion < 1)
	      throw new RangeError("config schemaVersion 必须是正安全整数");
	    if (this.#schemaVersion = options.schemaVersion, this.#scriptVersion = nonEmpty(options.scriptVersion, "scriptVersion"), this.#defaults = options.defaults, this.#normalize = options.normalize, this.#settingKeys = Object.freeze(Object.keys(options.defaults)), !this.#settingKeys.length) throw new Error("preferences defaults 不能为空");
	    this.#settingKeySet = new Set(this.#settingKeys), this.#legacyRules = Object.freeze((options.legacyImportRules ?? []).map((rule) => {
	      const missingDefaults = plainRecord(rule.missingDefaults, "legacy missingDefaults"), keys = Object.keys(missingDefaults);
	      if (!keys.length || keys.some((key) => !this.#settingKeySet.has(key)))
	        throw new Error("legacy missingDefaults 必须只包含已知偏好字段");
	      return Object.freeze({ missingDefaults: Object.freeze({ ...missingDefaults }) });
	    })), this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
	  }
	  get settingKeys() {
	    return this.#settingKeys;
	  }
	  export(value) {
	    const normalized = this.#normalize({
	      ...this.#defaults,
	      ...value
	    }), settings = Object.freeze(Object.fromEntries(
	      this.#settingKeys.map((key) => [key, normalized[key]])
	    ));
	    return Object.freeze({
	      format: this.#format,
	      schemaVersion: this.#schemaVersion,
	      scriptVersion: this.#scriptVersion,
	      exportedAt: this.#now().toISOString(),
	      settingsCount: this.#settingKeys.length,
	      settings
	    });
	  }
	  import(payload) {
	    try {
	      return this.#import(payload);
	    } catch (cause) {
	      throw cause instanceof Error && cause.message === "invalid_config" ? cause : new Error("invalid_config", { cause });
	    }
	  }
	  #import(payload) {
	    const record = plainRecord(payload, "config payload");
	    if (record.format !== this.#format || Number(record.schemaVersion) !== this.#schemaVersion)
	      throw new Error("invalid_config");
	    const settingsRecord = plainRecord(record.settings, "config settings"), originalKeys = Object.keys(settingsRecord);
	    if (Number(record.settingsCount) !== originalKeys.length || originalKeys.some((key) => !this.#settingKeySet.has(key)))
	      throw new Error("invalid_config");
	    const migrated = { ...settingsRecord };
	    for (const rule of this.#legacyRules) {
	      const allowedMissing = new Set(Object.keys(rule.missingDefaults));
	      if (this.#settingKeys.every((key) => Object.hasOwn(migrated, key) || allowedMissing.has(key)))
	        for (const [key, value] of Object.entries(rule.missingDefaults))
	          Object.hasOwn(migrated, key) || (migrated[key] = value);
	    }
	    if (this.#settingKeys.some((key) => !Object.hasOwn(migrated, key)))
	      throw new Error("invalid_config");
	    const selected = Object.fromEntries(
	      this.#settingKeys.map((key) => [key, migrated[key]])
	    );
	    return this.#normalize({
	      ...this.#defaults,
	      ...selected
	    });
	  }
	}
}, "bab6d3c95c652c4b4ebcf85c43f2da0cdd299b92f3025e2aa5f36e2eed638d40");

/* Source: lite/src/state/preferences-repository.ts */
runtime.register("src/state/preferences-repository.js", function(module, exports, require) {
	var preferences_repository_exports = {};
	__export(preferences_repository_exports, {
	  PreferencesRepository: () => PreferencesRepository
	});
	module.exports = __toCommonJS(preferences_repository_exports);
	var import_signal = require("../kernel/signal.js");
	function plainRecord(value) {
	  if (!value || typeof value != "object" || Array.isArray(value))
	    throw new TypeError("偏好存储值必须是对象");
	  return value;
	}
	function nonEmptyKey(value) {
	  const key = String(value).trim();
	  if (!key) throw new Error("preferences storage key 不能为空");
	  return key;
	}
	function freezePreferenceValue(value, seen = /* @__PURE__ */ new WeakSet()) {
	  if (!value || typeof value != "object") return value;
	  const object = value;
	  if (seen.has(object)) return value;
	  if (seen.add(object), Array.isArray(value)) {
	    for (const entry of value) freezePreferenceValue(entry, seen);
	    return Object.freeze(value);
	  }
	  const prototype = Object.getPrototypeOf(value);
	  if (prototype !== Object.prototype && prototype !== null) return value;
	  for (const entry of Object.values(value)) freezePreferenceValue(entry, seen);
	  return Object.freeze(value);
	}
	class PreferencesRepository {
	  changes = new import_signal.Signal();
	  diagnostics = new import_signal.Signal();
	  #key;
	  #storage;
	  #defaults;
	  #normalize;
	  #prepareStored;
	  #serialize;
	  #parse;
	  #snapshot;
	  constructor(options) {
	    this.#key = nonEmptyKey(options.key), this.#storage = options.storage, this.#normalize = options.normalize, this.#prepareStored = options.prepareStored ?? ((input) => input), this.#serialize = options.serialize ?? JSON.stringify, this.#parse = options.parse ?? JSON.parse, this.#defaults = freezePreferenceValue({
	      ...this.#normalize({ ...options.defaults })
	    }), this.#snapshot = Object.freeze({
	      value: this.#defaults,
	      revision: 0,
	      source: "fallback"
	    });
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  load() {
	    return this.#readAndCommit("initial");
	  }
	  reloadExternal() {
	    return this.#readAndCommit("external-reload");
	  }
	  update(patch) {
	    return this.#persistAndCommit(
	      this.#normalize({
	        ...this.#snapshot.value,
	        ...patch
	      }),
	      "local-update"
	    );
	  }
	  replace(value) {
	    return this.#persistAndCommit(
	      this.#normalize({
	        ...this.#defaults,
	        ...value
	      }),
	      "replace"
	    );
	  }
	  #readAndCommit(source) {
	    let value = this.#defaults, commitSource = source;
	    try {
	      const stored = this.#storage.getItem(this.#key);
	      if (stored !== null) {
	        const parsed = plainRecord(this.#parse(stored)), prepared = plainRecord(this.#prepareStored(parsed));
	        value = this.#normalize({
	          ...this.#defaults,
	          ...prepared
	        });
	      }
	    } catch (cause) {
	      commitSource = "fallback", this.diagnostics.emit(Object.freeze({
	        code: cause instanceof TypeError ? "invalid-stored-value" : "read-failed",
	        cause
	      }));
	    }
	    return this.#commit(value, commitSource);
	  }
	  #persistAndCommit(value, source) {
	    const frozen = freezePreferenceValue({ ...value });
	    try {
	      this.#storage.setItem(this.#key, this.#serialize(frozen));
	    } catch (cause) {
	      throw this.diagnostics.emit(Object.freeze({ code: "write-failed", cause })), cause;
	    }
	    return this.#commit(frozen, source);
	  }
	  #commit(value, source) {
	    const snapshot = Object.freeze({
	      value: freezePreferenceValue({ ...value }),
	      revision: this.#snapshot.revision + 1,
	      source
	    });
	    return this.#snapshot = snapshot, this.changes.emit(snapshot), snapshot;
	  }
	}
}, "7dbda24beac5a9c311c3653615cadbc783aa23e655c1baf2fd5fa146fefbf7b4");

/* Source: lite/src/state/reader-account-scoped-storage.ts */
runtime.register("src/state/reader-account-scoped-storage.js", function(module, exports, require) {
	var reader_account_scoped_storage_exports = {};
	__export(reader_account_scoped_storage_exports, {
	  readReaderAccountScopedString: () => readReaderAccountScopedString,
	  readReaderAccountScopedValue: () => readReaderAccountScopedValue,
	  readerAccountScopedStorageIdentity: () => readerAccountScopedStorageIdentity
	});
	module.exports = __toCommonJS(reader_account_scoped_storage_exports);
	var import_identifiers = require("../discourse/identifiers.js");
	function baseStorageKey(value) {
	  const normalized = String(value ?? "").trim();
	  if (!normalized) throw new Error("account scoped storage base key 不能为空");
	  return normalized;
	}
	function missing(value) {
	  return value == null;
	}
	function readerAccountScopedStorageIdentity(legacyKeyValue, authScopeValue) {
	  const legacyKey = baseStorageKey(legacyKeyValue), authScope = (0, import_identifiers.discourseAuthScope)(authScopeValue);
	  return Object.freeze({
	    authScope,
	    key: `${legacyKey}:scope:v2:${encodeURIComponent(authScope)}`,
	    legacyKey,
	    legacyOwnerKey: `${legacyKey}:legacy-owner:v2`,
	    canClaimLegacy: authScope.startsWith("account:")
	  });
	}
	function readReaderAccountScopedString(storage, identity) {
	  const scoped = storage.getItem(identity.key);
	  if (scoped !== null) return scoped;
	  if (!identity.canClaimLegacy) return null;
	  const legacy = storage.getItem(identity.legacyKey);
	  if (legacy === null) return null;
	  const owner = storage.getItem(identity.legacyOwnerKey);
	  return owner !== null && owner !== identity.authScope || owner === null && (storage.setItem(identity.legacyOwnerKey, identity.authScope), storage.getItem(identity.legacyOwnerKey) !== identity.authScope) ? null : (storage.setItem(identity.key, legacy), storage.getItem(identity.key));
	}
	async function readReaderAccountScopedValue(storage, identity) {
	  const scoped = await storage.getValue(identity.key);
	  if (!missing(scoped)) return scoped;
	  if (!identity.canClaimLegacy) return null;
	  const legacy = await storage.getValue(identity.legacyKey);
	  if (missing(legacy)) return null;
	  const owner = await storage.getValue(identity.legacyOwnerKey);
	  if (!missing(owner) && String(owner) !== identity.authScope || missing(owner) && (await storage.setValue(identity.legacyOwnerKey, identity.authScope), String(await storage.getValue(identity.legacyOwnerKey)) !== identity.authScope))
	    return null;
	  await storage.setValue(identity.key, legacy);
	  const migrated = await storage.getValue(identity.key);
	  return missing(migrated) ? null : migrated;
	}
}, "4ef3eb13ce397fec240b7b3fa2c8798d30a38f2ec4b11956c44131fd85e18f23");

/* Source: lite/src/state/reader-boost-copy-settings.ts */
runtime.register("src/state/reader-boost-copy-settings.js", function(module, exports, require) {
	var reader_boost_copy_settings_exports = {};
	__export(reader_boost_copy_settings_exports, {
	  BOOST_COPY_MAX_LENGTH: () => BOOST_COPY_MAX_LENGTH,
	  DEFAULT_BOOST_COPY_SETTINGS: () => DEFAULT_BOOST_COPY_SETTINGS,
	  normalizeBoostCopySettings: () => normalizeBoostCopySettings
	});
	module.exports = __toCommonJS(reader_boost_copy_settings_exports);
	const BOOST_COPY_MAX_LENGTH = 16, DEFAULT_BOOST_COPY_SETTINGS = Object.freeze({
	  mode: "counter",
	  prefix: "",
	  counterMarker: "+",
	  counterStep: 1,
	  fixedSuffix: ""
	});
	function boundedText(value, fallback = "") {
	  return [...String(value ?? fallback).replace(/\s+/g, " ")].slice(0, 16).join("");
	}
	function normalizeBoostCopySettings(input) {
	  const source = input ?? {}, markerInput = boundedText(
	    source.counterMarker,
	    DEFAULT_BOOST_COPY_SETTINGS.counterMarker
	  ).trim(), marker = markerInput && !/\d$/.test(markerInput) ? markerInput : DEFAULT_BOOST_COPY_SETTINGS.counterMarker, step = Number(source.counterStep);
	  return Object.freeze({
	    mode: source.mode === "text" ? "text" : "counter",
	    prefix: boundedText(source.prefix),
	    counterMarker: marker,
	    counterStep: Number.isFinite(step) ? Math.min(99, Math.max(1, Math.round(step))) : DEFAULT_BOOST_COPY_SETTINGS.counterStep,
	    fixedSuffix: boundedText(source.fixedSuffix)
	  });
	}
}, "b53c781301e282e8c6d15cf16425640164ea8d4d6fd06570f325d605596228d3");

/* Source: lite/src/state/reader-preferences-schema.ts */
runtime.register("src/state/reader-preferences-schema.js", function(module, exports, require) {
	var reader_preferences_schema_exports = {};
	__export(reader_preferences_schema_exports, {
	  IMAGE_PROFILE_DEFAULT: () => IMAGE_PROFILE_DEFAULT,
	  LIGHTBOX_COMMENTS_WIDTH_DEFAULT: () => LIGHTBOX_COMMENTS_WIDTH_DEFAULT,
	  LIGHTBOX_COMMENTS_WIDTH_MAX: () => LIGHTBOX_COMMENTS_WIDTH_MAX,
	  LIGHTBOX_COMMENTS_WIDTH_MIN: () => LIGHTBOX_COMMENTS_WIDTH_MIN,
	  LIGHTBOX_DESCRIPTION_HEIGHT_DEFAULT: () => LIGHTBOX_DESCRIPTION_HEIGHT_DEFAULT,
	  LIGHTBOX_DESCRIPTION_HEIGHT_MIN: () => LIGHTBOX_DESCRIPTION_HEIGHT_MIN,
	  READER_APPEARANCE_COLOR_NAMES: () => READER_APPEARANCE_COLOR_NAMES,
	  READER_APPEARANCE_DEFAULT: () => READER_APPEARANCE_DEFAULT,
	  READER_APPEARANCE_NUMERIC_LIMITS: () => READER_APPEARANCE_NUMERIC_LIMITS,
	  READER_APPEARANCE_SETTING_NAMES: () => READER_APPEARANCE_SETTING_NAMES,
	  READER_CONFIG_EXPORT_FORMAT: () => READER_CONFIG_EXPORT_FORMAT,
	  READER_CONFIG_EXPORT_VERSION: () => READER_CONFIG_EXPORT_VERSION,
	  READER_FONT_DEFAULT: () => READER_FONT_DEFAULT,
	  READER_FONT_FAMILIES: () => READER_FONT_FAMILIES,
	  READER_FONT_SCALE_LIMITS: () => READER_FONT_SCALE_LIMITS,
	  READER_FONT_WEIGHTS: () => READER_FONT_WEIGHTS,
	  READER_FULLPAGE_LAYOUT_DEFAULT: () => READER_FULLPAGE_LAYOUT_DEFAULT,
	  READER_HOST_FONT_SCALE_DEFAULTS: () => READER_HOST_FONT_SCALE_DEFAULTS,
	  READER_HOST_FONT_SCALE_LIMITS: () => READER_HOST_FONT_SCALE_LIMITS,
	  READER_JUMP_HIGHLIGHT_DEFAULTS: () => READER_JUMP_HIGHLIGHT_DEFAULTS,
	  READER_JUMP_HIGHLIGHT_LIMITS: () => READER_JUMP_HIGHLIGHT_LIMITS,
	  READER_LAYOUT_DEFAULT: () => READER_LAYOUT_DEFAULT,
	  READER_LAYOUT_MINIMUM_RATIOS: () => READER_LAYOUT_MINIMUM_RATIOS,
	  READER_LAYOUT_REGIONS: () => READER_LAYOUT_REGIONS,
	  READER_LOADING_ANIMATION_KEYS: () => READER_LOADING_ANIMATION_KEYS,
	  READER_PERFORMANCE_LIMITS: () => READER_PERFORMANCE_LIMITS,
	  READER_PERFORMANCE_PRESETS: () => READER_PERFORMANCE_PRESETS,
	  READER_PREFERENCES_STORAGE_KEY: () => READER_PREFERENCES_STORAGE_KEY,
	  READER_SHORTCUT_ACTIONS: () => READER_SHORTCUT_ACTIONS,
	  READER_SHORTCUT_DEFAULTS: () => READER_SHORTCUT_DEFAULTS,
	  createReaderPerformancePreferencesPatch: () => createReaderPerformancePreferencesPatch,
	  createReaderPreferencesConfigCodec: () => createReaderPreferencesConfigCodec,
	  createReaderPreferencesDefaults: () => createReaderPreferencesDefaults,
	  createReaderPreferencesRepository: () => createReaderPreferencesRepository,
	  normalizeImageProfile: () => normalizeImageProfile,
	  normalizeReaderAppearanceProfile: () => normalizeReaderAppearanceProfile,
	  normalizeReaderFontProfile: () => normalizeReaderFontProfile,
	  normalizeReaderPreferences: () => normalizeReaderPreferences,
	  normalizeReaderShortcutBinding: () => normalizeReaderShortcutBinding,
	  normalizeReaderShortcutBindings: () => normalizeReaderShortcutBindings,
	  prepareStoredReaderPreferences: () => prepareStoredReaderPreferences,
	  readReaderPerformanceConfig: () => readReaderPerformanceConfig,
	  readerAppearanceEditableProfile: () => readerAppearanceEditableProfile,
	  readerLayoutProfileTotal: () => readerLayoutProfileTotal,
	  readerLayoutRegionMaximum: () => readerLayoutRegionMaximum,
	  readerPerformancePresetForConfig: () => readerPerformancePresetForConfig,
	  rebalanceReaderLayoutProfile: () => rebalanceReaderLayoutProfile,
	  resolveReaderAppearanceColor: () => resolveReaderAppearanceColor
	});
	module.exports = __toCommonJS(reader_preferences_schema_exports);
	var import_preferences_config_codec = require("./preferences-config-codec.js"), import_preferences_repository = require("./preferences-repository.js"), import_reader_boost_copy_settings = require("./reader-boost-copy-settings.js");
	const READER_PREFERENCES_STORAGE_KEY = "linuxdo-enhanced-reader:prefs", READER_CONFIG_EXPORT_FORMAT = "awesome-linuxdo-reader-settings", READER_CONFIG_EXPORT_VERSION = 5, IMAGE_SCALE_OPTIONS = Object.freeze([25, 50, 100, 125, 150, 200]), LIGHTBOX_DESCRIPTION_HEIGHT_MIN = 56, LIGHTBOX_DESCRIPTION_HEIGHT_DEFAULT = 120, LIGHTBOX_COMMENTS_WIDTH_DEFAULT = 25, LIGHTBOX_COMMENTS_WIDTH_MIN = 18, LIGHTBOX_COMMENTS_WIDTH_MAX = 50, IMAGE_SCALE_MIN = 50, IMAGE_SCALE_MAX = 200, FONT_SCALE_MIN = 50, FONT_SCALE_MAX = 250, COMPOSER_FONT_SCALE_DEFAULT = 80, HOST_EMBED_SIZE_MIN = 50, HOST_EMBED_SIZE_MAX = 200, READER_WINDOW_MIN_WIDTH = 360, READER_WINDOW_MIN_HEIGHT = 320, READER_EMBED_MIN_WIDTH = 360, HISTORY_EDGE_TRIGGER_MIN = 0, HISTORY_EDGE_TRIGGER_MAX = 15, HISTORY_EDGE_TRIGGER_DEFAULT = 15, INLINE_REPLY_TREE_DEFAULT_DEPTH = 3, INLINE_REPLY_TREE_MAX_DEPTH = 5, FONT_FAMILIES = /* @__PURE__ */ new Set([
	  "site",
	  "system",
	  "cjkSans",
	  "serif",
	  "monospace",
	  "custom"
	]), FONT_WEIGHTS = /* @__PURE__ */ new Set([300, 400, 500, 600]), BOOKMARK_TAB_TYPES = Object.freeze([
	  "Reaction",
	  "Topic",
	  "Post"
	]), READER_LOADING_ANIMATION_KEYS = Object.freeze([
	  "portal",
	  "constellation",
	  "corridor",
	  "typewave",
	  "crystal",
	  "marginalia",
	  "chapters",
	  "quoteecho",
	  "footnotes",
	  "inkverse"
	]), READER_LOADING_ANIMATIONS = new Set(
	  READER_LOADING_ANIMATION_KEYS
	), LAYOUT_KEYS = Object.freeze([
	  "left",
	  "main",
	  "gap",
	  "timeline",
	  "right"
	]), READER_LAYOUT_DEFAULT = Object.freeze({
	  left: 0,
	  main: 88,
	  gap: 0,
	  timeline: 8,
	  right: 4
	}), READER_FULLPAGE_LAYOUT_DEFAULT = Object.freeze({
	  left: 15,
	  main: 70,
	  gap: 5,
	  timeline: 8,
	  right: 2
	}), LAYOUT_MIN_RATIOS = Object.freeze({
	  left: 0,
	  main: 40,
	  gap: 0,
	  timeline: 6,
	  right: 0
	}), READER_LAYOUT_REGIONS = LAYOUT_KEYS, READER_LAYOUT_MINIMUM_RATIOS = LAYOUT_MIN_RATIOS, READER_FONT_DEFAULT = Object.freeze({
	  family: "system",
	  customFamily: "",
	  weight: 400,
	  interfaceColor: "",
	  interface: 92,
	  postFamily: "system",
	  postCustomFamily: "",
	  postWeight: 400,
	  postColor: "",
	  post: 95,
	  composerFamily: "system",
	  composerCustomFamily: "",
	  composerWeight: 400,
	  composerColor: "",
	  composer: COMPOSER_FONT_SCALE_DEFAULT
	}), READER_FONT_FAMILIES = Object.freeze([...FONT_FAMILIES]), READER_FONT_WEIGHTS = Object.freeze([...FONT_WEIGHTS]), READER_FONT_SCALE_LIMITS = Object.freeze({
	  min: FONT_SCALE_MIN,
	  max: FONT_SCALE_MAX,
	  step: 1
	}), READER_HOST_FONT_SCALE_LIMITS = Object.freeze({
	  min: HOST_EMBED_SIZE_MIN,
	  max: HOST_EMBED_SIZE_MAX,
	  step: 1
	}), READER_HOST_FONT_SCALE_DEFAULTS = Object.freeze({
	  title: 110,
	  avatar: 80,
	  stats: 120,
	  labelCard: 100
	}), IMAGE_PROFILE_DEFAULT = Object.freeze({
	  preset: "50",
	  custom: 50
	}), APPEARANCE_PROFILE_DEFAULT = Object.freeze({
	  accentColor: "#47855f",
	  accentColorDark: "#78c295",
	  linkColor: "#2870b8",
	  linkColorDark: "#71b7ff",
	  zebraColor: "#f7f7f7",
	  zebraColorDark: "#1b2b21",
	  zebraRadius: 10,
	  listZebraColor: "#f7f7f7",
	  listZebraColorDark: "#242a31",
	  structureColorsEnabled: !0,
	  replyLineColor: "#6dab85",
	  replyLineColorDark: "#78c295",
	  replyLineWidth: 1,
	  replyLineRadius: 15,
	  quoteLineColor: "#d7d7d7",
	  quoteLineColorDark: "#46505a",
	  quoteLineWidth: 0.5,
	  dividerLineColor: "#e5e5e5",
	  dividerLineColorDark: "#343b44",
	  dividerLineWidth: 0.5
	}), READER_APPEARANCE_DEFAULT = APPEARANCE_PROFILE_DEFAULT, READER_APPEARANCE_COLOR_NAMES = Object.freeze([
	  "accentColor",
	  "linkColor",
	  "zebraColor",
	  "listZebraColor",
	  "replyLineColor",
	  "quoteLineColor",
	  "dividerLineColor"
	]), READER_APPEARANCE_SETTING_NAMES = Object.freeze([
	  "accentColor",
	  "linkColor",
	  "zebraColor",
	  "zebraRadius",
	  "listZebraColor",
	  "structureColorsEnabled",
	  "replyLineColor",
	  "replyLineWidth",
	  "replyLineRadius",
	  "quoteLineColor",
	  "quoteLineWidth",
	  "dividerLineColor",
	  "dividerLineWidth"
	]), READER_APPEARANCE_NUMERIC_LIMITS = Object.freeze({
	  zebraRadius: Object.freeze({ min: 0, max: 16, step: 1 }),
	  replyLineWidth: Object.freeze({ min: 0.5, max: 4, step: 0.5 }),
	  replyLineRadius: Object.freeze({ min: 0, max: 16, step: 1 }),
	  quoteLineWidth: Object.freeze({ min: 0.5, max: 4, step: 0.5 }),
	  dividerLineWidth: Object.freeze({ min: 0.5, max: 4, step: 0.5 })
	}), READER_APPEARANCE_THEME_LIMITS = Object.freeze({
	  accentColor: Object.freeze({ saturationMax: 82, light: [30, 55], dark: [58, 78] }),
	  linkColor: Object.freeze({ saturationMax: 90, light: [30, 55], dark: [60, 80] }),
	  zebraColor: Object.freeze({ saturationMax: 70, light: [92, 100], dark: [10, 20] }),
	  listZebraColor: Object.freeze({ saturationMax: 38, light: [88, 98], dark: [10, 20] }),
	  replyLineColor: Object.freeze({ saturationMax: 76, light: [30, 58], dark: [55, 78] }),
	  quoteLineColor: Object.freeze({ saturationMax: 50, light: [72, 92], dark: [22, 42] }),
	  dividerLineColor: Object.freeze({ saturationMax: 40, light: [80, 94], dark: [18, 34] })
	}), PERFORMANCE_PRESETS = Object.freeze({
	  low: Object.freeze({
	    pageSize: 24,
	    streamOverscanViewports: 1,
	    streamMaxItems: 48,
	    nestedPrefetchViewports: 1.25,
	    requestMaxConcurrent: 2,
	    requestMinInterval: 180,
	    requestRateTarget: 75
	  }),
	  balanced: Object.freeze({
	    pageSize: 48,
	    streamOverscanViewports: 1.5,
	    streamMaxItems: 80,
	    nestedPrefetchViewports: 2.5,
	    requestMaxConcurrent: 3,
	    requestMinInterval: 100,
	    requestRateTarget: 85
	  }),
	  high: Object.freeze({
	    pageSize: 64,
	    streamOverscanViewports: 2,
	    streamMaxItems: 96,
	    nestedPrefetchViewports: 3,
	    requestMaxConcurrent: 4,
	    requestMinInterval: 80,
	    requestRateTarget: 90
	  })
	}), PERFORMANCE_NAMES = Object.freeze([
	  "pageSize",
	  "streamOverscanViewports",
	  "streamMaxItems",
	  "nestedPrefetchViewports",
	  "requestMaxConcurrent",
	  "requestMinInterval",
	  "requestRateTarget"
	]), PERFORMANCE_LIMITS = Object.freeze({
	  pageSize: Object.freeze({ min: 12, max: 64, integer: !0 }),
	  streamOverscanViewports: Object.freeze({ min: 0.25, max: 3 }),
	  streamMaxItems: Object.freeze({ min: 24, max: 128, integer: !0 }),
	  nestedPrefetchViewports: Object.freeze({ min: 1, max: 3 }),
	  requestMaxConcurrent: Object.freeze({ min: 1, max: 4, integer: !0 }),
	  requestMinInterval: Object.freeze({ min: 80, max: 500, integer: !0 }),
	  requestRateTarget: Object.freeze({ min: 50, max: 95, integer: !0 })
	}), READER_PERFORMANCE_PRESETS = PERFORMANCE_PRESETS, READER_PERFORMANCE_LIMITS = PERFORMANCE_LIMITS, READER_SHORTCUT_DEFAULTS = Object.freeze({
	  historyBack: Object.freeze(["ArrowLeft", "Mouse3"]),
	  historyForward: Object.freeze(["ArrowRight", "Mouse4"]),
	  topicTop: Object.freeze(["Home"]),
	  topicBottom: Object.freeze(["End"]),
	  floorJump: Object.freeze([]),
	  discussionHorizontalScroll: Object.freeze(["Shift+Wheel"]),
	  onlyAuthor: Object.freeze([]),
	  translate: Object.freeze([]),
	  refreshTopic: Object.freeze([]),
	  refreshHost: Object.freeze(["F5"]),
	  openOriginal: Object.freeze([]),
	  settings: Object.freeze(["Ctrl+Comma"]),
	  notifications: Object.freeze([]),
	  historyPanel: Object.freeze([]),
	  bookmarksPanel: Object.freeze([]),
	  likeTopic: Object.freeze([]),
	  replyTopic: Object.freeze([]),
	  bookmarkTopic: Object.freeze([]),
	  toggleFullscreen: Object.freeze([]),
	  toggleQueue: Object.freeze([]),
	  closeReader: Object.freeze(["Escape"])
	}), READER_SHORTCUT_ACTIONS = Object.freeze(
	  Object.keys(READER_SHORTCUT_DEFAULTS)
	), SHORTCUT_MODIFIERS = Object.freeze(["Ctrl", "Alt", "Shift", "Meta"]), JUMP_HIGHLIGHT_DEFAULTS = Object.freeze({
	  color: "#0888cc",
	  radius: 10,
	  borderWidth: 1,
	  rate: 0.8,
	  count: 1
	}), JUMP_HIGHLIGHT_LIMITS = Object.freeze({
	  radius: Object.freeze({ min: 0, max: 24, step: 1, integer: !0 }),
	  borderWidth: Object.freeze({ min: 0, max: 4, step: 1, integer: !0 }),
	  rate: Object.freeze({ min: 0.5, max: 2, step: 0.1, integer: !1 }),
	  count: Object.freeze({ min: 1, max: 6, step: 1, integer: !0 })
	}), READER_JUMP_HIGHLIGHT_DEFAULTS = JUMP_HIGHLIGHT_DEFAULTS, READER_JUMP_HIGHLIGHT_LIMITS = JUMP_HIGHLIGHT_LIMITS;
	function finiteViewport(value, name) {
	  if (!Number.isFinite(value) || value < 0)
	    throw new RangeError(`${name} 必须是非负有限数`);
	  return value;
	}
	function plainRecord(value) {
	  return value && typeof value == "object" && !Array.isArray(value) ? value : {};
	}
	function normalizeTopicActionRailPosition(value) {
	  const source = plainRecord(value), rawX = source.x, numericX = Number(rawX), x = rawX === "left" || rawX === "right" ? rawX : Number.isFinite(numericX) ? Math.max(0, Math.min(1, numericX)) : "left", numericY = Number(source.y);
	  return Object.freeze({
	    x,
	    y: Number.isFinite(numericY) ? Math.max(0, Math.min(1, numericY)) : 0.95
	  });
	}
	function normalizeTopicActionRailPositions(value, legacyValue) {
	  const source = plainRecord(value), legacy = normalizeTopicActionRailPosition(legacyValue);
	  return Object.freeze({
	    floating: Object.hasOwn(source, "floating") ? normalizeTopicActionRailPosition(source.floating) : legacy,
	    fullpage: Object.hasOwn(source, "fullpage") ? normalizeTopicActionRailPosition(source.fullpage) : legacy,
	    embedded: Object.hasOwn(source, "embedded") ? normalizeTopicActionRailPosition(source.embedded) : legacy
	  });
	}
	function roundedRange(value, fallback, minimum, maximum) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? Math.min(maximum, Math.max(minimum, Math.round(numeric))) : fallback;
	}
	function steppedRange(value, fallback, minimum, maximum, step) {
	  const numeric = Number(value), safe = Number.isFinite(numeric) ? numeric : fallback;
	  return Math.min(maximum, Math.max(minimum, Math.round(safe / step) * step));
	}
	function normalizeHexColor(value, fallback = "") {
	  const color = String(value || "").trim().toLowerCase();
	  if (/^#[0-9a-f]{6}$/.test(color)) return color;
	  const fallbackColor = String(fallback || "").trim().toLowerCase();
	  return /^#[0-9a-f]{6}$/.test(fallbackColor) ? fallbackColor : "";
	}
	function normalizeFontFamily(value, fallback) {
	  return FONT_FAMILIES.has(value) ? value : fallback;
	}
	function normalizeCustomFontFamily(value, fallback = "") {
	  return [...String(value ?? fallback).replace(/[\u0000-\u001f\u007f"'`,;{}<>\\]/g, "").replace(/\s+/g, " ").trim()].slice(0, 64).join("");
	}
	function normalizeFontWeight(value, fallback) {
	  const numeric = Number(value);
	  return FONT_WEIGHTS.has(numeric) ? numeric : fallback;
	}
	function normalizeImageProfile(value) {
	  const source = plainRecord(value), sourcePreset = source.preset == null ? IMAGE_PROFILE_DEFAULT.preset : source.preset, preset = sourcePreset === "custom" ? "custom" : IMAGE_SCALE_OPTIONS.includes(Number(sourcePreset)) ? String(Number(sourcePreset)) : "100";
	  return Object.freeze({
	    preset,
	    custom: roundedRange(
	      source.custom,
	      IMAGE_PROFILE_DEFAULT.custom,
	      IMAGE_SCALE_MIN,
	      IMAGE_SCALE_MAX
	    )
	  });
	}
	function normalizeFontProfile(value) {
	  const source = plainRecord(value), family = normalizeFontFamily(source.family, READER_FONT_DEFAULT.family), customFamily = normalizeCustomFontFamily(
	    source.customFamily,
	    READER_FONT_DEFAULT.customFamily
	  ), weight = normalizeFontWeight(source.weight, READER_FONT_DEFAULT.weight);
	  return Object.freeze({
	    family,
	    customFamily,
	    weight,
	    interfaceColor: normalizeHexColor(
	      source.interfaceColor,
	      READER_FONT_DEFAULT.interfaceColor
	    ),
	    interface: roundedRange(
	      source.interface,
	      READER_FONT_DEFAULT.interface,
	      FONT_SCALE_MIN,
	      FONT_SCALE_MAX
	    ),
	    postFamily: normalizeFontFamily(source.postFamily, family),
	    postCustomFamily: normalizeCustomFontFamily(source.postCustomFamily, customFamily),
	    postWeight: normalizeFontWeight(source.postWeight, weight),
	    postColor: normalizeHexColor(source.postColor, READER_FONT_DEFAULT.postColor),
	    post: roundedRange(
	      source.post,
	      READER_FONT_DEFAULT.post,
	      FONT_SCALE_MIN,
	      FONT_SCALE_MAX
	    ),
	    composerFamily: normalizeFontFamily(source.composerFamily, family),
	    composerCustomFamily: normalizeCustomFontFamily(
	      source.composerCustomFamily,
	      customFamily
	    ),
	    composerWeight: normalizeFontWeight(source.composerWeight, weight),
	    composerColor: normalizeHexColor(
	      source.composerColor,
	      READER_FONT_DEFAULT.composerColor
	    ),
	    composer: roundedRange(
	      source.composer,
	      READER_FONT_DEFAULT.composer,
	      FONT_SCALE_MIN,
	      FONT_SCALE_MAX
	    )
	  });
	}
	function normalizeReaderFontProfile(value) {
	  return normalizeFontProfile(value);
	}
	function appearanceColorPair(source, key) {
	  const darkKey = `${key}Dark`, lightFallback = normalizeHexColor(
	    APPEARANCE_PROFILE_DEFAULT[key],
	    APPEARANCE_PROFILE_DEFAULT[key]
	  ), sourceColor = normalizeHexColor(source[key]) || normalizeHexColor(source[darkKey]) || lightFallback;
	  return [sourceColor, sourceColor];
	}
	function normalizeAppearanceProfile(value) {
	  const source = plainRecord(value), [accentColor, accentColorDark] = appearanceColorPair(source, "accentColor"), [linkColor, linkColorDark] = appearanceColorPair(source, "linkColor"), [zebraColor, zebraColorDark] = appearanceColorPair(source, "zebraColor"), [listZebraColor, listZebraColorDark] = appearanceColorPair(
	    source,
	    "listZebraColor"
	  ), [replyLineColor, replyLineColorDark] = appearanceColorPair(
	    source,
	    "replyLineColor"
	  ), [quoteLineColor, quoteLineColorDark] = appearanceColorPair(
	    source,
	    "quoteLineColor"
	  ), [dividerLineColor, dividerLineColorDark] = appearanceColorPair(
	    source,
	    "dividerLineColor"
	  );
	  return Object.freeze({
	    accentColor,
	    accentColorDark,
	    linkColor,
	    linkColorDark,
	    zebraColor,
	    zebraColorDark,
	    zebraRadius: steppedRange(source.zebraRadius, 10, 0, 16, 1),
	    listZebraColor,
	    listZebraColorDark,
	    structureColorsEnabled: typeof source.structureColorsEnabled == "boolean" ? source.structureColorsEnabled : !0,
	    replyLineColor,
	    replyLineColorDark,
	    replyLineWidth: steppedRange(source.replyLineWidth, 1, 0.5, 4, 0.5),
	    replyLineRadius: steppedRange(source.replyLineRadius, 15, 0, 16, 1),
	    quoteLineColor,
	    quoteLineColorDark,
	    quoteLineWidth: steppedRange(source.quoteLineWidth, 0.5, 0.5, 4, 0.5),
	    dividerLineColor,
	    dividerLineColorDark,
	    dividerLineWidth: steppedRange(source.dividerLineWidth, 0.5, 0.5, 4, 0.5)
	  });
	}
	function hexColorToHsl(value) {
	  const color = normalizeHexColor(value);
	  if (!color) return null;
	  const [red, green, blue] = [1, 3, 5].map(
	    (index) => Number.parseInt(color.slice(index, index + 2), 16) / 255
	  ), maximum = Math.max(red, green, blue), minimum = Math.min(red, green, blue), delta = maximum - minimum, lightness = (maximum + minimum) / 2, saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
	  let hue = 0;
	  return delta !== 0 && (maximum === red ? hue = 60 * ((green - blue) / delta % 6) : maximum === green ? hue = 60 * ((blue - red) / delta + 2) : hue = 60 * ((red - green) / delta + 4)), Object.freeze({
	    hue: hue < 0 ? hue + 360 : hue,
	    saturation: saturation * 100,
	    lightness: lightness * 100
	  });
	}
	function hslColorToHex(hue, saturation, lightness) {
	  const normalizedHue = (hue % 360 + 360) % 360, normalizedSaturation = Math.min(100, Math.max(0, saturation)) / 100, normalizedLightness = Math.min(100, Math.max(0, lightness)) / 100, chroma = (1 - Math.abs(2 * normalizedLightness - 1)) * normalizedSaturation, intermediate = chroma * (1 - Math.abs(normalizedHue / 60 % 2 - 1)), offset = normalizedLightness - chroma / 2, sector = Math.floor(normalizedHue / 60);
	  return `#${([
	    [chroma, intermediate, 0],
	    [intermediate, chroma, 0],
	    [0, chroma, intermediate],
	    [0, intermediate, chroma],
	    [intermediate, 0, chroma],
	    [chroma, 0, intermediate]
	  ][sector] ?? [0, 0, 0]).map(
	    (channel) => Math.round((channel + offset) * 255).toString(16).padStart(2, "0")
	  ).join("")}`;
	}
	function normalizeReaderAppearanceProfile(value) {
	  return normalizeAppearanceProfile(value);
	}
	function readerAppearanceEditableProfile(profile) {
	  return Object.freeze(Object.fromEntries(
	    READER_APPEARANCE_SETTING_NAMES.map((name) => [name, profile[name]])
	  ));
	}
	function resolveReaderAppearanceColor(profile, name, theme) {
	  const darkName = `${name}Dark`, defaultColor = String(theme === "dark" ? READER_APPEARANCE_DEFAULT[darkName] : READER_APPEARANCE_DEFAULT[name]), sourceColor = normalizeHexColor(
	    profile[name],
	    READER_APPEARANCE_DEFAULT[name]
	  );
	  if (sourceColor === READER_APPEARANCE_DEFAULT[name]) return defaultColor;
	  const hsl = hexColorToHsl(sourceColor);
	  if (!hsl) return defaultColor;
	  const limit = READER_APPEARANCE_THEME_LIMITS[name], [minimumLightness, maximumLightness] = limit[theme], saturation = Math.min(hsl.saturation, limit.saturationMax), lightness = Math.min(
	    maximumLightness,
	    Math.max(minimumLightness, hsl.lightness)
	  );
	  return Math.abs(saturation - hsl.saturation) < 0.01 && Math.abs(lightness - hsl.lightness) < 0.01 ? sourceColor : hslColorToHex(hsl.hue, saturation, lightness);
	}
	function normalizePerformanceValue(name, value, fallback) {
	  const limit = PERFORMANCE_LIMITS[name], numeric = Number(value);
	  if (!Number.isFinite(numeric)) return fallback;
	  const clamped = Math.min(limit.max, Math.max(limit.min, numeric));
	  return limit.integer ? Math.round(clamped) : Math.round(clamped * 100) / 100;
	}
	function performanceConfigFromInput(input) {
	  const fallback = PERFORMANCE_PRESETS.balanced;
	  return Object.freeze({
	    pageSize: normalizePerformanceValue(
	      "pageSize",
	      input.performancePageSize,
	      fallback.pageSize
	    ),
	    streamOverscanViewports: normalizePerformanceValue(
	      "streamOverscanViewports",
	      input.performanceStreamOverscan,
	      fallback.streamOverscanViewports
	    ),
	    streamMaxItems: normalizePerformanceValue(
	      "streamMaxItems",
	      input.performanceStreamMaxItems,
	      fallback.streamMaxItems
	    ),
	    nestedPrefetchViewports: normalizePerformanceValue(
	      "nestedPrefetchViewports",
	      input.performanceNestedPrefetch,
	      fallback.nestedPrefetchViewports
	    ),
	    requestMaxConcurrent: normalizePerformanceValue(
	      "requestMaxConcurrent",
	      input.performanceRequestConcurrency,
	      fallback.requestMaxConcurrent
	    ),
	    requestMinInterval: normalizePerformanceValue(
	      "requestMinInterval",
	      input.performanceRequestInterval,
	      fallback.requestMinInterval
	    ),
	    requestRateTarget: normalizePerformanceValue(
	      "requestRateTarget",
	      input.performanceRequestRateTarget,
	      fallback.requestRateTarget
	    )
	  });
	}
	function performancePresetForConfig(config) {
	  for (const preset of ["low", "balanced", "high"])
	    if (PERFORMANCE_NAMES.every((name) => config[name] === PERFORMANCE_PRESETS[preset][name]))
	      return preset;
	  return "custom";
	}
	function performancePreferencesPatch(config, preset = performancePresetForConfig(config)) {
	  return {
	    performancePreset: preset,
	    performancePageSize: config.pageSize,
	    performanceStreamOverscan: config.streamOverscanViewports,
	    performanceStreamMaxItems: config.streamMaxItems,
	    performanceNestedPrefetch: config.nestedPrefetchViewports,
	    performanceRequestConcurrency: config.requestMaxConcurrent,
	    performanceRequestInterval: config.requestMinInterval,
	    performanceRequestRateTarget: config.requestRateTarget
	  };
	}
	function readReaderPerformanceConfig(input) {
	  return performanceConfigFromInput(
	    input
	  );
	}
	function createReaderPerformancePreferencesPatch(config, preset) {
	  return performancePreferencesPatch(config, preset);
	}
	function readerPerformancePresetForConfig(config) {
	  return performancePresetForConfig(config);
	}
	function roundLayoutRatio(value) {
	  return Math.round(Number(value) * 100) / 100;
	}
	function layoutTotal(value) {
	  return roundLayoutRatio(LAYOUT_KEYS.reduce((sum, key) => sum + value[key], 0));
	}
	function normalizeLayoutProfile(value, fallback) {
	  const source = plainRecord(value), result = Object.fromEntries(LAYOUT_KEYS.map((key) => {
	    const numeric = Number(source[key]), safe = Number.isFinite(numeric) ? numeric : fallback[key];
	    return [
	      key,
	      roundLayoutRatio(Math.min(100, Math.max(LAYOUT_MIN_RATIOS[key], safe)))
	    ];
	  }));
	  if (layoutTotal(result) > 100) {
	    const extraBudget = 100 - LAYOUT_KEYS.reduce(
	      (sum, key) => sum + LAYOUT_MIN_RATIOS[key],
	      0
	    ), extras = Object.fromEntries(LAYOUT_KEYS.map((key) => [
	      key,
	      Math.max(0, result[key] - LAYOUT_MIN_RATIOS[key])
	    ])), extraTotal = LAYOUT_KEYS.reduce((sum, key) => sum + extras[key], 0) || 1;
	    for (const key of LAYOUT_KEYS)
	      result[key] = roundLayoutRatio(
	        LAYOUT_MIN_RATIOS[key] + extraBudget * extras[key] / extraTotal
	      );
	    let overflow = roundLayoutRatio(layoutTotal(result) - 100);
	    if (overflow > 0) {
	      const correctionKey = LAYOUT_KEYS.find(
	        (key) => result[key] - overflow >= LAYOUT_MIN_RATIOS[key]
	      );
	      correctionKey && (result[correctionKey] = roundLayoutRatio(result[correctionKey] - overflow)), overflow = roundLayoutRatio(layoutTotal(result) - 100), overflow > 0 && (result.main = roundLayoutRatio(result.main - overflow));
	    }
	  }
	  return Object.freeze({ ...result });
	}
	function readerLayoutProfileTotal(profile) {
	  return layoutTotal(profile);
	}
	function readerLayoutRegionMaximum(region) {
	  return roundLayoutRatio(
	    100 - LAYOUT_KEYS.reduce(
	      (total, name) => total + (name === region ? 0 : LAYOUT_MIN_RATIOS[name]),
	      0
	    )
	  );
	}
	function rebalanceReaderLayoutProfile(profile, editedRegion) {
	  const result = { ...profile }, difference = roundLayoutRatio(100 - layoutTotal(result)), direction = difference > 0 ? 1 : -1;
	  let remaining = Math.abs(difference);
	  for (const name of [
	    "main",
	    "left",
	    "right",
	    "gap",
	    "timeline"
	  ]) {
	    if (name === editedRegion || remaining <= 0) continue;
	    const capacity = roundLayoutRatio(
	      direction > 0 ? 100 - result[name] : Math.max(0, result[name] - LAYOUT_MIN_RATIOS[name])
	    ), change = Math.min(capacity, remaining);
	    result[name] = roundLayoutRatio(
	      result[name] + direction * change
	    ), remaining = roundLayoutRatio(remaining - change);
	  }
	  return normalizeLayoutProfile(result, profile);
	}
	function normalizeReaderWindowGroup(source) {
	  const width = Number(source.readerWindowWidth), height = Number(source.readerWindowHeight), x = Number(source.readerWindowX), y = Number(source.readerWindowY);
	  return {
	    readerWindowWidth: Number.isFinite(width) && width > 0 ? Math.max(READER_WINDOW_MIN_WIDTH, Math.round(width)) : 0,
	    readerWindowHeight: Number.isFinite(height) && height > 0 ? Math.max(READER_WINDOW_MIN_HEIGHT, Math.round(height)) : 0,
	    readerWindowX: Number.isFinite(x) && x > 0 ? Math.round(x) : 0,
	    readerWindowY: Number.isFinite(y) && y > 0 ? Math.round(y) : 0,
	    readerWindowLocked: !!source.readerWindowLocked,
	    readerWindowPinned: !!source.readerWindowPinned
	  };
	}
	function normalizeComposerWindowGroup(source) {
	  const result = {};
	  for (const key of [
	    "composerWindowWidth",
	    "composerWindowHeight",
	    "composerWindowX",
	    "composerWindowY"
	  ]) {
	    const value = Number(source[key]);
	    result[key] = Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
	  }
	  return result;
	}
	function normalizeReaderShortcutBinding(value) {
	  const parts = String(value || "").split("+").map((part) => part.trim()).filter(Boolean), code = parts.pop() || "";
	  if (!/^(?:[A-Za-z][A-Za-z0-9]*|Mouse(?:1|3|4|[5-9]))$/.test(code) || /^(?:Control|Alt|Shift|Meta)(?:Left|Right)?$/.test(code))
	    return "";
	  const modifiers = SHORTCUT_MODIFIERS.filter((modifier) => parts.includes(modifier));
	  return parts.some((part) => !SHORTCUT_MODIFIERS.includes(part)) ? "" : [...modifiers, code].join("+");
	}
	function normalizeReaderShortcutBindings(value) {
	  const source = plainRecord(value), used = /* @__PURE__ */ new Set(), result = {};
	  for (const action of READER_SHORTCUT_ACTIONS) {
	    const selected = Object.hasOwn(source, action) && Array.isArray(source[action]) ? source[action] : READER_SHORTCUT_DEFAULTS[action], bindings = [...new Set(
	      selected.map(normalizeReaderShortcutBinding).filter(Boolean)
	    )].filter((binding) => used.has(binding) ? !1 : (used.add(binding), !0)).slice(0, 3);
	    result[action] = Object.freeze(bindings);
	  }
	  return Object.freeze(result);
	}
	function normalizeBookmarkTabOrder(value) {
	  const stored = Array.isArray(value) ? [...new Set(value.filter(
	    (type) => BOOKMARK_TAB_TYPES.includes(type)
	  ))] : [];
	  return Object.freeze([
	    ...stored,
	    ...BOOKMARK_TAB_TYPES.filter((type) => !stored.includes(type))
	  ]);
	}
	function normalizeJumpValue(name, value) {
	  const limit = JUMP_HIGHLIGHT_LIMITS[name], fallback = JUMP_HIGHLIGHT_DEFAULTS[name], numeric = Number(value);
	  if (!Number.isFinite(numeric)) return fallback;
	  const clamped = Math.min(limit.max, Math.max(limit.min, numeric));
	  return limit.integer ? Math.round(clamped) : Math.round(Math.round(clamped / limit.step) * limit.step * 100) / 100;
	}
	function normalizeBoostPreferences(source) {
	  const normalized = (0, import_reader_boost_copy_settings.normalizeBoostCopySettings)({
	    mode: source.boostCopyMode === "text" ? "text" : "counter",
	    prefix: String(source.boostCopyPrefix ?? ""),
	    counterMarker: String(source.boostCopyCounterMarker ?? ""),
	    counterStep: Number(source.boostCopyCounterStep),
	    fixedSuffix: String(source.boostCopyFixedSuffix ?? "")
	  });
	  return {
	    boostCopyMode: normalized.mode,
	    boostCopyPrefix: normalized.prefix,
	    boostCopyCounterMarker: normalized.counterMarker,
	    boostCopyCounterStep: normalized.counterStep,
	    boostCopyFixedSuffix: normalized.fixedSuffix
	  };
	}
	function normalizeEnvironment(environment) {
	  return Object.freeze({
	    viewportWidth: finiteViewport(environment.viewportWidth, "viewportWidth"),
	    viewportHeight: finiteViewport(environment.viewportHeight, "viewportHeight")
	  });
	}
	function createReaderPreferencesDefaults(environment) {
	  const viewport = normalizeEnvironment(environment), performance = performancePreferencesPatch(
	    PERFORMANCE_PRESETS.balanced,
	    "balanced"
	  );
	  return Object.freeze({
	    topicReaderMode: "fullpage",
	    imageProfile: IMAGE_PROFILE_DEFAULT,
	    imageProfilesShared: !0,
	    floatingImageProfile: IMAGE_PROFILE_DEFAULT,
	    fullpageImageProfile: IMAGE_PROFILE_DEFAULT,
	    mobileImageProfile: IMAGE_PROFILE_DEFAULT,
	    lightboxOriginalByDefault: !0,
	    lightboxCommentsExpandedByDefault: !0,
	    lightboxDescriptionExpanded: !1,
	    lightboxDescriptionHeight: LIGHTBOX_DESCRIPTION_HEIGHT_DEFAULT,
	    lightboxCommentsWidthPercent: LIGHTBOX_COMMENTS_WIDTH_DEFAULT,
	    themeMode: "system",
	    fontRenderingEnabled: !0,
	    fontRenderingOnHost: !0,
	    hostFontFamily: "system",
	    hostFontCustomFamily: "",
	    hostFontWeight: 400,
	    hostFontColor: "",
	    hostEmbeddedTitleScale: READER_HOST_FONT_SCALE_DEFAULTS.title,
	    hostEmbeddedAvatarScale: READER_HOST_FONT_SCALE_DEFAULTS.avatar,
	    hostEmbeddedStatsScale: READER_HOST_FONT_SCALE_DEFAULTS.stats,
	    hostEmbeddedLabelCardScale: READER_HOST_FONT_SCALE_DEFAULTS.labelCard,
	    fontProfile: READER_FONT_DEFAULT,
	    appearanceProfile: APPEARANCE_PROFILE_DEFAULT,
	    ...performance,
	    layoutProfile: READER_LAYOUT_DEFAULT,
	    fullpageLayoutProfile: READER_FULLPAGE_LAYOUT_DEFAULT,
	    readerWindowWidth: 0,
	    readerWindowHeight: 0,
	    readerWindowX: 0,
	    readerWindowY: 0,
	    readerWindowLocked: !1,
	    readerWindowPinned: !1,
	    listReaderMode: "floating",
	    listReaderEmbedWidth: Math.round(viewport.viewportWidth * 0.45),
	    composerWindowWidth: 0,
	    composerWindowHeight: 0,
	    composerWindowX: 0,
	    composerWindowY: 0,
	    historySortMode: "recent-viewed",
	    bookmarkTabOrder: BOOKMARK_TAB_TYPES,
	    historyButtonsAlwaysVisible: !0,
	    readerQueueAlwaysVisibleWhenEmpty: !0,
	    historyEdgeTriggerPercent: HISTORY_EDGE_TRIGGER_DEFAULT,
	    loadingAnimation: "quoteecho",
	    translationMode: "original",
	    openTopicsAtFirstPost: !1,
	    doubleEscapeToCloseReader: !1,
	    confirmNativeComposerClose: !1,
	    readerShortcutBindings: normalizeReaderShortcutBindings(READER_SHORTCUT_DEFAULTS),
	    topicActionRailVisible: !0,
	    topicActionRailFixed: !1,
	    topicActionRailMode: "compact",
	    topicActionRailPositions: Object.freeze({
	      floating: Object.freeze({ x: "left", y: 0.95 }),
	      fullpage: Object.freeze({ x: "left", y: 0.95 }),
	      embedded: Object.freeze({ x: "left", y: 0.95 })
	    }),
	    expandNestedRepliesByDefault: !0,
	    expandLeafNestedReplies: !1,
	    aggregateDescendantReplies: !0,
	    inlineReplyTreeMaxDepth: INLINE_REPLY_TREE_DEFAULT_DEPTH,
	    hideNestedReplyFloors: !0,
	    jumpHighlightColor: JUMP_HIGHLIGHT_DEFAULTS.color,
	    jumpHighlightRadius: JUMP_HIGHLIGHT_DEFAULTS.radius,
	    jumpHighlightBorderWidth: JUMP_HIGHLIGHT_DEFAULTS.borderWidth,
	    jumpHighlightRate: JUMP_HIGHLIGHT_DEFAULTS.rate,
	    jumpHighlightCount: JUMP_HIGHLIGHT_DEFAULTS.count,
	    boostCopyMode: import_reader_boost_copy_settings.DEFAULT_BOOST_COPY_SETTINGS.mode,
	    boostCopyPrefix: import_reader_boost_copy_settings.DEFAULT_BOOST_COPY_SETTINGS.prefix,
	    boostCopyCounterMarker: import_reader_boost_copy_settings.DEFAULT_BOOST_COPY_SETTINGS.counterMarker,
	    boostCopyCounterStep: import_reader_boost_copy_settings.DEFAULT_BOOST_COPY_SETTINGS.counterStep,
	    boostCopyFixedSuffix: import_reader_boost_copy_settings.DEFAULT_BOOST_COPY_SETTINGS.fixedSuffix
	  });
	}
	function prepareStoredReaderPreferences(value) {
	  const source = { ...plainRecord(value) }, preset = source.performancePreset;
	  if (preset === "low" || preset === "balanced" || preset === "high") {
	    const patch = performancePreferencesPatch(PERFORMANCE_PRESETS[preset], preset);
	    return Object.freeze({ ...source, ...patch });
	  }
	  return Object.freeze(source);
	}
	function normalizeReaderPreferences(value, environment) {
	  const viewport = normalizeEnvironment(environment), defaults = createReaderPreferencesDefaults(viewport), known = Object.fromEntries(
	    Object.keys(defaults).filter((key) => Object.hasOwn(value, key)).map((key) => [key, value[key]])
	  ), source = { ...defaults, ...known }, performance = performancePreferencesPatch(performanceConfigFromInput(source)), expandLeafNestedReplies = source.expandLeafNestedReplies === !0, aggregateDescendantReplies = source.aggregateDescendantReplies === !0, expandNestedRepliesByDefault = aggregateDescendantReplies || source.expandNestedRepliesByDefault !== !1 || !expandLeafNestedReplies, windowPreferences = normalizeReaderWindowGroup(source), composerPreferences = normalizeComposerWindowGroup(source), boostPreferences = normalizeBoostPreferences(source), lightboxMaximum = Math.max(
	    LIGHTBOX_DESCRIPTION_HEIGHT_MIN,
	    Math.floor(viewport.viewportHeight * 0.4)
	  ), lightboxHeight = Math.round(Number(source.lightboxDescriptionHeight)), listWidth = Number(source.listReaderEmbedWidth), historyEdge = Number(source.historyEdgeTriggerPercent), depth = Number.parseInt(String(source.inlineReplyTreeMaxDepth), 10), loadingAnimation = source.loadingAnimation === "random" || READER_LOADING_ANIMATIONS.has(source.loadingAnimation) ? source.loadingAnimation : "quoteecho", topicReaderMode = source.topicReaderMode === "floating" ? "floating" : "fullpage", imageProfile = normalizeImageProfile(source.imageProfile), imageProfilesShared = source.imageProfilesShared !== !1, floatingImageProfile = imageProfilesShared ? imageProfile : normalizeImageProfile(
	    source.floatingImageProfile ?? imageProfile
	  ), fullpageImageProfile = imageProfilesShared ? imageProfile : normalizeImageProfile(
	    source.fullpageImageProfile ?? imageProfile
	  ), mobileImageProfile = imageProfilesShared ? imageProfile : normalizeImageProfile(
	    source.mobileImageProfile ?? imageProfile
	  ), listReaderMode = [
	    "floating",
	    "fullpage",
	    "embed-left",
	    "embed-right"
	  ].includes(String(source.listReaderMode)) ? source.listReaderMode : "floating", themeMode = ["light", "dark", "system"].includes(String(source.themeMode)) ? source.themeMode : "system", translationMode = ["bilingual", "translation"].includes(
	    String(source.translationMode)
	  ) ? source.translationMode : "original", jumpColor = String(source.jumpHighlightColor || "").trim().toLowerCase();
	  return Object.freeze({
	    topicReaderMode,
	    imageProfile,
	    imageProfilesShared,
	    floatingImageProfile,
	    fullpageImageProfile,
	    mobileImageProfile,
	    lightboxOriginalByDefault: source.lightboxOriginalByDefault === !0,
	    lightboxCommentsExpandedByDefault: source.lightboxCommentsExpandedByDefault === !0,
	    lightboxDescriptionExpanded: source.lightboxDescriptionExpanded === !0,
	    lightboxDescriptionHeight: Math.min(
	      lightboxMaximum,
	      Math.max(
	        LIGHTBOX_DESCRIPTION_HEIGHT_MIN,
	        Number.isFinite(lightboxHeight) ? lightboxHeight : LIGHTBOX_DESCRIPTION_HEIGHT_DEFAULT
	      )
	    ),
	    lightboxCommentsWidthPercent: Math.min(
	      LIGHTBOX_COMMENTS_WIDTH_MAX,
	      Math.max(
	        LIGHTBOX_COMMENTS_WIDTH_MIN,
	        Number.isFinite(Number(source.lightboxCommentsWidthPercent)) ? Number(source.lightboxCommentsWidthPercent) : LIGHTBOX_COMMENTS_WIDTH_DEFAULT
	      )
	    ),
	    themeMode,
	    fontRenderingEnabled: source.fontRenderingEnabled !== !1,
	    fontRenderingOnHost: source.fontRenderingOnHost === !0,
	    hostFontFamily: normalizeFontFamily(source.hostFontFamily, "system"),
	    hostFontCustomFamily: normalizeCustomFontFamily(source.hostFontCustomFamily),
	    hostFontWeight: normalizeFontWeight(source.hostFontWeight, 400),
	    hostFontColor: normalizeHexColor(source.hostFontColor),
	    hostEmbeddedTitleScale: roundedRange(
	      source.hostEmbeddedTitleScale,
	      READER_HOST_FONT_SCALE_DEFAULTS.title,
	      HOST_EMBED_SIZE_MIN,
	      HOST_EMBED_SIZE_MAX
	    ),
	    hostEmbeddedAvatarScale: roundedRange(
	      source.hostEmbeddedAvatarScale,
	      READER_HOST_FONT_SCALE_DEFAULTS.avatar,
	      HOST_EMBED_SIZE_MIN,
	      HOST_EMBED_SIZE_MAX
	    ),
	    hostEmbeddedStatsScale: roundedRange(
	      source.hostEmbeddedStatsScale,
	      READER_HOST_FONT_SCALE_DEFAULTS.stats,
	      HOST_EMBED_SIZE_MIN,
	      HOST_EMBED_SIZE_MAX
	    ),
	    hostEmbeddedLabelCardScale: roundedRange(
	      source.hostEmbeddedLabelCardScale,
	      READER_HOST_FONT_SCALE_DEFAULTS.labelCard,
	      HOST_EMBED_SIZE_MIN,
	      HOST_EMBED_SIZE_MAX
	    ),
	    fontProfile: normalizeFontProfile(source.fontProfile),
	    appearanceProfile: normalizeAppearanceProfile(source.appearanceProfile),
	    ...performance,
	    layoutProfile: normalizeLayoutProfile(source.layoutProfile, READER_LAYOUT_DEFAULT),
	    fullpageLayoutProfile: normalizeLayoutProfile(
	      source.fullpageLayoutProfile,
	      READER_FULLPAGE_LAYOUT_DEFAULT
	    ),
	    ...windowPreferences,
	    listReaderMode,
	    listReaderEmbedWidth: Number.isFinite(listWidth) ? Math.max(READER_EMBED_MIN_WIDTH, Math.round(listWidth)) : defaults.listReaderEmbedWidth,
	    ...composerPreferences,
	    historySortMode: source.historySortMode === "first-viewed" ? "first-viewed" : "recent-viewed",
	    bookmarkTabOrder: normalizeBookmarkTabOrder(source.bookmarkTabOrder),
	    historyButtonsAlwaysVisible: source.historyButtonsAlwaysVisible === !0,
	    readerQueueAlwaysVisibleWhenEmpty: source.readerQueueAlwaysVisibleWhenEmpty !== !1,
	    historyEdgeTriggerPercent: Number.isFinite(historyEdge) ? Math.min(
	      HISTORY_EDGE_TRIGGER_MAX,
	      Math.max(HISTORY_EDGE_TRIGGER_MIN, Math.round(historyEdge))
	    ) : HISTORY_EDGE_TRIGGER_DEFAULT,
	    loadingAnimation,
	    translationMode,
	    openTopicsAtFirstPost: source.openTopicsAtFirstPost === !0,
	    doubleEscapeToCloseReader: source.doubleEscapeToCloseReader === !0,
	    confirmNativeComposerClose: source.confirmNativeComposerClose === !0,
	    readerShortcutBindings: normalizeReaderShortcutBindings(source.readerShortcutBindings),
	    topicActionRailVisible: source.topicActionRailVisible !== !1,
	    topicActionRailFixed: source.topicActionRailFixed === !0,
	    topicActionRailMode: source.topicActionRailMode === "collapsed" ? "collapsed" : "compact",
	    topicActionRailPositions: normalizeTopicActionRailPositions(
	      Object.hasOwn(value, "topicActionRailPositions") ? source.topicActionRailPositions : null,
	      value.topicActionRailPosition
	    ),
	    expandNestedRepliesByDefault,
	    expandLeafNestedReplies,
	    aggregateDescendantReplies,
	    inlineReplyTreeMaxDepth: Math.min(
	      INLINE_REPLY_TREE_MAX_DEPTH,
	      Math.max(1, depth || INLINE_REPLY_TREE_DEFAULT_DEPTH)
	    ),
	    hideNestedReplyFloors: source.hideNestedReplyFloors === !0,
	    jumpHighlightColor: /^#[0-9a-f]{6}$/.test(jumpColor) ? jumpColor : JUMP_HIGHLIGHT_DEFAULTS.color,
	    jumpHighlightRadius: normalizeJumpValue("radius", source.jumpHighlightRadius),
	    jumpHighlightBorderWidth: normalizeJumpValue(
	      "borderWidth",
	      source.jumpHighlightBorderWidth
	    ),
	    jumpHighlightRate: normalizeJumpValue("rate", source.jumpHighlightRate),
	    jumpHighlightCount: normalizeJumpValue("count", source.jumpHighlightCount),
	    ...boostPreferences
	  });
	}
	function createReaderPreferencesConfigCodec(options) {
	  const defaults = createReaderPreferencesDefaults(options.environment), normalize = (value) => normalizeReaderPreferences(value, options.environment);
	  return new import_preferences_config_codec.PreferencesConfigCodec({
	    format: READER_CONFIG_EXPORT_FORMAT,
	    schemaVersion: READER_CONFIG_EXPORT_VERSION,
	    scriptVersion: options.scriptVersion,
	    defaults,
	    normalize,
	    legacyImportRules: [
	      {
	        missingDefaults: {
	          fullpageLayoutProfile: READER_FULLPAGE_LAYOUT_DEFAULT
	        }
	      },
	      {
	        missingDefaults: {
	          fullpageLayoutProfile: READER_FULLPAGE_LAYOUT_DEFAULT,
	          confirmNativeComposerClose: !0
	        }
	      },
	      {
	        missingDefaults: {
	          readerShortcutBindings: defaults.readerShortcutBindings,
	          topicActionRailMode: "compact",
	          inlineReplyTreeMaxDepth: 1
	        }
	      }
	    ],
	    ...options.now ? { now: options.now } : {}
	  });
	}
	function createReaderPreferencesRepository(options) {
	  const defaults = createReaderPreferencesDefaults(options.environment);
	  return new import_preferences_repository.PreferencesRepository({
	    key: READER_PREFERENCES_STORAGE_KEY,
	    storage: options.storage,
	    defaults,
	    normalize: (value) => normalizeReaderPreferences(value, options.environment),
	    prepareStored: prepareStoredReaderPreferences
	  });
	}
}, "9d8e1169c95b9bcf6b6ec631233d1a08199383c23b738540ca209a03ae59a72e");

/* Source: lite/src/state/reader-settings-config-manager.ts */
runtime.register("src/state/reader-settings-config-manager.js", function(module, exports, require) {
	var reader_settings_config_manager_exports = {};
	__export(reader_settings_config_manager_exports, {
	  READER_SETTINGS_CONFIG_EXPORT_VERSION: () => READER_SETTINGS_CONFIG_EXPORT_VERSION,
	  READER_SETTINGS_CONFIG_OMITTED_SECRETS: () => READER_SETTINGS_CONFIG_OMITTED_SECRETS,
	  ReaderSettingsConfigCodec: () => ReaderSettingsConfigCodec,
	  ReaderSettingsConfigManager: () => ReaderSettingsConfigManager
	});
	module.exports = __toCommonJS(reader_settings_config_manager_exports);
	var import_reader_custom_site_repository = require("../site/reader-custom-site-repository.js"), import_reader_webdav_model = require("../sync/reader-webdav-model.js"), import_reader_translation_config = require("../translation/reader-translation-config.js"), import_reader_preferences_schema = require("./reader-preferences-schema.js");
	const READER_SETTINGS_CONFIG_EXPORT_VERSION = 7, READER_SETTINGS_CONFIG_PREVIOUS_PORTABLE_VERSION = 6, READER_SETTINGS_CONFIG_OMITTED_SECRETS = Object.freeze([
	  "translation.apiKey",
	  "webDav.username",
	  "webDav.password"
	]);
	function invalidConfig(cause) {
	  return cause === void 0 ? new Error("invalid_config") : new Error("invalid_config", { cause });
	}
	function record(value) {
	  if (!value || typeof value != "object" || Array.isArray(value))
	    throw invalidConfig();
	  return value;
	}
	function exactKeys(value, expected) {
	  const actual = Object.keys(value).sort(), canonical = [...expected].sort();
	  if (actual.length !== canonical.length || actual.some((key, index) => key !== canonical[index])) throw invalidConfig();
	}
	function portableTranslationProfile(profile) {
	  return Object.freeze({
	    baseUrl: profile.baseUrl,
	    model: profile.model,
	    prompt: profile.prompt,
	    temperature: profile.temperature,
	    reasoningEffort: profile.reasoningEffort,
	    requestsPerMinute: profile.requestsPerMinute,
	    tokensPerMinute: profile.tokensPerMinute,
	    animation: profile.animation
	  });
	}
	function portableTranslationConfig(value) {
	  const normalized = (0, import_reader_translation_config.normalizeReaderTranslationConfig)(value);
	  return Object.freeze({
	    profiles: Object.freeze(normalized.profiles.map(
	      portableTranslationProfile
	    )),
	    activeBaseUrl: normalized.activeBaseUrl
	  });
	}
	function parsePortableTranslationConfig(value) {
	  if (value === null) return null;
	  const source = record(value);
	  if (exactKeys(source, ["profiles", "activeBaseUrl"]), !Array.isArray(source.profiles) || !source.profiles.length)
	    throw invalidConfig();
	  const profiles = source.profiles.map((value2) => {
	    const profile = record(value2);
	    exactKeys(profile, [
	      "baseUrl",
	      "model",
	      "prompt",
	      "temperature",
	      "reasoningEffort",
	      "requestsPerMinute",
	      "tokensPerMinute",
	      "animation"
	    ]);
	    const parsed = (0, import_reader_translation_config.normalizeReaderTranslationConfig)({
	      profiles: [{ ...profile, apiKey: "" }],
	      activeBaseUrl: profile.baseUrl
	    }).profiles[0];
	    if (!parsed) throw invalidConfig();
	    const portable = portableTranslationProfile(parsed);
	    if (Object.entries(portable).some(([key, normalizedValue]) => profile[key] !== normalizedValue))
	      throw invalidConfig();
	    return parsed;
	  });
	  if (new Set(profiles.map((profile) => profile.baseUrl)).size !== profiles.length)
	    throw invalidConfig();
	  const activeBaseUrl = (0, import_reader_translation_config.normalizeReaderTranslationBaseUrl)(source.activeBaseUrl);
	  if (activeBaseUrl !== source.activeBaseUrl || !profiles.some((profile) => profile.baseUrl === activeBaseUrl)) throw invalidConfig();
	  return Object.freeze({
	    profiles: Object.freeze(profiles),
	    activeBaseUrl
	  });
	}
	function portableWebDavConfig(value) {
	  const normalized = (0, import_reader_webdav_model.normalizeReaderWebDavConfig)(value);
	  return Object.freeze({
	    endpoint: normalized.endpoint,
	    remotePath: normalized.remotePath,
	    categories: Object.freeze({ ...normalized.categories }),
	    autoSyncEnabled: normalized.autoSyncEnabled,
	    autoSyncIntervalMinutes: normalized.autoSyncIntervalMinutes
	  });
	}
	function parsePortableWebDavConfig(value, legacyWithoutOfflineTopics = !1) {
	  if (value === null) return null;
	  const source = record(value);
	  exactKeys(source, [
	    "endpoint",
	    "remotePath",
	    "categories",
	    "autoSyncEnabled",
	    "autoSyncIntervalMinutes"
	  ]);
	  const categories = record(source.categories), expectedCategories = legacyWithoutOfflineTopics ? import_reader_webdav_model.READER_WEBDAV_CATEGORIES.filter((category) => category !== "offline-topics") : import_reader_webdav_model.READER_WEBDAV_CATEGORIES;
	  if (exactKeys(categories, expectedCategories), Object.values(categories).some((selected) => typeof selected != "boolean"))
	    throw invalidConfig();
	  const normalizedCategories = (0, import_reader_webdav_model.createReaderWebDavCategorySelection)(categories), normalized = (0, import_reader_webdav_model.normalizeReaderWebDavConfig)({
	    ...source,
	    categories: normalizedCategories,
	    username: "",
	    password: ""
	  }), portable = portableWebDavConfig(normalized);
	  if (portable.endpoint !== source.endpoint || portable.remotePath !== source.remotePath || portable.autoSyncEnabled !== source.autoSyncEnabled || portable.autoSyncIntervalMinutes !== source.autoSyncIntervalMinutes || import_reader_webdav_model.READER_WEBDAV_CATEGORIES.some((category) => portable.categories[category] !== normalizedCategories[category])) throw invalidConfig();
	  return normalized;
	}
	function parseCustomSites(value) {
	  if (!Array.isArray(value)) throw invalidConfig();
	  const sites = value.map((entry) => {
	    if (typeof entry != "string") throw invalidConfig();
	    const normalized = (0, import_reader_custom_site_repository.normalizeReaderCustomSiteHost)(entry);
	    if (!normalized || normalized !== entry || (0, import_reader_custom_site_repository.readerBuiltinDiscourseHost)(normalized)) throw invalidConfig();
	    return normalized;
	  });
	  if (new Set(sites).size !== sites.length) throw invalidConfig();
	  return Object.freeze([...sites].sort());
	}
	class ReaderSettingsConfigCodec {
	  #preferences;
	  constructor(preferences) {
	    this.#preferences = preferences;
	  }
	  export(input) {
	    const preferences = this.#preferences.export(input.preferences);
	    return Object.freeze({
	      ...preferences,
	      schemaVersion: READER_SETTINGS_CONFIG_EXPORT_VERSION,
	      customSites: parseCustomSites(input.customSites),
	      translation: input.translation ? portableTranslationConfig(input.translation) : null,
	      webDav: input.webDav ? portableWebDavConfig(input.webDav) : null,
	      omittedSecrets: READER_SETTINGS_CONFIG_OMITTED_SECRETS
	    });
	  }
	  import(payload) {
	    try {
	      return this.#import(payload);
	    } catch (cause) {
	      throw cause instanceof Error && cause.message === "invalid_config" ? cause : invalidConfig(cause);
	    }
	  }
	  #import(payload) {
	    const source = record(payload), schemaVersion = Number(source.schemaVersion);
	    if (schemaVersion === import_reader_preferences_schema.READER_CONFIG_EXPORT_VERSION) {
	      const preferences2 = this.#preferences.import(payload);
	      return Object.freeze({
	        sourceVersion: schemaVersion,
	        settingsCount: Number(source.settingsCount),
	        preferences: preferences2,
	        includesPortableSections: !1,
	        customSites: null,
	        translation: null,
	        webDav: null
	      });
	    }
	    if (schemaVersion !== READER_SETTINGS_CONFIG_EXPORT_VERSION && schemaVersion !== READER_SETTINGS_CONFIG_PREVIOUS_PORTABLE_VERSION || (exactKeys(source, [
	      "format",
	      "schemaVersion",
	      "scriptVersion",
	      "exportedAt",
	      "settingsCount",
	      "settings",
	      "customSites",
	      "translation",
	      "webDav",
	      "omittedSecrets"
	    ]), source.format !== import_reader_preferences_schema.READER_CONFIG_EXPORT_FORMAT || !Array.isArray(source.omittedSecrets) || source.omittedSecrets.length !== READER_SETTINGS_CONFIG_OMITTED_SECRETS.length || source.omittedSecrets.some((key, index) => key !== READER_SETTINGS_CONFIG_OMITTED_SECRETS[index]))) throw invalidConfig();
	    const preferences = this.#preferences.import({
	      format: source.format,
	      schemaVersion: import_reader_preferences_schema.READER_CONFIG_EXPORT_VERSION,
	      scriptVersion: source.scriptVersion,
	      exportedAt: source.exportedAt,
	      settingsCount: source.settingsCount,
	      settings: source.settings
	    });
	    return Object.freeze({
	      sourceVersion: schemaVersion,
	      settingsCount: Number(source.settingsCount),
	      preferences,
	      includesPortableSections: !0,
	      customSites: parseCustomSites(source.customSites),
	      translation: parsePortableTranslationConfig(source.translation),
	      webDav: parsePortableWebDavConfig(
	        source.webDav,
	        schemaVersion === READER_SETTINGS_CONFIG_PREVIOUS_PORTABLE_VERSION
	      )
	    });
	  }
	}
	class ReaderSettingsConfigManager {
	  #codec;
	  #defaults;
	  #preferences;
	  #customSites;
	  #translation;
	  #webDav;
	  constructor(options) {
	    this.#codec = options.codec, this.#defaults = options.defaults, this.#preferences = options.preferences, this.#customSites = options.customSites, this.#translation = options.translation, this.#webDav = options.webDav;
	  }
	  async export() {
	    return await Promise.all([
	      this.#customSites.load(),
	      this.#translation?.load(),
	      this.#webDav?.load()
	    ]), this.#codec.export({
	      preferences: this.#preferences.read(),
	      customSites: this.#customSites.snapshot,
	      translation: this.#translation?.snapshot.config ?? null,
	      webDav: this.#webDav?.snapshot.config ?? null
	    });
	  }
	  prepare(payload) {
	    return this.#codec.import(payload);
	  }
	  apply(prepared) {
	    return this.#apply(prepared, !0);
	  }
	  reset() {
	    return this.#apply(Object.freeze({
	      sourceVersion: READER_SETTINGS_CONFIG_EXPORT_VERSION,
	      settingsCount: Object.keys(this.#defaults).length,
	      preferences: this.#defaults,
	      includesPortableSections: !0,
	      customSites: Object.freeze([]),
	      translation: (0, import_reader_translation_config.createReaderTranslationDefaultConfig)(),
	      webDav: (0, import_reader_webdav_model.createReaderWebDavDefaultConfig)()
	    }), !1);
	  }
	  async #apply(prepared, preserveLocalSecrets) {
	    const rollbacks = [], skippedSections = [];
	    let customSitesApplied = !1, translationApplied = !1, webDavApplied = !1, preservedTranslationApiKeys = 0, preservedWebDavCredentials = !1, webDavAutoSyncDisabled = !1;
	    try {
	      if (prepared.includesPortableSections && prepared.customSites)
	        if (this.#customSites.writable) {
	          const previous = await this.#customSites.load();
	          await this.#customSites.replaceExternal(prepared.customSites), rollbacks.unshift(async () => {
	            await this.#customSites.replaceExternal(previous);
	          }), customSitesApplied = !0;
	        } else
	          skippedSections.push("customSites");
	      if (prepared.includesPortableSections && prepared.translation)
	        if (this.#translation) {
	          const previous = (await this.#translation.load()).config, apiKeys = new Map(previous.profiles.map((profile) => [
	            profile.baseUrl,
	            profile.apiKey
	          ])), next = Object.freeze({
	            ...prepared.translation,
	            profiles: Object.freeze(prepared.translation.profiles.map(
	              (profile) => {
	                const apiKey = preserveLocalSecrets ? apiKeys.get(profile.baseUrl) ?? "" : "";
	                return apiKey && (preservedTranslationApiKeys += 1), Object.freeze({ ...profile, apiKey });
	              }
	            ))
	          });
	          await this.#translation.saveConfig(next), rollbacks.unshift(async () => {
	            await this.#translation.saveConfig(previous);
	          }), translationApplied = !0;
	        } else
	          skippedSections.push("translation");
	      if (prepared.includesPortableSections && prepared.webDav)
	        if (this.#webDav) {
	          const previous = (await this.#webDav.load()).config, sameEndpoint = previous.endpoint === prepared.webDav.endpoint, username = preserveLocalSecrets && sameEndpoint ? previous.username : "", password = preserveLocalSecrets && sameEndpoint ? previous.password : "";
	          preservedWebDavCredentials = !!(username && password), webDavAutoSyncDisabled = prepared.webDav.autoSyncEnabled && !preservedWebDavCredentials;
	          const next = Object.freeze({
	            ...prepared.webDav,
	            username,
	            password,
	            autoSyncEnabled: prepared.webDav.autoSyncEnabled && preservedWebDavCredentials
	          });
	          await this.#webDav.saveConfig(next), rollbacks.unshift(async () => {
	            await this.#webDav.saveConfig(previous);
	          }), webDavApplied = !0;
	        } else
	          skippedSections.push("webDav");
	      const previousPreferences = this.#preferences.read();
	      return await this.#preferences.update(prepared.preferences), rollbacks.unshift(async () => {
	        await this.#preferences.update(previousPreferences);
	      }), Object.freeze({
	        sourceVersion: prepared.sourceVersion,
	        settingsCount: prepared.settingsCount,
	        customSitesApplied,
	        translationApplied,
	        webDavApplied,
	        preservedTranslationApiKeys,
	        preservedWebDavCredentials,
	        webDavAutoSyncDisabled,
	        skippedSections: Object.freeze(skippedSections)
	      });
	    } catch (cause) {
	      const rollbackFailures = [];
	      for (const rollback of rollbacks)
	        try {
	          await rollback();
	        } catch (rollbackCause) {
	          rollbackFailures.push(rollbackCause);
	        }
	      throw rollbackFailures.length ? new Error("设置配置写入失败且回滚不完整", {
	        cause: new AggregateError([cause, ...rollbackFailures])
	      }) : cause;
	    }
	  }
	}
}, "c6f5319872ac0a9877996fe8c70a6239ef32a10dba0e61bc2b93a7953c2d7e25");

/* Source: lite/src/stream/reply-tree-viewport-layout.ts */
runtime.register("src/stream/reply-tree-viewport-layout.js", function(module, exports, require) {
	var reply_tree_viewport_layout_exports = {};
	__export(reply_tree_viewport_layout_exports, {
	  ReplyTreeViewportLayout: () => ReplyTreeViewportLayout
	});
	module.exports = __toCommonJS(reply_tree_viewport_layout_exports);
	function lowerBoundEndingAfter(count, endAt, offset) {
	  let low = 0, high = count;
	  for (; low < high; ) {
	    const middle = Math.floor((low + high) / 2);
	    endAt(middle) <= offset ? low = middle + 1 : high = middle;
	  }
	  return low;
	}
	function lowerBoundStartingAtOrAfter(count, startAt, offset) {
	  let low = 0, high = count;
	  for (; low < high; ) {
	    const middle = Math.floor((low + high) / 2);
	    startAt(middle) < offset ? low = middle + 1 : high = middle;
	  }
	  return low;
	}
	class ReplyTreeViewportLayout {
	  topology;
	  rootLayout;
	  #estimatedPostSize;
	  #measuredOwnSizes = /* @__PURE__ */ new Map();
	  #revision = "";
	  #branches = /* @__PURE__ */ new Map();
	  #visiblePostNumbers = Object.freeze([]);
	  constructor(topology, rootLayout, estimatedPostSize) {
	    if (!Number.isFinite(estimatedPostSize) || estimatedPostSize <= 0)
	      throw new RangeError("estimatedPostSize 必须是正有限数值");
	    this.topology = topology, this.rootLayout = rootLayout, this.#estimatedPostSize = estimatedPostSize;
	  }
	  /**
	   * 记录已完整投影节点自身的真实高度,不包含它的 replyList 子树。
	   *
	   * 节点退出正文窗口后,祖先壳和虚拟 spacer 必须继续占用相同高度;否则大正文会
	   * 在真实高度与固定估算之间反复切换,浏览器锚定补偿又会被误当成新的窗口输入。
	   */
	  measureOwnSize(postNumber, blockSize) {
	    this.#syncRevision();
	    const normalized = Math.round(Number(blockSize));
	    if (!Number.isFinite(normalized) || normalized <= 0 || (this.#measuredOwnSizes.get(postNumber) ?? this.#estimatedPostSize) === normalized) return !1;
	    this.#measuredOwnSizes.set(postNumber, normalized);
	    const rootPostNumber = this.topology.rootOf(postNumber), branch = rootPostNumber === void 0 ? void 0 : this.#branches.get(rootPostNumber), index = branch?.indexByPost.get(postNumber);
	    return branch && index !== void 0 && (branch.prefixDirtyFrom = Math.min(branch.prefixDirtyFrom, index)), !0;
	  }
	  /** 当前计划中真正与物理视口相交的 DFS 节点,供锚点读取缩小几何查询范围。 */
	  get visiblePostNumbers() {
	    return this.#visiblePostNumbers;
	  }
	  plan(rootWindow, input) {
	    this.#syncRevision();
	    const materializationStep = input.viewportSize * Math.max(0, input.materializationStepScreens ?? 0), materializationStart = materializationStep > 0 ? Math.floor(input.scrollOffset / materializationStep) * materializationStep : input.scrollOffset, overscanStart = Math.max(
	      0,
	      materializationStart - input.viewportSize * (input.overscanBeforeScreens ?? 1)
	    ), overscanEnd = materializationStart + materializationStep + input.viewportSize * (1 + (input.overscanAfterScreens ?? 1)), visibleStart = input.scrollOffset, visibleEnd = input.scrollOffset + input.viewportSize, candidates = [];
	    for (const rootPostNumber of rootWindow.postNumbers) {
	      const branch = this.#branch(rootPostNumber), branchPrefix = this.#branchPrefix(branch), rootStart = this.rootLayout.offsetOf(rootPostNumber), rootSize = this.rootLayout.blockSizeOf(rootPostNumber);
	      if (rootStart === void 0 || rootSize === void 0 || branch.entries.length === 0) continue;
	      const branchSize = branchPrefix.at(-1) ?? 0;
	      if (overscanEnd <= rootStart || overscanStart >= rootStart + rootSize) continue;
	      const localStart = Math.min(
	        Math.max(0, branchSize - 1),
	        Math.max(0, overscanStart - rootStart)
	      ), localEnd = Math.min(
	        branchSize,
	        Math.max(localStart + 1, overscanEnd - rootStart)
	      ), startIndex = lowerBoundEndingAfter(
	        branch.entries.length,
	        (index) => branchPrefix[index + 1] ?? 0,
	        localStart
	      ), endIndex = lowerBoundStartingAtOrAfter(
	        branch.entries.length,
	        (index) => branchPrefix[index] ?? 0,
	        localEnd
	      );
	      for (let index = startIndex; index < Math.max(startIndex + 1, endIndex) && index < branch.entries.length; index += 1)
	        candidates.push(Object.freeze({
	          branch,
	          entryIndex: index,
	          absoluteStart: rootStart + (branchPrefix[index] ?? 0),
	          absoluteEnd: rootStart + (branchPrefix[index + 1] ?? 0)
	        }));
	    }
	    this.#visiblePostNumbers = Object.freeze([
	      ...new Set(candidates.filter(
	        (candidate) => candidate.absoluteEnd > visibleStart && candidate.absoluteStart < visibleEnd
	      ).map(
	        (candidate) => candidate.branch.entries[candidate.entryIndex].postNumber
	      ))
	    ]);
	    const content = new Set(this.#budget(
	      candidates,
	      visibleStart,
	      visibleEnd,
	      input.maxMountedPostCount
	    )), preservePostNumber = input.preservePostNumber;
	    if (preservePostNumber !== void 0) {
	      const preserveRootPostNumber = this.topology.rootOf(preservePostNumber);
	      preserveRootPostNumber !== void 0 && rootWindow.postNumbers.includes(preserveRootPostNumber) && content.add(preservePostNumber);
	    }
	    const mounted = new Set(content);
	    for (const postNumber of content) {
	      let parentPostNumber = this.topology.parentOf(postNumber);
	      for (; parentPostNumber != null && !mounted.has(parentPostNumber); )
	        mounted.add(parentPostNumber), parentPostNumber = this.topology.parentOf(parentPostNumber);
	    }
	    const shells = new Set(
	      [...mounted].filter((postNumber) => !content.has(postNumber))
	    ), ownSizes = /* @__PURE__ */ new Map(), mountedChildrenByParent = /* @__PURE__ */ new Map();
	    for (const postNumber of mounted) {
	      const parentPostNumber = this.topology.parentOf(postNumber);
	      if (parentPostNumber == null || !mounted.has(parentPostNumber)) continue;
	      const children = mountedChildrenByParent.get(parentPostNumber) ?? [];
	      children.push(postNumber), mountedChildrenByParent.set(parentPostNumber, children);
	    }
	    const childLayouts = /* @__PURE__ */ new Map();
	    for (const postNumber of mounted) {
	      const rootPostNumber = this.topology.rootOf(postNumber);
	      if (rootPostNumber === void 0) continue;
	      const branch = this.#branch(rootPostNumber), branchPrefix = this.#branchPrefix(branch), index = branch.indexByPost.get(postNumber), rootSize = this.rootLayout.blockSizeOf(rootPostNumber);
	      if (index === void 0 || rootSize === void 0) continue;
	      ownSizes.set(postNumber, this.#ownSize(postNumber));
	      const parentEntry = branch.entries[index], mountedChildren = mountedChildrenByParent.get(postNumber) ?? [];
	      mountedChildren.sort(
	        (left, right) => (branch.indexByPost.get(left) ?? 0) - (branch.indexByPost.get(right) ?? 0)
	      );
	      const beforeSizes = [];
	      let cursor = index + 1;
	      for (const childPostNumber of mountedChildren) {
	        const childIndex = branch.indexByPost.get(childPostNumber);
	        childIndex !== void 0 && (beforeSizes.push(Math.max(
	          0,
	          (branchPrefix[childIndex] ?? 0) - (branchPrefix[cursor] ?? 0)
	        )), cursor = branch.entries[childIndex].subtreeEndIndex);
	      }
	      childLayouts.set(postNumber, Object.freeze({
	        postNumbers: Object.freeze([...mountedChildren]),
	        beforeSizes: Object.freeze(beforeSizes),
	        afterSize: Math.max(
	          0,
	          (branchPrefix[parentEntry.subtreeEndIndex] ?? 0) - (branchPrefix[cursor] ?? 0)
	        )
	      }));
	    }
	    const rootVirtualInsets = this.#externalizeWindowEdgeSpacers(
	      rootWindow,
	      content,
	      shells,
	      ownSizes,
	      childLayouts
	    );
	    return Object.freeze({
	      mountedPostNumbers: mounted,
	      contentPostNumbers: content,
	      shellPostNumbers: shells,
	      ownSizes,
	      rootVirtualInsets,
	      childLayouts
	    });
	  }
	  /**
	   * 窗口首尾之外的 DFS 高度属于流级占位,不应留在递归 replyList 内穿过视口。
	   * 这里只改占位的归属;正文集合、祖先闭包和半屏物化分段均保持不变。
	   */
	  #externalizeWindowEdgeSpacers(rootWindow, content, shells, ownSizes, childLayouts) {
	    const firstRoot = rootWindow.postNumbers[0], lastRoot = rootWindow.postNumbers.at(-1), edgeRoots = new Set(
	      [firstRoot, lastRoot].filter(
	        (postNumber) => postNumber !== void 0
	      )
	    ), insets = /* @__PURE__ */ new Map();
	    for (const rootPostNumber of edgeRoots) {
	      const branch = this.#branch(rootPostNumber);
	      let firstIndex = Number.POSITIVE_INFINITY, lastIndex = -1;
	      for (const postNumber of content) {
	        const index = branch.indexByPost.get(postNumber);
	        index !== void 0 && (firstIndex = Math.min(firstIndex, index), lastIndex = Math.max(lastIndex, index));
	      }
	      if (lastIndex < 0) continue;
	      const prefix = this.#branchPrefix(branch), beforeSize = rootPostNumber === firstRoot ? prefix[firstIndex] ?? 0 : 0, afterSize = rootPostNumber === lastRoot ? Math.max(
	        0,
	        (prefix.at(-1) ?? 0) - (prefix[lastIndex + 1] ?? 0)
	      ) : 0;
	      beforeSize > 0 && this.#trimLeadingPath(
	        branch,
	        firstIndex,
	        shells,
	        ownSizes,
	        childLayouts
	      ), afterSize > 0 && this.#trimTrailingPath(branch, lastIndex, childLayouts), (beforeSize > 0 || afterSize > 0) && insets.set(rootPostNumber, Object.freeze({ beforeSize, afterSize }));
	    }
	    return insets;
	  }
	  #trimLeadingPath(branch, entryIndex, shells, ownSizes, childLayouts) {
	    const path = this.#entryPath(branch, entryIndex);
	    for (let index = 0; index + 1 < path.length; index += 1) {
	      const parentPostNumber = path[index], childPostNumber = path[index + 1];
	      shells.has(parentPostNumber) && ownSizes.set(parentPostNumber, 0);
	      const layout = childLayouts.get(parentPostNumber), childIndex = layout?.postNumbers.indexOf(childPostNumber) ?? -1;
	      if (!layout || childIndex < 0 || layout.beforeSizes[childIndex] === 0)
	        continue;
	      const beforeSizes = [...layout.beforeSizes];
	      beforeSizes[childIndex] = 0, childLayouts.set(parentPostNumber, Object.freeze({
	        ...layout,
	        beforeSizes: Object.freeze(beforeSizes)
	      }));
	    }
	  }
	  #trimTrailingPath(branch, entryIndex, childLayouts) {
	    for (const postNumber of this.#entryPath(branch, entryIndex)) {
	      const layout = childLayouts.get(postNumber);
	      !layout || layout.afterSize === 0 || childLayouts.set(postNumber, Object.freeze({
	        ...layout,
	        afterSize: 0
	      }));
	    }
	  }
	  #entryPath(branch, entryIndex) {
	    const path = [];
	    let entry = branch.entries[entryIndex];
	    for (; entry && (path.push(entry.postNumber), entry.parentPostNumber !== null); ) {
	      const parentIndex = branch.indexByPost.get(entry.parentPostNumber);
	      entry = parentIndex === void 0 ? void 0 : branch.entries[parentIndex];
	    }
	    return Object.freeze(path.reverse());
	  }
	  offsetOf(postNumber) {
	    this.#syncRevision();
	    const rootPostNumber = this.topology.rootOf(postNumber);
	    if (rootPostNumber === void 0) return;
	    const rootOffset = this.rootLayout.offsetOf(rootPostNumber), rootSize = this.rootLayout.blockSizeOf(rootPostNumber);
	    if (rootOffset === void 0 || rootSize === void 0) return;
	    const branch = this.#branch(rootPostNumber), branchPrefix = this.#branchPrefix(branch), index = branch.indexByPost.get(postNumber);
	    if (!(index === void 0 || !branch.entries.length))
	      return rootOffset + Math.min(
	        branchPrefix[index] ?? 0,
	        Math.max(0, rootSize - 1)
	      );
	  }
	  #budget(candidates, visibleStart, visibleEnd, rawBudget) {
	    if (!candidates.length) return /* @__PURE__ */ new Set();
	    const visibleIndexes = [];
	    for (let index = 0; index < candidates.length; index += 1) {
	      const candidate = candidates[index];
	      candidate.absoluteEnd > visibleStart && candidate.absoluteStart < visibleEnd && visibleIndexes.push(index);
	    }
	    const firstVisible = visibleIndexes[0] ?? 0, lastVisible = visibleIndexes.at(-1) ?? firstVisible, budget = rawBudget === void 0 ? candidates.length : Math.max(rawBudget, lastVisible - firstVisible + 1);
	    let start = firstVisible, end = lastVisible + 1;
	    for (; end - start < budget && (start > 0 || end < candidates.length); ) {
	      const beforeDistance = start > 0 ? Math.max(0, visibleStart - candidates[start - 1].absoluteEnd) : Number.POSITIVE_INFINITY, afterDistance = end < candidates.length ? Math.max(0, candidates[end].absoluteStart - visibleEnd) : Number.POSITIVE_INFINITY;
	      if (beforeDistance <= afterDistance && start > 0) start -= 1;
	      else if (end < candidates.length) end += 1;
	      else break;
	    }
	    return new Set(
	      candidates.slice(start, end).map(
	        (candidate) => candidate.branch.entries[candidate.entryIndex].postNumber
	      )
	    );
	  }
	  #syncRevision() {
	    this.#revision !== this.topology.revision && (this.#revision = this.topology.revision, this.#branches.clear(), this.#visiblePostNumbers = Object.freeze([]));
	  }
	  #branch(rootPostNumber) {
	    const cached = this.#branches.get(rootPostNumber);
	    if (cached) return cached;
	    const entries = [], indexByPost = /* @__PURE__ */ new Map(), stack = [{
	      postNumber: rootPostNumber,
	      parentPostNumber: null,
	      depth: 0,
	      closing: !1
	    }];
	    for (; stack.length; ) {
	      const current = stack.pop();
	      if (current.closing) {
	        const index2 = indexByPost.get(current.postNumber);
	        index2 !== void 0 && (entries[index2].subtreeEndIndex = entries.length);
	        continue;
	      }
	      const index = entries.length;
	      indexByPost.set(current.postNumber, index), entries.push({
	        postNumber: current.postNumber,
	        parentPostNumber: current.parentPostNumber,
	        depth: current.depth,
	        subtreeEndIndex: index + 1
	      }), stack.push({ ...current, closing: !0 });
	      const children = this.topology.childrenOf(current.postNumber);
	      for (let childIndex = children.length - 1; childIndex >= 0; childIndex -= 1)
	        stack.push({
	          postNumber: children[childIndex],
	          parentPostNumber: current.postNumber,
	          depth: current.depth + 1,
	          closing: !1
	        });
	    }
	    const projection = {
	      entries: Object.freeze(entries),
	      indexByPost,
	      prefix: new Array(entries.length + 1).fill(0),
	      prefixDirtyFrom: 0
	    };
	    return this.#branches.set(rootPostNumber, projection), projection;
	  }
	  #ownSize(postNumber) {
	    return this.#measuredOwnSizes.get(postNumber) ?? this.#estimatedPostSize;
	  }
	  #branchPrefix(branch) {
	    for (let index = branch.prefixDirtyFrom; index < branch.entries.length; index += 1)
	      branch.prefix[index + 1] = (branch.prefix[index] ?? 0) + this.#ownSize(branch.entries[index].postNumber);
	    return branch.prefixDirtyFrom = branch.entries.length, branch.prefix;
	  }
	}
}, "a5c984546f0ef4ebd63fd285cb274678fb101407c6aa63f87f9104deff2e0efc");

/* Source: lite/src/stream/reply-tree-virtual-layout-controller.ts */
runtime.register("src/stream/reply-tree-virtual-layout-controller.js", function(module, exports, require) {
	var reply_tree_virtual_layout_controller_exports = {};
	__export(reply_tree_virtual_layout_controller_exports, {
	  ReplyTreeVirtualLayoutController: () => ReplyTreeVirtualLayoutController
	});
	module.exports = __toCommonJS(reply_tree_virtual_layout_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReplyTreeVirtualLayoutController {
	  repository;
	  topology;
	  layout;
	  scope;
	  constructor(repository, layout, parentScope, topology = repository.topology) {
	    this.repository = repository, this.topology = topology, this.layout = layout, this.scope = import_lifecycle.LifecycleScope.ownedBy(parentScope), this.syncRoots(), repository.changes.subscribe(() => this.syncRoots(), this.scope);
	  }
	  syncRoots() {
	    this.layout.setRoots(this.topology.rootBranches());
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	}
}, "901b94a73439d93951b30737ec7953945db7993756d113c53b43e072d0eb568b");

/* Source: lite/src/stream/virtual-root-layout.ts */
runtime.register("src/stream/virtual-root-layout.js", function(module, exports, require) {
	var virtual_root_layout_exports = {};
	__export(virtual_root_layout_exports, {
	  VirtualRootLayout: () => VirtualRootLayout
	});
	module.exports = __toCommonJS(virtual_root_layout_exports);
	var import_identifiers = require("../discourse/identifiers.js");
	function finiteNonNegative(value, name) {
	  if (!Number.isFinite(value) || value < 0)
	    throw new RangeError(`${name} 必须是非负有限数值`);
	  return value;
	}
	function positiveSize(value, name) {
	  if (!Number.isFinite(value) || value <= 0)
	    throw new RangeError(`${name} 必须是正有限数值`);
	  return value;
	}
	function assertPostNumber(value) {
	  try {
	    (0, import_identifiers.discoursePostNumber)(value);
	  } catch {
	    throw new RangeError("根楼层号必须是正安全整数");
	  }
	}
	function positiveSafeInteger(value, name) {
	  if (!Number.isSafeInteger(value) || value <= 0)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return value;
	}
	class VirtualRootLayout {
	  #estimatedSize;
	  #estimateSubtreeSize;
	  #measuredSizes = /* @__PURE__ */ new Map();
	  #postNumbers = [];
	  #subtreePostCounts = [];
	  #indexByPost = /* @__PURE__ */ new Map();
	  #prefix = [0];
	  #subtreeCountPrefix = [0];
	  #dirtyFrom = 0;
	  constructor(estimatedSize, estimateSubtreeSize = !1) {
	    this.#estimatedSize = positiveSize(estimatedSize, "estimatedSize"), this.#estimateSubtreeSize = estimateSubtreeSize;
	  }
	  setRoots(roots) {
	    const previousSubtreePostCountByPost = new Map(
	      this.#postNumbers.map((postNumber, index) => [
	        postNumber,
	        this.#subtreePostCounts[index] ?? 1
	      ])
	    ), unique = /* @__PURE__ */ new Set(), subtreePostCountByPost = /* @__PURE__ */ new Map();
	    for (const root of roots) {
	      const postNumber = typeof root == "number" ? root : root.postNumber;
	      if (assertPostNumber(postNumber), unique.has(postNumber)) throw new Error(`根楼层 #${postNumber} 重复`);
	      unique.add(postNumber), subtreePostCountByPost.set(
	        postNumber,
	        typeof root == "number" ? 1 : positiveSafeInteger(root.subtreePostCount, "subtreePostCount")
	      );
	    }
	    this.#postNumbers = [...unique].sort((left, right) => left - right), this.#subtreePostCounts = this.#postNumbers.map(
	      (postNumber) => subtreePostCountByPost.get(postNumber) ?? 1
	    );
	    for (const postNumber of this.#measuredSizes.keys()) {
	      const subtreePostCountChanged = this.#estimateSubtreeSize && previousSubtreePostCountByPost.get(postNumber) !== subtreePostCountByPost.get(postNumber);
	      (!unique.has(postNumber) || subtreePostCountChanged) && this.#measuredSizes.delete(postNumber);
	    }
	    this.#indexByPost = new Map(
	      this.#postNumbers.map((postNumber, index) => [postNumber, index])
	    ), this.#prefix = new Array(this.#postNumbers.length + 1).fill(0), this.#subtreeCountPrefix = new Array(this.#postNumbers.length + 1).fill(0);
	    for (let index = 0; index < this.#postNumbers.length; index += 1)
	      this.#subtreeCountPrefix[index + 1] = (this.#subtreeCountPrefix[index] ?? 0) + (this.#subtreePostCounts[index] ?? 1);
	    this.#dirtyFrom = 0;
	  }
	  roots() {
	    return Object.freeze([...this.#postNumbers]);
	  }
	  offsetOf(postNumber) {
	    assertPostNumber(postNumber);
	    const index = this.#indexByPost.get(postNumber);
	    if (index !== void 0)
	      return this.#ensurePrefix(), this.#prefix[index] ?? 0;
	  }
	  blockSizeOf(postNumber) {
	    assertPostNumber(postNumber);
	    const index = this.#indexByPost.get(postNumber);
	    return index === void 0 ? void 0 : this.#sizeAt(index);
	  }
	  measure(postNumber, blockSize, anchorPostNumber) {
	    assertPostNumber(postNumber);
	    const normalizedSize = positiveSize(blockSize, "blockSize"), index = this.#indexByPost.get(postNumber);
	    if (index === void 0)
	      return Object.freeze({ changed: !1, sizeDelta: 0, scrollCompensation: 0 });
	    const previousSize = this.#sizeAt(index);
	    if (previousSize === normalizedSize)
	      return Object.freeze({ changed: !1, sizeDelta: 0, scrollCompensation: 0 });
	    this.#measuredSizes.set(postNumber, normalizedSize), this.#dirtyFrom = Math.min(this.#dirtyFrom, index);
	    const sizeDelta = normalizedSize - previousSize, anchorIndex = anchorPostNumber === void 0 ? void 0 : this.#indexByPost.get(anchorPostNumber);
	    return Object.freeze({
	      changed: !0,
	      sizeDelta,
	      scrollCompensation: anchorIndex !== void 0 && index < anchorIndex ? sizeDelta : 0
	    });
	  }
	  window(input) {
	    const scrollOffset = finiteNonNegative(input.scrollOffset, "scrollOffset"), viewportSize = positiveSize(input.viewportSize, "viewportSize"), beforeScreens = finiteNonNegative(
	      input.overscanBeforeScreens ?? 1,
	      "overscanBeforeScreens"
	    ), afterScreens = finiteNonNegative(
	      input.overscanAfterScreens ?? 1,
	      "overscanAfterScreens"
	    ), materializationStepScreens = finiteNonNegative(
	      input.materializationStepScreens ?? 0,
	      "materializationStepScreens"
	    ), maxMountedPostCount = input.maxMountedPostCount === void 0 ? void 0 : positiveSafeInteger(
	      input.maxMountedPostCount,
	      "maxMountedPostCount"
	    );
	    this.#ensurePrefix();
	    const totalSize = this.#prefix.at(-1) ?? 0;
	    if (!this.#postNumbers.length)
	      return Object.freeze({
	        startIndex: 0,
	        endIndex: 0,
	        postNumbers: Object.freeze([]),
	        visiblePostNumbers: Object.freeze([]),
	        atStart: !0,
	        atEnd: !0,
	        beforeSpacer: 0,
	        afterSpacer: 0,
	        totalSize: 0
	      });
	    const materializationStep = viewportSize * materializationStepScreens, materializationStart = materializationStep > 0 ? Math.floor(scrollOffset / materializationStep) * materializationStep : scrollOffset, rangeStart = Math.max(
	      0,
	      materializationStart - viewportSize * beforeScreens
	    ), rangeEnd = Math.min(
	      totalSize,
	      materializationStart + materializationStep + viewportSize * (1 + afterScreens)
	    ), overscanStartIndex = this.#firstBlockEndingAfter(rangeStart), overscanEndIndex = Math.min(
	      this.#postNumbers.length,
	      Math.max(
	        overscanStartIndex + 1,
	        this.#firstBlockStartingAtOrAfter(rangeEnd)
	      )
	    ), visibleStartIndex = this.#firstBlockEndingAfter(
	      Math.min(scrollOffset, Math.max(0, totalSize - 1))
	    ), visibleEndIndex = Math.min(
	      this.#postNumbers.length,
	      Math.max(
	        visibleStartIndex + 1,
	        this.#firstBlockStartingAtOrAfter(
	          Math.min(totalSize, scrollOffset + viewportSize)
	        )
	      )
	    );
	    let { startIndex, endIndex } = maxMountedPostCount === void 0 ? {
	      startIndex: overscanStartIndex,
	      endIndex: overscanEndIndex
	    } : this.#budgetedRange({
	      overscanStartIndex,
	      overscanEndIndex,
	      visibleStartIndex,
	      visibleEndIndex,
	      maxMountedPostCount,
	      scrollOffset,
	      viewportSize
	    });
	    const preserveRootIndex = input.preserveRootPostNumber === void 0 ? void 0 : this.#indexByPost.get(input.preserveRootPostNumber);
	    preserveRootIndex !== void 0 && (startIndex = Math.min(startIndex, preserveRootIndex), endIndex = Math.max(endIndex, preserveRootIndex + 1));
	    const boundedEnd = Math.min(this.#postNumbers.length, endIndex);
	    return Object.freeze({
	      startIndex,
	      endIndex: boundedEnd,
	      postNumbers: Object.freeze(this.#postNumbers.slice(startIndex, boundedEnd)),
	      visiblePostNumbers: Object.freeze(
	        this.#postNumbers.slice(visibleStartIndex, visibleEndIndex)
	      ),
	      atStart: scrollOffset <= 10,
	      atEnd: scrollOffset + viewportSize >= Math.max(0, totalSize - 16),
	      beforeSpacer: this.#prefix[startIndex] ?? 0,
	      afterSpacer: Math.max(0, totalSize - (this.#prefix[boundedEnd] ?? totalSize)),
	      totalSize
	    });
	  }
	  #budgetedRange(input) {
	    let startIndex = input.visibleStartIndex, endIndex = input.visibleEndIndex, mountedPostCount = this.#subtreePostCountBetween(startIndex, endIndex), beforeBlocked = !1, afterBlocked = !1;
	    for (; !beforeBlocked && startIndex > input.overscanStartIndex || !afterBlocked && endIndex < input.overscanEndIndex; ) {
	      const beforeIndex = startIndex - 1, afterIndex = endIndex, beforeDistance = !beforeBlocked && beforeIndex >= input.overscanStartIndex ? Math.max(
	        0,
	        input.scrollOffset - (this.#prefix[beforeIndex + 1] ?? 0)
	      ) : Number.POSITIVE_INFINITY, afterDistance = !afterBlocked && afterIndex < input.overscanEndIndex ? Math.max(
	        0,
	        (this.#prefix[afterIndex] ?? 0) - (input.scrollOffset + input.viewportSize)
	      ) : Number.POSITIVE_INFINITY;
	      if (beforeDistance === Number.POSITIVE_INFINITY && afterDistance === Number.POSITIVE_INFINITY)
	        break;
	      const addBefore = beforeDistance <= afterDistance, candidateIndex = addBefore ? beforeIndex : afterIndex, candidateWeight = this.#subtreePostCounts[candidateIndex] ?? 1;
	      if (mountedPostCount + candidateWeight > input.maxMountedPostCount) {
	        addBefore ? beforeBlocked = !0 : afterBlocked = !0;
	        continue;
	      }
	      mountedPostCount += candidateWeight, addBefore ? startIndex = candidateIndex : endIndex = candidateIndex + 1;
	    }
	    return Object.freeze({ startIndex, endIndex });
	  }
	  #subtreePostCountBetween(startIndex, endIndex) {
	    return (this.#subtreeCountPrefix[endIndex] ?? 0) - (this.#subtreeCountPrefix[startIndex] ?? 0);
	  }
	  #sizeAt(index) {
	    const postNumber = this.#postNumbers[index];
	    return this.#measuredSizes.get(postNumber) ?? this.#estimatedSize * (this.#estimateSubtreeSize ? this.#subtreePostCounts[index] ?? 1 : 1);
	  }
	  #ensurePrefix() {
	    const start = Math.min(this.#dirtyFrom, this.#postNumbers.length);
	    start === 0 && (this.#prefix[0] = 0);
	    for (let index = start; index < this.#postNumbers.length; index += 1)
	      this.#prefix[index + 1] = (this.#prefix[index] ?? 0) + this.#sizeAt(index);
	    this.#prefix.length = this.#postNumbers.length + 1, this.#dirtyFrom = this.#postNumbers.length;
	  }
	  #firstBlockEndingAfter(offset) {
	    let low = 0, high = this.#postNumbers.length;
	    for (; low < high; ) {
	      const middle = Math.floor((low + high) / 2);
	      (this.#prefix[middle + 1] ?? 0) <= offset ? low = middle + 1 : high = middle;
	    }
	    return Math.min(low, this.#postNumbers.length - 1);
	  }
	  #firstBlockStartingAtOrAfter(offset) {
	    let low = 0, high = this.#postNumbers.length;
	    for (; low < high; ) {
	      const middle = Math.floor((low + high) / 2);
	      (this.#prefix[middle] ?? 0) < offset ? low = middle + 1 : high = middle;
	    }
	    return low;
	  }
	}
}, "a6d89feae412cca138ee496e05b3299ce4d8c5ca5526cf89aa46bc1024260320");

/* Source: lite/src/stream/virtual-stream-dom-controller.ts */
runtime.register("src/stream/virtual-stream-dom-controller.js", function(module, exports, require) {
	var virtual_stream_dom_controller_exports = {};
	__export(virtual_stream_dom_controller_exports, {
	  VirtualStreamDomController: () => VirtualStreamDomController
	});
	module.exports = __toCommonJS(virtual_stream_dom_controller_exports);
	class VirtualStreamDomController {
	  repository;
	  layout;
	  streamView;
	  domOwner;
	  #prepareRoots;
	  #roots;
	  #connectedRoots = /* @__PURE__ */ new Set();
	  #committed = !1;
	  constructor(repository, layout, streamView, domOwner, options = {}) {
	    if (streamView.slots.rootList !== domOwner.rootList)
	      throw new Error("ReplyTreeDomOwner 必须挂载到 VirtualStreamView.rootList");
	    this.repository = repository, this.layout = layout, this.streamView = streamView, this.domOwner = domOwner, this.#prepareRoots = options.prepareRoots ?? (() => {
	    }), this.#roots = options.roots ?? (() => repository.topology.roots());
	  }
	  commit(input) {
	    const window = this.layout.window(input), mountPlan = this.#prepareRoots(window.postNumbers, input, window), desired = new Set(window.postNumbers), connectedBefore = this.#committed ? this.#connectedRoots : new Set(this.#roots().filter((postNumber) => {
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      return !!root && this.streamView.slots.rootList.contains(root);
	    })), tree = this.domOwner.sync(desired, mountPlan);
	    for (let index = 0; index < window.postNumbers.length; index += 1) {
	      const postNumber = window.postNumbers[index], root = this.domOwner.view(postNumber)?.slots.root, zebra = (window.startIndex + index) % 2 === 1;
	      root?.classList.contains("ldp-zebra-alt") !== zebra && root?.classList.toggle("ldp-zebra-alt", zebra);
	      const beforeZebra = index + 1 < window.postNumbers.length && (window.startIndex + index + 1) % 2 === 1;
	      root?.classList.contains("ldp-before-zebra") !== beforeZebra && root?.classList.toggle("ldp-before-zebra", beforeZebra);
	    }
	    const connectedAfter = new Set(
	      [...desired].filter(
	        (postNumber) => {
	          const root = this.domOwner.view(postNumber)?.slots.root;
	          return !!root && this.streamView.slots.rootList.contains(root);
	        }
	      )
	    ), attachedRoots = [...connectedAfter].filter((postNumber) => !connectedBefore.has(postNumber)), detachedRoots = [...connectedBefore].filter((postNumber) => !connectedAfter.has(postNumber));
	    this.#connectedRoots = connectedAfter, this.#committed = !0;
	    const firstRootInset = window.postNumbers[0] === void 0 ? void 0 : mountPlan?.rootVirtualInsets?.get(window.postNumbers[0]), lastRootPostNumber = window.postNumbers.at(-1), lastRootInset = lastRootPostNumber === void 0 ? void 0 : mountPlan?.rootVirtualInsets?.get(lastRootPostNumber);
	    return this.streamView.setSpacerSizes(
	      window.beforeSpacer + (firstRootInset?.beforeSize ?? 0),
	      window.afterSpacer + (lastRootInset?.afterSize ?? 0)
	    ), Object.freeze({
	      window,
	      tree,
	      attachedRoots: Object.freeze(
	        attachedRoots.sort((left, right) => left - right)
	      ),
	      detachedRoots: Object.freeze(
	        detachedRoots.sort((left, right) => left - right)
	      )
	    });
	  }
	}
}, "375b0521c824c0c6ea4e7f69612af9ed71a0f0f89992ed531b86a3166d207be0");

/* Source: lite/src/stream/virtual-stream-frame-controller.ts */
runtime.register("src/stream/virtual-stream-frame-controller.js", function(module, exports, require) {
	var virtual_stream_frame_controller_exports = {};
	__export(virtual_stream_frame_controller_exports, {
	  VirtualStreamFrameController: () => VirtualStreamFrameController
	});
	module.exports = __toCommonJS(virtual_stream_frame_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_identifiers = require("../discourse/identifiers.js");
	function browserObserverFactory(callback) {
	  return new ResizeObserver((entries) => {
	    callback(
	      entries.map((entry) => {
	        const borderBox = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize;
	        return Object.freeze({
	          target: entry.target,
	          blockSize: borderBox?.blockSize ?? entry.contentRect.height
	        });
	      })
	    );
	  });
	}
	const browserFrameScheduler = Object.freeze({
	  request: (callback) => requestAnimationFrame(callback),
	  cancel: (handle) => cancelAnimationFrame(handle)
	}), SCROLL_OFFSET_EPSILON = 0.5;
	function postNumberFromElement(target) {
	  return (0, import_identifiers.tryDiscoursePostNumber)(target.getAttribute("data-post-number"));
	}
	class VirtualStreamFrameController {
	  domController;
	  scope;
	  #readWindowInput;
	  #applyScrollCompensation;
	  #shouldApplyScrollCompensation;
	  #shouldDeferMeasurements;
	  #onMeasurementsDeferred;
	  #resolveRootBlockSize;
	  #onCommit;
	  #observer;
	  #frames;
	  #observedRoots = /* @__PURE__ */ new Map();
	  #deferredMeasurements = /* @__PURE__ */ new Map();
	  #frameHandle = null;
	  #pendingCompensation = 0;
	  #measurementScrollOffset = null;
	  #lastCommit = null;
	  constructor(domController, options) {
	    this.domController = domController, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.scope), this.#readWindowInput = options.readWindowInput, this.#applyScrollCompensation = options.applyScrollCompensation, this.#shouldApplyScrollCompensation = options.shouldApplyScrollCompensation ?? (() => !0), this.#shouldDeferMeasurements = options.shouldDeferMeasurements ?? (() => !1), this.#onMeasurementsDeferred = options.onMeasurementsDeferred ?? (() => {
	    }), this.#resolveRootBlockSize = options.resolveRootBlockSize ?? ((_target, blockSize) => blockSize), this.#onCommit = options.onCommit, this.#frames = options.frameScheduler ?? browserFrameScheduler, this.#observer = (options.observerFactory ?? browserObserverFactory)((entries) => {
	      this.#recordMeasurements(entries);
	    }), this.scope.add(() => {
	      this.#frameHandle !== null && this.#frames.cancel(this.#frameHandle), this.#frameHandle = null, this.#clearMeasurementTransaction(), this.#observedRoots.clear(), this.#deferredMeasurements.clear(), this.#observer.disconnect();
	    });
	  }
	  get lastCommit() {
	    return this.#lastCommit;
	  }
	  observeRoot(target) {
	    this.#observedRoots.set(target, postNumberFromElement(target)), this.#observer.observe(target);
	    const cleanup = () => {
	      this.#observedRoots.delete(target), this.#deferredMeasurements.delete(target), this.#observer.unobserve(target);
	    };
	    return this.scope.add(cleanup);
	  }
	  notifyScroll() {
	    this.#schedule();
	  }
	  flushDeferredMeasurements() {
	    if (!this.#deferredMeasurements.size || this.scope.destroyed) return;
	    const entries = [...this.#deferredMeasurements.values()];
	    this.#deferredMeasurements.clear(), this.#recordMeasurements(entries, !0);
	  }
	  /** 同级投影(例如隐藏楼层分隔条)变化后主动刷新该根的物理占位。 */
	  refreshRootMeasurement(target) {
	    this.#observedRoots.has(target) && this.#recordMeasurements([Object.freeze({
	      target,
	      blockSize: target.getBoundingClientRect().height
	    })]);
	  }
	  flushNow() {
	    this.#frameHandle !== null && (this.#frames.cancel(this.#frameHandle), this.#frameHandle = null);
	    const inputBeforeCompensation = this.#readWindowInput();
	    if (this.#pendingCompensation !== 0) {
	      const compensation = this.#pendingCompensation, measurementScrollOffset = this.#measurementScrollOffset;
	      this.#clearMeasurementTransaction(), this.#shouldApplyScrollCompensation() && measurementScrollOffset !== null && Math.abs(inputBeforeCompensation.scrollOffset - measurementScrollOffset) <= SCROLL_OFFSET_EPSILON && this.#applyScrollCompensation(compensation);
	    } else
	      this.#measurementScrollOffset = null;
	    return this.#lastCommit = this.domController.commit(this.#readWindowInput()), this.#onCommit?.(this.#lastCommit), this.#lastCommit;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #recordMeasurements(entries, force = !1) {
	    if (!force && this.#shouldDeferMeasurements()) {
	      for (const entry of entries)
	        this.#deferredMeasurements.set(entry.target, entry);
	      this.#onMeasurementsDeferred();
	      return;
	    }
	    const input = this.#readWindowInput();
	    this.#measurementScrollOffset !== null && Math.abs(input.scrollOffset - this.#measurementScrollOffset) > SCROLL_OFFSET_EPSILON && this.#clearMeasurementTransaction();
	    const anchorPostNumber = this.domController.layout.window({
	      ...input,
	      overscanBeforeScreens: 0,
	      overscanAfterScreens: 0
	    }).postNumbers[0];
	    let changed = !1;
	    for (const entry of entries) {
	      const postNumber = postNumberFromElement(entry.target), blockSize = Math.round(
	        this.#resolveRootBlockSize(entry.target, entry.blockSize)
	      );
	      if (!this.#observedRoots.has(entry.target) || this.#observedRoots.get(entry.target) !== postNumber || postNumber === null || !Number.isFinite(blockSize) || blockSize <= 0) continue;
	      const result = this.domController.layout.measure(
	        postNumber,
	        blockSize,
	        anchorPostNumber
	      );
	      result.changed && (changed = !0, this.#measurementScrollOffset ??= input.scrollOffset, this.#pendingCompensation += result.scrollCompensation);
	    }
	    changed && this.#schedule();
	  }
	  #clearMeasurementTransaction() {
	    this.#pendingCompensation = 0, this.#measurementScrollOffset = null;
	  }
	  #schedule() {
	    this.scope.destroyed || this.#frameHandle !== null || (this.#frameHandle = this.#frames.request(() => {
	      this.#frameHandle = null, this.flushNow();
	    }));
	  }
	}
}, "cea160b0950ac7ebd97e7f8622aeb5e47f2fe9283617fce84143ff94c418a1d0");

/* Source: lite/src/stream/virtual-stream-view.ts */
runtime.register("src/stream/virtual-stream-view.js", function(module, exports, require) {
	var virtual_stream_view_exports = {};
	__export(virtual_stream_view_exports, {
	  VirtualStreamView: () => VirtualStreamView
	});
	module.exports = __toCommonJS(virtual_stream_view_exports);
	var import_html_element = require("../dom/html-element.js");
	class VirtualStreamView {
	  slots;
	  constructor(document) {
	    const root = (0, import_html_element.htmlElement)(document, "div", "ldp-virtual-stream"), beforeSpacer = (0, import_html_element.htmlElement)(document, "div", "ldp-virtual-spacer ldp-virtual-spacer-before"), rootList = (0, import_html_element.htmlElement)(document, "div", "ldp-virtual-root-list"), afterSpacer = (0, import_html_element.htmlElement)(document, "div", "ldp-virtual-spacer ldp-virtual-spacer-after"), empty = (0, import_html_element.htmlElement)(document, "div", "ldp-comments-empty"), loadingTip = (0, import_html_element.htmlElement)(document, "div", "ldp-loading-tip"), loadingCopy = document.createElement("span"), endTip = (0, import_html_element.htmlElement)(document, "div", "ldp-end-tip");
	    beforeSpacer.setAttribute("aria-hidden", "true"), afterSpacer.setAttribute("aria-hidden", "true"), empty.textContent = "暂无评论", empty.hidden = !0, loadingCopy.textContent = "正在加载楼层…", loadingTip.append(loadingCopy), loadingTip.hidden = !0, loadingTip.setAttribute("role", "status"), loadingTip.setAttribute("aria-live", "polite"), endTip.textContent = "已经到底了~", endTip.hidden = !0, endTip.setAttribute("role", "status"), endTip.setAttribute("aria-live", "polite"), root.append(beforeSpacer, rootList, afterSpacer, empty, loadingTip, endTip), this.slots = Object.freeze({
	      root,
	      beforeSpacer,
	      rootList,
	      afterSpacer,
	      empty,
	      loadingTip,
	      endTip
	    });
	  }
	  setSpacerSizes(before, after) {
	    const beforeSize = `${Math.max(0, before)}px`, afterSize = `${Math.max(0, after)}px`;
	    this.slots.beforeSpacer.style.blockSize !== beforeSize && (this.slots.beforeSpacer.style.blockSize = beforeSize), this.slots.afterSpacer.style.blockSize !== afterSize && (this.slots.afterSpacer.style.blockSize = afterSize);
	  }
	  setFlowState(state) {
	    this.slots.root.setAttribute("aria-busy", String(state.loading)), this.slots.loadingTip.hidden = !state.loading, this.slots.loadingTip.classList.toggle("show", state.loading), this.slots.endTip.hidden = !state.done, this.slots.endTip.classList.toggle("show", state.done), this.slots.empty.hidden = !state.empty;
	  }
	  destroy() {
	    this.slots.root.remove();
	  }
	}
}, "2174765b588dd5309fc62c1f1032549fb614488a294e6aa431a12fd4f437db2a");

/* Source: lite/src/topic/reader-native-topic-route.ts */
runtime.register("src/topic/reader-native-topic-route.js", function(module, exports, require) {
	var reader_native_topic_route_exports = {};
	__export(reader_native_topic_route_exports, {
	  READER_NATIVE_BYPASS_PARAMETER: () => READER_NATIVE_BYPASS_PARAMETER,
	  READER_NATIVE_BYPASS_TAB_KEY: () => READER_NATIVE_BYPASS_TAB_KEY,
	  consumeReaderNativeBypass: () => consumeReaderNativeBypass,
	  consumeReaderNativeTabBypass: () => consumeReaderNativeTabBypass,
	  openReaderNativeTopicTab: () => openReaderNativeTopicTab,
	  readerNativeBypassCleanHref: () => readerNativeBypassCleanHref,
	  readerNativeTopicHref: () => readerNativeTopicHref
	});
	module.exports = __toCommonJS(reader_native_topic_route_exports);
	const READER_NATIVE_BYPASS_PARAMETER = "ldp_native", READER_NATIVE_BYPASS_TAB_KEY = "linuxdo-enhanced-reader:native-tab";
	function httpUrl(value, baseValue) {
	  try {
	    const base = new URL(baseValue), url = new URL(value, base);
	    return /^https?:$/i.test(url.protocol) && url.origin === base.origin ? url : null;
	  } catch {
	    return null;
	  }
	}
	function readerNativeTopicHref(value, baseValue) {
	  const url = httpUrl(value, baseValue);
	  return url ? (url.searchParams.set(READER_NATIVE_BYPASS_PARAMETER, "1"), url.href) : "";
	}
	function readerNativeBypassCleanHref(value, baseValue) {
	  const url = httpUrl(value, baseValue);
	  return url?.searchParams.has(READER_NATIVE_BYPASS_PARAMETER) ? (url.searchParams.delete(READER_NATIVE_BYPASS_PARAMETER), url.href) : null;
	}
	function consumeReaderNativeBypass(value, baseValue, replace) {
	  const cleanHref = readerNativeBypassCleanHref(value, baseValue);
	  return cleanHref ? (replace(cleanHref), !0) : !1;
	}
	function consumeReaderNativeTabBypass(window) {
	  try {
	    const bypass = window.sessionStorage.getItem(
	      READER_NATIVE_BYPASS_TAB_KEY
	    ) === "1";
	    return bypass && window.sessionStorage.removeItem(READER_NATIVE_BYPASS_TAB_KEY), bypass;
	  } catch {
	    return !1;
	  }
	}
	function openReaderNativeTopicTab(window, value) {
	  let tab = null;
	  try {
	    tab = window.open("about:blank", "_blank");
	  } catch {
	    return !1;
	  }
	  if (!tab) return !1;
	  try {
	    tab.sessionStorage.setItem(READER_NATIVE_BYPASS_TAB_KEY, "1");
	  } catch {
	  }
	  try {
	    tab.opener = null;
	  } catch {
	  }
	  try {
	    tab.location.replace(value);
	  } catch {
	    tab.location.href = value;
	  }
	  return !0;
	}
}, "0f9505808f65e295e5a2963229477e4ad9daae1d1aa9a282907d558a769c35aa");

/* Source: lite/src/topic/reader-post-presentation.ts */
runtime.register("src/topic/reader-post-presentation.js", function(module, exports, require) {
	var reader_post_presentation_exports = {};
	__export(reader_post_presentation_exports, {
	  createReaderPostPresentation: () => createReaderPostPresentation,
	  createReaderPostReadStateFeature: () => createReaderPostReadStateFeature
	});
	module.exports = __toCommonJS(reader_post_presentation_exports);
	var import_reader_image_fallback = require("../components/reader-image-fallback.js"), import_reader_icon = require("../components/reader-icon.js"), import_reader_topic_header = require("./reader-topic-header.js");
	const EMPTY_RECORD = Object.freeze({});
	function record(value) {
	  return value !== null && (typeof value == "object" || typeof value == "function") ? value : EMPTY_RECORD;
	}
	function text(value) {
	  return String(value ?? "").trim();
	}
	function positiveInteger(value, name) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return numeric;
	}
	function appendUserLink(document, parent, className, label, href, username) {
	  const link = document.createElement("a");
	  link.className = `ldp-user-link ${className}`, link.textContent = label, link.href = href || "#", link.target = "_blank", link.rel = "noopener", link.dataset.userCard = username, parent.append(link);
	}
	function appendAvatar(options, parent, post, username, displayName) {
	  const source = options.presentation.avatarSource(
	    text(post.avatar_template),
	    48
	  ), trigger = options.document.createElement("button");
	  if (trigger.type = "button", trigger.className = "ldp-user-link ldp-avatar-link", trigger.dataset.readerAvatar = "", trigger.dataset.userAvatarPreview = "", trigger.dataset.userCard = username, trigger.setAttribute("aria-label", `查看 ${displayName} 的头像原图`), source) {
	    const avatar = options.document.createElement("img");
	    avatar.className = "ldp-avatar", (0, import_reader_image_fallback.replaceImageWithFallbackOnError)(avatar, () => {
	      const fallback = options.document.createElement("span");
	      return fallback.className = "ldp-avatar ldp-persistent-avatar-fallback", fallback.textContent = [...displayName || username || "?"][0] ?? "?", fallback.setAttribute("aria-hidden", "true"), fallback;
	    }), avatar.src = source, avatar.alt = "", avatar.loading = "lazy", avatar.decoding = "async", trigger.append(avatar);
	  } else {
	    const fallback = options.document.createElement("span");
	    fallback.className = "ldp-avatar ldp-persistent-avatar-fallback", fallback.textContent = [...displayName || username || "?"][0] ?? "?", fallback.setAttribute("aria-hidden", "true"), trigger.append(fallback);
	  }
	  parent.append(trigger);
	}
	function appendBadge(document, parent, className, label) {
	  const badge = document.createElement("span");
	  badge.className = className, badge.textContent = label, parent.append(badge);
	}
	function appendFloor(document, parent, postNumber, _replyToPostNumber) {
	  const floor = document.createElement("span");
	  floor.className = "ldp-floor ldp-body-floor", floor.textContent = `#${postNumber}`, parent.append(floor);
	}
	function readStateIcon(document, read, renderIcon) {
	  let icon = read ? (0, import_reader_icon.renderReaderIcon)(document, "check", renderIcon) : null;
	  if (!icon) {
	    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
	    if (svg.setAttribute("viewBox", "0 0 24 24"), svg.setAttribute("aria-hidden", "true"), read) {
	      const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
	      path.setAttribute("d", "M5 12.5 9.5 17 19 7.5"), path.setAttribute("fill", "none"), path.setAttribute("stroke", "currentColor"), path.setAttribute("stroke-linecap", "round"), path.setAttribute("stroke-linejoin", "round"), svg.append(path);
	    } else {
	      const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
	      circle.setAttribute("cx", "12"), circle.setAttribute("cy", "12"), circle.setAttribute("r", "5.5"), svg.append(circle);
	    }
	    icon = svg;
	  }
	  return icon.nodeType === 1 && (icon.classList.add("ldp-icon", "ldp-post-read-state-icon"), icon.setAttribute("aria-hidden", "true")), icon;
	}
	function appendReadState(document, parent, read, renderIcon) {
	  const state = document.createElement("span");
	  state.className = "ldp-post-read-state", projectReadStateMarker(state, read, renderIcon), parent.append(state);
	}
	function projectReadStateMarker(state, read, renderIcon) {
	  const label = read ? "该楼层已读" : "该楼层未读";
	  state.classList.toggle("is-read", read), state.classList.toggle("is-unread", !read), state.dataset.readState = read ? "read" : "unread", state.dataset.ldpTooltipLabel = label, state.setAttribute("role", "img"), state.setAttribute("aria-label", label), state.replaceChildren(readStateIcon(state.ownerDocument, read, renderIcon));
	}
	function createReaderPostReadStateFeature(options) {
	  const scope = options.parentScope.child(), views = /* @__PURE__ */ new Map(), boundViews = /* @__PURE__ */ new WeakSet(), viewMarkers = /* @__PURE__ */ new WeakMap(), transitions = /* @__PURE__ */ new Map(), setTimer = options.setTimer ?? ((callback, milliseconds) => setTimeout(callback, milliseconds)), clearTimer = options.clearTimer ?? clearTimeout, isVisible = options.isVisible ?? ((view) => view.slots.root.isConnected && typeof view.slots.root.getClientRects == "function" && view.slots.root.getClientRects().length > 0), prefersReducedMotion = options.prefersReducedMotion ?? (() => !1), clearTransition = (marker) => {
	    const transition = transitions.get(marker);
	    transition && (clearTimer(transition.swapTimer), clearTimer(transition.finishTimer), marker.removeEventListener("animationend", transition.finish), transitions.delete(marker)), marker.classList.remove("is-confirming"), delete marker.dataset.readTransitionState;
	  }, syncViewReadState = (view, read, animate) => {
	    const marker = view.slots.header.querySelector(
	      ":scope > .ldp-post-read-state"
	    );
	    if (!marker) return;
	    const previousMarker = viewMarkers.get(view);
	    previousMarker && previousMarker !== marker && clearTransition(previousMarker), viewMarkers.set(view, marker);
	    const state = read ? "read" : "unread";
	    if (marker.dataset.readState === state || marker.dataset.readTransitionState === state) return;
	    if (clearTransition(marker), !animate || !read || !isVisible(view)) {
	      projectReadStateMarker(marker, read, options.renderIcon);
	      return;
	    }
	    marker.dataset.readTransitionState = state, marker.classList.add("is-confirming");
	    const finish = () => {
	      const transition = transitions.get(marker);
	      !transition || transition.finish !== finish || (clearTransition(marker), marker.dataset.readState !== state && projectReadStateMarker(marker, read, options.renderIcon));
	    };
	    marker.addEventListener("animationend", finish, { once: !0 });
	    const swapTimer = setTimer(() => {
	      const transition = transitions.get(marker);
	      !transition || transition.finish !== finish || projectReadStateMarker(marker, read, options.renderIcon);
	    }, 625), finishTimer = setTimer(finish, 1450);
	    transitions.set(marker, Object.freeze({
	      swapTimer,
	      finishTimer,
	      finish
	    }));
	  };
	  return options.readState.changes.subscribe((change) => {
	    for (const postNumber of change.postNumbers) {
	      const bindings = views.get(postNumber);
	      if (bindings)
	        for (const [view, sourceRead] of bindings)
	          syncViewReadState(
	            view,
	            sourceRead || options.readState.isOptimistic(postNumber),
	            change.kind === "optimistic" && !prefersReducedMotion()
	          );
	    }
	  }, scope), scope.add(() => {
	    for (const marker of transitions.keys()) clearTransition(marker);
	    views.clear();
	  }), Object.freeze({
	    afterRender(postValue, view) {
	      const post = record(postValue), postNumber = positiveInteger(
	        post.post_number,
	        "post.post_number"
	      ), bindings = views.get(postNumber) ?? /* @__PURE__ */ new Map();
	      bindings.set(view, post.read === !0), views.set(postNumber, bindings), boundViews.has(view) || (boundViews.add(view), view.scope.add(() => {
	        const marker = viewMarkers.get(view);
	        marker && clearTransition(marker), viewMarkers.delete(view);
	        const current = views.get(postNumber);
	        current?.delete(view), current?.size === 0 && views.delete(postNumber);
	      })), syncViewReadState(
	        view,
	        post.read === !0 || options.readState.isOptimistic(postNumber),
	        !1
	      );
	    }
	  });
	}
	function createReaderPostPresentation(options) {
	  const currentUsername = text(options.currentUsername), exactTimeByView = /* @__PURE__ */ new WeakMap();
	  return Object.freeze({
	    identity(postValue) {
	      const post = record(postValue);
	      return Object.freeze({
	        postId: positiveInteger(post.id, "post.id"),
	        postNumber: positiveInteger(
	          post.post_number,
	          "post.post_number"
	        ),
	        username: text(post.username),
	        createdAt: text(post.created_at)
	      });
	    },
	    render(postValue, view) {
	      const post = record(postValue), postNumber = positiveInteger(
	        post.post_number,
	        "post.post_number"
	      ), replyNumber = Number(post.reply_to_post_number), replyToPostNumber = Number.isSafeInteger(replyNumber) && replyNumber > 0 ? replyNumber : null, username = text(post.username), displayName = text(post.name) || username || "未知用户", profileHref = options.presentation.userHref(username), header = view.slots.header;
	      header.replaceChildren(), appendAvatar(options, header, post, username, displayName), appendUserLink(
	        options.document,
	        header,
	        "ldp-author",
	        displayName,
	        profileHref,
	        username
	      ), currentUsername && username === currentUsername && appendBadge(options.document, header, "ldp-me", "ME"), appendUserLink(
	        options.document,
	        header,
	        "ldp-user",
	        `@${username}`,
	        profileHref,
	        username
	      ), username && username === (0, import_reader_topic_header.readerTopicOwnerUsername)(options.readTopic()) && appendBadge(options.document, header, "ldp-op", "OP");
	      const createdAt = text(post.created_at) || text(view.identity.createdAt), relative = options.relativeTime(createdAt);
	      if (relative) {
	        const time = options.document.createElement("span");
	        time.className = "ldp-time";
	        const cachedExact = exactTimeByView.get(view), exact = cachedExact?.timestamp === createdAt ? cachedExact.value : options.exactTime(createdAt);
	        exact && cachedExact?.timestamp !== createdAt && exactTimeByView.set(view, Object.freeze({
	          timestamp: createdAt,
	          value: exact
	        })), exact && (time.dataset.exactTime = exact);
	        const label = options.document.createElement("span");
	        label.className = "ldp-time-relative", label.textContent = `· ${relative}`, time.append(label), header.append(time);
	      }
	      post.hidden === !0 && appendBadge(
	        options.document,
	        header,
	        "ldp-special-badge ldp-hidden-badge warn",
	        "已隐藏"
	      ), appendFloor(
	        options.document,
	        header,
	        postNumber,
	        replyToPostNumber
	      ), appendReadState(
	        options.document,
	        header,
	        post.read === !0,
	        options.renderIcon
	      ), view.slots.content.innerHTML = text(post.cooked);
	    }
	  });
	}
}, "5548e75bed3772379a527cab98c7b1714104915243a85f15901bb917c4c156cd");

/* Source: lite/src/topic/reader-post-view-projector.ts */
runtime.register("src/topic/reader-post-view-projector.js", function(module, exports, require) {
	var reader_post_view_projector_exports = {};
	__export(reader_post_view_projector_exports, {
	  ReaderPostViewProjector: () => ReaderPostViewProjector
	});
	module.exports = __toCommonJS(reader_post_view_projector_exports);
	var import_post_view = require("../dom/post-view.js");
	class ReaderPostViewProjector {
	  #document;
	  #identity;
	  #render;
	  #features;
	  #onError;
	  constructor(options) {
	    this.#document = options.document, this.#identity = options.identity, this.#render = options.render, this.#features = Object.freeze([...options.features ?? []]), this.#onError = options.onError ?? (() => {
	    });
	  }
	  identity(post) {
	    return this.#identity(post);
	  }
	  create(post, parentScope, expectedPostNumber) {
	    const view = this.createShell(post, parentScope, expectedPostNumber);
	    try {
	      return this.render(post, view), view;
	    } catch (error) {
	      throw view.destroy(), error;
	    }
	  }
	  /** 创建 canonical 固定槽位,但暂不解析 cooked 或投影 feature。 */
	  createShell(post, parentScope, expectedPostNumber) {
	    const view = new import_post_view.PostView(
	      this.#document,
	      this.#identity(post),
	      parentScope
	    );
	    if (expectedPostNumber !== void 0 && view.postNumber !== expectedPostNumber)
	      throw view.destroy(), new Error(
	        `PostView identity #${view.postNumber} 与 canonical 楼层 #${expectedPostNumber} 不一致`
	      );
	    return view;
	  }
	  render(post, view) {
	    for (const feature of this.#features)
	      try {
	        feature.beforeRender?.(post, view);
	      } catch (error) {
	        this.#onError(error);
	      }
	    try {
	      this.#render(post, view);
	    } finally {
	      for (const feature of this.#features)
	        try {
	          feature.afterRender?.(post, view);
	        } catch (error) {
	          this.#onError(error);
	        }
	    }
	  }
	  attach(root, postNumber, target = "all") {
	    this.#visitFeatures(target, (feature) => {
	      feature.attachRoot?.(root, postNumber);
	    });
	  }
	  detach(root, postNumber, target = "all") {
	    this.#visitFeatures(target, (feature) => {
	      feature.detachRoot?.(root, postNumber);
	    });
	  }
	  syncProjection() {
	    for (const feature of this.#features)
	      try {
	        feature.syncProjection?.();
	      } catch (error) {
	        this.#onError(error);
	      }
	  }
	  #visitFeatures(target, visit) {
	    for (const feature of this.#features) {
	      const scope = feature.activationScope ?? "branch";
	      if (!(target !== "all" && scope !== target))
	        try {
	          visit(feature);
	        } catch (error) {
	          this.#onError(error);
	        }
	    }
	  }
	}
}, "973fb472a06fa9ea020a5c3755fc8a12cb7682b791ffbb4bc0121490d7d6a10e");

/* Source: lite/src/topic/reader-reply-ancestor-resolver.ts */
runtime.register("src/topic/reader-reply-ancestor-resolver.js", function(module, exports, require) {
	var reader_reply_ancestor_resolver_exports = {};
	__export(reader_reply_ancestor_resolver_exports, {
	  resolveReaderReplyAncestors: () => resolveReaderReplyAncestors
	});
	module.exports = __toCommonJS(reader_reply_ancestor_resolver_exports);
	var import_identifiers = require("../discourse/identifiers.js");
	async function resolveReaderReplyAncestors(session, targetPostNumberValue, options = {}) {
	  const targetPostNumber = (0, import_identifiers.discoursePostNumber)(targetPostNumberValue), stopBeforePostNumber = options.stopBeforePostNumber === void 0 ? null : (0, import_identifiers.discoursePostNumber)(options.stopBeforePostNumber), maxDepth = Math.max(1, Math.floor(options.maxDepth ?? 128)), isActive = options.isActive ?? (() => !0), seen = /* @__PURE__ */ new Set(), loadedPostNumbers = [];
	  let current = targetPostNumber, availableRoot = targetPostNumber;
	  for (let depth = 0; depth < maxDepth; depth += 1) {
	    if (!isActive())
	      return Object.freeze({
	        rootPostNumber: availableRoot,
	        complete: !1,
	        loadedPostNumbers: Object.freeze(loadedPostNumbers)
	      });
	    if (seen.has(current))
	      throw new Error(`楼层祖先链存在环,经过 #${current}`);
	    seen.add(current);
	    let post = session.postByNumber(current);
	    if (!post) {
	      try {
	        await session.loadTarget(current, {
	          scope: "single",
	          advanceCursor: !1
	        });
	      } catch (error) {
	        return Object.freeze({
	          rootPostNumber: availableRoot,
	          complete: !1,
	          loadedPostNumbers: Object.freeze(loadedPostNumbers),
	          ...isActive() ? { error } : {}
	        });
	      }
	      if (!isActive()) continue;
	      if (post = session.postByNumber(current), !post)
	        return Object.freeze({
	          rootPostNumber: availableRoot,
	          complete: !1,
	          loadedPostNumbers: Object.freeze(loadedPostNumbers)
	        });
	      loadedPostNumbers.push(current);
	    }
	    availableRoot = current;
	    const parent = (0, import_identifiers.discoursePostReference)(post).replyToPostNumber;
	    if (parent === null || parent === stopBeforePostNumber)
	      return Object.freeze({
	        rootPostNumber: current,
	        complete: !0,
	        loadedPostNumbers: Object.freeze(loadedPostNumbers)
	      });
	    current = (0, import_identifiers.discoursePostNumber)(parent);
	  }
	  return Object.freeze({
	    rootPostNumber: availableRoot,
	    complete: !1,
	    loadedPostNumbers: Object.freeze(loadedPostNumbers)
	  });
	}
}, "0e9a00d23cc3baf9951c000c767973a28696998cd9581061a576aa3590528d4e");

/* Source: lite/src/topic/reader-reply-tree-preferences.ts */
runtime.register("src/topic/reader-reply-tree-preferences.js", function(module, exports, require) {
	var reader_reply_tree_preferences_exports = {};
	__export(reader_reply_tree_preferences_exports, {
	  DEFAULT_READER_REPLY_TREE_PREFERENCES: () => DEFAULT_READER_REPLY_TREE_PREFERENCES,
	  ReaderReplyTreePreferencesPreview: () => ReaderReplyTreePreferencesPreview,
	  ReaderReplyTreePresentation: () => ReaderReplyTreePresentation,
	  normalizeReaderReplyTreePreferences: () => normalizeReaderReplyTreePreferences,
	  readerPreferencesReplyTreeAdapter: () => readerPreferencesReplyTreeAdapter
	});
	module.exports = __toCommonJS(reader_reply_tree_preferences_exports);
	var import_signal = require("../kernel/signal.js");
	const DEFAULT_READER_REPLY_TREE_PREFERENCES = Object.freeze({
	  expandNestedRepliesByDefault: !0,
	  expandLeafNestedReplies: !1,
	  aggregateDescendantReplies: !0,
	  inlineReplyTreeMaxDepth: 3,
	  hideNestedReplyFloors: !0
	});
	function normalizeReaderReplyTreePreferences(value) {
	  const expandLeafNestedReplies = value.expandLeafNestedReplies === !0, aggregateDescendantReplies = value.aggregateDescendantReplies === !0, expandNestedRepliesByDefault = aggregateDescendantReplies || value.expandNestedRepliesByDefault !== !1 || !expandLeafNestedReplies, rawDepth = Number(value.inlineReplyTreeMaxDepth);
	  return Object.freeze({
	    expandNestedRepliesByDefault,
	    expandLeafNestedReplies,
	    aggregateDescendantReplies: expandNestedRepliesByDefault && aggregateDescendantReplies,
	    inlineReplyTreeMaxDepth: Number.isFinite(rawDepth) ? Math.min(5, Math.max(1, Math.trunc(rawDepth))) : DEFAULT_READER_REPLY_TREE_PREFERENCES.inlineReplyTreeMaxDepth,
	    hideNestedReplyFloors: value.hideNestedReplyFloors === !0
	  });
	}
	class ReaderReplyTreePreferencesPreview {
	  changes = new import_signal.Signal();
	  #onError;
	  #value;
	  constructor(value, onError = () => {
	  }) {
	    this.#value = normalizeReaderReplyTreePreferences(value), this.#onError = onError;
	  }
	  read() {
	    return this.#value;
	  }
	  update(value) {
	    const next = normalizeReaderReplyTreePreferences(value);
	    if (!Object.keys(next).some((name) => !Object.is(next[name], this.#value[name]))) return !1;
	    this.#value = next;
	    for (const error of this.changes.emit(next)) this.#onError(error);
	    return !0;
	  }
	  subscribe(listener, scope) {
	    return this.changes.subscribe(listener, scope);
	  }
	  destroy() {
	    this.changes.clear();
	  }
	}
	const readerPreferencesReplyTreeAdapter = Object.freeze({
	  read: (preferences) => normalizeReaderReplyTreePreferences(preferences),
	  createPatch: (value) => normalizeReaderReplyTreePreferences(value)
	});
	function inlineDepth(preferences) {
	  return preferences.expandNestedRepliesByDefault ? preferences.aggregateDescendantReplies ? preferences.inlineReplyTreeMaxDepth : 1 : 0;
	}
	class ReaderReplyTreePresentation {
	  canonical;
	  #frozenCanonical = null;
	  #frozenCanonicalSourceRevision = -1;
	  #frozenCanonicalCoverageComplete = !0;
	  #preferences;
	  #canonicalCoverageComplete;
	  #revealedFloors = /* @__PURE__ */ new Set();
	  #revealedParents = /* @__PURE__ */ new Set();
	  #degradedFloorRoots = /* @__PURE__ */ new Set();
	  #degradedFloorCanonicalRevision = -1;
	  #postFilter = null;
	  #projectionRevision = 0;
	  #cachedCanonicalRevision = -1;
	  #cachedCanonicalCoverageComplete = null;
	  #cachedProjectionRevision = -1;
	  #cachedRoots = Object.freeze([]);
	  #cachedRootBranches = Object.freeze([]);
	  #cachedHiddenFloorRunsAfter = /* @__PURE__ */ new Map();
	  constructor(canonical, preferences = DEFAULT_READER_REPLY_TREE_PREFERENCES, options = {}) {
	    this.canonical = canonical, this.#preferences = normalizeReaderReplyTreePreferences(preferences), this.#canonicalCoverageComplete = options.canonicalCoverageComplete ?? (() => !0);
	  }
	  get preferences() {
	    return this.#preferences;
	  }
	  get revision() {
	    return `${this.#activeCanonicalRevision()}:${Number(this.#activeCanonicalCoverageComplete())}:${this.#projectionRevision}`;
	  }
	  get postFilterKey() {
	    return this.#postFilter?.key ?? null;
	  }
	  postFilterMatches(postNumber) {
	    return this.#postFilter?.matches(postNumber) ?? !0;
	  }
	  /**
	   * 用户连续滚动期间固定一次只读关系快照。
	   *
	   * canonical 仍可被 MessageBus 即时更新并持久化;虚拟窗口、DOM owner 和回复线只读
	   * 这个短生命周期投影,避免同一个滚轮手势中途改变根高或父子挂载。重复调用不会复制。
	   */
	  freezeCanonical() {
	    return this.#frozenCanonical ? !1 : (this.#frozenCanonical = this.canonical.clone(), this.#frozenCanonicalSourceRevision = this.canonical.revision, this.#frozenCanonicalCoverageComplete = this.#canonicalCoverageComplete(), !0);
	  }
	  /** 停滚后切回最新 canonical;返回冻结期间关系是否发生变化。 */
	  thawCanonical() {
	    if (!this.#frozenCanonical) return !1;
	    const changed = this.#frozenCanonicalSourceRevision !== this.canonical.revision || this.#frozenCanonicalCoverageComplete !== this.#canonicalCoverageComplete();
	    return this.#frozenCanonical = null, this.#frozenCanonicalSourceRevision = -1, this.#frozenCanonicalCoverageComplete = !0, changed;
	  }
	  get canonicalFrozen() {
	    return this.#frozenCanonical !== null;
	  }
	  update(preferences) {
	    const next = normalizeReaderReplyTreePreferences(preferences), changed = Object.keys(next).some((name) => !Object.is(next[name], this.#preferences[name]));
	    return changed && (this.#preferences = next, this.#revealedFloors.clear(), this.#revealedParents.clear(), this.#degradedFloorRoots.clear(), this.#degradedFloorCanonicalRevision = -1, this.#projectionRevision += 1), changed;
	  }
	  setPostFilter(filter) {
	    const nextKey = filter?.key ?? "", currentKey = this.#postFilter?.key ?? "";
	    return nextKey === currentKey ? !1 : (this.#postFilter = filter, this.#revealedFloors.clear(), this.#revealedParents.clear(), this.#degradedFloorRoots.clear(), this.#degradedFloorCanonicalRevision = -1, this.#projectionRevision += 1, !0);
	  }
	  invalidatePostFilter() {
	    return this.#postFilter ? (this.#projectionRevision += 1, !0) : !1;
	  }
	  revealAsFloor(postNumber) {
	    return !this.#activeCanonical().has(postNumber) || this.rootOf(postNumber) !== void 0 ? !1 : (this.#postFilter && this.#revealedFloors.clear(), this.#revealedFloors.add(postNumber), this.#projectionRevision += 1, !0);
	  }
	  /**
	   * canonical 父链在某个不可读楼层处断开时,把最高可用祖先投影成临时根,并沿已确认
	   * 的真实 parent 关系揭示到目标。只修改短生命周期显示投影,不伪造 canonical 关系。
	   */
	  revealDegradedBranch(rootPostNumber, targetPostNumber) {
	    const canonical = this.#activeCanonical();
	    if (!canonical.has(rootPostNumber) || !canonical.has(targetPostNumber) || canonical.depthOf(rootPostNumber) !== void 0)
	      return !1;
	    const parents = [], seen = /* @__PURE__ */ new Set([targetPostNumber]);
	    let current = targetPostNumber;
	    for (; current !== rootPostNumber; ) {
	      const parent = canonical.parentOf(current);
	      if (parent == null || seen.has(parent))
	        return !1;
	      parents.push(parent), seen.add(parent), current = parent;
	    }
	    let changed = !1;
	    this.#degradedFloorRoots.has(rootPostNumber) || (this.#degradedFloorRoots.add(rootPostNumber), changed = !0), this.#revealedFloors.has(rootPostNumber) || (this.#revealedFloors.add(rootPostNumber), changed = !0);
	    for (const parent of parents)
	      parent === targetPostNumber || this.#revealedParents.has(parent) || (this.#revealedParents.add(parent), changed = !0);
	    return this.#degradedFloorCanonicalRevision = this.#activeCanonicalRevision(), changed && (this.#projectionRevision += 1), changed;
	  }
	  parentOf(postNumber) {
	    const canonical = this.#activeCanonical(), canonicalDepth = canonical.depthOf(postNumber), degradedPath = this.#degradedFloorPath(postNumber, canonical), projectedDepth = degradedPath?.depth ?? canonicalDepth;
	    if (projectedDepth === void 0)
	      return this.#postFilter && this.#matchesPostFilter(postNumber) ? null : void 0;
	    if (degradedPath?.depth === 0 || this.#isRevealedFloor(postNumber, canonicalDepth)) return null;
	    if (this.#matchesPostFilter(postNumber))
	      return this.#postFilter || projectedDepth === 0 ? null : this.#isInlineVisible(postNumber, projectedDepth) ? canonical.parentOf(postNumber) : this.#showAsFloor() ? null : void 0;
	  }
	  childrenOf(postNumber) {
	    const canonical = this.#activeCanonical();
	    if (this.#postFilter) return Object.freeze([]);
	    const parentDepth = this.#projectedDepth(postNumber, canonical);
	    return parentDepth === void 0 || !this.#isInlineVisible(postNumber, parentDepth) || parentDepth >= inlineDepth(this.#preferences) && !this.#revealedParents.has(postNumber) ? Object.freeze([]) : canonical.childrenOf(postNumber);
	  }
	  hiddenDirectChildrenOf(postNumber) {
	    if (this.#postFilter) return Object.freeze([]);
	    const canonical = this.#activeCanonical(), visibleChildren = new Set(this.childrenOf(postNumber));
	    return Object.freeze(
	      canonical.childrenOf(postNumber).filter((childPostNumber) => !visibleChildren.has(childPostNumber) && this.rootOf(childPostNumber) === void 0)
	    );
	  }
	  /** 用户只揭示当前父节点的直属下一层;更深后代仍停在各自的“+”之后。 */
	  revealNextLevel(postNumber) {
	    if (this.#postFilter) return !1;
	    const canonical = this.#activeCanonical(), projectedDepth = this.#projectedDepth(postNumber, canonical);
	    return projectedDepth === void 0 || !this.#isInlineVisible(postNumber, projectedDepth) || this.#revealedParents.has(postNumber) || this.hiddenDirectChildrenOf(postNumber).length === 0 ? !1 : (this.#revealedParents.add(postNumber), this.#projectionRevision += 1, !0);
	  }
	  hiddenFloorRunAfter(postNumber) {
	    return this.#syncCache(), this.#cachedHiddenFloorRunsAfter.get(postNumber) ?? Object.freeze([]);
	  }
	  depthOf(postNumber) {
	    const canonical = this.#activeCanonical(), canonicalDepth = canonical.depthOf(postNumber), degradedPath = this.#degradedFloorPath(postNumber, canonical), projectedDepth = degradedPath?.depth ?? canonicalDepth;
	    if (projectedDepth === void 0)
	      return this.#postFilter && this.#matchesPostFilter(postNumber) ? 0 : void 0;
	    if (degradedPath?.depth === 0 || this.#isRevealedFloor(postNumber, canonicalDepth)) return 0;
	    if (this.#matchesPostFilter(postNumber))
	      return this.#postFilter ? 0 : this.#isInlineVisible(postNumber, projectedDepth) ? projectedDepth : this.#showAsFloor() ? 0 : void 0;
	  }
	  rootOf(postNumber) {
	    const canonical = this.#activeCanonical(), canonicalDepth = canonical.depthOf(postNumber), degradedPath = this.#degradedFloorPath(postNumber, canonical), projectedDepth = degradedPath?.depth ?? canonicalDepth;
	    if (projectedDepth === void 0)
	      return this.#postFilter && this.#matchesPostFilter(postNumber) ? postNumber : void 0;
	    if (degradedPath?.depth === 0 || this.#isRevealedFloor(postNumber, canonicalDepth)) return postNumber;
	    if (this.#matchesPostFilter(postNumber))
	      return this.#postFilter ? postNumber : this.#isInlineVisible(postNumber, projectedDepth) ? degradedPath?.rootPostNumber ?? canonical.rootOf(postNumber) : this.#showAsFloor() ? postNumber : void 0;
	  }
	  roots() {
	    return this.#syncCache(), this.#cachedRoots;
	  }
	  rootBranches() {
	    return this.#syncCache(), this.#cachedRootBranches;
	  }
	  #syncCache() {
	    const canonical = this.#activeCanonical(), canonicalRevision = this.#activeCanonicalRevision(), canonicalCoverageComplete = this.#activeCanonicalCoverageComplete();
	    if (this.#cachedCanonicalRevision === canonicalRevision && this.#cachedCanonicalCoverageComplete === canonicalCoverageComplete && this.#cachedProjectionRevision === this.#projectionRevision) return;
	    const relations = canonical.snapshot().relations, roots = Object.freeze(relations.map((relation) => relation.postNumber).filter((postNumber) => this.parentOf(postNumber) === null).sort((left, right) => left - right)), rootBranches = Object.freeze(roots.map((postNumber) => {
	      let subtreePostCount = 0;
	      const pending = [postNumber];
	      for (; pending.length; ) {
	        const current = pending.pop();
	        subtreePostCount += 1, pending.push(...this.childrenOf(current));
	      }
	      return Object.freeze({ postNumber, subtreePostCount });
	    })), hiddenFloorRunsAfter = /* @__PURE__ */ new Map();
	    if (!this.#postFilter && this.#preferences.hideNestedReplyFloors) {
	      let relationIndex = 0;
	      for (let index = 0; index < roots.length; index += 1) {
	        const rootPostNumber = roots[index], nextRootPostNumber = roots[index + 1] ?? Number.POSITIVE_INFINITY;
	        for (; relationIndex < relations.length && relations[relationIndex].postNumber <= rootPostNumber; )
	          relationIndex += 1;
	        const run = [];
	        let previousPostNumber = rootPostNumber;
	        for (; relationIndex < relations.length && relations[relationIndex].postNumber < nextRootPostNumber; ) {
	          const hiddenPostNumber = relations[relationIndex].postNumber;
	          if (!canonicalCoverageComplete && hiddenPostNumber !== previousPostNumber + 1) break;
	          run.push(hiddenPostNumber), previousPostNumber = hiddenPostNumber, relationIndex += 1;
	        }
	        run.length > 0 && hiddenFloorRunsAfter.set(
	          rootPostNumber,
	          Object.freeze(run)
	        );
	      }
	    }
	    this.#cachedRoots = roots, this.#cachedRootBranches = rootBranches, this.#cachedHiddenFloorRunsAfter = hiddenFloorRunsAfter, this.#cachedCanonicalRevision = canonicalRevision, this.#cachedCanonicalCoverageComplete = canonicalCoverageComplete, this.#cachedProjectionRevision = this.#projectionRevision;
	  }
	  #activeCanonical() {
	    return this.#frozenCanonical ?? this.canonical;
	  }
	  #activeCanonicalRevision() {
	    return this.#frozenCanonical ? this.#frozenCanonicalSourceRevision : this.canonical.revision;
	  }
	  #activeCanonicalCoverageComplete() {
	    return this.#frozenCanonical ? this.#frozenCanonicalCoverageComplete : this.#canonicalCoverageComplete();
	  }
	  #showAsFloor() {
	    return this.#preferences.expandLeafNestedReplies || !this.#preferences.hideNestedReplyFloors;
	  }
	  #syncDegradedFloorRoots(canonical) {
	    const revision = this.#activeCanonicalRevision();
	    if (revision !== this.#degradedFloorCanonicalRevision) {
	      for (const rootPostNumber of [...this.#degradedFloorRoots])
	        canonical.depthOf(rootPostNumber) !== void 0 && (this.#degradedFloorRoots.delete(rootPostNumber), this.#revealedFloors.delete(rootPostNumber));
	      this.#degradedFloorCanonicalRevision = revision;
	    }
	  }
	  #degradedFloorPath(postNumber, canonical) {
	    if (this.#syncDegradedFloorRoots(canonical), !this.#degradedFloorRoots.size || !canonical.has(postNumber))
	      return;
	    const seen = /* @__PURE__ */ new Set();
	    let current = postNumber, depth = 0;
	    for (; !seen.has(current); ) {
	      if (this.#degradedFloorRoots.has(current))
	        return Object.freeze({ rootPostNumber: current, depth });
	      seen.add(current);
	      const parent = canonical.parentOf(current);
	      if (parent == null) return;
	      current = parent, depth += 1;
	    }
	  }
	  #projectedDepth(postNumber, canonical) {
	    return this.#degradedFloorPath(postNumber, canonical)?.depth ?? canonical.depthOf(postNumber);
	  }
	  #isRevealedFloor(postNumber, canonicalDepth) {
	    return this.#degradedFloorRoots.has(postNumber) ? !0 : !this.#revealedFloors.has(postNumber) || canonicalDepth === void 0 ? !1 : this.#postFilter && !this.#matchesPostFilter(postNumber) || canonicalDepth > inlineDepth(this.#preferences) ? !0 : (this.#revealedFloors.delete(postNumber), !1);
	  }
	  #isInlineVisible(postNumber, projectedDepth) {
	    if (projectedDepth <= inlineDepth(this.#preferences)) return !0;
	    const canonical = this.#activeCanonical(), parentPostNumber = canonical.parentOf(postNumber);
	    if (parentPostNumber == null || !this.#revealedParents.has(parentPostNumber)) return !1;
	    const parentDepth = this.#projectedDepth(parentPostNumber, canonical);
	    return parentDepth !== void 0 && this.#isInlineVisible(parentPostNumber, parentDepth);
	  }
	  #matchesPostFilter(postNumber) {
	    const filter = this.#postFilter;
	    if (!filter) return !0;
	    if (!filter.matches(postNumber)) return !1;
	    if (!filter.hideDescendantMatches) return !0;
	    const canonical = this.#activeCanonical(), seen = /* @__PURE__ */ new Set([postNumber]);
	    let parent = canonical.parentOf(postNumber);
	    for (; parent != null && !seen.has(parent); ) {
	      if (parent === filter.ancestorBoundaryPostNumber) return !0;
	      if (filter.matches(parent)) return !1;
	      seen.add(parent), parent = canonical.parentOf(parent);
	    }
	    return !0;
	  }
	}
}, "7dd19bdf297410b6b32916d5b66cc3a12dd03557259efe7234ab302f909b6689");

/* Source: lite/src/topic/reader-topic-comments-header.ts */
runtime.register("src/topic/reader-topic-comments-header.js", function(module, exports, require) {
	var reader_topic_comments_header_exports = {};
	__export(reader_topic_comments_header_exports, {
	  ReaderTopicCommentsHeader: () => ReaderTopicCommentsHeader
	});
	module.exports = __toCommonJS(reader_topic_comments_header_exports);
	var import_reader_icon = require("../components/reader-icon.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_value_record = require("../kernel/value-record.js");
	function positiveCount(value) {
	  const numeric = Number(value);
	  return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : 0;
	}
	function commentCount(topicValue, posts) {
	  const topic = (0, import_value_record.objectRecord)(topicValue), total = Math.max(
	    positiveCount(topic?.posts_count),
	    positiveCount(topic?.highest_post_number),
	    posts.reduce(
	      (maximum, post) => Math.max(maximum, positiveCount(post.post_number)),
	      0
	    )
	  );
	  return Math.max(0, total - 1);
	}
	class ReaderTopicCommentsHeader {
	  scope;
	  #document;
	  #session;
	  #presentation;
	  #currentUsername;
	  #renderIcon;
	  #onError;
	  #count = null;
	  #presence = null;
	  #presenceUsers = Object.freeze([]);
	  constructor(options) {
	    this.#document = options.document, this.#session = options.session, this.#presentation = options.presentation, this.#currentUsername = String(options.currentUsername ?? "").trim(), this.#renderIcon = options.renderIcon, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#session.changes.subscribe(() => this.#refreshCount(), this.scope), this.scope.add(options.presence.watchReplying(
	      options.topicId,
	      (users) => {
	        this.#presenceUsers = Object.freeze(users.filter(
	          (user) => user.username !== this.#currentUsername
	        )), this.#renderPresence();
	      },
	      this.#onError
	    )), this.scope.add(() => {
	      this.#count = null, this.#presence = null;
	    });
	  }
	  afterRender(post, view) {
	    if (positiveCount(post.post_number) !== 1) return;
	    let header = view.slots.root.querySelector(
	      ":scope > .ldp-comments-header"
	    );
	    header || (header = this.#createHeader(), view.slots.replyTree.after(header)), this.#count = header.querySelector(
	      ":scope > .ldp-comments-count"
	    ), this.#presence = header.querySelector(
	      ":scope > .ldp-topic-presence"
	    ), this.#refreshCount(), this.#renderPresence();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #createHeader() {
	    const header = this.#document.createElement("div");
	    header.className = "ldp-comments-header", header.append((0, import_reader_icon.renderReaderIcon)(
	      this.#document,
	      "message-square",
	      this.#renderIcon
	    ));
	    const label = this.#document.createElement("span");
	    label.className = "ldp-comments-label", label.textContent = "评论";
	    const count = this.#document.createElement("span");
	    count.className = "ldp-comments-count", count.setAttribute("aria-live", "polite");
	    const presence = this.#document.createElement("span");
	    return presence.className = "ldp-topic-presence", presence.hidden = !0, presence.setAttribute("aria-live", "polite"), header.append(label, count, presence), header;
	  }
	  #refreshCount() {
	    if (!this.#count) return;
	    const count = commentCount(
	      this.#session.topic,
	      this.#session.cachedPosts()
	    );
	    this.#count.textContent = `(${count})`;
	  }
	  #renderPresence() {
	    const root = this.#presence;
	    if (!root?.isConnected) return;
	    root.replaceChildren();
	    const users = this.#presenceUsers;
	    if (!users.length) {
	      root.hidden = !0;
	      return;
	    }
	    const avatars = this.#document.createElement("span");
	    avatars.className = "ldp-topic-presence-avatars";
	    for (const user of users.slice(0, 3)) {
	      const link = this.#document.createElement("a");
	      link.className = "ldp-topic-presence-user ldp-user-link", link.href = this.#presentation.userHref(user.username), link.target = "_blank", link.rel = "noopener noreferrer", link.dataset.userCard = user.username, link.setAttribute("aria-label", user.name);
	      const source = user.avatarTemplate ? this.#presentation.avatarSource(user.avatarTemplate, 24) : "";
	      if (source) {
	        const image = this.#document.createElement("img");
	        image.className = "ldp-topic-presence-avatar", image.src = source, image.alt = "", image.loading = "lazy", image.decoding = "async", link.append(image);
	      } else {
	        const fallback = this.#document.createElement("span");
	        fallback.className = "ldp-topic-presence-avatar ldp-avatar-fallback", fallback.textContent = user.name.slice(0, 1).toUpperCase(), link.append(fallback);
	      }
	      avatars.append(link);
	    }
	    const label = this.#document.createElement("span");
	    label.className = "ldp-topic-presence-text", label.textContent = users.length === 1 ? `${users[0].name} 正在回复…` : `${users.length} 人正在回复…`, root.append(avatars, label), root.hidden = !1;
	  }
	}
}, "8b1bf40506b52a9ea96a53da13c90de849ed71a3a90e138944b1b6618dc1c90d");

/* Source: lite/src/topic/reader-topic-context-controller.ts */
runtime.register("src/topic/reader-topic-context-controller.js", function(module, exports, require) {
	var reader_topic_context_controller_exports = {};
	__export(reader_topic_context_controller_exports, {
	  ReaderTopicContextController: () => ReaderTopicContextController
	});
	module.exports = __toCommonJS(reader_topic_context_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_reader_reply_ancestor_resolver = require("./reader-reply-ancestor-resolver.js");
	function frozenPostNumbers(values) {
	  return Object.freeze([...values].sort((left, right) => left - right));
	}
	class ReaderTopicContextController {
	  scope;
	  changes = new import_signal.Signal();
	  #session;
	  #replies;
	  #loadCrossTopicQuotedPost;
	  #onError;
	  #quotedPosts = /* @__PURE__ */ new Map();
	  #unavailableQuotedPostKeys = /* @__PURE__ */ new Set();
	  #collapsedPostNumbers = /* @__PURE__ */ new Set();
	  #discussionContextualParents = /* @__PURE__ */ new Map();
	  #discussionRootPostNumber = null;
	  #discussionDescendantRootPostNumber = null;
	  #discussionTargetPostNumber = null;
	  #discussionLoading = !1;
	  #discussionBranchPartial = !1;
	  #discussionUsesGlobalCoverage = !1;
	  #revision = 0;
	  #discussionEpoch = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#session = options.session, this.#replies = options.replies, this.#loadCrossTopicQuotedPost = options.loadCrossTopicQuotedPost, this.#onError = options.onError ?? (() => {
	    }), this.#session.changes.subscribe(() => this.#emit(), this.scope), this.#replies.changes.subscribe(() => this.#emit(), this.scope), this.scope.add(() => {
	      this.#discussionEpoch += 1, this.#collapsedPostNumbers.clear(), this.#discussionContextualParents.clear(), this.#quotedPosts.clear(), this.#unavailableQuotedPostKeys.clear(), this.changes.clear();
	    });
	  }
	  get topicId() {
	    return (0, import_identifiers.discourseTopicId)(this.#session.topicId);
	  }
	  postByNumber(postNumberValue) {
	    return this.#session.postByNumber(
	      (0, import_identifiers.discoursePostNumber)(postNumberValue)
	    );
	  }
	  quotedPost(topicIdValue, postNumberValue) {
	    const topicId = (0, import_identifiers.discourseTopicId)(topicIdValue), postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue);
	    return topicId === this.topicId ? this.#session.postByNumber(postNumber) : this.#quotedPosts.get(`${topicId}:${postNumber}`);
	  }
	  snapshot() {
	    return Object.freeze({
	      discussion: this.#discussionSnapshot(),
	      revision: this.#revision
	    });
	  }
	  /** 同 Topic 复用 canonical session;跨 Topic 通过组合根注入的统一请求网关读取。 */
	  async loadQuotedPost(topicIdValue, postNumberValue) {
	    this.#assertActive();
	    const topicId = (0, import_identifiers.discourseTopicId)(topicIdValue), postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue), key = `${topicId}:${postNumber}`;
	    if (topicId !== this.topicId) {
	      const cached2 = this.#quotedPosts.get(key);
	      if (cached2) return cached2;
	      if (this.#unavailableQuotedPostKeys.has(key) || !this.#loadCrossTopicQuotedPost) return null;
	      try {
	        const post = await this.#loadCrossTopicQuotedPost(
	          topicId,
	          postNumber
	        );
	        return this.scope.destroyed ? null : post ? (this.#unavailableQuotedPostKeys.delete(key), this.#quotedPosts.set(key, post), post) : (this.#unavailableQuotedPostKeys.add(key), null);
	      } catch (error) {
	        return this.scope.destroyed || this.#onError(error), null;
	      }
	    }
	    const cached = this.#session.postByNumber(postNumber);
	    if (cached) return cached;
	    try {
	      await this.#session.loadTarget(postNumber, {
	        scope: "single",
	        advanceCursor: !1
	      });
	    } catch (error) {
	      return this.scope.destroyed || this.#onError(error), null;
	    }
	    return this.scope.destroyed ? null : this.#session.postByNumber(postNumber) ?? null;
	  }
	  async openDiscussion(postNumberValue, options = {}) {
	    this.#assertActive();
	    const requestedPostNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue), targetPostNumber = options.targetPostNumber === null ? null : (0, import_identifiers.discoursePostNumber)(
	      options.targetPostNumber ?? requestedPostNumber
	    ), requestedDescendantRootPostNumber = options.descendantRootPostNumber === void 0 ? null : (0, import_identifiers.discoursePostNumber)(options.descendantRootPostNumber), epoch = ++this.#discussionEpoch;
	    this.#discussionLoading = !0, this.#discussionBranchPartial = !1, this.#discussionUsesGlobalCoverage = !1, this.#collapsedPostNumbers.clear(), this.#discussionContextualParents.clear();
	    let rootPostNumber = requestedPostNumber;
	    try {
	      await this.#ensurePost(requestedPostNumber), requestedDescendantRootPostNumber !== null && requestedDescendantRootPostNumber !== requestedPostNumber && await this.#ensurePost(requestedDescendantRootPostNumber), options.explicitRoot || (rootPostNumber = await this.#branchRootOf(
	        requestedPostNumber,
	        epoch
	      ));
	    } catch (error) {
	      epoch === this.#discussionEpoch && !this.scope.destroyed && (this.#discussionBranchPartial = !0, this.#onError(error));
	    }
	    if (epoch !== this.#discussionEpoch || this.scope.destroyed)
	      return this.snapshot();
	    if (!this.#session.postByNumber(rootPostNumber))
	      return this.#discussionLoading = !1, this.#discussionRootPostNumber = null, this.#discussionDescendantRootPostNumber = null, this.#discussionTargetPostNumber = null, this.#emit();
	    const descendantRootPostNumber = requestedDescendantRootPostNumber ?? rootPostNumber;
	    this.#discussionRootPostNumber = rootPostNumber, this.#discussionDescendantRootPostNumber = descendantRootPostNumber, this.#discussionTargetPostNumber = targetPostNumber, this.#emit();
	    try {
	      if (this.#session.loadReplyBranches) {
	        const result = await this.#session.loadReplyBranches(
	          [descendantRootPostNumber],
	          { background: !1, maxPages: 32 }
	        );
	        if (epoch !== this.#discussionEpoch || this.scope.destroyed)
	          return this.snapshot();
	        this.#discussionBranchPartial ||= !result.complete;
	        for (const relation of result.contextualReplyRelations) {
	          const parentPostNumber = (0, import_identifiers.discoursePostNumber)(
	            relation.parentPostNumber
	          ), postNumber = (0, import_identifiers.discoursePostNumber)(relation.postNumber);
	          postNumber === parentPostNumber || !this.#session.postByNumber(parentPostNumber) || !this.#session.postByNumber(postNumber) || this.#discussionContextualParents.has(postNumber) || this.#discussionContextualParents.set(
	            postNumber,
	            parentPostNumber
	          );
	        }
	        for (const error of result.errors) this.#onError(error);
	      } else
	        this.#discussionUsesGlobalCoverage = !0, await this.#session.ensurePostStream({
	          background: !1,
	          maxAttempts: 2
	        });
	    } catch (error) {
	      epoch === this.#discussionEpoch && !this.scope.destroyed && (this.#discussionBranchPartial = !0, this.#onError(error));
	    }
	    return epoch !== this.#discussionEpoch || this.scope.destroyed ? this.snapshot() : (this.#discussionLoading = !1, this.#emit());
	  }
	  closeDiscussion() {
	    return this.#assertActive(), !this.#discussionRootPostNumber && !this.#discussionLoading ? this.snapshot() : (this.#discussionEpoch += 1, this.#discussionRootPostNumber = null, this.#discussionDescendantRootPostNumber = null, this.#discussionTargetPostNumber = null, this.#discussionLoading = !1, this.#discussionBranchPartial = !1, this.#discussionUsesGlobalCoverage = !1, this.#collapsedPostNumbers.clear(), this.#discussionContextualParents.clear(), this.#emit());
	  }
	  toggleDiscussionBranch(postNumberValue) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue);
	    return this.#collapsedPostNumbers.has(postNumber) ? this.#collapsedPostNumbers.delete(postNumber) : this.#collapsedPostNumbers.add(postNumber), this.#emit();
	  }
	  clearDiscussionTarget() {
	    return this.#assertActive(), this.#discussionTargetPostNumber === null ? this.snapshot() : (this.#discussionTargetPostNumber = null, this.#emit());
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #ensurePost(postNumber) {
	    const cached = this.#session.postByNumber(postNumber);
	    return cached || (await this.#session.loadTarget(postNumber, {
	      scope: "single",
	      advanceCursor: !1
	    }), this.#session.postByNumber(postNumber) ?? null);
	  }
	  async #branchRootOf(targetPostNumber, epoch) {
	    const resolution = await (0, import_reader_reply_ancestor_resolver.resolveReaderReplyAncestors)(
	      this.#session,
	      targetPostNumber,
	      {
	        stopBeforePostNumber: 1,
	        isActive: () => epoch === this.#discussionEpoch && !this.scope.destroyed
	      }
	    );
	    return !resolution.complete && epoch === this.#discussionEpoch && !this.scope.destroyed && (this.#discussionBranchPartial = !0), resolution.error !== void 0 && this.#onError(resolution.error), resolution.rootPostNumber;
	  }
	  #replyCount(post) {
	    const count = Number(post.reply_count ?? 0);
	    return Number.isSafeInteger(count) && count > 0 ? count : 0;
	  }
	  #discussionSnapshot() {
	    const rootPostNumber = this.#discussionRootPostNumber, descendantRootPostNumber = this.#discussionDescendantRootPostNumber;
	    if (rootPostNumber === null || descendantRootPostNumber === null)
	      return null;
	    const lineage = this.#discussionLineage(
	      rootPostNumber,
	      descendantRootPostNumber
	    ), lineageNext = /* @__PURE__ */ new Map();
	    for (let index = 0; index < lineage.length - 1; index += 1)
	      lineageNext.set(lineage[index], lineage[index + 1]);
	    const entries = [], pending = [{
	      postNumber: rootPostNumber,
	      parentPostNumber: null,
	      depth: 0
	    }], seen = /* @__PURE__ */ new Set();
	    let missingCanonicalPost = !1;
	    for (let index = 0; index < pending.length; index += 1) {
	      const entry = pending[index];
	      if (seen.has(entry.postNumber)) continue;
	      seen.add(entry.postNumber);
	      const post = this.#session.postByNumber(entry.postNumber);
	      post ? entries.push(Object.freeze({ ...entry, post })) : missingCanonicalPost = !0;
	      const nextLineagePostNumber = lineageNext.get(entry.postNumber);
	      for (const child of this.#discussionChildrenOf(entry.postNumber))
	        nextLineagePostNumber !== void 0 && child !== nextLineagePostNumber || pending.push(Object.freeze({
	          postNumber: (0, import_identifiers.discoursePostNumber)(child),
	          parentPostNumber: entry.postNumber,
	          depth: entry.depth + 1
	        }));
	    }
	    const postCoverage = this.#session.postStreamCoverage(), treeCoverage = this.#replies.coverage(), descendantEntry = entries.find((entry) => entry.postNumber === descendantRootPostNumber), branchHasMissingReplies = entries.some((entry) => entry.depth >= (descendantEntry?.depth ?? 0) && this.#replyCount(entry.post) > this.#discussionChildrenOf(entry.postNumber).length);
	    return Object.freeze({
	      rootPostNumber,
	      descendantRootPostNumber,
	      targetPostNumber: this.#discussionTargetPostNumber,
	      entries: Object.freeze(entries),
	      collapsedPostNumbers: frozenPostNumbers(
	        this.#collapsedPostNumbers
	      ),
	      loading: this.#discussionLoading,
	      partial: this.#discussionLoading || missingCanonicalPost || this.#discussionBranchPartial || branchHasMissingReplies || this.#discussionUsesGlobalCoverage && (!postCoverage.complete || !treeCoverage.complete)
	    });
	  }
	  #discussionChildrenOf(parentPostNumber) {
	    const children = /* @__PURE__ */ new Set();
	    for (const child of this.#replies.topology.childrenOf(parentPostNumber)) {
	      const postNumber = (0, import_identifiers.discoursePostNumber)(child), contextualParent = this.#discussionContextualParents.get(postNumber);
	      contextualParent !== void 0 && contextualParent !== parentPostNumber || children.add(postNumber);
	    }
	    for (const [postNumber, contextualParent] of this.#discussionContextualParents)
	      contextualParent === parentPostNumber && children.add(postNumber);
	    return Object.freeze([...children].sort((left, right) => left - right));
	  }
	  #discussionLineage(rootPostNumber, descendantRootPostNumber) {
	    const reversed = [], seen = /* @__PURE__ */ new Set();
	    let current = descendantRootPostNumber;
	    for (; !seen.has(current); ) {
	      if (seen.add(current), reversed.push(current), current === rootPostNumber)
	        return Object.freeze(reversed.reverse());
	      const parent = this.#replies.topology.parentOf(current);
	      if (parent == null) break;
	      current = (0, import_identifiers.discoursePostNumber)(parent);
	    }
	    return Object.freeze([rootPostNumber]);
	  }
	  #emit() {
	    if (this.scope.destroyed) return this.snapshot();
	    this.#revision += 1;
	    const snapshot = this.snapshot();
	    for (const error of this.changes.emit(snapshot)) this.#onError(error);
	    return snapshot;
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("ReaderTopicContextController 已销毁");
	  }
	}
}, "45e132944170204a8aebb5e07d6d70f747a0b720cadf4b3c37206bee7f165552");

/* Source: lite/src/topic/reader-topic-context-state.ts */
runtime.register("src/topic/reader-topic-context-state.js", function(module, exports, require) {
	var reader_topic_context_state_exports = {};
	__export(reader_topic_context_state_exports, {
	  READER_TOPIC_CONTEXT_STATE_KEY: () => READER_TOPIC_CONTEXT_STATE_KEY,
	  ReaderTopicContextStateRepository: () => ReaderTopicContextStateRepository,
	  readerTopicContextWebStorage: () => readerTopicContextWebStorage
	});
	module.exports = __toCommonJS(reader_topic_context_state_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_reader_account_scoped_storage = require("../state/reader-account-scoped-storage.js");
	const READER_TOPIC_CONTEXT_STATE_KEY = "linuxdo-enhanced-reader:reply-window:v1";
	function record(value) {
	  if (typeof value == "string")
	    try {
	      return record(JSON.parse(value));
	    } catch {
	      return null;
	    }
	  return value && typeof value == "object" && !Array.isArray(value) ? value : null;
	}
	function finite(value, fallback = 0) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? numeric : fallback;
	}
	function geometry(value) {
	  const source = record(value);
	  if (!source) return null;
	  const width = finite(source.width), height = finite(source.height);
	  return !(width > 0) || !(height > 0) ? null : Object.freeze({
	    left: finite(source.left),
	    top: finite(source.top),
	    width,
	    height
	  });
	}
	function anchorPoint(value) {
	  const source = record(value);
	  if (!source) return null;
	  try {
	    return Object.freeze({
	      number: (0, import_identifiers.discoursePostNumber)(source.number),
	      scrollTop: Math.max(0, finite(source.scrollTop)),
	      scrollLeft: Math.max(0, finite(source.scrollLeft)),
	      offset: finite(source.offset, 12)
	    });
	  } catch {
	    return null;
	  }
	}
	function viewKey(host, topicId, rootPostNumber) {
	  return `${String(host).trim()}:${topicId}:${rootPostNumber}:0`;
	}
	function normalizedState(value, maxViews) {
	  const source = record(value), entries = [];
	  for (const [key, rawPoint] of Object.entries(record(source?.views) ?? {})) {
	    const point = anchorPoint(rawPoint);
	    point && entries.push(Object.freeze([
	      key,
	      Object.freeze({
	        ...point,
	        at: Math.max(0, finite(record(rawPoint)?.at))
	      })
	    ]));
	  }
	  return entries.sort((left, right) => left[1].at - right[1].at), Object.freeze({
	    fullPageGeometry: geometry(source?.fullPageGeometry),
	    views: Object.freeze(Object.fromEntries(entries.slice(-maxViews)))
	  });
	}
	class ReaderTopicContextStateRepository {
	  #storage;
	  #key;
	  #accountStorage;
	  #maxViews;
	  #onError;
	  #state;
	  #write = Promise.resolve();
	  #loadPromise = null;
	  constructor(options) {
	    if (this.#storage = options.storage, this.#accountStorage = options.key === void 0 && options.authScope !== void 0 ? (0, import_reader_account_scoped_storage.readerAccountScopedStorageIdentity)(
	      READER_TOPIC_CONTEXT_STATE_KEY,
	      options.authScope
	    ) : null, this.#key = String(options.key ?? this.#accountStorage?.key ?? READER_TOPIC_CONTEXT_STATE_KEY).trim(), !this.#key) throw new Error("完整讨论状态 storage key 不能为空");
	    const maxViews = Math.floor(Number(options.maxViews ?? 128));
	    if (!Number.isSafeInteger(maxViews) || maxViews < 1)
	      throw new RangeError("完整讨论持久锚点上限必须是正整数");
	    this.#maxViews = maxViews, this.#onError = options.onError ?? (() => {
	    }), this.#state = normalizedState(null, maxViews);
	  }
	  get snapshot() {
	    return this.#state;
	  }
	  load() {
	    return this.#loadPromise ? this.#loadPromise : (this.#loadPromise = (async () => {
	      try {
	        const loaded = normalizedState(
	          this.#accountStorage ? await (0, import_reader_account_scoped_storage.readReaderAccountScopedValue)(
	            this.#storage,
	            this.#accountStorage
	          ) : await this.#storage.getValue(this.#key),
	          this.#maxViews
	        ), current = this.#state, hasLocalState = current.fullPageGeometry !== null || Object.keys(current.views).length > 0;
	        this.#state = normalizedState({
	          fullPageGeometry: current.fullPageGeometry ?? loaded.fullPageGeometry,
	          views: {
	            ...loaded.views,
	            ...current.views
	          }
	        }, this.#maxViews), hasLocalState && this.#persist();
	      } catch (error) {
	        this.#onError(error);
	      }
	      return this.#state;
	    })(), this.#loadPromise);
	  }
	  point(host, topicIdValue, rootPostNumberValue) {
	    const topicId = (0, import_identifiers.discourseTopicId)(topicIdValue), rootPostNumber = (0, import_identifiers.discoursePostNumber)(rootPostNumberValue), value = this.#state.views[viewKey(host, topicId, rootPostNumber)];
	    return value ? Object.freeze({
	      number: value.number,
	      scrollTop: value.scrollTop,
	      scrollLeft: value.scrollLeft,
	      offset: value.offset
	    }) : null;
	  }
	  rememberGeometry(value) {
	    const nextGeometry = geometry(value);
	    nextGeometry && (this.#state = Object.freeze({
	      ...this.#state,
	      fullPageGeometry: nextGeometry
	    }), this.#persist());
	  }
	  rememberPoint(host, topicIdValue, rootPostNumberValue, point, now = Date.now()) {
	    if (!point) return;
	    const topicId = (0, import_identifiers.discourseTopicId)(topicIdValue), rootPostNumber = (0, import_identifiers.discoursePostNumber)(rootPostNumberValue), key = viewKey(host, topicId, rootPostNumber), entries = Object.entries(this.#state.views).filter(
	      ([entryKey]) => entryKey !== key
	    );
	    entries.push([
	      key,
	      Object.freeze({
	        ...point,
	        at: Math.max(0, finite(now))
	      })
	    ]);
	    const views = Object.fromEntries(entries);
	    this.#state = normalizedState({
	      fullPageGeometry: this.#state.fullPageGeometry,
	      views
	    }, this.#maxViews), this.#persist();
	  }
	  replaceExternal(value) {
	    return this.#state = normalizedState(value, this.#maxViews), this.#persist(), this.#state;
	  }
	  async flush() {
	    await this.#write;
	  }
	  #persist() {
	    const state = this.#state;
	    this.#write = this.#write.catch(() => {
	    }).then(async () => {
	      try {
	        await this.#storage.setValue(
	          this.#key,
	          state
	        );
	      } catch (error) {
	        this.#onError(error);
	      }
	    });
	  }
	}
	function readerTopicContextWebStorage(storage) {
	  return Object.freeze({
	    getValue: (key) => storage.getItem(key),
	    setValue: (key, value) => {
	      storage.setItem(key, JSON.stringify(value));
	    }
	  });
	}
}, "9f024429af2d4fe1570b1e820d3a65516eb5108af753cae8ad821c881298be94");

/* Source: lite/src/topic/reader-topic-context-surface.ts */
runtime.register("src/topic/reader-topic-context-surface.js", function(module, exports, require) {
	var reader_topic_context_surface_exports = {};
	__export(reader_topic_context_surface_exports, {
	  ReaderTopicContextFeature: () => ReaderTopicContextFeature,
	  ReaderTopicContextSurface: () => ReaderTopicContextSurface
	});
	module.exports = __toCommonJS(reader_topic_context_surface_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_event_target = require("../dom/event-target.js"), import_html_element = require("../dom/html-element.js"), import_reply_tree_dom_owner = require("../dom/reply-tree-dom-owner.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_reader_icon = require("../components/reader-icon.js"), import_branch_overlay = require("../layout/branch-overlay.js"), import_reader_workspace = require("../shell/reader-workspace.js"), import_reader_escape_surface = require("../shell/reader-escape-surface.js"), import_reader_post_view_projector = require("./reader-post-view-projector.js"), import_reader_topic_context_state = require("./reader-topic-context-state.js");
	function button(document, className, label, text = "") {
	  const value = (0, import_html_element.htmlElement)(document, "button", className);
	  return value.type = "button", value.setAttribute("aria-label", label), value.textContent = text, value;
	}
	const HIDDEN_REPLY_MATERIALIZE_BATCH_SIZE = 100, QUOTE_HINT_POINTER_GAP_PX = 4, QUOTE_HINT_HIDE_GRACE_MS = 480;
	function postCooked(post) {
	  return String(
	    post.cooked ?? ""
	  );
	}
	function postReplyCount(post) {
	  const value = Number(
	    post.reply_count ?? 0
	  );
	  return Number.isSafeInteger(value) && value > 0 ? value : 0;
	}
	function comparableQuoteText(value) {
	  return value.replace(/[ \t\r\n]/g, "");
	}
	function quoteExcerptText(document, html) {
	  const container = document.createElement("div");
	  return container.innerHTML = html, String(container.textContent ?? "").trim();
	}
	function markQuoteTextMatch(document, root, text) {
	  const comparable = comparableQuoteText(text);
	  if (!comparable) return Object.freeze([]);
	  const characters = [], positions = [], showText = document.defaultView?.NodeFilter?.SHOW_TEXT ?? 4, walker = document.createTreeWalker(root, showText);
	  for (let node = walker.nextNode(); node; node = walker.nextNode()) {
	    const textNode = node, parent = textNode.parentElement;
	    if (!parent || parent.closest(
	      "button,script,style,noscript,.ldp-quote-highlight-close"
	    ))
	      continue;
	    const value = textNode.nodeValue ?? "";
	    for (let offset = 0; offset < value.length; offset += 1) {
	      const character = value[offset];
	      /[ \t\r\n]/.test(character) || (characters.push(character), positions.push(Object.freeze({ node: textNode, offset })));
	    }
	  }
	  const matchStart = characters.join("").indexOf(comparable);
	  if (matchStart < 0) return Object.freeze([]);
	  const groups = [];
	  for (let index = matchStart; index < matchStart + comparable.length; index += 1) {
	    const position = positions[index];
	    if (!position) continue;
	    const previous = groups.at(-1);
	    previous?.node === position.node ? previous.end = position.offset + 1 : groups.push({
	      node: position.node,
	      start: position.offset,
	      end: position.offset + 1
	    });
	  }
	  const marks = [];
	  for (let index = groups.length - 1; index >= 0; index -= 1) {
	    const group = groups[index], parent = group.node.parentNode;
	    if (!parent) continue;
	    const value = group.node.nodeValue ?? "", fragment = document.createDocumentFragment();
	    group.start > 0 && fragment.append(document.createTextNode(value.slice(0, group.start)));
	    const mark = document.createElement("mark");
	    mark.className = "ldp-quote-match", mark.textContent = value.slice(group.start, group.end), fragment.append(mark), group.end < value.length && fragment.append(document.createTextNode(value.slice(group.end))), parent.replaceChild(fragment, group.node), marks.unshift(mark);
	  }
	  return Object.freeze(marks);
	}
	class ReaderTopicContextFeature {
	  activationScope = "branch";
	  scope;
	  #document;
	  #controller;
	  #replies;
	  #presentation;
	  #scrollRoot;
	  #navigate;
	  #target;
	  #avatarSource;
	  #renderIcon;
	  #onQuoteBodyChanged;
	  #onRevealNextReplyLevel;
	  #revealQuoteTarget;
	  #navigationRetryDelay;
	  #quoteHintHost;
	  #notify;
	  #onError;
	  #rootCleanups = /* @__PURE__ */ new WeakMap();
	  #viewByRoot = /* @__PURE__ */ new WeakMap();
	  #hiddenReplyMarkers = /* @__PURE__ */ new Map();
	  #hiddenReplyPostNumbers = /* @__PURE__ */ new WeakMap();
	  #rootsByPostNumber = /* @__PURE__ */ new Map();
	  #collapsedRootReplies = /* @__PURE__ */ new Set();
	  #quoteJumpExcerptByElement = /* @__PURE__ */ new WeakMap();
	  #quotePostLoads = /* @__PURE__ */ new Map();
	  #expandedQuoteKeys = /* @__PURE__ */ new Set();
	  #quoteSourcePort = null;
	  #quoteHighlight = null;
	  #quoteHighlightMarks = Object.freeze([]);
	  #quoteHighlightHint = null;
	  #quoteReturnEntries = /* @__PURE__ */ new Set();
	  #quoteHintHideTimer = null;
	  #quotePositionEpoch = 0;
	  #quoteJumpEpoch = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#document = options.document, this.#controller = options.controller, this.#replies = options.replies, this.#presentation = options.presentation, this.#scrollRoot = options.scrollRoot, this.#navigate = options.navigate, this.#target = options.target, this.#avatarSource = options.avatarSource, this.#renderIcon = options.renderIcon, this.#onQuoteBodyChanged = options.onQuoteBodyChanged, this.#onRevealNextReplyLevel = options.onRevealNextReplyLevel, this.#revealQuoteTarget = options.revealQuoteTarget, this.#navigationRetryDelay = options.navigationRetryDelay ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs))), this.#quoteHintHost = options.quoteHintHost ?? options.document.body ?? options.document.documentElement, this.#notify = options.notify ?? (() => {
	    }), this.#onError = options.onError ?? (() => {
	    }), (options.presentationChanges ?? this.#replies.changes).subscribe(() => {
	      for (const roots of this.#rootsByPostNumber.values())
	        for (const root of roots) this.#syncMountedTree(root);
	    }, this.scope), this.scope.add(() => {
	      this.#quoteJumpEpoch += 1, this.#quotePositionEpoch += 1, this.#scrollRoot.classList.remove("ldp-quote-positioning");
	      for (const roots of this.#rootsByPostNumber.values())
	        for (const root of roots) this.#rootCleanups.get(root)?.();
	      this.#rootsByPostNumber.clear(), this.#collapsedRootReplies.clear(), this.#quotePostLoads.clear(), this.#expandedQuoteKeys.clear(), this.#clearQuoteHighlight();
	    });
	  }
	  afterRender(post, view) {
	    this.#viewByRoot.set(view.slots.root, view), this.#prepareQuotes(post, view), this.#syncRelationshipControls(
	      view.slots.root,
	      (0, import_identifiers.discoursePostReference)(post).postNumber,
	      post
	    );
	    const highlight = this.#quoteHighlight;
	    highlight?.postNumber === view.postNumber && highlight.source && this.#mountQuoteReturnEntry(view.slots.root, highlight.source), highlight?.postNumber === view.postNumber && this.#quoteHighlightMarks.every((mark) => !mark.parentNode) && this.#applyQuoteHighlight(
	      view.slots.root,
	      highlight.text,
	      highlight.active,
	      highlight.source
	    );
	  }
	  attachRoot(root, postNumberValue) {
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue);
	    if (this.#rootCleanups.has(root)) return;
	    const roots = this.#rootsByPostNumber.get(postNumber) ?? /* @__PURE__ */ new Set();
	    roots.add(root), this.#rootsByPostNumber.set(postNumber, roots);
	    const onClick = (event) => {
	      this.#handleClick(event, root, postNumber).catch((error) => {
	        !this.scope.destroyed && this.#rootCleanups.has(root) && this.#onError(error);
	      });
	    };
	    root.addEventListener("click", onClick);
	    const cleanup = () => {
	      for (const postRoot of [
	        root,
	        ...root.querySelectorAll(".ldp-post")
	      ])
	        this.#removeHiddenReplyMarker(postRoot), postRoot.classList.remove("ldp-has-hidden-child-branches"), postRoot.querySelectorAll(
	          ":scope > .ldp-reply-tree > .ldp-reply-controls > [data-reader-context-hidden-branch-controls]"
	        ).forEach((control) => control.remove());
	      root.removeEventListener("click", onClick);
	      for (const entry of root.querySelectorAll(
	        "[data-reader-context-quote-return]"
	      ))
	        this.#quoteReturnEntries.delete(entry), entry.remove();
	      this.#rootCleanups.delete(root);
	      const current = this.#rootsByPostNumber.get(postNumber);
	      current?.delete(root), current?.size || this.#rootsByPostNumber.delete(postNumber);
	    };
	    this.#rootCleanups.set(root, cleanup), this.#syncMountedTree(root);
	    const highlight = this.#quoteHighlight;
	    highlight?.postNumber === postNumber && highlight.source && this.#mountQuoteReturnEntry(root, highlight.source);
	  }
	  detachRoot(root) {
	    this.#rootCleanups.get(root)?.();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  syncProjection() {
	    for (const roots of this.#rootsByPostNumber.values())
	      for (const root of roots) this.#syncMountedTree(root);
	  }
	  /** 收起当前视口内一个作为普通楼层显示的叶子回复;优先鼠标所在楼层。 */
	  collapseExpandedDefaultPost() {
	    const viewport = this.#scrollRoot.getBoundingClientRect(), candidates = [...this.#rootsByPostNumber.entries()].flatMap(([postNumber, roots]) => [...roots].map((root) => ({
	      postNumber,
	      root,
	      rect: root.getBoundingClientRect()
	    }))).filter(
	      ({ root, rect }) => root.isConnected && !root.closest(".ldp-descendant-replies-layer") && root.classList.contains("ldp-reply-collapsible") && !root.classList.contains("ldp-nested-collapsed") && !root.classList.contains("ldp-nested-preview") && rect.bottom > viewport.top && rect.top < viewport.bottom && rect.right > viewport.left && rect.left < viewport.right
	    ).sort((left, right) => left.rect.top - right.rect.top), candidate = candidates.find(({ root }) => {
	      try {
	        return root.matches(":hover");
	      } catch {
	        return !1;
	      }
	    }) ?? candidates[0];
	    return candidate ? (this.#setRootReplyCollapsed(
	      candidate.root,
	      candidate.postNumber,
	      !0
	    ), candidate.postNumber) : null;
	  }
	  connectQuoteSource(port) {
	    if (this.scope.destroyed)
	      throw new Error("线程上下文 feature 已销毁");
	    if (this.#quoteSourcePort && this.#quoteSourcePort !== port)
	      throw new Error("引用来源端口只能连接一次");
	    this.#quoteSourcePort = port;
	    let active = !0;
	    const cleanup = () => {
	      active && (active = !1, this.#quoteSourcePort === port && (this.#quoteSourcePort = null));
	    };
	    return this.scope.add(cleanup), cleanup;
	  }
	  captureQuoteHighlightState() {
	    return this.#quoteHighlight;
	  }
	  applyRevealedQuoteHighlight(state, postRoot) {
	    if (postRoot.dataset.postNumber !== String(state.postNumber)) return !1;
	    const matched = this.#applyQuoteHighlight(
	      postRoot,
	      state.text,
	      state.active,
	      state.source,
	      !0
	    );
	    return matched || this.#revealQuoteTarget?.(postRoot, "floor"), matched;
	  }
	  async restoreQuoteHighlightState(state) {
	    if (!state)
	      return this.#clearQuoteHighlight(), !0;
	    const navigation = this.#navigate();
	    if (!navigation) return !1;
	    const result = await navigation.navigate({
	      postNumber: state.postNumber,
	      source: "quote",
	      alignment: "nearest",
	      highlight: !1
	    });
	    return this.scope.destroyed || result.status !== "revealed" || !result.element ? !1 : this.applyRevealedQuoteHighlight(state, result.element);
	  }
	  #icon(name) {
	    return (0, import_reader_icon.renderReaderIcon)(this.#document, name, this.#renderIcon);
	  }
	  #prepareQuotes(post, view) {
	    const sourcePostNumber = (0, import_identifiers.discoursePostReference)(post).postNumber;
	    for (const quote of view.slots.content.querySelectorAll(
	      "aside.quote"
	    )) {
	      const title = quote.querySelector(":scope > .title"), body = quote.querySelector(":scope > blockquote");
	      if (!title || !body) continue;
	      quote.classList.add("ldp-post-quote"), title.classList.add("ldp-quote-title");
	      const targetPostNumber = Number(quote.dataset.post ?? 0), targetTopicId = Number(
	        quote.dataset.topic ?? this.#controller.topicId
	      );
	      if (!Number.isSafeInteger(targetPostNumber) || targetPostNumber < 1 || !Number.isSafeInteger(targetTopicId) || targetTopicId < 1)
	        continue;
	      this.#quoteJumpExcerptByElement.has(quote) || this.#quoteJumpExcerptByElement.set(quote, body.innerHTML);
	      let controls = title.querySelector(
	        ":scope > .quote-controls"
	      );
	      controls || (controls = (0, import_html_element.htmlElement)(this.#document, "span", "quote-controls"), title.append(controls)), controls.classList.add("ldp-quote-controls"), controls.querySelectorAll("[data-reader-context-quote]").forEach((control) => control.remove());
	      const key = `${sourcePostNumber}:${targetTopicId}:${targetPostNumber}`, expanded = this.#expandedQuoteKeys.has(key);
	      quote.classList.toggle("ldp-quote-expanded", expanded), quote.dataset.ldpQuoteExpanded = expanded ? "1" : "0";
	      const toggle = button(
	        this.#document,
	        "ldp-quote-toggle",
	        expanded ? "收起引用" : "展开完整引用"
	      );
	      toggle.dataset.readerContextQuote = "toggle", toggle.dataset.quoteKey = key, toggle.dataset.targetPostNumber = String(targetPostNumber), toggle.dataset.targetTopicId = String(targetTopicId), toggle.setAttribute("aria-expanded", String(expanded)), toggle.append(this.#icon(
	        expanded ? "chevron-up" : "chevron-down"
	      )), controls.append(toggle);
	      const jump = button(
	        this.#document,
	        "ldp-quote-jump",
	        `跳到被引用楼层 #${targetPostNumber}`
	      );
	      jump.dataset.readerContextQuote = "jump", jump.dataset.targetPostNumber = String(targetPostNumber), jump.dataset.targetTopicId = String(targetTopicId), jump.append(this.#icon("arrow-up")), controls.append(jump);
	      const fullPost = this.#controller.quotedPost(
	        targetTopicId,
	        targetPostNumber
	      );
	      if (fullPost) {
	        this.#applyQuotePost(quote, body, fullPost);
	        continue;
	      }
	      this.#hydrateQuoteBody(
	        view,
	        quote,
	        body,
	        key,
	        targetTopicId,
	        targetPostNumber
	      ).catch((error) => {
	        this.scope.destroyed || this.#onError(error);
	      });
	    }
	  }
	  #applyQuotePost(quote, body, fullPost) {
	    const cooked = postCooked(fullPost);
	    return quote.dataset.ldpQuoteHydrated = "1", body.innerHTML === cooked ? !1 : (body.innerHTML = cooked, !0);
	  }
	  async #hydrateQuoteBody(view, quote, body, key, targetTopicId, targetPostNumber) {
	    const fullPost = await this.#loadQuotePost(
	      targetTopicId,
	      targetPostNumber
	    );
	    !fullPost || this.scope.destroyed || this.#viewByRoot.get(view.slots.root) !== view || !view.slots.content.contains(quote) || quote.querySelector(":scope > blockquote") !== body || this.#applyQuotePost(quote, body, fullPost) && this.#notifyQuoteBodyChanged(
	      view.slots.root,
	      this.#expandedQuoteKeys.has(key) ? "expanded" : "collapsed"
	    );
	  }
	  async #loadQuotePost(targetTopicId, targetPostNumber) {
	    const cached = this.#controller.quotedPost(
	      targetTopicId,
	      targetPostNumber
	    );
	    if (cached) return cached;
	    const key = `${targetTopicId}:${targetPostNumber}`, pending = this.#quotePostLoads.get(key);
	    if (pending) return pending;
	    const request = this.#requestQuotePost(
	      key,
	      targetTopicId,
	      targetPostNumber
	    );
	    return this.#quotePostLoads.set(key, request), request;
	  }
	  async #requestQuotePost(key, targetTopicId, targetPostNumber) {
	    try {
	      return await this.#controller.loadQuotedPost(
	        targetTopicId,
	        targetPostNumber
	      );
	    } finally {
	      this.#quotePostLoads.delete(key);
	    }
	  }
	  #syncRelationshipControls(root, postNumber, post = null) {
	    const header = root.querySelector(
	      ":scope > .ldp-post-head"
	    ), replyControls = root.querySelector(
	      ":scope > .ldp-reply-tree > .ldp-reply-controls"
	    );
	    header?.querySelectorAll(":scope > [data-reader-context-parent]").forEach((control) => control.remove()), header?.querySelectorAll(":scope > .ldp-jump-parent").forEach((control) => control.remove()), replyControls?.querySelectorAll(":scope > [data-reader-context-discussion]").forEach((control) => control.remove()), replyControls?.querySelectorAll(
	      ":scope > [data-reader-context-hidden-branch-controls]"
	    ).forEach((control) => control.remove()), root.classList.remove("ldp-has-hidden-child-branches");
	    const parent = this.#replies.topology.parentOf(postNumber), onlyOpPost = this.#isOnlyOpPost(postNumber), currentFloor = header?.querySelector(
	      ":scope > :is(.ldp-body-floor,[data-reader-context-self])"
	    );
	    if (header && parent !== void 0 && parent !== null)
	      if (currentFloor?.dataset.readerContextSelf)
	        currentFloor.classList.add("ldp-current-floor"), currentFloor.textContent = `#${postNumber}`, currentFloor.dataset.targetPostNumber = String(postNumber), currentFloor.setAttribute("aria-label", `跳到楼层 #${postNumber}`);
	      else {
	        const selfButton = button(
	          this.#document,
	          "ldp-floor ldp-jump-self ldp-current-floor",
	          `跳到楼层 #${postNumber}`,
	          `#${postNumber}`
	        );
	        selfButton.dataset.readerContextSelf = "1", selfButton.dataset.targetPostNumber = String(postNumber), currentFloor ? currentFloor.replaceWith(selfButton) : header.insertBefore(
	          selfButton,
	          header.querySelector(":scope > .ldp-post-read-state")
	        );
	      }
	    else if (header && currentFloor?.dataset.readerContextSelf) {
	      const floor = (0, import_html_element.htmlElement)(this.#document, "span", "ldp-floor ldp-body-floor");
	      floor.textContent = `#${postNumber}`, currentFloor.replaceWith(floor);
	    }
	    const insideDiscussion = !!root.closest(".ldp-descendant-replies-layer"), hiddenDirectChildren = insideDiscussion ? Object.freeze([]) : this.#presentation?.hiddenDirectChildrenOf(postNumber) ?? Object.freeze([]);
	    if (replyControls && hiddenDirectChildren.length > 0) {
	      root.classList.add("ldp-has-hidden-child-branches");
	      const controls = (0, import_html_element.htmlElement)(
	        this.#document,
	        "div",
	        "ldp-hidden-branch-controls"
	      );
	      controls.dataset.readerContextHiddenBranchControls = "1";
	      const revealButton = button(
	        this.#document,
	        "ldp-collapse-replies ldp-reply-rail-control ldp-hidden-branch-reveal",
	        `展开楼层 #${postNumber} 的下一层回复`
	      );
	      revealButton.append(this.#icon("plus")), revealButton.dataset.readerContextRevealBranch = "1", revealButton.setAttribute("aria-expanded", "false");
	      const branchButton = button(
	        this.#document,
	        "ldp-btn ldp-sub-page-btn ldp-hidden-branch-discussion",
	        `查看楼层 #${postNumber} 以下的完整分支`
	      );
	      branchButton.dataset.readerContextBranchDiscussion = "1", branchButton.dataset.targetPostNumber = String(
	        hiddenDirectChildren[0]
	      ), branchButton.append(this.#icon("layers"));
	      const label = (0, import_html_element.htmlElement)(this.#document, "span", "");
	      label.textContent = "查看完整分支", branchButton.append(label), controls.append(revealButton, branchButton), replyControls.append(controls);
	    }
	    const discussionOwner = this.#discussionBranchOwner(postNumber), onlyOpDiscussion = onlyOpPost && parent !== void 0 && parent !== null && !insideDiscussion, hasParkedDiscussion = postNumber > 1 && !insideDiscussion && discussionOwner === postNumber && this.#branchHasParkedDiscussion(postNumber, post);
	    if (replyControls && (onlyOpDiscussion || hasParkedDiscussion)) {
	      const discussionButton = button(
	        this.#document,
	        "ldp-btn ldp-sub-page-btn ldp-descendant-replies-open",
	        `查看楼层 #${postNumber} 的完整讨论`
	      );
	      discussionButton.dataset.readerContextDiscussion = "1", discussionButton.append(this.#icon("layers"));
	      const label = (0, import_html_element.htmlElement)(this.#document, "span", "");
	      label.textContent = "查看完整讨论", discussionButton.append(label), replyControls.append(discussionButton);
	    }
	  }
	  #syncRootReplyCollapse(root, postNumber, post) {
	    const canonicalParent = this.#replies.topology.parentOf(postNumber), projectedParent = this.#presentation?.parentOf(postNumber), canonicalReply = post !== null && canonicalParent !== void 0 && canonicalParent !== null, canonicalLeafReply = canonicalReply && postReplyCount(post) === 0 && this.#replies.topology.childrenOf(postNumber).length === 0, nestedPreview = root.classList.contains("ldp-nested-preview"), streamReply = canonicalReply && !this.#isOnlyOpPost(postNumber) && !root.closest(".ldp-descendant-replies-layer") && (projectedParent === null || nestedPreview), collapseControlVisible = streamReply && projectedParent === null && !nestedPreview, previousToggle = root.querySelector(
	      ":scope > [data-reader-context-collapse-reply]"
	    ), previousHint = root.querySelector(
	      ":scope > .ldp-nested-esc-hint"
	    );
	    if (!streamReply) {
	      canonicalLeafReply || this.#collapsedRootReplies.delete(postNumber), root.classList.remove(
	        "ldp-reply",
	        "ldp-reply-collapsible",
	        "ldp-nested-collapsed"
	      ), delete root.dataset.replyToPostNumber, previousToggle?.remove(), previousHint?.remove();
	      return;
	    }
	    if (root.classList.add("ldp-reply", "ldp-reply-collapsible"), root.dataset.replyToPostNumber = String(canonicalParent), !collapseControlVisible) {
	      root.classList.remove("ldp-nested-collapsed"), previousToggle?.remove(), previousHint?.remove();
	      return;
	    }
	    let toggle = previousToggle;
	    toggle || (toggle = button(
	      this.#document,
	      "ldp-btn ldp-nested-toggle",
	      ""
	    ), toggle.dataset.readerContextCollapseReply = "1", root.insertBefore(toggle, root.firstChild)), this.#setRootReplyCollapsed(
	      root,
	      postNumber,
	      this.#collapsedRootReplies.has(postNumber)
	    );
	  }
	  #setRootReplyCollapsed(root, postNumber, collapsed) {
	    collapsed ? this.#collapsedRootReplies.add(postNumber) : this.#collapsedRootReplies.delete(postNumber), root.classList.toggle("ldp-nested-collapsed", collapsed);
	    const toggle = root.querySelector(
	      ":scope > [data-reader-context-collapse-reply]"
	    );
	    toggle && (toggle.setAttribute("aria-expanded", String(!collapsed)), toggle.setAttribute(
	      "aria-label",
	      `${collapsed ? "展开" : "收起"}楼层 #${postNumber}`
	    ), toggle.replaceChildren(this.#icon(
	      collapsed ? "chevron-down" : "chevron-up"
	    )));
	    let hint = root.querySelector(
	      ":scope > .ldp-nested-esc-hint"
	    );
	    if (collapsed) {
	      hint?.remove();
	      return;
	    }
	    !hint && toggle && (hint = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-nested-esc-hint"
	    ), toggle.insertAdjacentElement("afterend", hint)), hint && (hint.textContent = "Esc 收起");
	  }
	  #isOnlyOpPost(postNumber) {
	    return this.#presentation?.postFilterKey?.startsWith("only-op:") === !0 && this.#presentation.postFilterMatches(postNumber);
	  }
	  /**
	   * 一条连续可见树只允许一个完整讨论入口。
	   *
	   * 普通树由 projection root 持有;楼主 #1 下的每条直属回复分支分别由该直属回复
	   * 持有,避免把所有评论误合并为楼主的一条讨论。入口放在 owner 的 replyControls,
	   * 该槽位在内联 replyList 之后,因此视觉上天然位于整条可见分支末尾。
	   */
	  #discussionBranchOwner(postNumber) {
	    const presentation = this.#presentation;
	    if (!presentation) return postNumber;
	    const rootPostNumber = presentation.rootOf(postNumber);
	    if (rootPostNumber === void 0 || rootPostNumber !== 1)
	      return (0, import_identifiers.discoursePostNumber)(rootPostNumber ?? postNumber);
	    let owner = postNumber, parent = presentation.parentOf(owner);
	    for (; parent != null && parent !== 1; )
	      owner = (0, import_identifiers.discoursePostNumber)(parent), parent = presentation.parentOf(owner);
	    return (0, import_identifiers.discoursePostNumber)(owner);
	  }
	  #branchHasParkedDiscussion(rootPostNumber, rootPost) {
	    const presentation = this.#presentation, pending = [{ postNumber: rootPostNumber, post: rootPost }], visited = /* @__PURE__ */ new Set();
	    for (; pending.length; ) {
	      const current = pending.pop();
	      if (visited.has(current.postNumber)) continue;
	      visited.add(current.postNumber);
	      const knownChildren = this.#replies.topology.childrenOf(
	        current.postNumber
	      ), visibleChildren = presentation?.childrenOf(current.postNumber) ?? knownChildren, hiddenChildren = presentation?.hiddenDirectChildrenOf(
	        current.postNumber
	      ).length ?? 0, currentPost = current.post ?? this.#controller.postByNumber(current.postNumber), unresolvedChildren = Math.max(
	        0,
	        (currentPost ? postReplyCount(currentPost) : 0) - knownChildren.length
	      );
	      if (hiddenChildren > 0 || unresolvedChildren > 0 && visibleChildren.length === 0) return !0;
	      for (const childPostNumber of visibleChildren) {
	        const normalizedChild = (0, import_identifiers.discoursePostNumber)(childPostNumber);
	        pending.push({
	          postNumber: normalizedChild,
	          post: this.#controller.postByNumber(normalizedChild) ?? null
	        });
	      }
	    }
	    return !1;
	  }
	  #syncMountedTree(root) {
	    const postRoots = [
	      root,
	      ...root.querySelectorAll(".ldp-post")
	    ];
	    for (const postRoot of postRoots) {
	      const postNumber = (0, import_identifiers.discoursePostNumber)(
	        postRoot.dataset.postNumber
	      ), post = this.#controller.postByNumber(postNumber) ?? null;
	      this.#syncRelationshipControls(
	        postRoot,
	        postNumber,
	        post
	      ), this.#syncRootReplyCollapse(
	        postRoot,
	        postNumber,
	        post
	      ), !postRoot.closest(".ldp-descendant-replies-layer") && this.#syncHiddenReplyMarker(postRoot, postNumber);
	    }
	  }
	  #syncHiddenReplyMarker(root, postNumber) {
	    if (this.#presentation && this.#presentation.rootOf(postNumber) !== postNumber) {
	      this.#removeHiddenReplyMarker(root);
	      return;
	    }
	    const previous = this.#hiddenReplyMarkers.get(root) ?? null, hiddenPostNumbers = this.#presentation?.hiddenFloorRunAfter(postNumber) ?? Object.freeze([]);
	    if (hiddenPostNumbers.length === 0 || !this.#hiddenReplyTreeProjectionReady(
	      root,
	      postNumber,
	      hiddenPostNumbers
	    )) {
	      this.#removeHiddenReplyMarker(root);
	      return;
	    }
	    if (previous && this.#hiddenReplyPostNumbers.get(previous) === hiddenPostNumbers) {
	      root.classList.add("ldp-before-hidden-reply-marker"), previous.previousElementSibling !== root && root.after(previous);
	      return;
	    }
	    const expanded = previous?.querySelector(
	      "[data-reader-context-hidden-list]"
	    )?.hidden === !1;
	    this.#removeHiddenReplyMarker(root);
	    const marker = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-hidden-reply-marker"
	    );
	    marker.dataset.readerContextHiddenReplies = "1", marker.dataset.readerContextHiddenPostNumbers = [
	      hiddenPostNumbers.length,
	      hiddenPostNumbers[0],
	      hiddenPostNumbers.at(-1)
	    ].join(":"), this.#hiddenReplyPostNumbers.set(marker, hiddenPostNumbers);
	    const toggle = button(
	      this.#document,
	      "ldp-btn ldp-hidden-reply-toggle",
	      `${expanded ? "收起" : "查看"} ${hiddenPostNumbers.length} 个隐藏回复`
	    );
	    toggle.dataset.readerContextHiddenToggle = "1", toggle.setAttribute("aria-expanded", String(expanded));
	    const list = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-hidden-reply-list"
	    );
	    list.dataset.readerContextHiddenList = "1", list.dataset.readerContextHiddenRendered = "0", list.setAttribute("role", "list"), list.hidden = !expanded, expanded && this.#appendHiddenReplyBatch(marker, list), marker.append(toggle, list), this.#hiddenReplyMarkers.set(root, marker), root.classList.add("ldp-before-hidden-reply-marker"), marker.addEventListener("click", (event) => {
	      this.#handleClick(event, root, postNumber).catch((error) => {
	        !this.scope.destroyed && marker.isConnected && this.#onError(error);
	      });
	    }), root.after(marker);
	  }
	  #hiddenReplyTreeProjectionReady(root, postNumber, hiddenPostNumbers) {
	    const presentation = this.#presentation;
	    if (!presentation || !hiddenPostNumbers.some(
	      (hiddenPostNumber) => presentation.rootOf(hiddenPostNumber) === postNumber
	    ) || presentation.childrenOf(postNumber).length === 0)
	      return !0;
	    const replyList = root.querySelector(
	      ":scope > .ldp-children > .ldp-reply-list"
	    );
	    return replyList ? Array.from(replyList.children).some((child) => child.classList.contains("ldp-post") || child.classList.contains("ldp-tree-virtual-spacer")) : !1;
	  }
	  #appendHiddenReplyBatch(marker, list) {
	    list.querySelector("[data-reader-context-hidden-more]")?.remove();
	    const postNumbers = this.#hiddenReplyPostNumbers.get(marker) ?? [], rendered = Math.min(
	      postNumbers.length,
	      Math.max(
	        0,
	        Number(list.dataset.readerContextHiddenRendered) || 0
	      )
	    ), end = Math.min(
	      postNumbers.length,
	      rendered + HIDDEN_REPLY_MATERIALIZE_BATCH_SIZE
	    );
	    for (let index = rendered; index < end; index += 1) {
	      const hiddenPostNumber = postNumbers[index], identity = this.#controller.postByNumber(hiddenPostNumber), username = String(identity?.username ?? "").trim(), displayName = String(identity?.name ?? "").trim() || username || `#${hiddenPostNumber}`, avatar = button(
	        this.#document,
	        "ldp-hidden-reply-avatar",
	        `跳到 ${displayName} 的回复 #${hiddenPostNumber}`
	      );
	      avatar.dataset.readerContextHiddenPost = String(hiddenPostNumber), avatar.setAttribute("role", "listitem");
	      const source = this.#avatarSource?.(
	        String(identity?.avatar_template ?? ""),
	        24
	      ) ?? "";
	      if (source) {
	        const image = this.#document.createElement("img");
	        image.src = source, image.alt = "", image.loading = "lazy", image.decoding = "async", avatar.append(image);
	      } else
	        avatar.textContent = [...displayName][0] ?? "?";
	      list.append(avatar);
	    }
	    if (list.dataset.readerContextHiddenRendered = String(end), end >= postNumbers.length) return;
	    const remaining = postNumbers.length - end, nextBatchSize = Math.min(
	      remaining,
	      HIDDEN_REPLY_MATERIALIZE_BATCH_SIZE
	    ), more = button(
	      this.#document,
	      "ldp-btn ldp-hidden-reply-more",
	      `再显示 ${nextBatchSize} 个隐藏回复,剩余 ${remaining} 个`,
	      `+${nextBatchSize}`
	    );
	    more.dataset.readerContextHiddenMore = "1", more.setAttribute("role", "listitem"), list.append(more);
	  }
	  #removeHiddenReplyMarker(root) {
	    this.#hiddenReplyMarkers.get(root)?.remove(), this.#hiddenReplyMarkers.delete(root), root.classList.remove("ldp-before-hidden-reply-marker");
	  }
	  async #handleClick(event, root, rootPostNumber) {
	    const target = (0, import_event_target.eventElement)(event);
	    if (!target) return;
	    const postRoot = target.closest(".ldp-post"), sourcePostNumber = postRoot && root.contains(postRoot) ? (0, import_identifiers.discoursePostNumber)(postRoot.dataset.postNumber) : rootPostNumber, collapseReply = target.closest(
	      "[data-reader-context-collapse-reply]"
	    );
	    if (collapseReply && root.contains(collapseReply) && postRoot) {
	      event.preventDefault(), this.#setRootReplyCollapsed(
	        postRoot,
	        sourcePostNumber,
	        !postRoot.classList.contains("ldp-nested-collapsed")
	      );
	      return;
	    }
	    const hiddenToggle = target.closest(
	      "[data-reader-context-hidden-toggle]"
	    ), hiddenMarker = this.#hiddenReplyMarkers.get(root);
	    if (hiddenToggle && hiddenMarker?.contains(hiddenToggle)) {
	      event.preventDefault();
	      const list = hiddenToggle.closest(
	        "[data-reader-context-hidden-replies]"
	      )?.querySelector(
	        "[data-reader-context-hidden-list]"
	      );
	      if (!list) return;
	      const expanded2 = list.hidden;
	      expanded2 && list.dataset.readerContextHiddenRendered === "0" && this.#appendHiddenReplyBatch(hiddenMarker, list), list.hidden = !expanded2, hiddenToggle.setAttribute("aria-expanded", String(expanded2));
	      const hiddenPostCount = this.#hiddenReplyPostNumbers.get(hiddenMarker)?.length ?? 0;
	      hiddenToggle.setAttribute(
	        "aria-label",
	        `${expanded2 ? "收起" : "查看"} ${hiddenPostCount} 个隐藏回复`
	      );
	      return;
	    }
	    const hiddenMore = target.closest(
	      "[data-reader-context-hidden-more]"
	    );
	    if (hiddenMore && hiddenMarker?.contains(hiddenMore)) {
	      event.preventDefault();
	      const list = hiddenMore.closest(
	        "[data-reader-context-hidden-list]"
	      );
	      list && this.#appendHiddenReplyBatch(hiddenMarker, list);
	      return;
	    }
	    const hiddenReply = target.closest(
	      "[data-reader-context-hidden-post]"
	    );
	    if (hiddenReply && hiddenMarker?.contains(hiddenReply)) {
	      event.preventDefault();
	      const navigation = this.#navigate();
	      if (!navigation) throw new Error("隐藏楼层跳转时 navigation 尚未就绪");
	      await navigation.navigate({
	        postNumber: (0, import_identifiers.discoursePostNumber)(
	          hiddenReply.dataset.readerContextHiddenPost
	        ),
	        source: "timeline",
	        alignment: "nearest",
	        highlight: !0
	      });
	      return;
	    }
	    const selfButton = target.closest(
	      "[data-reader-context-self]"
	    );
	    if (selfButton && root.contains(selfButton)) {
	      event.preventDefault();
	      const navigation = this.#navigate();
	      if (!navigation) throw new Error("楼层跳转时 navigation 尚未就绪");
	      await navigation.navigate({
	        postNumber: (0, import_identifiers.discoursePostNumber)(
	          selfButton.dataset.targetPostNumber
	        ),
	        source: "link",
	        alignment: "start",
	        highlight: !0
	      });
	      return;
	    }
	    const discussionButton = target.closest(
	      "[data-reader-context-discussion]"
	    );
	    if (discussionButton && root.contains(discussionButton)) {
	      event.preventDefault(), await this.#controller.openDiscussion(sourcePostNumber);
	      return;
	    }
	    const revealBranchButton = target.closest(
	      "[data-reader-context-reveal-branch]"
	    );
	    if (revealBranchButton && root.contains(revealBranchButton)) {
	      event.preventDefault(), this.#onRevealNextReplyLevel?.(sourcePostNumber);
	      return;
	    }
	    const branchDiscussionButton = target.closest(
	      "[data-reader-context-branch-discussion]"
	    );
	    if (branchDiscussionButton && root.contains(branchDiscussionButton)) {
	      event.preventDefault(), await this.#controller.openDiscussion(sourcePostNumber, {
	        descendantRootPostNumber: sourcePostNumber,
	        targetPostNumber: (0, import_identifiers.discoursePostNumber)(
	          branchDiscussionButton.dataset.targetPostNumber ?? sourcePostNumber
	        )
	      });
	      return;
	    }
	    const quoteAction = target.closest(
	      "[data-reader-context-quote]"
	    );
	    if (!quoteAction || !root.contains(quoteAction)) return;
	    event.preventDefault();
	    const targetPostNumber = (0, import_identifiers.discoursePostNumber)(
	      quoteAction.dataset.targetPostNumber
	    );
	    if (quoteAction.dataset.readerContextQuote === "jump") {
	      const targetTopicId = Number(
	        quoteAction.dataset.targetTopicId ?? this.#controller.topicId
	      ), quote2 = quoteAction.closest(".ldp-post-quote"), excerptHtml = quote2 ? this.#quoteJumpExcerptByElement.get(quote2) ?? "" : "", excerpt = quoteExcerptText(this.#document, excerptHtml), rawParentPostNumber = this.#replies.topology.parentOf(sourcePostNumber), parentPostNumber = rawParentPostNumber == null ? null : (0, import_identifiers.discoursePostNumber)(rawParentPostNumber), source = Object.freeze({
	        topicId: this.#controller.topicId,
	        postNumber: sourcePostNumber,
	        parentPostNumber,
	        nested: parentPostNumber !== null && parentPostNumber > 1,
	        anchor: this.#quoteSourcePort?.captureAnchor() ?? null
	      }), quoteHighlight = Object.freeze({
	        postNumber: targetPostNumber,
	        text: excerpt,
	        active: !0,
	        source
	      });
	      if (targetTopicId !== this.#controller.topicId) {
	        if (!this.#target)
	          throw new Error("跨主题引用跳转时 target 尚未就绪");
	        await this.#target.open({
	          topicId: targetTopicId,
	          postNumber: targetPostNumber,
	          source: "quote",
	          alignment: "nearest",
	          highlight: !1,
	          forceRefresh: !0,
	          quoteHighlight
	        });
	        return;
	      }
	      const navigation = this.#navigate();
	      if (!navigation) throw new Error("引用跳转时 navigation 尚未就绪");
	      const jumpEpoch = ++this.#quoteJumpEpoch, result = await this.#navigateQuoteWithRetry(navigation, {
	        postNumber: targetPostNumber,
	        source: "quote",
	        alignment: "nearest",
	        highlight: !1
	      }, root, jumpEpoch);
	      if (!result || !this.#isActiveRoot(root)) return;
	      if (result.status !== "revealed") {
	        result.status === "unavailable" ? this.#notify(
	          `目的地楼层 #${targetPostNumber} 不存在或当前不可访问`
	        ) : result.status === "superseded" ? this.#notify("楼层跳转已取消;检测到新的定位或滚动操作") : result.status === "unresolved-tree" ? this.#notify(
	          `目的地楼层 #${targetPostNumber} 的回复树暂未完成挂载;请稍后重试`
	        ) : this.#notify(`目的地楼层 #${targetPostNumber} 定位失败`);
	        return;
	      }
	      result.element && !this.applyRevealedQuoteHighlight(quoteHighlight, result.element) && this.#notify(
	        `目的地内容已修改;已定位到楼层 #${targetPostNumber}`
	      );
	      return;
	    }
	    const quote = quoteAction.closest(".ldp-post-quote"), body = quote?.querySelector(":scope > blockquote");
	    if (!quote || !body) return;
	    const key = String(quoteAction.dataset.quoteKey ?? ""), expanded = this.#expandedQuoteKeys.has(key), anchorRoot = postRoot ?? root;
	    if (expanded)
	      this.#expandedQuoteKeys.delete(key), quote.dataset.ldpQuoteExpanded = "0", quote.classList.remove("ldp-quote-expanded"), quoteAction.setAttribute("aria-expanded", "false"), quoteAction.setAttribute("aria-label", "展开完整引用"), quoteAction.replaceChildren(this.#icon("chevron-down")), this.#notifyQuoteBodyChanged(anchorRoot, "collapsed");
	    else {
	      quoteAction.setAttribute("aria-busy", "true");
	      const targetTopicId = Number(
	        quoteAction.dataset.targetTopicId ?? this.#controller.topicId
	      );
	      try {
	        const fullPost = await this.#loadQuotePost(
	          targetTopicId,
	          targetPostNumber
	        );
	        if (!this.#isActiveRoot(root) || !quoteAction.isConnected || !body.isConnected)
	          return;
	        if (!fullPost) {
	          quoteAction.setAttribute(
	            "aria-label",
	            "完整引用不可用;可使用原生引用链接"
	          );
	          return;
	        }
	        this.#applyQuotePost(quote, body, fullPost), this.#expandedQuoteKeys.add(key), quote.dataset.ldpQuoteExpanded = "1", quote.classList.add("ldp-quote-expanded"), quoteAction.setAttribute("aria-expanded", "true"), quoteAction.setAttribute("aria-label", "收起引用"), quoteAction.replaceChildren(this.#icon("chevron-up")), this.#notifyQuoteBodyChanged(anchorRoot, "expanded");
	      } finally {
	        quoteAction.removeAttribute("aria-busy");
	      }
	    }
	  }
	  #isActiveRoot(root) {
	    return !this.scope.destroyed && this.#rootCleanups.has(root) && root.isConnected;
	  }
	  async #navigateQuoteWithRetry(navigation, request, root, jumpEpoch) {
	    for (let attempt = 0; ; attempt += 1) {
	      let result;
	      try {
	        result = await navigation.navigate(request);
	      } catch (error) {
	        throw this.#isActiveRoot(root) && jumpEpoch === this.#quoteJumpEpoch && this.#notify(
	          `目的地楼层 #${request.postNumber} 定位失败;请稍后重试`
	        ), error;
	      }
	      if (!this.#isActiveRoot(root) || jumpEpoch !== this.#quoteJumpEpoch)
	        return null;
	      if (result.status !== "unresolved-tree" || attempt >= 2)
	        return result;
	      this.#notify(
	        `目标楼层定位暂时失败,${attempt + 1} 秒后自动重试一次`
	      );
	      const navigationRevision = navigation.revision;
	      if (await this.#navigationRetryDelay((attempt + 1) * 1e3), !this.#isActiveRoot(root) || jumpEpoch !== this.#quoteJumpEpoch)
	        return null;
	      if (navigationRevision !== void 0 && navigation.isCurrent && !navigation.isCurrent(navigationRevision))
	        return Object.freeze({
	          ...result,
	          status: "superseded"
	        });
	    }
	  }
	  #notifyQuoteBodyChanged(root, state) {
	    if (!this.#onQuoteBodyChanged) return;
	    const view = this.#viewByRoot.get(root);
	    view && this.#onQuoteBodyChanged(view, state);
	  }
	  #applyQuoteHighlight(postRoot, text, active, source = null, revealMatch = !1) {
	    this.#clearQuoteHighlight();
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postRoot.dataset.postNumber);
	    this.#quoteHighlight = Object.freeze({
	      postNumber,
	      text,
	      source,
	      active
	    }), source && this.#syncQuoteReturnEntries(postRoot, postNumber, source);
	    const content = postRoot.querySelector(
	      ":scope > .ldp-post-body > .ldp-content"
	    );
	    if (!content) return !1;
	    const releasePositioning = this.#beginQuotePositioning(), marks = markQuoteTextMatch(this.#document, content, text);
	    if (releasePositioning(), !marks.length) return !1;
	    this.#quoteHighlightMarks = marks;
	    const lastMark = marks.at(-1);
	    lastMark?.classList.add("ldp-quote-match-end"), lastMark && (lastMark.tabIndex = 0, lastMark.setAttribute("role", "button"));
	    const hint = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-quote-highlight-hint ldp-action-surface"
	    ), toggle = button(
	      this.#document,
	      "ldp-quote-highlight-toggle",
	      active ? "关闭引用高亮" : "继续引用高亮",
	      active ? "关闭高亮" : "继续高亮"
	    );
	    hint.append(toggle);
	    let returnButton = null;
	    source && this.#quoteSourcePort && (returnButton = button(
	      this.#document,
	      "ldp-quote-highlight-return",
	      source.nested ? "返回二级回复" : "返回引用楼层",
	      source.nested ? "返回二级回复" : "返回引用楼层"
	    ), hint.append(returnButton), this.#bindQuoteReturn(returnButton, source, () => {
	      hint.classList.remove("ldp-quote-hint-visible");
	    })), this.#quoteHintHost.append(hint), this.#quoteHighlightHint = hint;
	    const setActive = (nextActive) => {
	      const current = this.#quoteHighlight;
	      if (current) {
	        this.#quoteHighlight = Object.freeze({
	          ...current,
	          active: nextActive
	        });
	        for (const currentMark of this.#quoteHighlightMarks)
	          currentMark.classList.toggle(
	            "ldp-quote-match-muted",
	            !nextActive
	          );
	        toggle.textContent = nextActive ? "关闭高亮" : "继续高亮", toggle.setAttribute(
	          "aria-label",
	          nextActive ? "关闭引用高亮" : "继续引用高亮"
	        ), lastMark?.setAttribute("aria-pressed", String(nextActive));
	      }
	    }, toggleActive = (event) => {
	      event.preventDefault(), event.stopPropagation(), setActive(!(this.#quoteHighlight?.active ?? !1));
	    }, cancelHintHide = () => {
	      this.#quoteHintHideTimer !== null && (clearTimeout(this.#quoteHintHideTimer), this.#quoteHintHideTimer = null);
	    }, showHint = (event) => {
	      if (!lastMark || this.#quoteHighlightHint !== hint) return;
	      cancelHintHide(), hint.classList.add("ldp-quote-hint-visible");
	      const markRect = lastMark.getBoundingClientRect(), rootRect = this.#scrollRoot.getBoundingClientRect(), hintRect = hint.getBoundingClientRect(), viewportWidth = this.#document.defaultView?.innerWidth ?? this.#document.documentElement.clientWidth, anchorX = event && Number.isFinite(event.clientX) ? event.clientX : markRect.left + markRect.width / 2, anchorY = event && Number.isFinite(event.clientY) ? event.clientY : markRect.top, edge = 8, gap = QUOTE_HINT_POINTER_GAP_PX, left = Math.max(
	        edge,
	        Math.min(
	          anchorX - hintRect.width / 2,
	          viewportWidth - hintRect.width - edge
	        )
	      ), top = anchorY - hintRect.height - gap < rootRect.top + edge ? anchorY + gap : anchorY - hintRect.height - gap;
	      hint.style.left = `${Math.round(left)}px`, hint.style.top = `${Math.round(Math.max(edge, top))}px`;
	    }, scheduleHintHide = () => {
	      cancelHintHide(), this.#quoteHintHideTimer = setTimeout(() => {
	        this.#quoteHintHideTimer = null, !(hint.matches(":hover,:focus-within") || this.#quoteHighlightMarks.some((mark) => mark.matches(":hover"))) && hint.classList.remove("ldp-quote-hint-visible");
	      }, QUOTE_HINT_HIDE_GRACE_MS);
	    };
	    toggle.addEventListener("click", toggleActive), hint.addEventListener("mouseenter", cancelHintHide), hint.addEventListener("mouseleave", scheduleHintHide);
	    for (const mark of marks)
	      mark.addEventListener("mouseenter", (event) => showHint(event)), mark.addEventListener("mouseleave", scheduleHintHide);
	    lastMark?.addEventListener("focus", () => showHint()), lastMark?.addEventListener("blur", scheduleHintHide), lastMark?.addEventListener("keydown", (event) => {
	      (event.key === "Enter" || event.key === " ") && toggleActive(event);
	    });
	    for (const mark of marks)
	      mark.classList.toggle("ldp-quote-match-muted", !active), mark.addEventListener("click", toggleActive);
	    if (setActive(active), revealMatch) {
	      const firstMark = marks[0];
	      firstMark && this.#revealQuoteTarget?.(firstMark, "match");
	    }
	    return !0;
	  }
	  #syncQuoteReturnEntries(postRoot, postNumber, source) {
	    const roots = /* @__PURE__ */ new Set([
	      postRoot,
	      ...this.#rootsByPostNumber.get(postNumber) ?? []
	    ]);
	    for (const root of roots) this.#mountQuoteReturnEntry(root, source);
	  }
	  #mountQuoteReturnEntry(postRoot, source) {
	    if (!this.#quoteSourcePort) return;
	    const header = postRoot.querySelector(
	      ":scope > .ldp-post-head"
	    );
	    if (!header) return;
	    const existing = header.querySelector(
	      ":scope > [data-reader-context-quote-return]"
	    );
	    if (existing) {
	      this.#quoteReturnEntries.add(existing);
	      return;
	    }
	    const label = source.nested ? "返回二级回复" : "返回引用楼层", entry = button(
	      this.#document,
	      "ldp-btn ldp-quote-return-entry",
	      label,
	      "← 返回引用处"
	    );
	    entry.dataset.readerContextQuoteReturn = "1";
	    const floor = header.querySelector(
	      ":scope > :is(.ldp-body-floor,[data-reader-context-self])"
	    );
	    floor ? floor.insertAdjacentElement("afterend", entry) : header.append(entry), this.#quoteReturnEntries.add(entry), this.#bindQuoteReturn(entry, source);
	  }
	  #bindQuoteReturn(control, source, onRestored = () => {
	  }) {
	    control.addEventListener("click", (event) => {
	      event.preventDefault(), event.stopPropagation();
	      const sourcePort = this.#quoteSourcePort;
	      if (control.disabled || !sourcePort) return;
	      control.disabled = !0;
	      const original = control.textContent;
	      control.textContent = "返回中…", sourcePort.restore(source).then((restored) => {
	        if (!this.scope.destroyed) {
	          if (!restored) throw new Error("引用来源暂不可用");
	          onRestored();
	        }
	      }).catch((error) => {
	        this.scope.destroyed || this.#onError(error);
	      }).finally(() => {
	        control.isConnected && (control.disabled = !1, control.textContent = original);
	      });
	    });
	  }
	  #beginQuotePositioning() {
	    const epoch = ++this.#quotePositionEpoch;
	    return this.#scrollRoot.classList.add("ldp-quote-positioning"), () => {
	      const release = () => {
	        this.scope.destroyed || epoch !== this.#quotePositionEpoch || this.#scrollRoot.classList.remove("ldp-quote-positioning");
	      }, view = this.#document.defaultView;
	      typeof view?.requestAnimationFrame == "function" ? view.requestAnimationFrame(release) : queueMicrotask(release);
	    };
	  }
	  #clearQuoteHighlight() {
	    this.#quoteHintHideTimer !== null && (clearTimeout(this.#quoteHintHideTimer), this.#quoteHintHideTimer = null), this.#quoteHighlightHint?.remove(), this.#quoteHighlightHint = null;
	    for (const entry of this.#quoteReturnEntries) entry.remove();
	    this.#quoteReturnEntries.clear();
	    for (const mark of this.#quoteHighlightMarks) {
	      if (!mark.parentNode) continue;
	      const parent = mark.parentNode;
	      mark.replaceWith(this.#document.createTextNode(mark.textContent ?? "")), parent.normalize();
	    }
	    this.#quoteHighlightMarks = Object.freeze([]), this.#quoteHighlight = null;
	  }
	}
	class ReaderTopicDiscussionTopology {
	  #snapshot = null;
	  #entries = /* @__PURE__ */ new Map();
	  update(snapshot) {
	    this.#snapshot = snapshot, this.#entries.clear();
	    for (const entry of snapshot?.entries ?? [])
	      this.#entries.set(entry.postNumber, entry);
	  }
	  parentOf(postNumber) {
	    return this.#entry(postNumber)?.parentPostNumber;
	  }
	  depthOf(postNumber) {
	    return this.#entry(postNumber)?.depth;
	  }
	  rootOf(postNumber) {
	    return this.#entry(postNumber) ? this.#snapshot?.rootPostNumber : void 0;
	  }
	  #entry(postNumber) {
	    return this.#entries.get(postNumber);
	  }
	}
	class ReaderTopicContextSurface {
	  scope;
	  discussionDomOwner;
	  discussionGeometry;
	  discussionPointer;
	  discussionBranchOverlay;
	  #document;
	  #controller;
	  #state;
	  #workspace;
	  #discussionHost;
	  #postProjector;
	  #highlight;
	  #requestFrame;
	  #cancelFrame;
	  #readComputedStyle;
	  #onError;
	  #discussionTopology = new ReaderTopicDiscussionTopology();
	  #discussionLayer;
	  #discussionPanel;
	  #discussionTitle;
	  #discussionList;
	  #discussionResizeHandles;
	  #mountedDiscussionPostNumbers = /* @__PURE__ */ new Set();
	  #discussionBranchPostNumbers = /* @__PURE__ */ new Set();
	  #discussionCollapsedPostNumbers = /* @__PURE__ */ new Set();
	  #observedDiscussionContent = /* @__PURE__ */ new Map();
	  #activeDiscussionContent = /* @__PURE__ */ new Set();
	  #renderedDiscussionPostNumbers = /* @__PURE__ */ new Set();
	  #discussionMaterializedLru = /* @__PURE__ */ new Map();
	  #discussionPostsByNumber = /* @__PURE__ */ new Map();
	  #discussionEagerPostLimit;
	  #readDiscussionMaterializedPostLimit;
	  #discussionContentObserver;
	  #layoutResizeObserver;
	  #returnFocus = null;
	  #pendingRestorePoint = null;
	  #activeDiscussionRoot = null;
	  #stateLoaded = !1;
	  #treeWidthFrame = 0;
	  #discussionBranchFrame = 0;
	  #discussionContentWidth = 0;
	  #observesDiscussionSize = !1;
	  #treePan = null;
	  #suppressTreeClick = !1;
	  #lastWorkspaceFullPage;
	  get discussionContentHost() {
	    return this.#discussionLayer;
	  }
	  scrollDiscussionHorizontal(delta) {
	    return this.scope.destroyed || this.#discussionLayer.hidden || !Number.isFinite(delta) || delta === 0 ? !1 : (this.#discussionList.scrollLeft += delta, !0);
	  }
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#document = options.document, this.#controller = options.controller, this.#state = options.stateRepository ?? new import_reader_topic_context_state.ReaderTopicContextStateRepository({
	      storage: options.stateStorage ?? Object.freeze({
	        getValue: () => null,
	        setValue: () => {
	        }
	      }),
	      ...options.onError ? { onError: options.onError } : {}
	    }), this.#workspace = options.workspace, this.#discussionHost = options.discussionHost, this.#postProjector = options.postProjector ?? new import_reader_post_view_projector.ReaderPostViewProjector({
	      document: options.document,
	      identity: options.identity,
	      render: options.renderPost,
	      ...options.postFeatures ? { features: options.postFeatures } : {},
	      ...options.onError ? { onError: options.onError } : {}
	    }), this.#highlight = options.highlight ?? (() => {
	    }), this.#requestFrame = options.requestFrame ?? (typeof requestAnimationFrame == "function" ? (callback) => requestAnimationFrame(callback) : (callback) => (callback(0), 0)), this.#cancelFrame = options.cancelFrame ?? (typeof cancelAnimationFrame == "function" ? (id) => cancelAnimationFrame(id) : () => {
	    }), this.#readComputedStyle = options.readComputedStyle, this.#onError = options.onError ?? (() => {
	    }), this.#discussionEagerPostLimit = Number.isFinite(
	      options.discussionEagerPostLimit
	    ) ? Math.max(1, Math.floor(Number(options.discussionEagerPostLimit))) : 12, this.#readDiscussionMaterializedPostLimit = options.readDiscussionMaterializedPostLimit ?? (() => Math.max(24, this.#discussionEagerPostLimit * 3)), this.#discussionLayer = (0, import_html_element.htmlElement)(
	      options.document,
	      "div",
	      "ldp-descendant-replies-layer"
	    ), this.#discussionLayer.hidden = !0, this.#discussionPanel = (0, import_html_element.htmlElement)(
	      options.document,
	      "section",
	      "ldp-descendant-replies-window"
	    );
	    const header = (0, import_html_element.htmlElement)(
	      options.document,
	      "header",
	      "ldp-descendant-replies-header"
	    );
	    header.dataset.readerContextDragSurface = "1";
	    const close = button(
	      options.document,
	      "ldp-btn ldp-author ldp-descendant-replies-close",
	      "关闭完整讨论(Esc)",
	      "<"
	    );
	    this.#discussionTitle = (0, import_html_element.htmlElement)(
	      options.document,
	      "span",
	      "ldp-author ldp-descendant-replies-title"
	    );
	    const top = button(
	      options.document,
	      "ldp-btn ldp-descendant-replies-top",
	      "回到完整讨论顶部",
	      "↑"
	    );
	    header.append(close, this.#discussionTitle, top), this.#discussionList = (0, import_html_element.htmlElement)(
	      options.document,
	      "div",
	      "ldp-descendant-replies-list ldp-comments ldp-descendant-replies-tree"
	    ), this.#discussionList.scrollTop = 0, this.#discussionList.scrollLeft = 0;
	    const resizeDirections = Object.freeze([
	      "n",
	      "s",
	      "e",
	      "w",
	      "ne",
	      "nw",
	      "se",
	      "sw"
	    ]);
	    this.#discussionResizeHandles = Object.freeze(
	      resizeDirections.map((direction) => {
	        const handle = (0, import_html_element.htmlElement)(
	          options.document,
	          "span",
	          "ldp-descendant-replies-resize-handle"
	        );
	        return handle.dataset.readerResize = direction, handle.dataset.resize = direction, handle.setAttribute("aria-hidden", "true"), handle;
	      })
	    ), this.#discussionPanel.append(
	      header,
	      this.#discussionList,
	      ...this.#discussionResizeHandles
	    ), this.#discussionLayer.append(this.#discussionPanel), options.discussionHost.append(this.#discussionLayer), this.#discussionTopology.update(null), this.discussionDomOwner = new import_reply_tree_dom_owner.ReplyTreeDomOwner(
	      this.#discussionTopology,
	      this.#discussionList
	    ), this.discussionBranchOverlay = new import_branch_overlay.ReaderBranchOverlayController({
	      domOwner: this.discussionDomOwner,
	      renderMode: "segmented-css",
	      onToggleBranch: (postNumber) => {
	        this.#controller.toggleDiscussionBranch(postNumber);
	      },
	      readCollapsed: (postNumber) => this.#discussionCollapsedPostNumbers.has(postNumber),
	      onLayoutChange: () => this.#scheduleDiscussionBranchPaint(),
	      parentScope: this.scope
	    });
	    const createContentObserver = options.createContentObserver ?? (options.document.defaultView?.IntersectionObserver ? (callback, init) => new options.document.defaultView.IntersectionObserver(callback, init) : null);
	    this.#discussionContentObserver = createContentObserver ? createContentObserver(
	      (entries) => this.#onDiscussionContentIntersection(entries),
	      {
	        root: this.#discussionList,
	        rootMargin: "100% 0px 100% 0px",
	        threshold: 0
	      }
	    ) : null, this.scope.add(() => this.#discussionContentObserver?.disconnect()), this.#lastWorkspaceFullPage = options.workspace.snapshot.presentation.fullPage;
	    const defaultView = options.document.defaultView, readViewport = options.readViewport ?? (() => Object.freeze({
	      width: Math.max(
	        1,
	        Number(defaultView?.innerWidth) || options.discussionHost.clientWidth || 1
	      ),
	      height: Math.max(
	        1,
	        Number(defaultView?.innerHeight) || options.discussionHost.clientHeight || 1
	      )
	    })), viewport = readViewport(), viewportTarget = options.viewportTarget ?? defaultView;
	    this.discussionGeometry = new import_reader_workspace.ReaderWindowGeometryModel({
	      preferences: {
	        readerWindowWidth: 0,
	        readerWindowHeight: 0,
	        readerWindowX: 0,
	        readerWindowY: 0,
	        readerWindowLocked: !1,
	        readerWindowPinned: !1
	      },
	      viewportWidth: viewport.width,
	      viewportHeight: viewport.height,
	      mode: this.#lastWorkspaceFullPage ? "floating" : "fullpage",
	      policy: {
	        margin: 16,
	        minWidth: 320,
	        minHeight: 240,
	        compactWidth: 0,
	        defaultWidth: Math.max(1, Math.min(960, viewport.width - 48)),
	        defaultHeight: 720,
	        defaultViewportWidth: 1,
	        defaultViewportHeight: 0.78
	      }
	    }), this.discussionGeometry.changes.subscribe(
	      (snapshot) => this.#applyDiscussionGeometry(snapshot),
	      this.scope
	    ), this.discussionPointer = new import_reader_workspace.ReaderWindowPointerController({
	      model: this.discussionGeometry,
	      overlay: this.#discussionLayer,
	      modal: this.#discussionPanel,
	      header,
	      ...viewportTarget ? { viewportTarget } : {},
	      readViewport,
	      onPersist: () => {
	        this.discussionGeometry.snapshot.managed && this.#state.rememberGeometry(
	          this.discussionGeometry.snapshot.geometry
	        );
	      },
	      requestFrame: this.#requestFrame,
	      cancelFrame: this.#cancelFrame,
	      dragSurfaceSelector: ".ldp-descendant-replies-header[data-reader-context-drag-surface]",
	      interactingClassName: "ldp-descendant-replies-interacting",
	      restingTransform: "none",
	      projectPlacement: () => {
	      },
	      parentScope: this.scope
	    }), this.#syncWorkspaceMode(this.#lastWorkspaceFullPage), this.#applyDiscussionGeometry(this.discussionGeometry.snapshot), this.#discussionLayer.addEventListener("click", (event) => {
	      const target = (0, import_event_target.eventElement)(event);
	      if (target) {
	        if (target === this.#discussionLayer || target.closest(".ldp-descendant-replies-close")) {
	          event.preventDefault(), this.#controller.closeDiscussion();
	          return;
	        }
	        if (target.closest(".ldp-descendant-replies-top")) {
	          event.preventDefault(), this.#discussionList.scrollTop = 0, this.#discussionList.scrollLeft = 0;
	          return;
	        }
	      }
	    }), this.scope.listen(this.#discussionList, "pointerdown", (event) => {
	      this.#onTreePanPointerDown(event);
	    }), this.scope.listen(this.#discussionList, "pointermove", (event) => {
	      this.#onTreePanPointerMove(event);
	    });
	    for (const type of ["pointerup", "pointercancel", "lostpointercapture"])
	      this.scope.listen(this.#discussionList, type, (event) => {
	        this.#stopTreePan(event);
	      });
	    this.scope.listen(this.#discussionList, "click", (event) => {
	      this.#suppressTreeClick && (event.preventDefault(), event.stopImmediatePropagation());
	    }, !0);
	    const NativeResizeObserver = defaultView?.ResizeObserver, createResizeObserver = options.createResizeObserver ?? (NativeResizeObserver ? (callback) => new NativeResizeObserver(callback) : null);
	    this.#layoutResizeObserver = createResizeObserver ? createResizeObserver((entries) => {
	      const entry = entries.find((candidate) => candidate.target === this.#discussionList);
	      if (entry) {
	        const width = Number(entry.contentRect.width);
	        Number.isFinite(width) && width > 0 && (this.#discussionContentWidth = width), this.#applyPendingRestorePoint(), this.#scheduleDiscussionTreeWidth(), this.#scheduleDiscussionBranchPaint();
	      }
	    }) : null, this.#layoutResizeObserver && (this.#observesDiscussionSize = !0, this.#layoutResizeObserver.observe(this.#discussionList), this.scope.add(() => this.#layoutResizeObserver?.disconnect()));
	    const keydownTargets = /* @__PURE__ */ new Set([
	      options.document,
	      options.discussionHost.getRootNode()
	    ]);
	    for (const target of keydownTargets)
	      this.scope.listen(target, "keydown", (event) => {
	        this.handleEscape(event);
	      }, !0);
	    this.#controller.changes.subscribe((snapshot) => {
	      this.#project(snapshot);
	    }, this.scope), this.#workspace.changes.subscribe((snapshot) => {
	      const fullPage = snapshot.presentation.fullPage;
	      fullPage !== this.#lastWorkspaceFullPage && (this.#lastWorkspaceFullPage = fullPage, this.#controller.closeDiscussion()), this.discussionGeometry.setMode(
	        fullPage ? "floating" : "fullpage"
	      ), this.#syncWorkspaceMode(fullPage), this.#applyDiscussionGeometry(this.discussionGeometry.snapshot);
	    }, this.scope), this.scope.add(() => {
	      this.#persistActiveDiscussionPoint(), this.#treeWidthFrame && (this.#cancelFrame(this.#treeWidthFrame), this.#treeWidthFrame = 0), this.#discussionBranchFrame && (this.#cancelFrame(this.#discussionBranchFrame), this.#discussionBranchFrame = 0), this.#stopTreePan();
	      for (const postNumber of this.#mountedDiscussionPostNumbers) {
	        const root = this.discussionDomOwner.view(postNumber)?.slots.root;
	        root && this.#detachDiscussionFeatures(root, postNumber);
	      }
	      this.#mountedDiscussionPostNumbers.clear(), this.#discussionBranchPostNumbers.clear(), this.#discussionCollapsedPostNumbers.clear(), this.#observedDiscussionContent.clear(), this.#activeDiscussionContent.clear(), this.discussionDomOwner.destroy(), this.#discussionLayer.remove();
	    }), this.#state.load().then((state) => {
	      if (!this.scope.destroyed) {
	        if (this.#stateLoaded = !0, state.fullPageGeometry) {
	          const stored = state.fullPageGeometry;
	          this.discussionGeometry.setGeometry(
	            stored.width,
	            stored.height,
	            stored.left,
	            stored.top
	          );
	        }
	        this.#activeDiscussionRoot !== null && this.#pendingRestorePoint === null && (this.#pendingRestorePoint = this.#storedDiscussionPoint(
	          this.#activeDiscussionRoot
	        ), this.#applyPendingRestorePoint());
	      }
	    }).catch(this.#onError), this.#project(this.#controller.snapshot());
	  }
	  /**
	   * 消费当前完整讨论拥有的 Esc。
	   *
	   * 除了 Document/ShadowRoot 的局部监听,userscript entry 还会在 Window
	   * capture 阶段调用它,避免宿主更早的监听器截断事件传播。
	   */
	  handleEscape(event) {
	    return event.key !== "Escape" || !this.#controller.snapshot().discussion || !(0, import_reader_escape_surface.readerEscapeOwnedBy)(this.#document, this.#discussionLayer) ? !1 : (event.preventDefault(), event.stopImmediatePropagation(), this.#controller.closeDiscussion(), !0);
	  }
	  captureDiscussionState() {
	    const discussion = this.#controller.snapshot().discussion;
	    return !discussion || this.#discussionLayer.hidden ? null : Object.freeze({
	      rootPostNumber: discussion.rootPostNumber,
	      ...discussion.descendantRootPostNumber !== discussion.rootPostNumber ? {
	        descendantRootPostNumber: discussion.descendantRootPostNumber
	      } : {},
	      point: this.#captureDiscussionPoint()
	    });
	  }
	  async restoreDiscussionState(state) {
	    this.#pendingRestorePoint = state.point, await this.#controller.openDiscussion(state.rootPostNumber, {
	      explicitRoot: !0,
	      targetPostNumber: null,
	      ...state.descendantRootPostNumber === void 0 ? {} : {
	        descendantRootPostNumber: state.descendantRootPostNumber
	      }
	    }), this.#applyPendingRestorePoint();
	  }
	  /**
	   * 在完整讨论局部投影中揭示目标;不会把停放楼层提升进主信息流。
	   */
	  async revealDiscussionPost(postNumberValue) {
	    if (this.scope.destroyed)
	      throw new Error("完整讨论 surface 已销毁");
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue), wasMounted = this.discussionDomOwner.view(postNumber)?.slots.root?.isConnected === !0, snapshot = await this.#controller.openDiscussion(postNumber, {
	      targetPostNumber: postNumber
	    });
	    if (this.scope.destroyed) return null;
	    const discussion = snapshot.discussion, target = this.discussionDomOwner.view(postNumber)?.slots.root;
	    return !discussion || !target?.isConnected || target.classList.contains("ldp-post-projection-pending") ? null : Object.freeze({
	      postNumber,
	      rootPostNumber: discussion.rootPostNumber,
	      element: target,
	      mounted: !wasMounted
	    });
	  }
	  /** 只闪烁完整讨论中已挂载的楼层,不移动浮窗滚动位置。 */
	  highlightDiscussionPost(postNumberValue) {
	    if (this.scope.destroyed) return !1;
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue), target = this.discussionDomOwner.view(postNumber)?.slots.root;
	    return !target?.isConnected || target.classList.contains("ldp-post-projection-pending") ? !1 : (this.#highlight(target), !0);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #project(snapshot) {
	    this.scope.destroyed || this.#projectDiscussion(snapshot.discussion);
	  }
	  #projectDiscussion(snapshot) {
	    if (this.#discussionTopology.update(snapshot), !snapshot) {
	      this.#persistActiveDiscussionPoint(), this.#activeDiscussionRoot = null, this.#discussionLayer.hidden = !0, this.#discussionHost.classList.remove(
	        "ldp-descendant-replies-host-open"
	      ), this.#clearDiscussionViews(), this.#returnFocus?.focus?.(), this.#returnFocus = null;
	      return;
	    }
	    if (this.#discussionLayer.hidden) {
	      const active = (0, import_event_target.deepActiveElement)(this.#document);
	      this.#returnFocus = active && active.nodeType === 1 ? active : null;
	    }
	    this.#activeDiscussionRoot !== snapshot.rootPostNumber && (this.#persistActiveDiscussionPoint(), this.#activeDiscussionRoot = snapshot.rootPostNumber, this.#pendingRestorePoint === null && this.#stateLoaded && (this.#pendingRestorePoint = this.#storedDiscussionPoint(
	      snapshot.rootPostNumber
	    ))), this.#discussionLayer.hidden = !1, this.#discussionHost.classList.toggle(
	      "ldp-descendant-replies-host-open",
	      this.discussionGeometry.snapshot.managed
	    );
	    const titleEntry = snapshot.entries.find((entry) => entry.postNumber === snapshot.descendantRootPostNumber), titleIdentity = titleEntry ? this.#postProjector.identity(titleEntry.post) : null, branchMode = snapshot.descendantRootPostNumber !== snapshot.rootPostNumber, titlePostNumber = snapshot.descendantRootPostNumber, titleAction = branchMode ? "查看完整分支" : "查看完整讨论";
	    this.#discussionTitle.textContent = titleIdentity ? `#${titlePostNumber} · ${titleIdentity.username} · ${titleAction}(${snapshot.entries.length})` : `#${titlePostNumber} · ${titleAction}(${snapshot.entries.length})`, this.#discussionTitle.dataset.partial = snapshot.partial ? "1" : "0";
	    const nextPostNumbers = new Set(
	      snapshot.entries.map((entry) => entry.postNumber)
	    );
	    this.#discussionPostsByNumber.clear();
	    for (const entry of snapshot.entries)
	      this.#discussionPostsByNumber.set(entry.postNumber, entry.post);
	    const eagerPostNumbers = this.#discussionEagerPostNumbers(snapshot);
	    for (const postNumber of [...this.#mountedDiscussionPostNumbers]) {
	      if (nextPostNumbers.has(postNumber)) continue;
	      const view = this.discussionDomOwner.view(postNumber);
	      view && this.#detachDiscussionFeatures(view.slots.root, postNumber), this.discussionDomOwner.unregister(postNumber, !0, !1), this.#mountedDiscussionPostNumbers.delete(postNumber), this.#renderedDiscussionPostNumbers.delete(postNumber), this.#discussionMaterializedLru.delete(postNumber);
	    }
	    const attachAfterSync = /* @__PURE__ */ new Set();
	    for (const entry of snapshot.entries) {
	      let view = this.discussionDomOwner.view(
	        entry.postNumber
	      ), created = !1;
	      const shouldRender = eagerPostNumbers.has(entry.postNumber);
	      if (!view)
	        try {
	          view = shouldRender ? this.#postProjector.create(
	            entry.post,
	            this.scope,
	            entry.postNumber
	          ) : this.#postProjector.createShell(
	            entry.post,
	            this.scope,
	            entry.postNumber
	          ), created = !0, shouldRender ? (this.#renderedDiscussionPostNumbers.add(entry.postNumber), this.#touchDiscussionMaterialized(entry.postNumber)) : (view.slots.root.classList.add("ldp-post-projection-pending"), view.slots.root.setAttribute("aria-busy", "true")), this.discussionDomOwner.register(view, !1), this.#mountedDiscussionPostNumbers.add(entry.postNumber), attachAfterSync.add(entry.postNumber);
	        } catch (error) {
	          view?.destroy(), this.#onError(error);
	          continue;
	        }
	      if (!created && shouldRender && !this.#renderedDiscussionPostNumbers.has(entry.postNumber))
	        this.#materializeDiscussionView(entry.postNumber);
	      else if (!created && this.#renderedDiscussionPostNumbers.has(entry.postNumber))
	        try {
	          this.#postProjector.render(entry.post, view);
	        } catch (error) {
	          this.#onError(error);
	        }
	      view.slots.root.classList.remove("ldp-nested-collapsed"), view.slots.root.classList.add(
	        entry.depth > 0 ? "ldp-nested-preview" : "ldp-discussion-root"
	      ), view.slots.root.isConnected || attachAfterSync.add(entry.postNumber);
	    }
	    this.discussionDomOwner.sync();
	    for (const postNumber of attachAfterSync) {
	      const rootElement = this.discussionDomOwner.view(postNumber)?.slots.root;
	      rootElement?.isConnected && this.#attachDiscussionFeatures(rootElement, postNumber);
	    }
	    this.#syncDiscussionBranches(snapshot), this.#scheduleDiscussionTreeWidth(), this.#scheduleDiscussionBranchPaint(), this.#applyPendingRestorePoint();
	    const targetPostNumber = snapshot.targetPostNumber;
	    if (targetPostNumber !== null) {
	      const target = this.discussionDomOwner.view(targetPostNumber)?.slots.root;
	      target?.isConnected && (this.#highlight(target), target.scrollIntoView?.({ block: "center", inline: "nearest" }), this.#requestFrame(() => {
	        this.scope.destroyed || this.#controller.clearDiscussionTarget();
	      }));
	    }
	  }
	  #syncDiscussionBranches(snapshot) {
	    const collapsed = new Set(snapshot.collapsedPostNumbers);
	    this.#discussionCollapsedPostNumbers.clear();
	    for (const postNumber of collapsed)
	      this.#discussionCollapsedPostNumbers.add(postNumber);
	    for (const entry of snapshot.entries) {
	      const view = this.discussionDomOwner.view(entry.postNumber);
	      view && (view.slots.replyList.hidden = collapsed.has(entry.postNumber), view.slots.replyControls.querySelectorAll(":scope > [data-reader-context-discussion]").forEach((control) => control.remove()));
	    }
	  }
	  #attachDiscussionFeatures(root, postNumber) {
	    this.#discussionContentObserver ? this.#observedDiscussionContent.get(postNumber) !== root && (this.#observedDiscussionContent.set(postNumber, root), this.#discussionContentObserver.observe(root)) : this.#activateDiscussionContent(root, postNumber), this.#renderedDiscussionPostNumbers.has(postNumber) && this.#attachDiscussionBranchFeatures(root, postNumber);
	  }
	  #attachDiscussionBranchFeatures(root, postNumber) {
	    this.#discussionTopology.parentOf(postNumber) === null && (this.#discussionBranchPostNumbers.has(postNumber) || (this.#discussionBranchPostNumbers.add(postNumber), this.#postProjector.attach(root, postNumber, "branch")));
	  }
	  #detachDiscussionFeatures(root, postNumber) {
	    this.#observedDiscussionContent.get(postNumber) === root && (this.#discussionContentObserver?.unobserve(root), this.#observedDiscussionContent.delete(postNumber)), this.#deactivateDiscussionContent(root, postNumber), this.#discussionBranchPostNumbers.delete(postNumber) && this.#postProjector.detach(root, postNumber, "branch");
	  }
	  #onDiscussionContentIntersection(entries) {
	    for (const entry of entries) {
	      const root = entry.target, postNumber = Number(root.dataset.postNumber);
	      !Number.isSafeInteger(postNumber) || postNumber <= 0 || this.#observedDiscussionContent.get(postNumber) === root && (entry.isIntersecting || entry.intersectionRatio > 0 ? this.#activateDiscussionContent(root, postNumber) : this.#deactivateDiscussionContent(root, postNumber));
	    }
	    this.#scheduleDiscussionBranchPaint();
	  }
	  #scheduleDiscussionBranchPaint() {
	    if (this.scope.destroyed || this.#discussionBranchFrame) return;
	    this.#discussionList.classList.add("ldp-branch-paint-pending");
	    let completed = !1;
	    const handle = this.#requestFrame(() => {
	      if (completed = !0, this.#discussionBranchFrame = 0, this.scope.destroyed || this.#discussionLayer.hidden) {
	        this.#discussionList.classList.remove(
	          "ldp-branch-paint-pending"
	        );
	        return;
	      }
	      this.discussionBranchOverlay.paint(), this.#discussionList.classList.remove("ldp-branch-paint-pending");
	    });
	    completed || (this.#discussionBranchFrame = handle);
	  }
	  #activateDiscussionContent(root, postNumber) {
	    this.#activeDiscussionContent.has(postNumber) || this.#materializeDiscussionView(postNumber) && (this.#activeDiscussionContent.add(postNumber), this.#postProjector.attach(root, postNumber, "node"));
	  }
	  #deactivateDiscussionContent(root, postNumber) {
	    this.#activeDiscussionContent.delete(postNumber) && (this.#postProjector.detach(root, postNumber, "node"), this.#touchDiscussionMaterialized(postNumber), this.#evictDiscussionMaterializedViews());
	  }
	  #clearDiscussionViews() {
	    this.#treeWidthFrame && (this.#cancelFrame(this.#treeWidthFrame), this.#treeWidthFrame = 0), this.#discussionBranchFrame && (this.#cancelFrame(this.#discussionBranchFrame), this.#discussionBranchFrame = 0), this.#discussionList.classList.remove("ldp-branch-paint-pending"), this.discussionBranchOverlay.releaseProjection();
	    for (const postNumber of [...this.#mountedDiscussionPostNumbers]) {
	      const view = this.discussionDomOwner.view(postNumber);
	      view && this.#detachDiscussionFeatures(view.slots.root, postNumber), this.discussionDomOwner.unregister(postNumber, !0, !1);
	    }
	    this.#mountedDiscussionPostNumbers.clear(), this.#discussionBranchPostNumbers.clear(), this.#discussionCollapsedPostNumbers.clear(), this.#observedDiscussionContent.clear(), this.#activeDiscussionContent.clear(), this.#renderedDiscussionPostNumbers.clear(), this.#discussionMaterializedLru.clear(), this.#discussionPostsByNumber.clear(), this.#discussionList.replaceChildren();
	  }
	  #discussionEagerPostNumbers(snapshot) {
	    if (!this.#discussionContentObserver)
	      return new Set(snapshot.entries.map((entry) => entry.postNumber));
	    const eager = new Set(
	      snapshot.entries.slice(0, this.#discussionEagerPostLimit).map((entry) => entry.postNumber)
	    );
	    eager.add(snapshot.rootPostNumber);
	    for (const target of [
	      snapshot.targetPostNumber,
	      this.#pendingRestorePoint?.number ?? null
	    ]) {
	      let cursor = target;
	      const visited = /* @__PURE__ */ new Set();
	      for (; cursor != null && !visited.has(cursor); ) {
	        visited.add(cursor), eager.add(cursor);
	        const parent = this.#discussionTopology.parentOf(cursor);
	        cursor = parent === void 0 ? null : parent;
	      }
	    }
	    return eager;
	  }
	  #materializeDiscussionView(postNumber) {
	    if (this.#renderedDiscussionPostNumbers.has(postNumber))
	      return this.#touchDiscussionMaterialized(postNumber), !0;
	    const post = this.#discussionPostsByNumber.get(postNumber), view = this.discussionDomOwner.view(postNumber);
	    if (!post || !view) return !1;
	    try {
	      this.#postProjector.render(post, view);
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	    return this.#renderedDiscussionPostNumbers.add(postNumber), this.#touchDiscussionMaterialized(postNumber), view.slots.root.classList.remove("ldp-post-projection-pending"), view.slots.root.removeAttribute("aria-busy"), view.slots.root.isConnected && this.#attachDiscussionBranchFeatures(view.slots.root, postNumber), this.#evictDiscussionMaterializedViews(postNumber), !0;
	  }
	  #touchDiscussionMaterialized(postNumber) {
	    this.#renderedDiscussionPostNumbers.has(postNumber) && (this.#discussionMaterializedLru.delete(postNumber), this.#discussionMaterializedLru.set(postNumber, !0));
	  }
	  #discussionMaterializedPostLimit() {
	    let configured = Number.NaN;
	    try {
	      configured = Number(this.#readDiscussionMaterializedPostLimit());
	    } catch (error) {
	      this.#onError(error);
	    }
	    return Math.max(
	      this.#discussionEagerPostLimit,
	      Number.isFinite(configured) ? Math.floor(configured) : 24
	    );
	  }
	  #evictDiscussionMaterializedViews(protectedPostNumber) {
	    const limit = this.#discussionMaterializedPostLimit();
	    if (!(this.#renderedDiscussionPostNumbers.size <= limit))
	      for (const postNumber of [...this.#discussionMaterializedLru.keys()]) {
	        if (this.#renderedDiscussionPostNumbers.size <= limit) break;
	        postNumber === protectedPostNumber || postNumber === this.#activeDiscussionRoot || this.#activeDiscussionContent.has(postNumber) || this.#replaceDiscussionViewWithShell(postNumber);
	      }
	  }
	  #replaceDiscussionViewWithShell(postNumber) {
	    if (!this.#renderedDiscussionPostNumbers.has(postNumber) || this.#activeDiscussionContent.has(postNumber))
	      return !1;
	    const post = this.#discussionPostsByNumber.get(postNumber), current = this.discussionDomOwner.view(postNumber);
	    if (!post || !current) return !1;
	    let shell;
	    try {
	      shell = this.#postProjector.createShell(
	        post,
	        this.scope,
	        postNumber
	      );
	    } catch (error) {
	      return this.#onError(error), !1;
	    }
	    shell.slots.root.classList.add("ldp-post-projection-pending"), shell.slots.root.setAttribute("aria-busy", "true");
	    const depth = this.#discussionTopology.depthOf(postNumber);
	    shell.slots.root.classList.add(
	      depth !== void 0 && depth > 0 ? "ldp-nested-preview" : "ldp-discussion-root"
	    ), shell.slots.root.classList.toggle(
	      "ldp-nested-collapsed",
	      current.slots.root.classList.contains("ldp-nested-collapsed")
	    ), shell.slots.replyList.hidden = current.slots.replyList.hidden, this.#detachDiscussionFeatures(current.slots.root, postNumber);
	    try {
	      this.discussionDomOwner.unregister(postNumber, !1, !1), this.discussionDomOwner.register(shell, !1), this.discussionDomOwner.sync();
	    } catch (error) {
	      this.discussionDomOwner.unregister(postNumber, !1, !1), shell.destroy();
	      try {
	        this.discussionDomOwner.register(current, !1), this.discussionDomOwner.sync(), current.slots.root.isConnected && this.#attachDiscussionFeatures(current.slots.root, postNumber);
	      } catch (rollbackError) {
	        this.#onError(rollbackError);
	      }
	      return this.#onError(error), !1;
	    }
	    return current.destroy(), this.#renderedDiscussionPostNumbers.delete(postNumber), this.#discussionMaterializedLru.delete(postNumber), shell.slots.root.isConnected && this.#attachDiscussionFeatures(shell.slots.root, postNumber), this.#scheduleDiscussionBranchPaint(), !0;
	  }
	  #captureDiscussionPoint() {
	    const listRect = this.#discussionList.getBoundingClientRect(), anchor = [...this.#discussionList.querySelectorAll(
	      ".ldp-post"
	    )].find((post) => post.getBoundingClientRect().bottom > listRect.top);
	    return anchor ? Object.freeze({
	      number: (0, import_identifiers.discoursePostNumber)(anchor.dataset.postNumber),
	      scrollTop: Math.max(0, this.#discussionList.scrollTop),
	      scrollLeft: Math.max(0, this.#discussionList.scrollLeft),
	      offset: anchor.getBoundingClientRect().top - listRect.top
	    }) : null;
	  }
	  #applyPendingRestorePoint() {
	    const point = this.#pendingRestorePoint;
	    if (!point) return;
	    const target = this.discussionDomOwner.view(point.number)?.slots.root;
	    if (!target?.isConnected) return;
	    const currentScrollTop = Math.max(0, this.#discussionList.scrollTop), targetRect = target.getBoundingClientRect(), listRect = this.#discussionList.getBoundingClientRect(), anchoredScrollTop = currentScrollTop + targetRect.top - listRect.top - point.offset;
	    this.#pendingRestorePoint = null, this.#discussionList.scrollLeft = point.scrollLeft, this.#discussionList.scrollTop = Math.max(
	      0,
	      Number.isFinite(anchoredScrollTop) ? anchoredScrollTop : point.scrollTop
	    );
	  }
	  #storedDiscussionPoint(rootPostNumber) {
	    return this.#state.point(
	      this.#document.location?.host ?? "",
	      this.#controller.topicId,
	      rootPostNumber
	    );
	  }
	  #persistActiveDiscussionPoint() {
	    this.#activeDiscussionRoot === null || this.#discussionLayer.hidden || this.#state.rememberPoint(
	      this.#document.location?.host ?? "",
	      this.#controller.topicId,
	      this.#activeDiscussionRoot,
	      this.#captureDiscussionPoint()
	    );
	  }
	  #applyDiscussionGeometry(snapshot) {
	    const managed = snapshot.managed;
	    for (const handle of this.#discussionResizeHandles)
	      handle.hidden = !managed;
	    const header = this.#discussionPanel.querySelector(
	      ":scope > .ldp-descendant-replies-header"
	    );
	    if (header && (header.style.cursor = managed ? "move" : "", header.style.touchAction = managed ? "none" : ""), managed) {
	      const value = snapshot.geometry;
	      this.#discussionPanel.style.left = `${value.left}px`, this.#discussionPanel.style.top = `${value.top}px`, this.#discussionPanel.style.width = `${value.width}px`, this.#discussionPanel.style.height = `${value.height}px`, this.#discussionPanel.style.transform = "none";
	    } else
	      for (const property of [
	        "left",
	        "top",
	        "width",
	        "height",
	        "transform"
	      ])
	        this.#discussionPanel.style.removeProperty(property);
	    this.#discussionHost.classList.toggle(
	      "ldp-descendant-replies-host-open",
	      managed && !this.#discussionLayer.hidden
	    ), this.#scheduleDiscussionTreeWidth();
	  }
	  #scheduleDiscussionTreeWidth() {
	    if (this.#treeWidthFrame) return;
	    let completed = !1;
	    const handle = this.#requestFrame(() => {
	      completed = !0, this.#treeWidthFrame = 0, this.#syncDiscussionTreeWidth();
	    });
	    completed || (this.#treeWidthFrame = handle);
	  }
	  #syncDiscussionTreeWidth() {
	    const snapshot = this.#controller.snapshot().discussion;
	    if (!snapshot || this.#discussionLayer.hidden) return;
	    let contentWidth = this.#discussionContentWidth;
	    if (contentWidth <= 0) {
	      if (this.#observesDiscussionSize) return;
	      const style = this.#readComputedStyle?.(this.#discussionList) ?? this.#document.defaultView?.getComputedStyle?.(
	        this.#discussionList
	      ), padding = Number.parseFloat(style?.paddingLeft ?? "") + Number.parseFloat(style?.paddingRight ?? ""), horizontalPadding = Number.isFinite(padding) ? padding : 0;
	      contentWidth = this.#discussionList.clientWidth - horizontalPadding;
	    }
	    const baseWidth = Math.max(320, contentWidth), maxDepth = snapshot.entries.reduce(
	      (depth, entry) => Math.max(depth, entry.depth),
	      0
	    ), visibleDepth = Math.max(
	      0,
	      Math.floor((baseWidth - 320) / 28)
	    ), overflowDepth = Math.max(0, maxDepth - visibleDepth);
	    this.#discussionList.style.setProperty(
	      "--ldp-descendant-tree-width",
	      `${baseWidth + overflowDepth * 28}px`
	    ), this.#discussionList.classList.toggle(
	      "ldp-descendant-tree-pannable",
	      overflowDepth > 0
	    ), overflowDepth || (this.#discussionList.scrollLeft = 0);
	  }
	  #onTreePanPointerDown(event) {
	    const target = (0, import_event_target.eventElement)(event);
	    if (!(event.button !== 0 || event.pointerType && event.pointerType !== "mouse" || !this.#discussionList.classList.contains(
	      "ldp-descendant-tree-pannable"
	    ) || !target || target.closest([
	      "button",
	      "a",
	      "input",
	      "select",
	      "textarea",
	      '[contenteditable="true"]',
	      '[role="button"]',
	      ".ldp-post-head",
	      ".ldp-content",
	      ".ldp-reactions",
	      ".ldp-boost-list",
	      ".ldp-sub-actions",
	      ".ldp-topic-footer-actions",
	      "img",
	      "video",
	      "audio",
	      "canvas",
	      "pre",
	      "code"
	    ].join(",")))) {
	      this.#treePan = {
	        pointerId: event.pointerId,
	        startX: event.clientX,
	        scrollLeft: this.#discussionList.scrollLeft,
	        moved: !1
	      };
	      try {
	        this.#discussionList.setPointerCapture?.(event.pointerId);
	      } catch {
	      }
	    }
	  }
	  #onTreePanPointerMove(event) {
	    const pan = this.#treePan;
	    if (!pan || event.pointerId !== pan.pointerId) return;
	    const deltaX = event.clientX - pan.startX;
	    !pan.moved && Math.abs(deltaX) < 3 || (pan.moved = !0, this.#discussionList.classList.add(
	      "ldp-descendant-tree-panning"
	    ), this.#discussionList.scrollLeft = pan.scrollLeft - deltaX, event.preventDefault());
	  }
	  #stopTreePan(event) {
	    const pan = this.#treePan;
	    if (!(!pan || event && event.pointerId !== pan.pointerId)) {
	      this.#treePan = null, this.#discussionList.classList.remove(
	        "ldp-descendant-tree-panning"
	      );
	      try {
	        this.#discussionList.hasPointerCapture?.(pan.pointerId) && this.#discussionList.releasePointerCapture?.(pan.pointerId);
	      } catch {
	      }
	      pan.moved && (this.#suppressTreeClick = !0, this.#requestFrame(() => {
	        this.#suppressTreeClick = !1;
	      }));
	    }
	  }
	  #syncWorkspaceMode(fullPage) {
	    this.#discussionLayer.classList.toggle(
	      "ldp-descendant-replies-layer-centered",
	      fullPage
	    ), this.#discussionLayer.classList.toggle(
	      "ldp-descendant-replies-layer-inline",
	      !fullPage
	    ), this.#discussionPanel.classList.toggle(
	      "ldp-descendant-replies-centered",
	      fullPage
	    ), this.#discussionPanel.classList.toggle(
	      "ldp-descendant-replies-inline",
	      !fullPage
	    );
	  }
	}
}, "a26b568140e3a7e97d1eb5e2b934b4207175b2205e7368e8767c3f2aa4bc9e3f");

/* Source: lite/src/topic/reader-topic-core-bundle.ts */
runtime.register("src/topic/reader-topic-core-bundle.js", function(module, exports, require) {
	var reader_topic_core_bundle_exports = {};
	__export(reader_topic_core_bundle_exports, {
	  createReaderTopicCoreBundle: () => createReaderTopicCoreBundle
	});
	module.exports = __toCommonJS(reader_topic_core_bundle_exports);
	var import_topic_snapshot_repository = require("../cache/topic-snapshot-repository.js"), import_native_message_bus = require("../discourse/native-message-bus.js"), import_native_host_api = require("../discourse/native-host-api.js"), import_native_composer = require("../discourse/native-composer.js"), import_reply_tree_repository = require("../dom/reply-tree-repository.js"), import_topic_live_controller = require("../live/topic-live-controller.js"), import_discourse_native_read_transport = require("../network/discourse-native-read-transport.js"), import_action_request_adapter = require("../post/action-request-adapter.js"), import_boost_report_access_adapter = require("../post/boost-report-access-adapter.js"), import_discourse_action_transport = require("../post/discourse-action-transport.js"), import_post_action_controller = require("../post/post-action-controller.js"), import_topic_post_action_adapter = require("../post/topic-post-action-adapter.js"), import_read_state_controller = require("../reading/read-state-controller.js"), import_read_state_request_adapter = require("../reading/read-state-request-adapter.js"), import_topic_session = require("./topic-session.js"), import_topic_read_request_adapter = require("./topic-read-request-adapter.js");
	const TOPIC_SNAPSHOT_PERSISTENCE_IDLE_MS = 1500;
	function cacheSettings(kind, topicId, lifetime) {
	  return Object.freeze({
	    kind,
	    tags: Object.freeze([`topic:${topicId}`]),
	    freshForMs: lifetime.freshForMs,
	    retainForMs: lifetime.retainForMs,
	    persist: lifetime.persist
	  });
	}
	function topicReadCaches(topicId, options) {
	  return Object.freeze({
	    topic: cacheSettings("discourse-topic-json", topicId, options.topic),
	    posts: cacheSettings("discourse-topic-posts", topicId, options.posts),
	    nested: cacheSettings("discourse-topic-replies", topicId, options.nested)
	  });
	}
	function readCandidate(post) {
	  const postNumber = Number(post.post_number);
	  if (!Number.isSafeInteger(postNumber) || postNumber < 1) return null;
	  const read = post.read;
	  return Object.freeze({
	    postNumber,
	    ...read === !0 ? { read: !0 } : {}
	  });
	}
	function createReaderTopicCoreBundle(context, options) {
	  const topicId = Number(context.topicId), report = (phase, cause) => {
	    options.onDiagnostic?.(Object.freeze({ phase, topicId, cause }));
	  }, nativeOptions = options.origin === void 0 ? {} : { origin: options.origin }, nativeAjax = options.nativeAjax ?? new import_discourse_native_read_transport.BrowserDiscourseNativeAjaxPort(
	    options.host,
	    nativeOptions
	  ), readTransport = new import_discourse_native_read_transport.BrowserDiscourseNativeReadTransport(nativeAjax), mutationTransport = new import_discourse_native_read_transport.BrowserDiscourseNativeMutationTransport(nativeAjax), topicReadController = context.scope.abortController(
	    new DOMException(`Topic ${topicId} 读取链已结束`, "AbortError"),
	    context.signal
	  ), requests = new import_topic_read_request_adapter.TopicReadRequestAdapter({
	    gateway: options.gateway,
	    transport: readTransport,
	    authScope: options.authScope,
	    topicId,
	    signal: topicReadController.signal,
	    caches: topicReadCaches(topicId, options.caches),
	    ...options.basePath === void 0 ? {} : { basePath: options.basePath }
	  }), snapshots = new import_topic_snapshot_repository.TopicSnapshotRepository({
	    responseRepository: options.responses,
	    topicId,
	    authScope: options.authScope,
	    freshForMs: options.caches.snapshot.freshForMs,
	    retainForMs: options.caches.snapshot.retainForMs,
	    persistenceIdleMs: TOPIC_SNAPSHOT_PERSISTENCE_IDLE_MS,
	    ...options.now === void 0 ? {} : { now: options.now },
	    onInvalidSnapshot: (cause) => report("snapshot", cause),
	    onInvalidTreeSnapshot: (cause) => report("reply-tree", cause)
	  }), replies = new import_reply_tree_repository.ReplyTreeRepository(
	    topicId,
	    snapshots.replyTreeSnapshotStore(),
	    {
	      ...options.now === void 0 ? {} : { now: options.now },
	      onPersistenceError: (cause) => report("reply-tree", cause)
	    }
	  ), session = new import_topic_session.TopicSession({
	    topicId,
	    requests,
	    snapshots,
	    replies,
	    pageSize: options.pageSize,
	    signal: topicReadController.signal,
	    ...options.refreshCachedInBackground === void 0 ? {} : { refreshCachedInBackground: options.refreshCachedInBackground },
	    ...options.now === void 0 ? {} : { now: options.now },
	    scope: context.scope,
	    onError: (cause) => report("session", cause),
	    ...options.onLoadingSource === void 0 ? {} : { onInitializeSource: options.onLoadingSource }
	  }), messageBus = new import_native_message_bus.BrowserDiscourseMessageBusPort(options.host), live = new import_topic_live_controller.TopicLiveController({
	    topicId,
	    messageBus,
	    session,
	    cache: options.responses,
	    currentUsername: (0, import_native_host_api.discourseNativeCurrentUsername)(options.host),
	    ...options.livePostDelayMs === void 0 ? {} : { postDelayMs: options.livePostDelayMs },
	    ...options.liveTopicDelayMs === void 0 ? {} : { topicDelayMs: options.liveTopicDelayMs },
	    scope: context.scope,
	    onError: (cause) => report("message-bus", cause)
	  }), composerEvents = new import_native_composer.DiscourseComposerTopicSyncController({
	    topicId,
	    events: options.composerEvents ?? new import_native_composer.DiscourseComposerEventPort(options.host),
	    session,
	    parentScope: context.scope,
	    ...options.now === void 0 ? {} : { now: options.now },
	    onError: (cause) => report("composer-events", cause)
	  }), readRequests = new import_read_state_request_adapter.ReadStateRequestAdapter({
	    gateway: options.gateway,
	    transport: mutationTransport,
	    authScope: options.authScope,
	    topicId,
	    signal: context.signal,
	    ...options.basePath === void 0 ? {} : { basePath: options.basePath },
	    ...options.readTimeMs === void 0 ? {} : { readTimeMs: options.readTimeMs }
	  }), read = new import_read_state_controller.ReadStateController({
	    authScope: options.authScope,
	    topicId,
	    submitter: readRequests,
	    ...options.readCoordination === void 0 ? {} : { coordination: options.readCoordination },
	    ...options.readBatchSize === void 0 ? {} : { batchSize: options.readBatchSize },
	    ...options.readRetryDelayMs === void 0 ? {} : { retryDelayMs: options.readRetryDelayMs },
	    ...options.readMaxAutomaticRetries === void 0 ? {} : { maxAutomaticRetries: options.readMaxAutomaticRetries },
	    scope: context.scope,
	    onError: (cause) => report("read-state", cause)
	  }), actionRequests = new import_action_request_adapter.ActionRequestAdapter({
	    gateway: options.gateway,
	    nativeActions: options.nativeActions ?? new import_discourse_action_transport.BrowserDiscourseNativeActionPort(options.host, nativeAjax),
	    authScope: options.authScope,
	    signal: context.signal
	  }), boostReportAccess = new import_boost_report_access_adapter.BoostReportAccessAdapter({
	    gateway: options.gateway,
	    transport: readTransport,
	    authScope: options.authScope,
	    signal: context.signal,
	    ...options.basePath === void 0 ? {} : { basePath: options.basePath }
	  }), actions = new import_post_action_controller.PostActionController({
	    mutation: actionRequests,
	    cache: options.responses,
	    scope: context.scope,
	    onError: (cause) => report("action", cause)
	  }), postActions = new import_topic_post_action_adapter.TopicPostActionAdapter({
	    session,
	    ...options.now === void 0 ? {} : { now: options.now }
	  }), preload = (posts) => {
	    const candidates = posts.map(readCandidate).filter((candidate) => candidate !== null);
	    candidates.length && read.preload(candidates);
	  };
	  session.changes.subscribe((commit) => {
	    preload(
	      commit.changedPostNumbers.map((postNumber) => session.postByNumber(postNumber)).filter((post) => post !== void 0)
	    );
	  }, context.scope);
	  const services = Object.freeze({
	    requests,
	    snapshots,
	    replies,
	    session,
	    live,
	    composerEvents,
	    readRequests,
	    read,
	    actionRequests,
	    boostReportAccess,
	    actions,
	    postActions
	  });
	  let closePromise = null;
	  return Object.freeze({
	    session,
	    replies,
	    services,
	    activate: () => {
	      preload(session.cachedPosts()), live.setActive(!0, {
	        refresh: session.initializedFromCache && session.localArchiveState().topic === null
	      });
	      try {
	        composerEvents.start();
	      } catch (cause) {
	        report("composer-events", cause);
	      }
	      return read.start(), () => {
	        read.stop(), live.active && live.setActive(!1), composerEvents.stop();
	      };
	    },
	    prepareClose: (reason) => closePromise || (topicReadController.signal.aborted || topicReadController.abort(
	      new DOMException(
	        `Topic ${topicId} 已${reason === "switch" ? "切换" : "关闭"}`,
	        "AbortError"
	      )
	    ), read.stop(), live.setActive(!1), composerEvents.stop(), closePromise = (async () => {
	      const activeResults = await Promise.allSettled([
	        read.flush({ force: !0 }),
	        live.flush()
	      ]);
	      for (const result of activeResults)
	        result.status === "rejected" && report("prepare-close", result.reason);
	      try {
	        await session.flush();
	      } catch (cause) {
	        report("prepare-close", cause);
	      }
	    })(), closePromise)
	  });
	}
}, "8bf41c6e63221b26fef77731b5a672a978ff5e7bbb71e2854b269cbc3bd579e1");

/* Source: lite/src/topic/reader-topic-dom-coordinator.ts */
runtime.register("src/topic/reader-topic-dom-coordinator.js", function(module, exports, require) {
	var reader_topic_dom_coordinator_exports = {};
	__export(reader_topic_dom_coordinator_exports, {
	  ReaderTopicDomCoordinator: () => ReaderTopicDomCoordinator
	});
	module.exports = __toCommonJS(reader_topic_dom_coordinator_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_reply_tree_dom_owner = require("../dom/reply-tree-dom-owner.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_branch_overlay = require("../layout/branch-overlay.js"), import_reader_reply_tree_preferences = require("./reader-reply-tree-preferences.js"), import_reply_tree_virtual_layout_controller = require("../stream/reply-tree-virtual-layout-controller.js"), import_reply_tree_viewport_layout = require("../stream/reply-tree-viewport-layout.js"), import_virtual_root_layout = require("../stream/virtual-root-layout.js"), import_virtual_stream_dom_controller = require("../stream/virtual-stream-dom-controller.js"), import_virtual_stream_frame_controller = require("../stream/virtual-stream-frame-controller.js"), import_virtual_stream_view = require("../stream/virtual-stream-view.js"), import_reader_post_view_projector = require("./reader-post-view-projector.js"), import_reader_topic_scroll_lifecycle = require("./reader-topic-scroll-lifecycle.js");
	const DIRECT_REPLY_VISIBLE_PIPELINE_DEPTH = 3, DIRECT_REPLY_NEARBY_PIPELINE_DEPTH = 2, DIRECT_REPLY_PREFETCH_MAX_PAGES = 5, MATERIALIZATION_STEP_SCREENS = 0.5, HIDDEN_REPLY_MARKER_BLOCK_SIZE = 18;
	function isAbortFailure(error) {
	  return error instanceof DOMException && error.name === "AbortError" || String(error?.name ?? "") === "AbortError";
	}
	class ReaderTopicDomCoordinator {
	  scope;
	  streamView;
	  domOwner;
	  layout;
	  domController;
	  frame;
	  branchOverlay;
	  replyTreePresentation;
	  postProjector;
	  visibleRootChanges = new import_signal.Signal();
	  windowChanges = new import_signal.Signal();
	  presentationChanges;
	  #session;
	  #scroll;
	  #onError;
	  #branchFrames;
	  #directReplyPrefetchScheduler;
	  #readDirectReplyPrefetchScreens;
	  #readDirectReplyPrefetchIdleMs;
	  #readDirectReplyPrefetchConcurrency;
	  #now;
	  #scrollLifecycle;
	  #ownsPresentationChanges;
	  #rootObservers = /* @__PURE__ */ new Map();
	  #retainedViews = /* @__PURE__ */ new Map();
	  #topicDirtyRetainedPostNumbers = /* @__PURE__ */ new Set();
	  #directReplyPrefetches = /* @__PURE__ */ new Map();
	  #queuedDirectReplyPrefetches = /* @__PURE__ */ new Set();
	  #directReplyPrefetchedExpectedCounts = /* @__PURE__ */ new Map();
	  #directReplyPrefetchAttemptedExpectedCounts = /* @__PURE__ */ new Map();
	  #rootProjection;
	  #treeViewport;
	  #ownSizeObserver;
	  #ownSizeTargets = /* @__PURE__ */ new Map();
	  #ownSizeSamples = /* @__PURE__ */ new Map();
	  #pendingOwnSizePostNumbers = /* @__PURE__ */ new Set();
	  #rootVirtualInsets = /* @__PURE__ */ new Map();
	  #mountedPostNumbers = /* @__PURE__ */ new Set();
	  #activeContentPostNumbers = /* @__PURE__ */ new Set();
	  #nextContentPostNumbers = /* @__PURE__ */ new Set();
	  #directReplyPrefetchCandidatePostNumbers = /* @__PURE__ */ new Set();
	  #directReplyPrefetchOrderedCandidates = Object.freeze([]);
	  #directReplyVisiblePostNumbers = /* @__PURE__ */ new Set();
	  #directReplyVisibleKey = "";
	  #directReplyPrefetchCandidateKey = "";
	  #branchPaintHandle = null;
	  #branchPaintGeneration = 0;
	  #pendingBranchCollapseAnchor = null;
	  #directReplyPrefetchHandle = null;
	  #retainedViewLimit = 0;
	  #lastVisibleRootChangeKey = "";
	  #destroyed = !1;
	  constructor(options) {
	    if (this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#session = options.session, this.#scroll = options.scroll, this.#onError = options.onError ?? (() => {
	    }), this.presentationChanges = options.presentationChanges ?? new import_signal.Signal(), this.#ownsPresentationChanges = options.presentationChanges === void 0, this.postProjector = new import_reader_post_view_projector.ReaderPostViewProjector({
	      document: options.document,
	      identity: options.identity,
	      render: options.render,
	      ...options.postFeatures ? { features: options.postFeatures } : {},
	      onError: this.#onError
	    }), this.#branchFrames = options.frameScheduler ?? Object.freeze({
	      request: (callback) => requestAnimationFrame(callback),
	      cancel: (handle) => cancelAnimationFrame(handle)
	    }), this.#directReplyPrefetchScheduler = options.directReplyPrefetchScheduler ?? Object.freeze({
	      schedule: (callback, delayMs) => setTimeout(callback, delayMs),
	      cancel: (handle) => clearTimeout(handle)
	    }), this.#readDirectReplyPrefetchScreens = options.readDirectReplyPrefetchScreens ?? (() => 0), this.#readDirectReplyPrefetchIdleMs = options.readDirectReplyPrefetchIdleMs ?? (() => 180), this.#readDirectReplyPrefetchConcurrency = options.readDirectReplyPrefetchConcurrency ?? (() => 1), this.#now = options.now ?? (() => performance.now()), this.#scrollLifecycle = new import_reader_topic_scroll_lifecycle.ReaderTopicScrollLifecycle({
	      readLastUserScrollAt: () => options.scroll.lastUserScrollAt?.() ?? 0,
	      readIdleMs: this.#readDirectReplyPrefetchIdleMs,
	      scheduler: this.#directReplyPrefetchScheduler,
	      now: this.#now,
	      parentScope: this.scope
	    }), this.streamView = new import_virtual_stream_view.VirtualStreamView(options.document), options.topicHost.append(this.streamView.slots.root), this.scope.add(() => this.streamView.destroy()), options.replyTreePresentation && options.replyTreePresentation.canonical !== options.replies.topology)
	      throw new Error(
	        "外部 ReplyTree presentation 必须投影当前 canonical topology"
	      );
	    this.replyTreePresentation = options.replyTreePresentation ?? new import_reader_reply_tree_preferences.ReaderReplyTreePresentation(
	      options.replies.topology,
	      options.replyTreePreferences?.read(),
	      {
	        canonicalCoverageComplete: () => options.replies.coverage().complete
	      }
	    ), this.domOwner = new import_reply_tree_dom_owner.ReplyTreeDomOwner(
	      this.replyTreePresentation,
	      this.streamView.slots.rootList
	    ), this.scope.add(() => this.domOwner.destroy()), this.scope.add(() => {
	      for (const view of this.#retainedViews.values()) view.destroy();
	      this.#retainedViews.clear(), this.#topicDirtyRetainedPostNumbers.clear();
	    }), this.layout = new import_virtual_root_layout.VirtualRootLayout(options.estimatedRootSize, !0), this.#rootProjection = new import_reply_tree_virtual_layout_controller.ReplyTreeVirtualLayoutController(
	      options.replies,
	      this.layout,
	      this.scope,
	      this.replyTreePresentation
	    ), this.#treeViewport = new import_reply_tree_viewport_layout.ReplyTreeViewportLayout(
	      this.replyTreePresentation,
	      this.layout,
	      options.estimatedRootSize
	    ), this.domController = new import_virtual_stream_dom_controller.VirtualStreamDomController(
	      options.replies,
	      this.layout,
	      this.streamView,
	      this.domOwner,
	      {
	        prepareRoots: (_postNumbers, input, window) => this.#prepareRootViews(input, window),
	        roots: () => this.replyTreePresentation.roots()
	      }
	    ), this.frame = new import_virtual_stream_frame_controller.VirtualStreamFrameController(this.domController, {
	      readWindowInput: () => this.#readVirtualWindowInput(),
	      applyScrollCompensation: (delta) => options.scroll.applyScrollCompensation(delta),
	      /*
	       * 根高测量只更新虚拟坐标。活跃滚动由 Chromium 独占;停稳后的物理
	       * 视野由 scroll adapter 的唯一视野锁持有,frame 不得再写第二份补偿。
	       */
	      shouldApplyScrollCompensation: () => !1,
	      shouldDeferMeasurements: () => !1,
	      resolveRootBlockSize: (target, observedBlockSize) => {
	        const postNumber = Number(target.getAttribute("data-post-number")), inset = Number.isSafeInteger(postNumber) ? this.#rootVirtualInsets.get(postNumber) : void 0, virtualBlockSize = observedBlockSize + (inset?.beforeSize ?? 0) + (inset?.afterSize ?? 0), marker = target.nextElementSibling;
	        return marker?.nodeType === 1 && marker.classList.contains("ldp-hidden-reply-marker") ? virtualBlockSize + HIDDEN_REPLY_MARKER_BLOCK_SIZE : virtualBlockSize;
	      },
	      onCommit: (commit) => {
	        for (const error of this.windowChanges.emit(commit))
	          this.#onError(error);
	        this.#syncRootObservers(
	          commit.attachedRoots,
	          commit.detachedRoots
	        );
	        const projectionChanged = commit.tree.changed !== !1;
	        projectionChanged && this.#syncProjectionFeatures(), this.#syncContentFeatures(), this.#syncDirectReplyPrefetchCandidates(), this.#releaseParkedViews(commit.tree.parked), projectionChanged && this.#scheduleBranchPaint();
	        const visiblePostNumber = this.#treeViewport.visiblePostNumbers[0] ?? commit.window.visiblePostNumbers[0], visibleRootPostNumber = visiblePostNumber === void 0 ? void 0 : this.replyTreePresentation.rootOf(visiblePostNumber) ?? visiblePostNumber;
	        if (visibleRootPostNumber !== void 0) {
	          const changeKey = `${visibleRootPostNumber}|${Number(commit.window.atStart)}|${Number(commit.window.atEnd)}`;
	          if (changeKey !== this.#lastVisibleRootChangeKey) {
	            this.#lastVisibleRootChangeKey = changeKey;
	            for (const error of this.visibleRootChanges.emit(Object.freeze({
	              postNumber: visibleRootPostNumber,
	              atStart: commit.window.atStart,
	              atEnd: commit.window.atEnd
	            })))
	              this.#onError(error);
	          }
	        }
	      },
	      ...options.observerFactory ? { observerFactory: options.observerFactory } : {},
	      ...options.frameScheduler ? { frameScheduler: options.frameScheduler } : {},
	      scope: this.scope
	    });
	    const NativeResizeObserver = options.document.defaultView?.ResizeObserver;
	    this.#ownSizeObserver = NativeResizeObserver ? new NativeResizeObserver((entries) => {
	      this.#recordOwnSizeMeasurements(entries);
	    }) : null, this.scope.add(() => {
	      this.#ownSizeObserver?.disconnect(), this.#ownSizeTargets.clear(), this.#ownSizeSamples.clear(), this.#pendingOwnSizePostNumbers.clear();
	    });
	    const createBranchResizeObserver = options.branchResizeObserverFactory ?? (NativeResizeObserver ? (callback) => new NativeResizeObserver(callback) : void 0);
	    this.branchOverlay = new import_branch_overlay.ReaderBranchOverlayController({
	      domOwner: this.domOwner,
	      renderMode: "segmented-css",
	      preserveCollapseAnchor: (root) => {
	        const postNumber = Number(root.dataset.postNumber);
	        if (!Number.isSafeInteger(postNumber) || postNumber <= 0)
	          return this.#pendingBranchCollapseAnchor = null, !1;
	        const scrollRoot = root.closest(".ldp-body"), header = root.querySelector(
	          ":scope > .ldp-post-head"
	        ), viewport = scrollRoot?.getBoundingClientRect(), frozenBottom = scrollRoot?.closest(".ldp-modal")?.querySelector(":scope > .ldp-header")?.getBoundingClientRect().bottom ?? viewport?.top, headerRect = header?.getBoundingClientRect(), rootRect = root.getBoundingClientRect(), visibleTop = Math.max(
	          viewport?.top ?? 0,
	          frozenBottom ?? 0
	        ), parentVisible = !!(viewport && headerRect && headerRect.bottom > visibleTop && headerRect.top < viewport.bottom);
	        return this.#pendingBranchCollapseAnchor = Object.freeze({
	          postNumber,
	          viewportOffset: parentVisible ? Math.max(0, rootRect.top - visibleTop) : 10
	        }), !0;
	      },
	      onLayoutChange: () => {
	        const anchor = this.#pendingBranchCollapseAnchor;
	        this.#pendingBranchCollapseAnchor = null;
	        const postNumber = anchor?.postNumber ?? null, rootPostNumber = postNumber === null ? void 0 : this.replyTreePresentation.rootOf(postNumber) ?? postNumber, offset = postNumber === null ? void 0 : this.#treeViewport.offsetOf(postNumber) ?? this.layout.offsetOf(rootPostNumber);
	        if (postNumber !== null && offset !== void 0) {
	          this.#scroll.writeScrollOffset(offset), this.frame.flushNow();
	          const target = this.domOwner.view(postNumber)?.slots.root;
	          if (target?.isConnected) {
	            const alignCollapsedBranch = (element) => this.#scroll.alignPost(element, {
	              source: "branch-collapse",
	              alignment: "start",
	              viewportOffset: anchor?.viewportOffset ?? 10,
	              highlight: !1
	            });
	            alignCollapsedBranch(target), this.frame.flushNow();
	            const committedTarget = this.domOwner.view(postNumber)?.slots.root;
	            committedTarget?.isConnected && alignCollapsedBranch(committedTarget);
	          }
	        } else
	          this.frame.notifyScroll();
	        this.#scheduleBranchPaint();
	      },
	      onObservedResize: () => this.#scheduleBranchPaint(),
	      ...createBranchResizeObserver ? { createResizeObserver: createBranchResizeObserver } : {},
	      parentScope: this.scope
	    }), this.scope.add(options.scroll.listenScroll(() => {
	      this.frame.notifyScroll();
	    })), this.scope.add(() => {
	      this.#directReplyPrefetchHandle !== null && this.#directReplyPrefetchScheduler.cancel(
	        this.#directReplyPrefetchHandle
	      ), this.#directReplyPrefetchHandle = null;
	      for (const [postNumber, request] of this.#directReplyPrefetches)
	        request.controller.signal.aborted || request.controller.abort(new DOMException(
	          `树状回复 #${postNumber} 已离开 Topic`,
	          "AbortError"
	        ));
	      this.#directReplyPrefetches.clear(), this.#queuedDirectReplyPrefetches.clear(), this.#syncDirectReplyLoadingIndicators(), this.#directReplyPrefetchedExpectedCounts.clear(), this.#directReplyPrefetchAttemptedExpectedCounts.clear(), this.#directReplyPrefetchCandidatePostNumbers = /* @__PURE__ */ new Set(), this.#directReplyPrefetchOrderedCandidates = Object.freeze([]), this.#directReplyVisiblePostNumbers = /* @__PURE__ */ new Set(), this.#directReplyVisibleKey = "";
	      for (const postNumber of this.#activeContentPostNumbers) {
	        const root = this.domOwner.view(postNumber)?.slots.root;
	        root && this.#deactivateNodeContent(root, postNumber);
	      }
	      for (const postNumber of this.#rootObservers.keys()) {
	        const root = this.domOwner.view(postNumber)?.slots.root;
	        root && this.#deactivateBranch(root, postNumber);
	      }
	      this.#activeContentPostNumbers = /* @__PURE__ */ new Set(), this.#nextContentPostNumbers = /* @__PURE__ */ new Set(), this.#branchPaintHandle !== null && this.#branchFrames.cancel(this.#branchPaintHandle), this.#branchPaintHandle = null, this.streamView.slots.rootList.classList.remove(
	        "ldp-branch-paint-pending"
	      ), this.visibleRootChanges.clear(), this.windowChanges.clear(), this.#ownsPresentationChanges && this.presentationChanges.clear();
	      for (const cleanup of this.#rootObservers.values()) cleanup();
	      this.#rootObservers.clear();
	    }), options.session.changes.subscribe((commit) => {
	      this.#queueSessionCommit(commit);
	    }, this.scope), options.replyTreePreferences?.subscribe((preferences) => {
	      this.replyTreePresentation.update(preferences) && this.refreshRootProjection();
	    }, this.scope);
	  }
	  async initialize() {
	    this.#assertActive();
	    const topic = await this.#session.init();
	    return this.#assertActive(), this.#scroll.writeScrollOffset(0), this.frame.flushNow(), topic;
	  }
	  get preparedPostViewCount() {
	    return this.domOwner.views().length + this.#retainedViews.size;
	  }
	  async loadNext(options = {}) {
	    this.#assertActive();
	    const result = await this.#session.next(options);
	    return this.#assertActive(), this.frame.notifyScroll(), result;
	  }
	  async prefetchAhead(batchCount) {
	    this.#assertActive(), this.#session.prefetchAhead && (await this.#session.prefetchAhead(batchCount, {
	      background: !0,
	      maxAttempts: 1
	    }), this.#assertActive(), this.frame.notifyScroll());
	  }
	  setFlowStatus(state) {
	    this.#assertActive(), this.streamView.setFlowState(Object.freeze({
	      ...state,
	      empty: state.done && this.replyTreePresentation.roots().length === 0
	    }));
	  }
	  notifyScroll() {
	    this.#assertActive(), this.frame.notifyScroll();
	  }
	  /**
	   * cooked、图片、Onebox、公式等迟到内容只报告“几何已经变化”。
	   *
	   * 根高度由 VirtualStreamFrameController 的唯一 ResizeObserver 读取并提交;
	   * 这里不得再伪造一次 scroll 帧,否则同一次资源 load 会同时走手工通知与
	   * ResizeObserver 两条路径。回复线只需在下一绘制帧读取最终锚点。
	   */
	  notifyContentLayoutChanged() {
	    this.#assertActive(), this.#scheduleBranchPaint();
	  }
	  flushNow() {
	    this.#assertActive(), this.frame.flushNow();
	  }
	  refreshRootProjection(resetScroll = !1) {
	    this.#assertActive(), this.#rootProjection.syncRoots(), resetScroll && this.#scroll.writeScrollOffset(0), this.frame.flushNow(), this.#syncProjectionFeatures();
	  }
	  revealNextReplyLevel(postNumberValue) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostNumber)(postNumberValue);
	    return this.replyTreePresentation.revealNextLevel(postNumber) ? (this.refreshRootProjection(), !0) : !1;
	  }
	  readWindowInput() {
	    return this.#assertActive(), this.#readVirtualWindowInput();
	  }
	  #readVirtualWindowInput() {
	    const input = this.#scroll.readWindowInput(), preservePostNumber = input.preservePostNumber, preserveRootPostNumber = preservePostNumber === void 0 ? void 0 : this.replyTreePresentation.rootOf(preservePostNumber) ?? preservePostNumber;
	    return Object.freeze({
	      ...input,
	      materializationStepScreens: MATERIALIZATION_STEP_SCREENS,
	      ...preserveRootPostNumber === void 0 ? {} : { preserveRootPostNumber }
	    });
	  }
	  hasVisibleDataGap() {
	    return this.#assertActive(), this.#treeViewport.visiblePostNumbers.some(
	      (postNumber) => !this.#session.postByNumber(postNumber)
	    );
	  }
	  lastUserScrollAt() {
	    return this.#assertActive(), this.#scrollLifecycle.lastUserScrollAt();
	  }
	  listenUserScrollIntent(listener) {
	    return this.#assertActive(), this.#scroll.listenUserScrollIntent?.(listener) ?? (() => {
	    });
	  }
	  captureViewportAnchor() {
	    this.#assertActive();
	    const physicalAnchor = this.#scroll.readVisibleViewportAnchor?.(
	      this.#visiblePostElements()
	    ), input = this.#scroll.readWindowInput();
	    if (physicalAnchor) {
	      const postNumber = (0, import_identifiers.discoursePostReference)({
	        post_number: physicalAnchor.postNumber
	      }).postNumber, rootPostNumber = this.domOwner.topology.rootOf(postNumber), postLayoutOffset = this.#treeViewport.offsetOf(postNumber) ?? (rootPostNumber === void 0 ? void 0 : this.layout.offsetOf(rootPostNumber));
	      if (postLayoutOffset !== void 0)
	        return Object.freeze({
	          postNumber,
	          postOffset: input.scrollOffset - postLayoutOffset,
	          scrollTop: input.scrollOffset
	        });
	    }
	    const visibleRootPostNumber = this.layout.window({
	      ...input,
	      overscanBeforeScreens: 0,
	      overscanAfterScreens: 0
	    }).visiblePostNumbers[0];
	    if (visibleRootPostNumber === void 0) return null;
	    const rootOffset = this.layout.offsetOf(visibleRootPostNumber);
	    return rootOffset === void 0 ? null : Object.freeze({
	      postNumber: visibleRootPostNumber,
	      postOffset: input.scrollOffset - rootOffset,
	      scrollTop: input.scrollOffset
	    });
	  }
	  #connectedPostElements() {
	    return this.domOwner.views().map((view) => view.slots.root).filter(
	      (root) => root.isConnected && this.streamView.slots.root.contains(root)
	    );
	  }
	  #visiblePostElements() {
	    const visible = this.#treeViewport.visiblePostNumbers.map((postNumber) => this.domOwner.view(postNumber)?.slots.root).filter(
	      (root) => !!root && root.isConnected && this.streamView.slots.root.contains(root)
	    );
	    return visible.length ? Object.freeze(visible) : this.#connectedPostElements();
	  }
	  restoreViewportAnchor(anchor) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: anchor.postNumber
	    }).postNumber, rootPostNumber = this.domOwner.topology.rootOf(postNumber);
	    if (rootPostNumber === void 0) return !1;
	    const postLayoutOffset = this.#treeViewport.offsetOf(postNumber) ?? this.layout.offsetOf(rootPostNumber);
	    if (postLayoutOffset === void 0) return !1;
	    const postOffset = Number(anchor.postOffset);
	    return this.#scroll.writeScrollOffset(
	      postLayoutOffset + (Number.isFinite(postOffset) ? postOffset : 0)
	    ), this.frame.flushNow(), !0;
	  }
	  /** 只闪烁已挂载楼层,不改变当前视口几何。 */
	  highlightPost(rawPostNumber) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: rawPostNumber
	    }).postNumber, element = this.domOwner.view(postNumber)?.slots.root;
	    return !element?.isConnected || element.classList.contains("ldp-virtual-ancestor-shell") || element.classList.contains("ldp-post-projection-pending") || !this.#scroll.highlightPost ? !1 : (this.#scroll.highlightPost(element), !0);
	  }
	  /** 当前 canonical 楼层被主信息流投影停放时,交由完整讨论 surface 揭示。 */
	  isPostHidden(rawPostNumber) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: rawPostNumber
	    }).postNumber;
	    return this.replyTreePresentation.canonical.has(postNumber) && this.replyTreePresentation.rootOf(postNumber) === void 0;
	  }
	  revealPost(rawPostNumber, options) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: rawPostNumber
	    }).postNumber;
	    let rootPostNumber = this.domOwner.topology.rootOf(postNumber);
	    if (rootPostNumber === void 0 && options.degradedRootPostNumber !== void 0 && (this.replyTreePresentation.revealDegradedBranch(
	      options.degradedRootPostNumber,
	      postNumber
	    ), this.#rootProjection.syncRoots(), this.#syncProjectionFeatures(), rootPostNumber = this.domOwner.topology.rootOf(postNumber)), rootPostNumber === void 0 && this.replyTreePresentation.revealAsFloor(postNumber) && (this.#rootProjection.syncRoots(), rootPostNumber = postNumber, this.#syncProjectionFeatures()), rootPostNumber === void 0) return null;
	    const currentRoot = this.domOwner.view(postNumber)?.slots.root, wasMaterialized = currentRoot?.isConnected === !0 && !currentRoot.classList.contains("ldp-virtual-ancestor-shell") && !currentRoot.classList.contains("ldp-post-projection-pending");
	    if (!wasMaterialized) {
	      const offset = this.#treeViewport.offsetOf(postNumber) ?? this.layout.offsetOf(rootPostNumber);
	      if (offset === void 0) return null;
	      this.#scroll.writeScrollOffset(offset), this.frame.flushNow();
	    }
	    const element = this.domOwner.view(postNumber)?.slots.root;
	    return !element?.isConnected || element.classList.contains("ldp-virtual-ancestor-shell") || element.classList.contains("ldp-post-projection-pending") ? null : (this.#scroll.alignPost(element, options), Object.freeze({
	      postNumber,
	      rootPostNumber,
	      element,
	      mounted: !wasMaterialized
	    }));
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #queueSessionCommit(commit) {
	    this.#rootProjection.syncRoots(), this.#applySessionCommit(commit), this.#emitPresentationCommit(commit);
	  }
	  #emitPresentationCommit(commit) {
	    for (const error of this.presentationChanges.emit(commit))
	      this.#onError(error);
	  }
	  #applySessionCommit(commit, notify = !0) {
	    this.#directReplyPrefetchCandidateKey = "";
	    for (const postNumber of commit.removedPostNumbers ?? []) {
	      this.#directReplyPrefetchedExpectedCounts.delete(postNumber), this.#directReplyPrefetchAttemptedExpectedCounts.delete(postNumber);
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      this.#activeContentPostNumbers.has(postNumber) && (root && this.#deactivateNodeContent(root, postNumber), this.#activeContentPostNumbers = new Set(
	        [...this.#activeContentPostNumbers].filter(
	          (candidate) => candidate !== postNumber
	        )
	      )), root && this.#rootObservers.has(postNumber) && this.#deactivateBranch(root, postNumber), this.#rootObservers.get(postNumber)?.(), this.#rootObservers.delete(postNumber), this.domOwner.unregister(postNumber, !0, !1), this.#retainedViews.get(postNumber)?.destroy(), this.#retainedViews.delete(postNumber), this.#topicDirtyRetainedPostNumbers.delete(postNumber);
	    }
	    const refreshPostNumbers = new Set(commit.changedPostNumbers);
	    if (commit.topicChanged) {
	      for (const postNumber of this.#mountedPostNumbers)
	        refreshPostNumbers.add(postNumber);
	      for (const postNumber of this.#retainedViews.keys())
	        this.#topicDirtyRetainedPostNumbers.add(postNumber);
	    }
	    const posts = [...refreshPostNumbers].map((postNumber) => this.#session.postByNumber(postNumber)).filter((post) => post !== void 0);
	    this.#refreshExistingPosts(posts);
	    for (const postNumber of refreshPostNumbers)
	      this.#directReplyPrefetchCandidatePostNumbers.has(postNumber) && this.#prefetchDirectReplies(postNumber);
	    notify && this.frame.notifyScroll();
	  }
	  #refreshExistingPosts(posts) {
	    for (const post of posts) {
	      let postNumber;
	      try {
	        postNumber = (0, import_identifiers.discoursePostReference)(post).postNumber;
	      } catch (error) {
	        this.#onError(error);
	        continue;
	      }
	      const existing = this.domOwner.view(postNumber) ?? this.#retainedViews.get(postNumber);
	      if (existing)
	        try {
	          this.postProjector.render(post, existing), this.#topicDirtyRetainedPostNumbers.delete(postNumber);
	        } catch (error) {
	          this.#onError(error);
	        }
	    }
	  }
	  #prepareRootViews(input, window) {
	    this.#updateRetainedViewLimit(input.maxMountedPostCount);
	    let plan = this.#treeViewport.plan(window, input);
	    if (!this.#ownSizeObserver)
	      for (let pass = 0; pass < 2; pass += 1) {
	        let measured = !1;
	        for (const postNumber of this.#activeContentPostNumbers)
	          plan.contentPostNumbers.has(postNumber) || (measured = this.#measureOwnPostSize(postNumber) || measured);
	        if (!measured) break;
	        plan = this.#treeViewport.plan(window, input);
	      }
	    this.#rootVirtualInsets = plan.rootVirtualInsets ?? /* @__PURE__ */ new Map(), this.#mountedPostNumbers = plan.mountedPostNumbers, this.#nextContentPostNumbers = plan.contentPostNumbers;
	    for (const postNumber of plan.mountedPostNumbers) {
	      if (this.domOwner.view(postNumber)) continue;
	      const retained = this.#retainedViews.get(postNumber);
	      if (retained) {
	        if (this.#retainedViews.delete(postNumber), this.domOwner.register(retained, !1), this.#topicDirtyRetainedPostNumbers.delete(postNumber)) {
	          const post2 = this.#session.postByNumber(postNumber);
	          if (post2)
	            try {
	              this.postProjector.render(post2, retained);
	            } catch (error) {
	              this.#onError(error);
	            }
	        }
	        continue;
	      }
	      const post = this.#session.postByNumber(postNumber);
	      if (post)
	        try {
	          const created = this.postProjector.create(
	            post,
	            this.scope,
	            postNumber
	          );
	          this.domOwner.register(created, !1);
	        } catch (error) {
	          this.#onError(error);
	        }
	    }
	    return plan;
	  }
	  #measureOwnPostSize(postNumber) {
	    const view = this.domOwner.view(postNumber), root = view?.slots.root;
	    if (!view || !root?.isConnected || root.classList.contains("ldp-virtual-ancestor-shell")) return !1;
	    const rootSize = root.getBoundingClientRect().height, replyTreeSize = view.slots.replyTree.getBoundingClientRect().height, ownSize = rootSize - replyTreeSize;
	    return this.#treeViewport.measureOwnSize(postNumber, ownSize);
	  }
	  #releaseParkedViews(parkedPostNumbers) {
	    for (const postNumber of parkedPostNumbers) {
	      if (this.#mountedPostNumbers.has(postNumber)) continue;
	      const view = this.domOwner.unregister(postNumber, !1, !1);
	      view && this.#retainView(view);
	    }
	  }
	  #updateRetainedViewLimit(maxMountedPostCount) {
	    this.#retainedViewLimit = maxMountedPostCount === void 0 ? 0 : Math.max(0, Math.floor(maxMountedPostCount / 4)), this.#trimRetainedViews();
	  }
	  #retainView(view) {
	    this.#retainedViews.delete(view.postNumber), this.#retainedViews.set(view.postNumber, view), this.#trimRetainedViews();
	  }
	  #trimRetainedViews() {
	    for (; this.#retainedViews.size > this.#retainedViewLimit; ) {
	      const postNumber = this.#retainedViews.keys().next().value;
	      if (postNumber === void 0) break;
	      const view = this.#retainedViews.get(postNumber);
	      this.#retainedViews.delete(postNumber), this.#topicDirtyRetainedPostNumbers.delete(postNumber), view?.destroy();
	    }
	  }
	  #syncContentFeatures() {
	    const previousPostNumbers = this.#activeContentPostNumbers, nextPostNumbers = new Set(this.#nextContentPostNumbers);
	    for (const postNumber of previousPostNumbers) {
	      if (nextPostNumbers.has(postNumber)) continue;
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      root && this.#deactivateNodeContent(root, postNumber);
	    }
	    this.#activeContentPostNumbers = nextPostNumbers;
	    for (const postNumber of nextPostNumbers) {
	      if (previousPostNumbers.has(postNumber)) continue;
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      root && this.#activateNodeContent(root, postNumber);
	    }
	  }
	  #activateNodeContent(root, postNumber) {
	    this.postProjector.attach(root, postNumber, "node");
	    const view = this.domOwner.view(postNumber);
	    view && this.#observeOwnSize(view);
	  }
	  #deactivateNodeContent(root, postNumber) {
	    const view = this.domOwner.view(postNumber);
	    view && this.#unobserveOwnSize(view), this.postProjector.detach(root, postNumber, "node");
	  }
	  #observeOwnSize(view) {
	    const observer = this.#ownSizeObserver;
	    if (!observer) return;
	    const postNumber = view.postNumber;
	    for (const [target, kind] of [
	      [view.slots.root, "root"],
	      [view.slots.replyTree, "reply-tree"]
	    ])
	      this.#ownSizeTargets.set(target, Object.freeze({ postNumber, kind })), observer.observe(target);
	  }
	  #unobserveOwnSize(view) {
	    const observer = this.#ownSizeObserver;
	    if (observer) {
	      for (const target of [view.slots.root, view.slots.replyTree])
	        observer.unobserve(target), this.#ownSizeTargets.delete(target);
	      this.#ownSizeSamples.delete(view.postNumber), this.#pendingOwnSizePostNumbers.delete(view.postNumber);
	    }
	  }
	  #recordOwnSizeMeasurements(entries) {
	    const touched = /* @__PURE__ */ new Set();
	    for (const entry of entries) {
	      const target = this.#ownSizeTargets.get(entry.target);
	      if (!target) continue;
	      const borderBox = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize, blockSize = Math.round(
	        borderBox?.blockSize ?? entry.contentRect.height
	      );
	      if (!Number.isFinite(blockSize) || blockSize < 0) continue;
	      const sample = this.#ownSizeSamples.get(target.postNumber) ?? {};
	      target.kind === "root" ? sample.root = blockSize : sample.replyTree = blockSize, this.#ownSizeSamples.set(target.postNumber, sample), touched.add(target.postNumber);
	    }
	    for (const postNumber of touched)
	      this.#pendingOwnSizePostNumbers.add(postNumber);
	    this.#commitPendingOwnSizeMeasurements();
	  }
	  #commitPendingOwnSizeMeasurements() {
	    if (!this.#pendingOwnSizePostNumbers.size) return;
	    const pending = [...this.#pendingOwnSizePostNumbers];
	    this.#pendingOwnSizePostNumbers.clear();
	    let changed = !1;
	    for (const postNumber of pending) {
	      const view = this.domOwner.view(postNumber), sample = this.#ownSizeSamples.get(postNumber);
	      !view?.slots.root.isConnected || view.slots.root.classList.contains("ldp-virtual-ancestor-shell") || sample?.root === void 0 || sample.replyTree === void 0 || (changed = this.#treeViewport.measureOwnSize(
	        postNumber,
	        Math.max(1, sample.root - sample.replyTree)
	      ) || changed);
	    }
	    changed && this.frame.notifyScroll();
	  }
	  #syncDirectReplyPrefetchCandidates() {
	    const input = this.#scroll.readWindowInput(), rawScreens = Number(this.#readDirectReplyPrefetchScreens()), prefetchScreens = Number.isFinite(rawScreens) ? Math.min(3, Math.max(0, rawScreens)) : 0, beforeScreens = Math.max(
	      prefetchScreens,
	      Number(input.overscanBeforeScreens) || 0
	    ), afterScreens = Math.max(
	      prefetchScreens,
	      Number(input.overscanAfterScreens) || 0
	    ), scrollBucketSize = Math.max(1, input.viewportSize / 2), candidateKey = `${this.replyTreePresentation.revision}|${Math.floor(input.scrollOffset / scrollBucketSize)}|${Math.round(input.viewportSize)}|${beforeScreens}|${afterScreens}`, candidatesChanged = candidateKey !== this.#directReplyPrefetchCandidateKey;
	    if (this.#directReplyPrefetchCandidateKey = candidateKey, candidatesChanged) {
	      const prefetchInput = Object.freeze({
	        scrollOffset: input.scrollOffset,
	        viewportSize: input.viewportSize,
	        overscanBeforeScreens: beforeScreens,
	        overscanAfterScreens: afterScreens
	      }), rootWindow = this.layout.window(prefetchInput), plan = this.#treeViewport.plan(rootWindow, prefetchInput), candidateOffsets = new Map(
	        [...plan.contentPostNumbers].map((postNumber) => [
	          postNumber,
	          this.#treeViewport.offsetOf(postNumber) ?? 0
	        ])
	      );
	      this.#directReplyPrefetchOrderedCandidates = Object.freeze(
	        [...plan.contentPostNumbers].sort((left, right) => (candidateOffsets.get(left) ?? 0) - (candidateOffsets.get(right) ?? 0) || left - right)
	      );
	      const previousCandidates = this.#directReplyPrefetchCandidatePostNumbers;
	      this.#directReplyPrefetchCandidatePostNumbers = new Set(
	        this.#directReplyPrefetchOrderedCandidates
	      );
	      for (const postNumber of previousCandidates)
	        this.#directReplyPrefetchCandidatePostNumbers.has(postNumber) || this.#directReplyPrefetchAttemptedExpectedCounts.delete(postNumber);
	    }
	    const visiblePostNumbers = new Set(
	      this.#treeViewport.visiblePostNumbers.filter(
	        (postNumber) => this.#directReplyPrefetchCandidatePostNumbers.has(postNumber)
	      )
	    ), visibleKey = [...visiblePostNumbers].join(","), visibleChanged = visibleKey !== this.#directReplyVisibleKey;
	    visibleChanged && (this.#directReplyVisibleKey = visibleKey), (candidatesChanged || visibleChanged) && this.#abortDirectReplyRequestsOutside(
	      this.#directReplyPrefetchCandidatePostNumbers,
	      visiblePostNumbers
	    ), this.#directReplyVisiblePostNumbers = visiblePostNumbers;
	    const orderedCandidates = [
	      ...this.#directReplyPrefetchOrderedCandidates.filter((postNumber) => visiblePostNumbers.has(postNumber)),
	      ...this.#directReplyPrefetchOrderedCandidates.filter((postNumber) => !visiblePostNumbers.has(postNumber))
	    ], stillQueued = orderedCandidates.filter(
	      (postNumber) => this.#queuedDirectReplyPrefetches.has(postNumber)
	    );
	    this.#queuedDirectReplyPrefetches.clear();
	    for (const postNumber of stillQueued)
	      this.#queuedDirectReplyPrefetches.add(postNumber);
	    if (candidatesChanged || visiblePostNumbers.size > 0)
	      for (const postNumber of orderedCandidates)
	        this.#prefetchDirectReplies(postNumber, !1);
	    this.#queuedDirectReplyPrefetches.size > 0 ? this.#scheduleDirectReplyPrefetchFlush() : this.#directReplyPrefetchHandle !== null && (this.#directReplyPrefetchScheduler.cancel(
	      this.#directReplyPrefetchHandle
	    ), this.#directReplyPrefetchHandle = null), this.#syncDirectReplyLoadingIndicators();
	  }
	  #prefetchDirectReplies(postNumber, schedule = !0) {
	    this.#directReplyPrefetches.has(postNumber) || this.#queuedDirectReplyPrefetches.has(postNumber) || this.scope.destroyed || !this.#directReplyPrefetchRequired(postNumber) || (this.#queuedDirectReplyPrefetches.add(postNumber), this.#syncDirectReplyLoadingIndicators(), schedule && this.#scheduleDirectReplyPrefetchFlush());
	  }
	  #directReplyPrefetchRequired(postNumber) {
	    if (!this.#session.loadDirectReplies) return !1;
	    const expectedCount = Number(
	      this.#session.postByNumber(postNumber)?.reply_count ?? 0
	    );
	    return Number.isSafeInteger(expectedCount) && expectedCount > 0 && Math.max(
	      this.#directReplyPrefetchedExpectedCounts.get(postNumber) ?? 0,
	      this.#directReplyPrefetchAttemptedExpectedCounts.get(postNumber) ?? 0
	    ) < expectedCount;
	  }
	  #scheduleDirectReplyPrefetchFlush(minimumDelayMs = 0) {
	    this.scope.destroyed || this.#queuedDirectReplyPrefetches.size === 0 || (this.#directReplyPrefetchHandle !== null && this.#directReplyPrefetchScheduler.cancel(
	      this.#directReplyPrefetchHandle
	    ), this.#directReplyPrefetchHandle = this.#directReplyPrefetchScheduler.schedule(() => {
	      this.#directReplyPrefetchHandle = null, this.#flushDirectReplyPrefetch();
	    }, Math.max(0, minimumDelayMs)));
	  }
	  #flushDirectReplyPrefetch() {
	    if (this.scope.destroyed) return;
	    for (const candidate of this.#queuedDirectReplyPrefetches)
	      this.#directReplyPrefetchCandidatePostNumbers.has(candidate) || this.#queuedDirectReplyPrefetches.delete(candidate);
	    let visiblePostNumber = [...this.#queuedDirectReplyPrefetches].find(
	      (candidate) => this.#directReplyVisiblePostNumbers.has(candidate)
	    );
	    for (; visiblePostNumber !== void 0 && this.#directReplyPrefetches.size < DIRECT_REPLY_VISIBLE_PIPELINE_DEPTH; )
	      this.#startDirectReplyRequest(visiblePostNumber, "visible"), visiblePostNumber = [...this.#queuedDirectReplyPrefetches].find(
	        (candidate) => this.#directReplyVisiblePostNumbers.has(candidate)
	      );
	    if (visiblePostNumber !== void 0 || [...this.#directReplyPrefetches.values()].some(
	      (request) => request.lane === "visible"
	    )) {
	      this.#syncDirectReplyLoadingIndicators();
	      return;
	    }
	    const configuredConcurrency = Number(
	      this.#readDirectReplyPrefetchConcurrency()
	    ), nearbyDepth = Math.min(
	      DIRECT_REPLY_NEARBY_PIPELINE_DEPTH,
	      Math.max(
	        1,
	        Number.isFinite(configuredConcurrency) ? Math.round(configuredConcurrency) : 1
	      )
	    );
	    let nearbyCount = [...this.#directReplyPrefetches.values()].filter(
	      (request) => request.lane === "nearby"
	    ).length, postNumber = [...this.#queuedDirectReplyPrefetches].find(
	      (candidate) => this.#directReplyPrefetchCandidatePostNumbers.has(candidate)
	    );
	    for (; postNumber !== void 0 && nearbyCount < nearbyDepth; )
	      this.#startDirectReplyRequest(postNumber, "nearby"), nearbyCount += 1, postNumber = [...this.#queuedDirectReplyPrefetches].find(
	        (candidate) => this.#directReplyPrefetchCandidatePostNumbers.has(candidate)
	      );
	    nearbyCount === 0 && this.#syncDirectReplyLoadingIndicators();
	  }
	  #startDirectReplyRequest(postNumber, lane) {
	    this.#queuedDirectReplyPrefetches.delete(postNumber);
	    const loadDirectReplies = this.#session.loadDirectReplies, post = this.#session.postByNumber(postNumber), expectedCount = Number(post?.reply_count ?? 0);
	    if (!loadDirectReplies || !Number.isSafeInteger(expectedCount) || expectedCount <= 0) {
	      this.#syncDirectReplyLoadingIndicators(), this.#scheduleDirectReplyPrefetchFlush();
	      return;
	    }
	    const request = Object.freeze({
	      lane,
	      controller: new AbortController()
	    });
	    this.#directReplyPrefetches.set(postNumber, request), this.#syncDirectReplyLoadingIndicators(), loadDirectReplies.call(this.#session, postNumber, {
	      background: lane === "nearby",
	      expectedCount,
	      maxPages: DIRECT_REPLY_PREFETCH_MAX_PAGES,
	      signal: request.controller.signal
	    }).then((result) => {
	      if (this.scope.destroyed || this.#directReplyPrefetches.get(postNumber) !== request) return;
	      this.#directReplyPrefetches.delete(postNumber), this.#syncDirectReplyLoadingIndicators();
	      const observedExpectedCount = Math.max(
	        expectedCount,
	        result.expectedCount
	      );
	      this.#directReplyPrefetchAttemptedExpectedCounts.set(
	        postNumber,
	        observedExpectedCount
	      ), result.complete && this.#directReplyPrefetchedExpectedCounts.set(
	        postNumber,
	        observedExpectedCount
	      ), this.frame.notifyScroll();
	    }).catch((error) => {
	      isAbortFailure(error) || this.#onError(error);
	    }).finally(() => {
	      this.#directReplyPrefetches.get(postNumber) === request && this.#directReplyPrefetches.delete(postNumber), this.#syncDirectReplyLoadingIndicators(), this.#scheduleDirectReplyPrefetchFlush();
	    });
	  }
	  #abortDirectReplyRequestsOutside(candidatePostNumbers, visiblePostNumbers) {
	    const visibleRequestPending = [...visiblePostNumbers].some(
	      (postNumber) => this.#directReplyPrefetchRequired(postNumber)
	    );
	    for (const [postNumber, request] of this.#directReplyPrefetches) {
	      if (visibleRequestPending) {
	        if (visiblePostNumbers.has(postNumber) && request.lane === "visible") continue;
	      } else if (candidatePostNumbers.has(postNumber))
	        continue;
	      this.#directReplyPrefetches.delete(postNumber), request.controller.signal.aborted || request.controller.abort(new DOMException(
	        visiblePostNumbers.has(postNumber) ? `树状回复 #${postNumber} 升级为可见快车道` : candidatePostNumbers.has(postNumber) ? `树状回复 #${postNumber} 为可见快车道让位` : `树状回复 #${postNumber} 已滚出当前视口`,
	        "AbortError"
	      ));
	    }
	  }
	  #syncDirectReplyLoadingIndicators() {
	    for (const view of this.domOwner.views()) {
	      const active = this.#directReplyPrefetches.has(view.postNumber), queued = this.#queuedDirectReplyPrefetches.has(view.postNumber), existing = Array.from(view.slots.replyControls.children).find(
	        (child) => child.classList.contains("ldp-direct-reply-loading")
	      );
	      if (!active && !queued) {
	        existing?.remove(), view.slots.replyTree.removeAttribute("aria-busy");
	        continue;
	      }
	      const indicator = existing ?? view.slots.root.ownerDocument.createElement("div");
	      existing || (indicator.className = "ldp-direct-reply-loading", indicator.setAttribute("role", "status"), indicator.setAttribute("aria-live", "polite"), view.slots.replyControls.append(indicator)), indicator.dataset.state = active ? "loading" : "queued", indicator.textContent = active ? "正在优先加载此处树状回复…" : "此处还有树状回复,等待加载…", view.slots.replyTree.setAttribute("aria-busy", "true");
	    }
	  }
	  #activateBranch(root, postNumber) {
	    this.postProjector.attach(root, postNumber, "branch");
	  }
	  #deactivateBranch(root, postNumber) {
	    this.postProjector.detach(root, postNumber, "branch");
	  }
	  #scheduleBranchPaint() {
	    this.scope.destroyed || (this.#branchPaintGeneration += 1, this.streamView.slots.rootList.classList.add(
	      "ldp-branch-paint-pending"
	    ), this.#branchPaintHandle === null && this.#queueBranchPaintStabilityCheck());
	  }
	  #queueBranchPaintStabilityCheck() {
	    const generation = this.#branchPaintGeneration;
	    this.#branchPaintHandle = this.#branchFrames.request(() => {
	      this.#branchPaintHandle = null, !this.scope.destroyed && (this.branchOverlay.paint(), this.streamView.slots.rootList.classList.remove(
	        "ldp-branch-paint-pending"
	      ), generation !== this.#branchPaintGeneration && (this.streamView.slots.rootList.classList.add(
	        "ldp-branch-paint-pending"
	      ), this.#queueBranchPaintStabilityCheck()));
	    });
	  }
	  #syncProjectionFeatures() {
	    this.postProjector.syncProjection();
	  }
	  #syncRootObservers(attachedRoots, detachedRoots) {
	    for (const postNumber of detachedRoots) {
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      root && this.#deactivateBranch(root, postNumber), this.#rootObservers.get(postNumber)?.(), this.#rootObservers.delete(postNumber);
	    }
	    for (const postNumber of attachedRoots) {
	      if (this.#rootObservers.has(postNumber)) continue;
	      const root = this.domOwner.view(postNumber)?.slots.root;
	      root && (this.#rootObservers.set(postNumber, this.frame.observeRoot(root)), this.#activateBranch(root, postNumber));
	    }
	  }
	  #assertActive() {
	    if (this.#destroyed || this.scope.destroyed)
	      throw new Error("ReaderTopicDomCoordinator 已销毁");
	  }
	}
}, "7e26da489b8db6407538fdca6762ab5a4583418dcdbea13558b8ac3395c7ae52");

/* Source: lite/src/topic/reader-topic-edit-controller.ts */
runtime.register("src/topic/reader-topic-edit-controller.js", function(module, exports, require) {
	var reader_topic_edit_controller_exports = {};
	__export(reader_topic_edit_controller_exports, {
	  ReaderTopicEditController: () => ReaderTopicEditController
	});
	module.exports = __toCommonJS(reader_topic_edit_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_value_record = require("../kernel/value-record.js"), import_topic_action_feature_commands = require("../post/topic-action-feature-commands.js");
	function text(value) {
	  return String(value ?? "").trim();
	}
	function topicCanEdit(value) {
	  return (0, import_value_record.valueRecord)((0, import_value_record.valueRecord)(value)?.details)?.can_edit === !0;
	}
	function categoryName(value) {
	  return text(value).replace(/\s*[,,]\s*Lv\s*\d+\s*$/i, "").trim();
	}
	function categoryLevel(value) {
	  const match = text(value).match(/(?:^|[,,]\s*)Lv\s*(\d+)\s*$/i);
	  return match?.[1] ? `Lv${match[1]}` : "";
	}
	function topicTags(value) {
	  const tags = (0, import_value_record.valueRecord)(value)?.tags;
	  if (!Array.isArray(tags)) return Object.freeze([]);
	  const byName = /* @__PURE__ */ new Map();
	  for (const value2 of tags) {
	    const source = (0, import_value_record.valueRecord)(value2), name = text(source?.name ?? source?.text ?? value2);
	    if (!name) continue;
	    const id = Number(source?.id);
	    byName.set(name.toLocaleLowerCase(), Object.freeze({
	      id: Number.isSafeInteger(id) && id > 0 ? id : null,
	      name
	    }));
	  }
	  return Object.freeze([...byName.values()]);
	}
	class ReaderTopicEditController {
	  topicId;
	  scope;
	  #session;
	  #trigger;
	  #form;
	  #catalog;
	  #actions;
	  #commands;
	  #descriptors;
	  #models;
	  #onError;
	  #notify;
	  #abort = new AbortController();
	  #opening = null;
	  constructor(options) {
	    this.topicId = (0, import_identifiers.discourseTopicId)(options.topicId), this.#session = options.session, this.#trigger = options.trigger, this.#form = options.form, this.#catalog = options.catalog, this.#actions = options.actions, this.#commands = new import_topic_action_feature_commands.TopicActionFeatureCommands({
	      topicId: this.topicId,
	      session: this.#session
	    }), this.#descriptors = options.descriptors, this.#models = options.models, this.#onError = options.onError ?? (() => {
	    }), this.#notify = options.notify ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.add(() => {
	      this.#abort.abort(new Error("Reader Topic 编辑生命周期已结束")), this.#trigger.hidden = !0, this.#trigger.setAttribute("aria-expanded", "false");
	    }), this.scope.listen(this.#trigger, "click", () => {
	      this.open().catch((error) => this.#report(error));
	    }), this.#session.changes.subscribe(() => this.sync(), this.scope), this.sync();
	  }
	  sync() {
	    const canEdit = topicCanEdit(this.#session.topic);
	    this.#trigger.hidden = !canEdit, this.#trigger.disabled = !canEdit, canEdit || this.#trigger.setAttribute("aria-expanded", "false");
	  }
	  open() {
	    if (this.#opening) return this.#opening;
	    const topic = this.#topic();
	    if (!topicCanEdit(topic))
	      return Promise.reject(new Error("当前账号没有编辑该帖子的权限"));
	    const categories = [...this.#catalog.categories()], categoryId = Number(topic.category_id ?? topic.categoryId);
	    Number.isSafeInteger(categoryId) && categoryId > 0 && !categories.some((category) => category.id === categoryId) && categories.unshift(Object.freeze({
	      id: categoryId,
	      name: categoryName(
	        topic.category_name ?? topic.categoryName ?? "当前类别"
	      ) || "当前类别",
	      slug: text(topic.category_slug ?? topic.categorySlug),
	      color: "",
	      parentCategoryId: null
	    })), this.#trigger.setAttribute("aria-expanded", "true");
	    const opening = this.#form.open({
	      title: text(topic.title ?? topic.fancy_title),
	      categoryId: Number.isSafeInteger(categoryId) && categoryId > 0 ? categoryId : 0,
	      tags: topicTags(topic),
	      categories: Object.freeze(categories),
	      signal: this.#abort.signal,
	      searchTags: (input) => this.#catalog.searchTags(input),
	      submit: (submission) => this.#submit(submission)
	    }).finally(() => {
	      this.#opening === opening && (this.#opening = null), this.#trigger.setAttribute("aria-expanded", "false");
	    });
	    return this.#opening = opening, opening;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #submit(submission) {
	    const topic = this.#topic();
	    if (!topicCanEdit(topic))
	      throw new Error("当前账号没有编辑该帖子的权限");
	    const nativeFields = Object.freeze({
	      title: submission.title,
	      category_id: submission.category.id,
	      tags: Object.freeze(submission.tags.map((tag) => Object.freeze({
	        name: tag.name,
	        ...tag.id ? { id: tag.id } : {}
	      })))
	    }), canonicalFields = Object.freeze({
	      title: submission.title,
	      category_id: submission.category.id,
	      category_name: categoryName(submission.category.name),
	      category_level: categoryLevel(submission.category.name),
	      category_slug: submission.category.slug,
	      tags: Object.freeze(submission.tags.map((tag) => tag.name))
	    });
	    await this.#actions.dispatch(this.#commands.edit(
	      canonicalFields,
	      this.#descriptors.topicEdit({
	        topicId: this.topicId,
	        topic: this.#models.createTopic(topic),
	        changedFields: nativeFields
	      })
	    )), this.#notify("帖子信息已更新");
	  }
	  #topic() {
	    const topic = this.#session.topic;
	    if (!topic) throw new Error("canonical Topic 尚未加载");
	    if ((0, import_identifiers.discourseTopicId)(topic.id) !== this.topicId)
	      throw new Error("Topic 编辑目标与当前会话不一致");
	    return topic;
	  }
	  #report(error) {
	    try {
	      this.#onError(error);
	    } catch {
	    }
	  }
	}
}, "f4a30971ecaa7f26c1382c1cc09a7d2ed29c1e375c2dc45e58bfcda8ea5d5547");

/* Source: lite/src/topic/reader-topic-factory.ts */
runtime.register("src/topic/reader-topic-factory.js", function(module, exports, require) {
	var reader_topic_factory_exports = {};
	__export(reader_topic_factory_exports, {
	  createReaderTopicFactory: () => createReaderTopicFactory
	});
	module.exports = __toCommonJS(reader_topic_factory_exports);
	var import_reader_topic_dom_coordinator = require("./reader-topic-dom-coordinator.js");
	function throwIfAborted(signal) {
	  if (signal.aborted) throw signal.reason;
	}
	function createTopicLifetimeContext(context) {
	  const controller = context.scope.abortController(
	    new DOMException(
	      `Topic ${context.topicId} 生命周期已结束`,
	      "AbortError"
	    ),
	    context.signal
	  );
	  return Object.freeze({
	    ...context,
	    signal: controller.signal
	  });
	}
	function createReaderTopicFactory(options) {
	  return async (context) => {
	    throwIfAborted(context.signal);
	    const topicContext = createTopicLifetimeContext(context);
	    options.onPhase?.("prepare", topicContext);
	    const bundle = await options.createBundle(topicContext);
	    bundle.cleanup && topicContext.scope.add(bundle.cleanup), throwIfAborted(topicContext.signal);
	    const root = options.createRoot?.(topicContext, options.document) ?? options.document.createElement("section");
	    if (root.isConnected || root.parentNode)
	      throw new Error("Topic root 在 mount 前必须是 detached");
	    root.classList.contains("ldp-topic-runtime") || root.classList.add("ldp-topic-runtime"), root.dataset.topicId = String(topicContext.topicId), topicContext.mount(root);
	    const dom = new import_reader_topic_dom_coordinator.ReaderTopicDomCoordinator({
	      ...options.createDomOptions(bundle, topicContext, root),
	      document: options.document,
	      topicHost: root,
	      session: bundle.session,
	      replies: bundle.replies,
	      parentScope: topicContext.scope
	    }), topic = await dom.initialize();
	    throwIfAborted(topicContext.signal), options.onPhase?.("render", topicContext);
	    const value = Object.freeze({
	      topic,
	      root,
	      session: bundle.session,
	      replies: bundle.replies,
	      dom,
	      services: bundle.services
	    }), assembledCleanup = options.onAssembled?.(value, topicContext);
	    typeof assembledCleanup == "function" && topicContext.scope.add(assembledCleanup), throwIfAborted(topicContext.signal);
	    const activationCleanup = bundle.activate?.();
	    typeof activationCleanup == "function" && topicContext.scope.add(activationCleanup), throwIfAborted(topicContext.signal);
	    const readyCleanup = options.onReady?.(value, topicContext);
	    return typeof readyCleanup == "function" && topicContext.scope.add(readyCleanup), Object.freeze({
	      value,
	      ...bundle.prepareClose ? { prepareClose: bundle.prepareClose } : {}
	    });
	  };
	}
}, "6bfc20f304a3e8ed6007bc10d14ac7cf18298b671f8063a34860556fc2dbc103");

/* Source: lite/src/topic/reader-topic-flow-controller.ts */
runtime.register("src/topic/reader-topic-flow-controller.js", function(module, exports, require) {
	var reader_topic_flow_controller_exports = {};
	__export(reader_topic_flow_controller_exports, {
	  ReaderTopicFlowController: () => ReaderTopicFlowController
	});
	module.exports = __toCommonJS(reader_topic_flow_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	const browserScheduler = Object.freeze({
	  schedule(callback, urgency, delayMs = urgency === "near-window" ? 0 : 600) {
	    return window.setTimeout(callback, delayMs);
	  },
	  cancel(handle) {
	    window.clearTimeout(handle);
	  }
	}), BACKGROUND_PREFETCH_DELAY_MS = 360;
	class ReaderTopicFlowController {
	  scope;
	  #dom;
	  #readPerformance;
	  #scheduler;
	  #onError;
	  #readLoadDone;
	  #scheduledHandle = null;
	  #scheduledUrgency = null;
	  #running = !1;
	  #rerun = !1;
	  #done = !1;
	  #projectionPriority = !1;
	  #retryCount = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#dom = options.dom, this.#readPerformance = options.readPerformance, this.#scheduler = options.scheduler ?? browserScheduler, this.#onError = options.onError ?? (() => {
	    }), this.#readLoadDone = options.readLoadDone ?? (() => {
	    }), this.#done = this.#readLoadDone() === !0, this.#syncStatus(!1), this.#dom.windowChanges.subscribe(() => {
	      this.#done || this.#queue(this.#urgency());
	    }, this.scope), options.sessionChanges?.subscribe((commit) => {
	      commit.streamChanged && this.#readLoadDone() !== !0 && (this.#done = !1, this.#retryCount = 0, this.#syncStatus(!1), this.#queue(this.#urgency()));
	    }, this.scope), this.scope.add(() => {
	      this.#scheduledHandle !== null && this.#scheduler.cancel(this.#scheduledHandle), this.#scheduledHandle = null, this.#scheduledUrgency = null;
	    }), this.#done || this.#queue(this.#urgency());
	  }
	  refreshPerformance() {
	    this.scope.destroyed || (this.#dom.flushNow(), !this.#done && this.#queue(this.#urgency(), !0));
	  }
	  /**
	   * 需要完整 Topic 投影的功能(目前为“只看楼主”)只提升 canonical Flow,
	   * 不创建第二个 cursor、帖子缓存或请求循环。
	   */
	  setProjectionPriority(enabled) {
	    this.scope.destroyed || this.#projectionPriority === enabled || (this.#projectionPriority = enabled, this.#dom.flushNow(), this.#done || this.#queue(this.#urgency(), !0));
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #urgency() {
	    if (this.#projectionPriority || this.#dom.hasVisibleDataGap?.() === !0) return "near-window";
	    const commit = this.#dom.frame.lastCommit;
	    if (!commit) return "near-window";
	    const input = this.#dom.readWindowInput(), forwardScreens = Math.max(
	      this.#readPerformance().nestedPrefetchScreens,
	      Number(input.overscanAfterScreens) || 0
	    ), horizon = input.viewportSize * forwardScreens;
	    return commit.window.afterSpacer <= horizon ? "near-window" : "background";
	  }
	  #queue(urgency, replace = !1, delayOverrideMs) {
	    if (this.scope.destroyed || this.#done) return;
	    if (this.#running) {
	      this.#rerun = !0;
	      return;
	    }
	    if (this.#scheduledHandle !== null) {
	      const promote = this.#scheduledUrgency === "background" && urgency === "near-window";
	      if (!replace && !promote) return;
	      this.#scheduler.cancel(this.#scheduledHandle);
	    }
	    this.#scheduledUrgency = urgency;
	    const delayMs = delayOverrideMs ?? (urgency === "near-window" ? 0 : Math.max(
	      BACKGROUND_PREFETCH_DELAY_MS,
	      this.#readPerformance().requestMinIntervalMs * 3
	    ));
	    this.#scheduledHandle = this.#scheduler.schedule(() => {
	      this.#scheduledHandle = null, this.#scheduledUrgency = null, this.#run();
	    }, urgency, delayMs);
	  }
	  async #run() {
	    if (this.scope.destroyed || this.#done || this.#running) return;
	    this.#running = !0, this.#rerun = !1, this.#syncStatus(!0);
	    const urgency = this.#urgency(), visibleDataGap = this.#dom.hasVisibleDataGap?.() === !0;
	    let source = null, retryDelayMs = null;
	    try {
	      const load = this.#dom.loadNext({
	        background: urgency === "background",
	        priority: visibleDataGap ? "nested" : "visible",
	        maxAttempts: urgency === "near-window" ? 2 : 1,
	        onSource(nextSource) {
	          source = nextSource;
	        }
	      }), aheadBatchCount = urgency === "near-window" ? Math.min(
	        2,
	        Math.max(0, this.#readPerformance().requestMaxConcurrent - 1)
	      ) : 0;
	      source !== null && aheadBatchCount > 0 && this.#dom.prefetchAhead && this.#dom.prefetchAhead(aheadBatchCount).catch(() => {
	      });
	      const result = await load;
	      if (this.scope.destroyed || (this.#done = this.#readLoadDone() ?? result.done, this.#dom.flushNow(), this.#syncStatus(!1), result.fatal)) return;
	      if (result.retry) {
	        this.#retryCount += 1;
	        const baseDelayMs = Math.max(
	          600,
	          this.#readPerformance().requestMinIntervalMs * 4
	        );
	        retryDelayMs = Math.min(
	          3e4,
	          baseDelayMs * 2 ** Math.min(5, this.#retryCount - 1)
	        );
	        return;
	      }
	      if (this.#retryCount = 0, !this.#done) {
	        const nextUrgency = this.#urgency();
	        nextUrgency === "near-window" && this.#queue(nextUrgency);
	      }
	    } catch (error) {
	      this.scope.destroyed || (this.#syncStatus(!1), this.#onError(error));
	    } finally {
	      if (this.#running = !1, retryDelayMs !== null && !this.#done && !this.scope.destroyed)
	        this.#queue(this.#urgency(), !0, retryDelayMs);
	      else if (this.#rerun && !this.#done && !this.scope.destroyed) {
	        const nextUrgency = this.#urgency();
	        nextUrgency === "near-window" && this.#queue(nextUrgency, !0);
	      }
	    }
	  }
	  #syncStatus(loading) {
	    this.#dom.setFlowStatus?.(Object.freeze({
	      loading,
	      done: !loading && this.#done
	    }));
	  }
	}
}, "b021a1321ba88e7ef0d78bea31f51015e445b13a19ef352ab7896a8f6f03514a");

/* Source: lite/src/topic/reader-topic-header.ts */
runtime.register("src/topic/reader-topic-header.js", function(module, exports, require) {
	var reader_topic_header_exports = {};
	__export(reader_topic_header_exports, {
	  ReaderTopicHeaderController: () => ReaderTopicHeaderController,
	  ReaderTopicHeaderView: () => ReaderTopicHeaderView,
	  clearReaderTopicHostIdentityCache: () => clearReaderTopicHostIdentityCache,
	  normalizeReaderTopicHeader: () => normalizeReaderTopicHeader,
	  readReaderTopicHostIconMetadata: () => readReaderTopicHostIconMetadata,
	  readerTopicHostIdentityCacheStats: () => readerTopicHostIdentityCacheStats,
	  readerTopicOwnerUsername: () => readerTopicOwnerUsername
	});
	module.exports = __toCommonJS(reader_topic_header_exports);
	var import_reader_icon = require("../components/reader-icon.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_value_record = require("../kernel/value-record.js");
	const EMPTY_RECORD = Object.freeze({});
	function text(value) {
	  return String(value ?? "").trim();
	}
	function count(value) {
	  if (value == null || value === "") return null;
	  const numeric = Number(value);
	  return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
	}
	function categoryLevel(value) {
	  const match = text(value).match(/(?:^|[,,]\s*)Lv\s*(\d+)\s*$/i);
	  return match?.[1] ? `Lv${match[1]}` : "";
	}
	function categoryName(value) {
	  return text(value).replace(/\s*[,,]\s*Lv\s*\d+\s*$/i, "").trim();
	}
	function safeIconName(value) {
	  const normalized = text(value).toLowerCase().replace(/^fa-/, "");
	  return normalized.length <= 80 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalized) ? normalized : "";
	}
	function topicTags(topic) {
	  if (!Array.isArray(topic.tags)) return Object.freeze([]);
	  const tags = topic.tags.map((value) => {
	    const source = (0, import_value_record.valueRecord)(value);
	    return Object.freeze({
	      name: text(source?.name ?? source?.id ?? value),
	      icon: safeIconName(
	        source?.icon ?? source?.icon_name ?? source?.iconName
	      )
	    });
	  }).filter((tag) => !!tag.name), byName = /* @__PURE__ */ new Map();
	  for (const tag of tags) {
	    const previous = byName.get(tag.name);
	    (!previous || !previous.icon && tag.icon) && byName.set(tag.name, tag);
	  }
	  return Object.freeze([...byName.values()]);
	}
	function readerTopicOwnerUsername(topicValue, posts = []) {
	  const topic = (0, import_value_record.valueRecord)(topicValue) ?? EMPTY_RECORD, details = (0, import_value_record.valueRecord)(topic.details), createdBy = (0, import_value_record.valueRecord)(details?.created_by), firstPost = posts.map((post) => (0, import_value_record.valueRecord)(post)).find((post) => Number(post?.post_number) === 1);
	  return text(
	    topic._opUsername || createdBy?.username || firstPost?.username || topic.original_poster_username
	  );
	}
	function normalizeReaderTopicHeader(topicValue, posts, presentation, fallbackTopicId = 0) {
	  const topic = (0, import_value_record.valueRecord)(topicValue) ?? EMPTY_RECORD, category = (0, import_value_record.valueRecord)(topic.category) ?? EMPTY_RECORD, owner = readerTopicOwnerUsername(topic, posts), categoryId = count(
	    topic.category_id ?? topic.categoryId ?? category.id
	  ) ?? 0, rawCategoryName = topic.category_name ?? topic.categoryName ?? category.name ?? topic.category_slug ?? category.slug ?? presentation.categoryName?.(categoryId), normalizedCategoryName = categoryName(rawCategoryName), level = [
	    topic.category_level,
	    topic.categoryLevel,
	    rawCategoryName
	  ].map(categoryLevel).find(Boolean) ?? "", tags = topicTags(topic).map((tag) => Object.freeze({
	    name: tag.name,
	    icon: tag.icon,
	    href: presentation.tagHref(tag.name)
	  })), voteCount = count(topic.vote_count) ?? 0, voted = topic.user_voted === !0, canVote = topic.can_vote === !0 || voted, hasVoteCapability = ["can_vote", "user_voted", "vote_count"].some((key) => Object.hasOwn(topic, key)), stats = [], postsCount = count(topic.posts_count), views = count(topic.views), likes = count(topic.like_count), participants = count(topic.participant_count);
	  return postsCount !== null && stats.push(`${postsCount} 帖`), views !== null && stats.push(`${views} 浏览`), likes !== null && stats.push(`${likes} 赞`), participants !== null && stats.push(`${participants} 用户`), Object.freeze({
	    topicId: count(topic.id) ?? count(fallbackTopicId) ?? 0,
	    categoryId,
	    title: text(topic.title ?? topic.fancy_title) || "未命名主题",
	    ownerUsername: owner,
	    ownerHref: presentation.userHref(owner),
	    statsText: stats.join(" · ") || "主题信息暂不可用",
	    category: normalizedCategoryName ? Object.freeze({
	      id: categoryId,
	      name: normalizedCategoryName,
	      level,
	      icon: safeIconName(
	        topic.category_icon ?? topic.categoryIcon ?? category.icon ?? category.icon_name ?? category.iconName ?? presentation.categoryIcon?.(categoryId)
	      ),
	      href: presentation.categoryHref(categoryId)
	    }) : null,
	    tags: Object.freeze(tags),
	    vote: hasVoteCapability && (canVote || voteCount > 0) ? Object.freeze({ count: voteCount, voted, canVote }) : null
	  });
	}
	class ReaderTopicHeaderController {
	  scope;
	  changes = new import_signal.Signal();
	  #session;
	  #presentation;
	  #topicId;
	  #onError;
	  #snapshot;
	  constructor(options) {
	    this.#session = options.session, this.#presentation = options.presentation, this.#topicId = count(options.session.topicId) ?? 0, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#snapshot = normalizeReaderTopicHeader(
	      this.#session.topic,
	      this.#session.cachedPosts(),
	      this.#presentation,
	      this.#topicId
	    ), this.#session.changes.subscribe(() => this.refresh(), this.scope), this.scope.add(() => this.changes.clear());
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  refresh() {
	    const next = normalizeReaderTopicHeader(
	      this.#session.topic,
	      this.#session.cachedPosts(),
	      this.#presentation,
	      this.#topicId
	    );
	    if (JSON.stringify(next) === JSON.stringify(this.#snapshot))
	      return this.#snapshot;
	    this.#snapshot = next;
	    for (const error of this.changes.emit(next)) this.#onError(error);
	    return next;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	}
	function appendLinkedIdentity(document, parent, className, label, href, icon, renderIcon, hostIcon = null) {
	  const node = document.createElement(href ? "a" : "span");
	  node.className = className, node.tagName === "A" && href && node.setAttribute("href", href);
	  const fallbackIcon = className.includes("ldp-topic-category") ? "code" : "tag", hostIconName = safeIconName(
	    hostIcon?.querySelector("use")?.getAttribute("href")?.replace(/^#/, "")
	  ), requestedIcon = icon || hostIconName;
	  if (requestedIcon) {
	    const rendered = (0, import_reader_icon.renderReaderIcon)(document, requestedIcon, renderIcon), renderedElement = rendered.nodeType === 1 ? rendered : null, unresolved = renderedElement?.matches(
	      "[data-reader-icon-fallback-for]"
	    ) || renderedElement?.querySelector(
	      "[data-reader-icon-fallback-for]"
	    );
	    node.append(
	      unresolved ? (0, import_reader_icon.createReaderIcon)(document, fallbackIcon) : rendered
	    );
	  } else hostIcon && hostIcon.tagName.toLowerCase() === "img" ? node.append(hostIcon) : node.append((0, import_reader_icon.createReaderIcon)(document, fallbackIcon));
	  const textNode = document.createElement("span");
	  return textNode.className = "ldp-topic-tag-text", textNode.textContent = label, node.append(textNode), parent.append(node), node;
	}
	const SAFE_ICON_FRAGMENT = /^#[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/, HOST_IDENTITY_CACHE = /* @__PURE__ */ new WeakMap(), HOST_IDENTITY_INDEX_LIMIT = 128;
	function readerTopicHostIdentityCacheStats(document) {
	  const cache = HOST_IDENTITY_CACHE.get(document);
	  return Object.freeze({
	    categoryEntries: cache ? (/* @__PURE__ */ new Set([
	      ...cache.categoryIcons.keys(),
	      ...cache.categoryHrefs.keys()
	    ])).size : 0,
	    tagEntries: cache ? (/* @__PURE__ */ new Set([
	      ...cache.tagIcons.keys(),
	      ...cache.tagHrefs.keys()
	    ])).size : 0,
	    indexLimit: HOST_IDENTITY_INDEX_LIMIT
	  });
	}
	function clearReaderTopicHostIdentityCache(document) {
	  HOST_IDENTITY_CACHE.delete(document);
	}
	function rememberHostIdentity(map, key, value) {
	  for (map.delete(key), map.set(key, value); map.size > HOST_IDENTITY_INDEX_LIMIT; ) {
	    const oldest = map.keys().next().value;
	    if (oldest === void 0) break;
	    map.delete(oldest);
	  }
	}
	function cachedHostIdentity(map, key) {
	  const value = map.get(key);
	  if (value !== void 0)
	    return map.delete(key), map.set(key, value), value;
	}
	function hostIdentityCache(document) {
	  let cache = HOST_IDENTITY_CACHE.get(document);
	  return cache || (cache = {
	    categoryIcons: /* @__PURE__ */ new Map(),
	    categoryHrefs: /* @__PURE__ */ new Map(),
	    tagIcons: /* @__PURE__ */ new Map(),
	    tagHrefs: /* @__PURE__ */ new Map()
	  }, HOST_IDENTITY_CACHE.set(document, cache)), cache;
	}
	function categoryCacheKeys(id, name) {
	  return Object.freeze([
	    ...id > 0 ? [`id:${id}`] : [],
	    ...name ? [`name:${name}`] : []
	  ]);
	}
	function cachedCategoryValue(values, id, name) {
	  for (const key of categoryCacheKeys(id, name)) {
	    const value = cachedHostIdentity(values, key);
	    if (value !== void 0) return value;
	  }
	  return null;
	}
	function cloneHostIdentityIcon(link) {
	  if (!link) return null;
	  const source = [...link.querySelectorAll("svg,img")].find((candidate) => candidate.closest("a") === link);
	  if (!source) return null;
	  const clone = source.cloneNode(!0);
	  clone.querySelectorAll("script,foreignObject").forEach((node) => node.remove());
	  for (const node of [clone, ...clone.querySelectorAll("*")])
	    for (const attribute of [...node.attributes])
	      /^on/i.test(attribute.name) && node.removeAttribute(attribute.name);
	  if (clone.tagName.toLowerCase() === "svg")
	    for (const use of clone.querySelectorAll("use")) {
	      const href = use.getAttribute("href") ?? use.getAttribute("xlink:href") ?? "";
	      if (!SAFE_ICON_FRAGMENT.test(href)) return null;
	    }
	  else {
	    const sourceUrl = clone.getAttribute("src") ?? "";
	    if (/^\s*javascript:/i.test(sourceUrl)) return null;
	    clone.removeAttribute("srcset"), clone.setAttribute("alt", "");
	  }
	  return clone.setAttribute("aria-hidden", "true"), clone;
	}
	function hostTopicRouteId(document) {
	  const pathname = document.defaultView?.location?.pathname ?? "";
	  return Number(
	    pathname.match(/\/t\/(?:[^/]+\/)?(\d+)(?:\/|$)/)?.[1] ?? 0
	  );
	}
	const HOST_TOPIC_SOURCE_ROOT_SELECTOR = "tr.topic-list-item,.topic-list-item,.latest-topic-list-item,.search-result-topic,.fps-result,.category-topic-link";
	function hostSourceTopicId(root) {
	  const direct = Number(
	    root.getAttribute("data-topic-id") ?? root.dataset.topicId ?? 0
	  );
	  if (Number.isSafeInteger(direct) && direct > 0) return direct;
	  const href = root.querySelector('a[href*="/t/"]')?.getAttribute("href") ?? "";
	  return Number(
	    href.match(/\/t\/(?:[^/]+\/)?(\d+)(?:\/|$)/)?.[1] ?? 0
	  );
	}
	function readReaderTopicHostIconMetadata(document, snapshot) {
	  const cache = hostIdentityCache(document), tagIcons = /* @__PURE__ */ new Map(), tagHrefs = /* @__PURE__ */ new Map(), empty = () => Object.freeze({
	    categoryIcon: null,
	    categoryId: 0,
	    categoryName: "",
	    categoryLevel: "",
	    categoryHref: "",
	    tagIcons,
	    tagHrefs
	  }), routeTopicId = hostTopicRouteId(document);
	  let root = routeTopicId > 0 && (!snapshot.topicId || routeTopicId === snapshot.topicId) ? [...document.querySelectorAll(
	    "#topic-title,.topic-title,.title-wrapper"
	  )].find((candidate) => !candidate.closest(".ldp-overlay")) ?? null : null;
	  root || (root = [...document.querySelectorAll(
	    HOST_TOPIC_SOURCE_ROOT_SELECTOR
	  )].find(
	    (candidate) => !candidate.closest(".ldp-overlay") && hostSourceTopicId(candidate) === snapshot.topicId
	  ) ?? null);
	  const categorySelector = 'a.badge-category__wrapper,a.badge-category,a.topic-category,a[href^="/c/"],a[href*="/c/"]', categoryLink = root?.querySelector(categorySelector) ?? (snapshot.categoryId > 0 ? [...document.querySelectorAll(categorySelector)].find((candidate) => {
	    const href = candidate.getAttribute("href") ?? "";
	    return (Number(
	      candidate.querySelector(
	        "[data-category-id]"
	      )?.dataset.categoryId
	    ) || Number(
	      href.match(/\/c\/(?:[^/]+\/)*(\d+)(?:\/|$)/)?.[1] ?? 0
	    )) === snapshot.categoryId;
	  }) ?? null : null), tagNames = new Set(snapshot.tags.map((tag) => tag.name));
	  for (const link of (root ?? document).querySelectorAll(
	    "a.discourse-tag,.discourse-tags a,.topic-tags a"
	  )) {
	    const name = text(link.dataset.tagName ?? link.textContent);
	    if (!root && !tagNames.has(name)) continue;
	    const icon = cloneHostIdentityIcon(link), href = link.getAttribute("href") ?? "";
	    name && icon && tagIcons.set(name, icon), name && href && tagHrefs.set(name, href);
	  }
	  for (const [name, icon] of tagIcons)
	    rememberHostIdentity(
	      cache.tagIcons,
	      name,
	      icon.cloneNode(!0)
	    );
	  for (const [name, href] of tagHrefs)
	    rememberHostIdentity(cache.tagHrefs, name, href);
	  for (const tag of snapshot.tags) {
	    if (!tagIcons.has(tag.name)) {
	      const cachedIcon = cachedHostIdentity(cache.tagIcons, tag.name);
	      cachedIcon && tagIcons.set(tag.name, cachedIcon.cloneNode(!0));
	    }
	    if (!tagHrefs.has(tag.name)) {
	      const cachedHref = cachedHostIdentity(cache.tagHrefs, tag.name);
	      cachedHref && tagHrefs.set(tag.name, cachedHref);
	    }
	  }
	  const categoryText = text(
	    categoryLink?.dataset.categoryName ?? categoryLink?.getAttribute("title") ?? categoryLink?.textContent
	  ), liveCategoryHref = categoryLink?.getAttribute("href") ?? "", hrefCategoryId = Number(
	    liveCategoryHref.match(/\/c\/(?:[^/]+\/)*(\d+)(?:\/|$)/)?.[1] ?? 0
	  ), categoryId = Number(categoryLink?.dataset.categoryId) || hrefCategoryId || snapshot.categoryId, normalizedCategoryName = categoryName(categoryText) || snapshot.category?.name || "", liveCategoryIcon = cloneHostIdentityIcon(categoryLink);
	  for (const key of categoryCacheKeys(categoryId, normalizedCategoryName))
	    liveCategoryIcon && rememberHostIdentity(
	      cache.categoryIcons,
	      key,
	      liveCategoryIcon.cloneNode(!0)
	    ), liveCategoryHref && rememberHostIdentity(cache.categoryHrefs, key, liveCategoryHref);
	  const cachedCategoryIcon = cachedCategoryValue(
	    cache.categoryIcons,
	    categoryId,
	    normalizedCategoryName
	  ), categoryIcon = liveCategoryIcon ?? cachedCategoryIcon?.cloneNode(!0) ?? null, categoryHref = liveCategoryHref || cachedCategoryValue(
	    cache.categoryHrefs,
	    categoryId,
	    normalizedCategoryName
	  ) || "";
	  return !root && !categoryLink && !categoryIcon && !categoryHref && !tagIcons.size && !tagHrefs.size ? empty() : Object.freeze({
	    categoryIcon,
	    categoryId,
	    categoryName: normalizedCategoryName,
	    categoryLevel: categoryLevel(categoryText),
	    categoryHref,
	    tagIcons,
	    tagHrefs
	  });
	}
	function hasCompleteHostIdentityIcons(metadata, snapshot) {
	  return (snapshot.category ? !!(snapshot.category.icon || metadata.categoryIcon) : snapshot.categoryId <= 0 || !!(metadata.categoryName && metadata.categoryIcon)) && snapshot.tags.every(
	    (tag) => !!(tag.icon || metadata.tagIcons.get(tag.name))
	  );
	}
	class ReaderTopicHeaderView {
	  scope;
	  #controller;
	  #elements;
	  #onJumpFirst;
	  #onlyOp;
	  #onToggleTopicVote;
	  #renderIcon;
	  #onError;
	  #hostDocument;
	  #topicScroller;
	  #topicTags;
	  #topicVote;
	  #requestFrame;
	  #cancelFrame;
	  #hostMetadataObserver = null;
	  #topicHintFrame = 0;
	  #hostMetadataFrame = 0;
	  #hostMetadataStopTimer = 0;
	  #hostMetadataRetryTimer = 0;
	  #hostMetadataRetryDelay = 80;
	  #topicVotePending = !1;
	  constructor(options) {
	    this.#controller = options.controller, this.#elements = options.elements, this.#onJumpFirst = options.onJumpFirst, this.#onlyOp = options.onlyOp ?? null, this.#onToggleTopicVote = options.onToggleTopicVote ?? null, this.#renderIcon = options.renderIcon ?? null, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const document = this.#elements.topicIdentityHost.ownerDocument, rootHost = this.#elements.topicIdentityHost.getRootNode().host;
	    this.#hostDocument = options.hostDocument ?? rootHost?.ownerDocument ?? document;
	    const defaultView = this.#hostDocument.defaultView ?? document.defaultView;
	    this.#requestFrame = options.requestFrame ?? ((callback) => defaultView?.requestAnimationFrame ? defaultView.requestAnimationFrame(callback) : setTimeout(() => callback(Date.now()), 0)), this.#cancelFrame = options.cancelFrame ?? ((id) => {
	      if (defaultView?.cancelAnimationFrame) {
	        defaultView.cancelAnimationFrame(id);
	        return;
	      }
	      clearTimeout(id);
	    });
	    const leftHint = document.createElement("span");
	    leftHint.className = "ldp-title-topic-scroll-hint ldp-title-topic-scroll-hint-left", leftHint.setAttribute("aria-hidden", "true");
	    const scroller = document.createElement("div");
	    scroller.className = "ldp-title-topic-scroller", scroller.setAttribute("role", "group"), scroller.tabIndex = 0, scroller.setAttribute(
	      "aria-label",
	      "主题分类和标签,滚轮可横向浏览"
	    );
	    const tags = document.createElement("div");
	    tags.className = "ldp-topic-tags";
	    const vote = document.createElement("div");
	    vote.className = "ldp-topic-vote-slot", vote.hidden = !0, scroller.append(tags, vote);
	    const rightHint = document.createElement("span");
	    rightHint.className = "ldp-title-topic-scroll-hint ldp-title-topic-scroll-hint-right", rightHint.setAttribute("aria-hidden", "true"), this.#elements.topicIdentityHost.replaceChildren(
	      leftHint,
	      scroller,
	      rightHint
	    ), this.#topicScroller = scroller, this.#topicTags = tags, this.#topicVote = vote, this.scope.listen(vote, "click", (event) => {
	      const button = event.target?.closest("[data-topic-vote]");
	      !button || button.disabled || (event.preventDefault(), this.#toggleTopicVote());
	    }), this.scope.listen(scroller, "scroll", () => this.#queueTopicHints(), {
	      passive: !0
	    }), this.scope.listen(
	      this.#elements.topicIdentityHost,
	      "wheel",
	      (event) => this.#onTopicWheel(event),
	      { passive: !1 }
	    );
	    const NativeResizeObserver = defaultView?.ResizeObserver, resizeObserver = options.createResizeObserver?.(
	      () => this.#queueTopicHints()
	    ) ?? (NativeResizeObserver ? new NativeResizeObserver(() => this.#queueTopicHints()) : null);
	    resizeObserver && (resizeObserver.observe(this.#elements.topicIdentityHost), resizeObserver.observe(scroller), resizeObserver.observe(tags), this.scope.add(() => resizeObserver.disconnect())), this.scope.add(() => {
	      this.#topicHintFrame && (this.#cancelFrame(this.#topicHintFrame), this.#topicHintFrame = 0), this.#stopHostMetadataHydration();
	    }), this.scope.listen(this.#elements.titleJump, "click", () => {
	      this.#jumpFirst();
	    }), this.scope.listen(this.#elements.titleJump, "keydown", (event) => {
	      const key = event.key;
	      key !== "Enter" && key !== " " || (event.preventDefault(), this.#jumpFirst());
	    }), this.scope.listen(this.#elements.onlyOpToggle, "click", () => {
	      try {
	        this.#onlyOp?.toggle();
	      } catch (error) {
	        this.#onError(error);
	      }
	    }), this.#controller.changes.subscribe(
	      (snapshot) => this.render(snapshot),
	      this.scope
	    ), this.#onlyOp?.changes.subscribe(
	      (snapshot) => this.#renderOnlyOp(snapshot),
	      this.scope
	    ), this.render(this.#controller.snapshot), this.#renderOnlyOp(this.#onlyOp?.snapshot ?? null), this.#startHostMetadataHydration(options.createMutationObserver);
	  }
	  render(snapshot) {
	    const document = this.#elements.titleJump.ownerDocument, hostIcons = readReaderTopicHostIconMetadata(
	      this.#hostDocument,
	      snapshot
	    );
	    this.#elements.titleJump.textContent = snapshot.title, this.#elements.metaStats.textContent = snapshot.statsText, this.#elements.metaOwner.hidden = !snapshot.ownerUsername, this.#elements.metaOwnerValue.textContent = snapshot.ownerUsername ? `@${snapshot.ownerUsername}` : "", snapshot.ownerUsername && snapshot.ownerHref ? (this.#elements.metaOwnerValue.href = snapshot.ownerHref, this.#elements.metaOwnerValue.dataset.userCard = snapshot.ownerUsername, this.#elements.metaOwnerValue.target = "_blank", this.#elements.metaOwnerValue.rel = "noopener") : (this.#elements.metaOwnerValue.removeAttribute("href"), this.#elements.metaOwnerValue.removeAttribute("data-user-card"), this.#elements.metaOwnerValue.removeAttribute("target"), this.#elements.metaOwnerValue.removeAttribute("rel")), this.#topicTags.replaceChildren();
	    const renderedCategory = snapshot.category ? Object.freeze({
	      ...snapshot.category,
	      href: snapshot.category.href || hostIcons.categoryHref
	    }) : hostIcons.categoryName ? Object.freeze({
	      id: hostIcons.categoryId,
	      name: hostIcons.categoryName,
	      level: hostIcons.categoryLevel,
	      icon: "",
	      href: hostIcons.categoryHref
	    }) : null;
	    if (renderedCategory) {
	      const label = renderedCategory.level ? `${renderedCategory.name}, ${renderedCategory.level}` : renderedCategory.name;
	      appendLinkedIdentity(
	        document,
	        this.#topicTags,
	        "ldp-topic-tag ldp-topic-category",
	        label,
	        renderedCategory.href,
	        renderedCategory.icon,
	        this.#renderIcon,
	        hostIcons.categoryIcon
	      );
	    }
	    for (const tag of snapshot.tags)
	      appendLinkedIdentity(
	        document,
	        this.#topicTags,
	        "ldp-topic-tag ldp-topic-label",
	        tag.name,
	        tag.href || hostIcons.tagHrefs.get(tag.name) || "",
	        tag.icon,
	        this.#renderIcon,
	        hostIcons.tagIcons.get(tag.name) ?? null
	      );
	    this.#topicTags.hidden = this.#topicTags.childElementCount === 0, this.#renderTopicVote(snapshot.vote), this.#queueTopicHints(), hasCompleteHostIdentityIcons(hostIcons, snapshot) && !(snapshot.categoryId > 0 && !snapshot.category && !hostIcons.categoryName) && this.#stopHostMetadataHydration();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #startHostMetadataHydration(createMutationObserver) {
	    const refreshed = this.#controller.refresh(), metadata = readReaderTopicHostIconMetadata(
	      this.#hostDocument,
	      refreshed
	    );
	    if (!(refreshed.categoryId > 0 && !refreshed.category && !metadata.categoryName) && hasCompleteHostIdentityIcons(metadata, refreshed)) {
	      this.render(refreshed);
	      return;
	    }
	    const document = this.#hostDocument, root = document.querySelector("#main-outlet,#ember-app") ?? document.body ?? document.documentElement, NativeMutationObserver = document.defaultView?.MutationObserver;
	    if (!root || !createMutationObserver && !NativeMutationObserver) return;
	    const onMutation = (records) => {
	      records.some((record2) => {
	        const target = record2.target;
	        return typeof target.closest != "function" || !target.closest(".ldp-overlay");
	      }) && this.#queueHostMetadataSync();
	    };
	    this.#hostMetadataObserver = createMutationObserver?.(onMutation) ?? (NativeMutationObserver ? new NativeMutationObserver(onMutation) : null), this.#hostMetadataObserver?.observe(root, {
	      childList: !0,
	      subtree: !0
	    }), this.#hostMetadataRetryDelay = 80, this.#queueHostMetadataSync(), this.#hostMetadataStopTimer = document.defaultView?.setTimeout(
	      () => this.#stopHostMetadataHydration(),
	      6e4
	    ) ?? 0;
	  }
	  #queueHostMetadataSync() {
	    this.#hostMetadataFrame || this.scope.destroyed || (this.#hostMetadataFrame = this.#requestFrame(() => {
	      if (this.#hostMetadataFrame = 0, this.scope.destroyed) return;
	      const previous = this.#controller.snapshot, next = this.#controller.refresh();
	      next === previous && this.render(next), this.#hostMetadataObserver && this.#scheduleHostMetadataRetry();
	    }));
	  }
	  #scheduleHostMetadataRetry() {
	    if (this.#hostMetadataRetryTimer || !this.#hostMetadataObserver) return;
	    const window = this.#hostDocument.defaultView;
	    if (!window) return;
	    const delay = this.#hostMetadataRetryDelay;
	    this.#hostMetadataRetryDelay = Math.min(delay * 2, 4e3), this.#hostMetadataRetryTimer = window.setTimeout(() => {
	      this.#hostMetadataRetryTimer = 0, this.#queueHostMetadataSync();
	    }, delay);
	  }
	  #stopHostMetadataHydration() {
	    this.#hostMetadataObserver?.disconnect(), this.#hostMetadataObserver = null, this.#hostMetadataFrame && (this.#cancelFrame(this.#hostMetadataFrame), this.#hostMetadataFrame = 0), this.#hostMetadataStopTimer && (this.#hostDocument.defaultView?.clearTimeout(
	      this.#hostMetadataStopTimer
	    ), this.#hostMetadataStopTimer = 0), this.#hostMetadataRetryTimer && (this.#hostDocument.defaultView?.clearTimeout(
	      this.#hostMetadataRetryTimer
	    ), this.#hostMetadataRetryTimer = 0);
	  }
	  async #jumpFirst() {
	    try {
	      await this.#onJumpFirst();
	    } catch (error) {
	      this.scope.destroyed || this.#onError(error);
	    }
	  }
	  async #toggleTopicVote() {
	    const vote = this.#controller.snapshot.vote;
	    if (!(!vote || !this.#onToggleTopicVote || this.#topicVotePending)) {
	      this.#topicVotePending = !0, this.#renderTopicVote(vote);
	      try {
	        await this.#onToggleTopicVote(vote.voted);
	      } catch (error) {
	        this.scope.destroyed || this.#onError(error);
	      } finally {
	        this.#topicVotePending = !1, this.scope.destroyed || this.#renderTopicVote(this.#controller.snapshot.vote);
	      }
	    }
	  }
	  #renderTopicVote(vote) {
	    if (!vote) {
	      this.#topicVote.replaceChildren(), this.#topicVote.hidden = !0;
	      return;
	    }
	    this.#topicVote.hidden = !1;
	    let button = this.#topicVote.querySelector(
	      ":scope > .ldp-topic-vote"
	    );
	    if (!button) {
	      button = this.#topicVote.ownerDocument.createElement("button"), button.type = "button", button.className = "ldp-topic-vote", button.dataset.topicVote = "";
	      const countNode = this.#topicVote.ownerDocument.createElement("span");
	      button.append("▲ ", countNode, " 票"), this.#topicVote.append(button);
	    }
	    button.classList.toggle("on", vote.voted), button.setAttribute("aria-pressed", String(vote.voted)), button.setAttribute(
	      "aria-label",
	      `${vote.voted ? "取消主题投票" : "为主题投票"},当前 ${vote.count} 票`
	    ), button.disabled = this.#topicVotePending || !vote.canVote && !vote.voted, button.toggleAttribute("aria-busy", this.#topicVotePending), button.querySelector("span").textContent = String(vote.count);
	  }
	  #queueTopicHints() {
	    this.scope.destroyed || this.#topicHintFrame || (this.#topicHintFrame = this.#requestFrame(() => {
	      if (this.#topicHintFrame = 0, this.scope.destroyed) return;
	      const row = this.#elements.topicIdentityHost, scroller = this.#topicScroller, hasOverflow = (!this.#topicTags.hidden || !this.#topicVote.hidden) && scroller.scrollWidth > row.clientWidth + 1, maxScrollLeft = Math.max(
	        0,
	        scroller.scrollWidth - scroller.clientWidth
	      );
	      row.classList.toggle("has-overflow", hasOverflow), row.classList.toggle(
	        "can-scroll-left",
	        hasOverflow && scroller.scrollLeft > 1
	      ), row.classList.toggle(
	        "can-scroll-right",
	        hasOverflow && scroller.scrollLeft < maxScrollLeft - 1
	      );
	    }));
	  }
	  #onTopicWheel(event) {
	    const rawDelta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
	    if (!rawDelta) return;
	    const maxScrollLeft = Math.max(
	      0,
	      this.#topicScroller.scrollWidth - this.#topicScroller.clientWidth
	    );
	    if (maxScrollLeft <= 1) return;
	    const delta = event.deltaMode === 1 ? rawDelta * 40 : event.deltaMode === 2 ? rawDelta * this.#topicScroller.clientWidth : rawDelta, nextScrollLeft = Math.min(
	      maxScrollLeft,
	      Math.max(0, this.#topicScroller.scrollLeft + delta)
	    );
	    nextScrollLeft !== this.#topicScroller.scrollLeft && (event.preventDefault(), event.stopPropagation(), this.#topicScroller.scrollLeft = nextScrollLeft, this.#queueTopicHints());
	  }
	  #renderOnlyOp(snapshot) {
	    const enabled = snapshot?.enabled === !0, available = snapshot?.available === !0;
	    this.#elements.onlyOpToggle.disabled = !available, this.#elements.onlyOpToggle.classList.toggle("active", enabled), this.#elements.onlyOpToggle.setAttribute(
	      "aria-pressed",
	      String(enabled)
	    ), this.#elements.onlyOpToggle.setAttribute(
	      "aria-label",
	      enabled ? "显示全部楼层" : "只看楼主"
	    );
	    const showProgress = !!(enabled && snapshot && !snapshot.complete && snapshot.totalPostCount > 0);
	    if (this.#elements.onlyOpProgress.hidden = !showProgress, !showProgress || !snapshot) {
	      this.#elements.onlyOpProgressValue.textContent = "", this.#elements.onlyOpProgress.style.removeProperty(
	        "--ldp-only-op-progress"
	      );
	      return;
	    }
	    const percent = Math.min(
	      100,
	      snapshot.loadedPostCount / snapshot.totalPostCount * 100
	    );
	    this.#elements.onlyOpProgress.style.setProperty(
	      "--ldp-only-op-progress",
	      `${percent.toFixed(1)}%`
	    ), this.#elements.onlyOpProgressValue.textContent = `已载入 ${snapshot.loadedPostCount}/${snapshot.totalPostCount} · 楼主 ${snapshot.ownerPostCount}`;
	  }
	}
}, "51f322842b7ef4890ced9a0218e8fdceaf3743c94d30c0f262ad7d007e9e42b0");

/* Source: lite/src/topic/reader-topic-local-archive-feature.ts */
runtime.register("src/topic/reader-topic-local-archive-feature.js", function(module, exports, require) {
	var reader_topic_local_archive_feature_exports = {};
	__export(reader_topic_local_archive_feature_exports, {
	  ReaderTopicLocalArchiveFeature: () => ReaderTopicLocalArchiveFeature
	});
	module.exports = __toCommonJS(reader_topic_local_archive_feature_exports);
	var import_html_element = require("../dom/html-element.js"), import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js");
	function archiveLabel(status) {
	  return status === 403 ? "已隐藏或无权访问" : `服务器返回 ${status}`;
	}
	class ReaderTopicLocalArchiveFeature {
	  activationScope = "node";
	  scope;
	  #document;
	  #topicRoot;
	  #session;
	  #nowLabel;
	  #notice;
	  #roots = /* @__PURE__ */ new Map();
	  constructor(options) {
	    this.#document = options.document, this.#topicRoot = options.topicRoot, this.#session = options.session, this.#nowLabel = options.nowLabel ?? ((timestamp) => new Date(timestamp).toLocaleString()), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#notice = (0, import_html_element.htmlElement)(options.document, "aside", "ldp-topic-local-archive-notice"), this.#notice.setAttribute("role", "note"), this.#notice.hidden = !0, this.#topicRoot.prepend(this.#notice), this.scope.add(() => {
	      this.#roots.clear(), this.#notice.remove(), this.#topicRoot.classList.remove("is-local-archive-topic"), delete this.#topicRoot.dataset.localArchiveStatus;
	    }), this.#session.archiveChanges.subscribe(() => this.syncProjection(), this.scope), this.syncProjection();
	  }
	  afterRender(post, view) {
	    const postNumber = (0, import_identifiers.discoursePostReference)(post).postNumber;
	    this.#roots.set(postNumber, view.slots.root), this.#projectPost(view.slots.root, postNumber);
	  }
	  attachRoot(root, postNumber) {
	    this.#roots.set(postNumber, root), this.#projectPost(root, postNumber);
	  }
	  detachRoot(root, postNumber) {
	    this.#roots.get(postNumber) === root && this.#roots.delete(postNumber);
	  }
	  syncProjection() {
	    const state = this.#session.localArchiveState(), topicUnavailable = state.topic;
	    this.#topicRoot.classList.toggle(
	      "is-local-archive-topic",
	      topicUnavailable !== null
	    ), topicUnavailable ? (this.#topicRoot.dataset.localArchiveStatus = String(topicUnavailable.status), this.#notice.hidden = !1, this.#notice.textContent = `本地存档 · ${archiveLabel(topicUnavailable.status)}。以下内容来自 ${this.#nowLabel(topicUnavailable.confirmedAt)} 前保留的正文,不会再按普通缓存期限自动清理,也不代表服务器当前版本。`) : (delete this.#topicRoot.dataset.localArchiveStatus, this.#notice.hidden = !0, this.#notice.textContent = "");
	    for (const [postNumber, root] of this.#roots)
	      this.#projectPost(root, postNumber, state);
	  }
	  #projectPost(root, postNumber, state = this.#session.localArchiveState()) {
	    const unavailable = state.posts.find((entry) => entry.postNumber === postNumber) ?? null;
	    root.classList.toggle("is-local-archive-post", unavailable !== null);
	    let note = root.querySelector(":scope > .ldp-post-body > .ldp-post-local-archive-note");
	    if (!unavailable) {
	      note?.remove(), delete root.dataset.localArchiveStatus;
	      return;
	    }
	    root.dataset.localArchiveStatus = String(unavailable.status), note || (note = (0, import_html_element.htmlElement)(
	      this.#document,
	      "aside",
	      "ldp-post-local-archive-note"
	    ), note.setAttribute("role", "note"), root.querySelector(":scope > .ldp-post-body")?.prepend(note)), note.textContent = `本地引用存档 · ${archiveLabel(unavailable.status)} · ${this.#nowLabel(unavailable.confirmedAt)} 前记录。`;
	  }
	}
}, "b80daecb23a57fceca4a649d7a642d55e60abb6e5b5b107359368c972d066d39");

/* Source: lite/src/topic/reader-topic-navigation-controller.ts */
runtime.register("src/topic/reader-topic-navigation-controller.js", function(module, exports, require) {
	var reader_topic_navigation_controller_exports = {};
	__export(reader_topic_navigation_controller_exports, {
	  ReaderTopicNavigationController: () => ReaderTopicNavigationController
	});
	module.exports = __toCommonJS(reader_topic_navigation_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_reader_reply_ancestor_resolver = require("./reader-reply-ancestor-resolver.js");
	class ReaderTopicNavigationController {
	  scope;
	  changes = new import_signal.Signal();
	  #session;
	  #dom;
	  #hidden;
	  #onError;
	  #epoch = 0;
	  constructor(options) {
	    this.#session = options.session, this.#dom = options.dom, this.#hidden = options.hidden ?? null, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), options.listenUserScrollIntent && this.scope.add(options.listenUserScrollIntent(() => {
	      this.scope.destroyed || (this.#epoch += 1);
	    })), this.scope.add(() => {
	      this.#epoch += 1, this.changes.clear();
	    });
	  }
	  get revision() {
	    return this.#epoch;
	  }
	  isCurrent(revision) {
	    return !this.scope.destroyed && revision === this.#epoch;
	  }
	  async navigate(request) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: request.postNumber
	    }).postNumber, epoch = ++this.#epoch;
	    try {
	      if ((request.forceRefresh === !0 || !this.#session.postByNumber(postNumber)) && await this.#session.loadTarget(postNumber, {
	        scope: "around",
	        advanceCursor: !0,
	        ...request.forceRefresh === !0 ? { forceRefresh: !0 } : {}
	      }), epoch !== this.#epoch || this.scope.destroyed)
	        return this.#result(request, postNumber, "superseded");
	      if (!this.#session.postByNumber(postNumber))
	        return this.#emit(this.#result(
	          request,
	          postNumber,
	          "unavailable"
	        ));
	      const ancestorResolution = await (0, import_reader_reply_ancestor_resolver.resolveReaderReplyAncestors)(
	        this.#session,
	        postNumber,
	        {
	          isActive: () => epoch === this.#epoch && !this.scope.destroyed
	        }
	      );
	      if (epoch !== this.#epoch || this.scope.destroyed)
	        return this.#result(request, postNumber, "superseded");
	      ancestorResolution.error !== void 0 && this.#onError(ancestorResolution.error);
	      const revealOptions = {
	        source: request.source,
	        ...ancestorResolution.complete ? {} : {
	          degradedRootPostNumber: ancestorResolution.rootPostNumber
	        },
	        ...request.alignment === void 0 ? {} : { alignment: request.alignment },
	        ...request.focus === void 0 ? {} : { focus: request.focus },
	        ...request.highlight === void 0 ? {} : { highlight: request.highlight }
	      }, reveal = this.#hidden?.isHidden(postNumber) ? await this.#hidden.revealPost(postNumber, revealOptions) : this.#dom.revealPost(postNumber, revealOptions);
	      return epoch !== this.#epoch || this.scope.destroyed ? this.#result(request, postNumber, "superseded") : reveal ? this.#emit(Object.freeze({
	        postNumber,
	        source: request.source,
	        status: "revealed",
	        rootPostNumber: (0, import_identifiers.discoursePostReference)({
	          post_number: reveal.rootPostNumber
	        }).postNumber,
	        mounted: reveal.mounted,
	        element: reveal.element
	      })) : this.#emit(this.#result(
	        request,
	        postNumber,
	        "unresolved-tree"
	      ));
	    } catch (error) {
	      if (epoch !== this.#epoch || this.scope.destroyed)
	        return this.#result(request, postNumber, "superseded");
	      throw this.#onError(error), error;
	    }
	  }
	  cancel() {
	    this.#assertActive(), this.#epoch += 1;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #result(request, postNumber, status) {
	    return Object.freeze({
	      postNumber,
	      source: request.source,
	      status,
	      rootPostNumber: null,
	      mounted: !1
	    });
	  }
	  #emit(result) {
	    for (const error of this.changes.emit(result)) this.#onError(error);
	    return result;
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("ReaderTopicNavigationController 已销毁");
	  }
	}
}, "e52deaa34066ec53ddf69f0edf481930d7a2810b6330e9421c0dfe5f1c81a381");

/* Source: lite/src/topic/reader-topic-navigation-preferences.ts */
runtime.register("src/topic/reader-topic-navigation-preferences.js", function(module, exports, require) {
	var reader_topic_navigation_preferences_exports = {};
	__export(reader_topic_navigation_preferences_exports, {
	  ReaderTopicNavigationPreferenceProjection: () => ReaderTopicNavigationPreferenceProjection
	});
	module.exports = __toCommonJS(reader_topic_navigation_preferences_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	const VARIABLES = Object.freeze([
	  "--ldp-jump-highlight-color",
	  "--ldp-jump-highlight-radius",
	  "--ldp-jump-highlight-border-width",
	  "--ldp-jump-highlight-duration",
	  "--ldp-jump-highlight-count"
	]), DEFAULTS = Object.freeze({
	  overscanScreens: 1.5,
	  maxMountedPostCount: 80,
	  highlightColor: "#0888cc",
	  highlightRadius: 10,
	  highlightBorderWidth: 1,
	  highlightRate: 0.8,
	  highlightCount: 2
	});
	function finiteRange(value, fallback, minimum, maximum) {
	  return Number.isFinite(value) ? Math.min(maximum, Math.max(minimum, value)) : fallback;
	}
	function normalize(preferences, performance) {
	  const overscanScreens = finiteRange(
	    performance?.streamOverscanScreens ?? DEFAULTS.overscanScreens,
	    DEFAULTS.overscanScreens,
	    0.25,
	    3
	  ), maxMountedPostCount = Math.round(finiteRange(
	    performance?.streamMaxMountedPostCount ?? DEFAULTS.maxMountedPostCount,
	    DEFAULTS.maxMountedPostCount,
	    24,
	    128
	  )), highlightColor = /^#[0-9a-f]{6}$/i.test(preferences.jumpHighlightColor) ? preferences.jumpHighlightColor.toLowerCase() : DEFAULTS.highlightColor, highlightRadius = Math.round(finiteRange(
	    preferences.jumpHighlightRadius,
	    DEFAULTS.highlightRadius,
	    0,
	    24
	  )), highlightBorderWidth = Math.round(finiteRange(
	    preferences.jumpHighlightBorderWidth,
	    DEFAULTS.highlightBorderWidth,
	    0,
	    4
	  )), highlightRate = Math.round(
	    finiteRange(
	      preferences.jumpHighlightRate,
	      DEFAULTS.highlightRate,
	      0.5,
	      2
	    ) * 10
	  ) / 10, highlightCount = Math.round(finiteRange(
	    preferences.jumpHighlightCount,
	    DEFAULTS.highlightCount,
	    1,
	    6
	  )), highlightStepDurationMs = Math.round(1e3 / highlightRate);
	  return Object.freeze({
	    overscanScreens,
	    maxMountedPostCount,
	    highlightColor,
	    highlightRadius,
	    highlightBorderWidth,
	    highlightRate,
	    highlightCount,
	    highlightStepDurationMs,
	    highlightLifetimeMs: highlightStepDurationMs * highlightCount
	  });
	}
	function snapshotsEqual(left, right) {
	  return left.overscanScreens === right.overscanScreens && left.maxMountedPostCount === right.maxMountedPostCount && left.highlightColor === right.highlightColor && left.highlightRadius === right.highlightRadius && left.highlightBorderWidth === right.highlightBorderWidth && left.highlightRate === right.highlightRate && left.highlightCount === right.highlightCount && left.highlightStepDurationMs === right.highlightStepDurationMs && left.highlightLifetimeMs === right.highlightLifetimeMs;
	}
	class ReaderTopicNavigationPreferenceProjection {
	  scope;
	  #root;
	  #readPerformance;
	  #previous = /* @__PURE__ */ new Map();
	  #preferences;
	  #preview = null;
	  #snapshot;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#root = options.root, this.#readPerformance = options.readPerformance ?? (() => null), this.#preferences = options.preferences;
	    for (const name of VARIABLES) {
	      const readPriority = this.#root.style.getPropertyPriority;
	      this.#previous.set(name, Object.freeze({
	        value: this.#root.style.getPropertyValue(name),
	        priority: typeof readPriority == "function" ? readPriority.call(this.#root.style, name) : ""
	      }));
	    }
	    this.#snapshot = normalize(
	      options.preferences,
	      this.#readPerformance()
	    ), this.#applyCss(), this.scope.add(() => this.#restoreCss());
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  apply(preferences) {
	    this.scope.destroyed || (this.#preferences = preferences, this.#commit(normalize(
	      this.#preview ?? this.#preferences,
	      this.#readPerformance()
	    )));
	  }
	  preview(preferences) {
	    if (this.scope.destroyed) return;
	    const performance = this.#readPerformance(), snapshot = normalize(preferences, performance);
	    this.#preview = snapshotsEqual(
	      snapshot,
	      normalize(this.#preferences, performance)
	    ) ? null : preferences, this.#commit(snapshot);
	  }
	  clearPreview() {
	    this.scope.destroyed || !this.#preview || (this.#preview = null, this.#commit(normalize(
	      this.#preferences,
	      this.#readPerformance()
	    )));
	  }
	  refreshPerformance() {
	    if (this.scope.destroyed) return;
	    const performance = this.#readPerformance();
	    this.#commit(Object.freeze({
	      ...this.#snapshot,
	      overscanScreens: finiteRange(
	        performance?.streamOverscanScreens ?? DEFAULTS.overscanScreens,
	        DEFAULTS.overscanScreens,
	        0.25,
	        3
	      ),
	      maxMountedPostCount: Math.round(finiteRange(
	        performance?.streamMaxMountedPostCount ?? DEFAULTS.maxMountedPostCount,
	        DEFAULTS.maxMountedPostCount,
	        24,
	        128
	      ))
	    }));
	  }
	  readOverscan() {
	    const overscanScreens = this.#snapshot.overscanScreens;
	    return Object.freeze({
	      beforeScreens: overscanScreens,
	      afterScreens: overscanScreens
	    });
	  }
	  readHighlightLifetimeMs() {
	    return this.#snapshot.highlightLifetimeMs;
	  }
	  readMaxMountedPostCount() {
	    return this.#snapshot.maxMountedPostCount;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #commit(snapshot) {
	    snapshotsEqual(snapshot, this.#snapshot) || (this.#snapshot = snapshot, this.#applyCss());
	  }
	  #applyCss() {
	    const snapshot = this.#snapshot;
	    this.#root.style.setProperty(
	      "--ldp-jump-highlight-color",
	      snapshot.highlightColor
	    ), this.#root.style.setProperty(
	      "--ldp-jump-highlight-radius",
	      `${snapshot.highlightRadius}px`
	    ), this.#root.style.setProperty(
	      "--ldp-jump-highlight-border-width",
	      `${snapshot.highlightBorderWidth}px`
	    ), this.#root.style.setProperty(
	      "--ldp-jump-highlight-duration",
	      `${snapshot.highlightStepDurationMs}ms`
	    ), this.#root.style.setProperty(
	      "--ldp-jump-highlight-count",
	      String(snapshot.highlightCount)
	    );
	  }
	  #restoreCss() {
	    for (const name of VARIABLES) {
	      const previous = this.#previous.get(name);
	      previous?.value ? this.#root.style.setProperty(
	        name,
	        previous.value,
	        previous.priority
	      ) : this.#root.style.removeProperty(name);
	    }
	  }
	}
}, "021614753d8d9c7366451df05ef36e606d15b2101d07f9b3bf45efa4ec826017");

/* Source: lite/src/topic/reader-topic-only-op-controller.ts */
runtime.register("src/topic/reader-topic-only-op-controller.js", function(module, exports, require) {
	var reader_topic_only_op_controller_exports = {};
	__export(reader_topic_only_op_controller_exports, {
	  ReaderTopicOnlyOpController: () => ReaderTopicOnlyOpController
	});
	module.exports = __toCommonJS(reader_topic_only_op_controller_exports);
	var import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js"), import_value_record = require("../kernel/value-record.js"), import_reader_topic_header = require("./reader-topic-header.js");
	function text(value) {
	  return String(value ?? "").trim();
	}
	function positiveCount(value) {
	  const numeric = Number(value);
	  return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : 0;
	}
	class ReaderTopicOnlyOpController {
	  scope;
	  changes = new import_signal.Signal();
	  #session;
	  #presentation;
	  #onProjectionChanged;
	  #onEnabledChanged;
	  #onError;
	  #enabled = !1;
	  #snapshot;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#session = options.session, this.#presentation = options.presentation, this.#onProjectionChanged = options.onProjectionChanged, this.#onEnabledChanged = options.onEnabledChanged ?? (() => {
	    }), this.#onError = options.onError ?? (() => {
	    }), this.#snapshot = this.#readSnapshot(), (options.presentationChanges ?? this.#session.changes).subscribe((commit) => {
	      this.refresh(commit.changedPostNumbers.length > 0);
	    }, this.scope), this.scope.add(() => {
	      this.#enabled && this.#notifyEnabledChanged(!1), this.#presentation.setPostFilter(null), this.changes.clear();
	    });
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  setEnabled(enabled) {
	    const next = enabled === !0 && this.#snapshot.available;
	    return next === this.#enabled ? !1 : (this.#enabled = next, this.#notifyEnabledChanged(next), this.#applyProjection(), this.refresh(), !0);
	  }
	  toggle() {
	    return this.setEnabled(!this.#enabled);
	  }
	  refresh(invalidateFilter = !1) {
	    const next = this.#readSnapshot();
	    if (this.#enabled && !next.available)
	      return this.#enabled = !1, this.#notifyEnabledChanged(!1), this.#applyProjection(), this.refresh();
	    if (this.#enabled && next.ownerUsername !== this.#snapshot.ownerUsername && this.#applyProjection(next.ownerUsername), invalidateFilter && this.#enabled && this.#presentation.invalidatePostFilter() && this.#notifyProjectionChanged(!1), next.enabled === this.#snapshot.enabled && next.available === this.#snapshot.available && next.ownerUsername === this.#snapshot.ownerUsername && next.loadedPostCount === this.#snapshot.loadedPostCount && next.totalPostCount === this.#snapshot.totalPostCount && next.ownerPostCount === this.#snapshot.ownerPostCount && next.complete === this.#snapshot.complete) return this.#snapshot;
	    this.#snapshot = next;
	    for (const error of this.changes.emit(next)) this.#onError(error);
	    return next;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #applyProjection(owner = this.#snapshot.ownerUsername) {
	    this.#presentation.setPostFilter(
	      this.#enabled && owner ? Object.freeze({
	        key: `only-op:${owner}`,
	        hideDescendantMatches: !0,
	        ancestorBoundaryPostNumber: 1,
	        matches: (postNumber) => text(this.#session.postByNumber(postNumber)?.username) === owner
	      }) : null
	    ) && this.#notifyProjectionChanged(!0);
	  }
	  #notifyProjectionChanged(resetScroll) {
	    try {
	      this.#onProjectionChanged(resetScroll);
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  #notifyEnabledChanged(enabled) {
	    try {
	      this.#onEnabledChanged(enabled);
	    } catch (error) {
	      this.#onError(error);
	    }
	  }
	  #readSnapshot() {
	    const posts = this.#session.cachedPosts(), owner = (0, import_reader_topic_header.readerTopicOwnerUsername)(this.#session.topic, posts), topic = (0, import_value_record.valueRecord)(this.#session.topic), totalPostCount = Math.max(
	      positiveCount(topic?.highest_post_number),
	      positiveCount(topic?.posts_count),
	      posts.reduce(
	        (maximum, post) => Math.max(maximum, positiveCount(post.post_number)),
	        0
	      )
	    );
	    return Object.freeze({
	      enabled: this.#enabled,
	      available: !!owner,
	      ownerUsername: owner,
	      loadedPostCount: posts.length,
	      totalPostCount,
	      ownerPostCount: owner ? posts.filter((post) => text(post.username) === owner).length : 0,
	      complete: this.#session.loadDone === !0 || totalPostCount > 0 && posts.length >= totalPostCount
	    });
	  }
	}
}, "26786da74701ab1ec96b5181a0950b1463e676432c25bc31fdd17eb8d88e0bbd");

/* Source: lite/src/topic/reader-topic-scroll-adapter.ts */
runtime.register("src/topic/reader-topic-scroll-adapter.js", function(module, exports, require) {
	var reader_topic_scroll_adapter_exports = {};
	__export(reader_topic_scroll_adapter_exports, {
	  ReaderTopicJumpHighlightController: () => ReaderTopicJumpHighlightController,
	  ReaderTopicScrollAdapter: () => ReaderTopicScrollAdapter
	});
	module.exports = __toCommonJS(reader_topic_scroll_adapter_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	function finiteNonNegative(value, fallback = 0) {
	  return Number.isFinite(value) && value >= 0 ? value : fallback;
	}
	function defaultResizeObserverFactory(callback) {
	  return typeof ResizeObserver != "function" ? null : new ResizeObserver((entries) => callback(entries.map((entry) => {
	    const borderBox = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize;
	    return Object.freeze({
	      target: entry.target,
	      blockSize: borderBox?.blockSize ?? entry.contentRect.height
	    });
	  })));
	}
	class ReaderTopicJumpHighlightController {
	  scope;
	  #readLifetimeMs;
	  #prefersReducedMotion;
	  #createResizeObserver;
	  #schedule;
	  #cancel;
	  #active = null;
	  #token = 0;
	  constructor(options = {}) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#readLifetimeMs = options.readLifetimeMs ?? (() => 2500), this.#prefersReducedMotion = options.prefersReducedMotion ?? (() => !1), this.#createResizeObserver = options.createResizeObserver ?? defaultResizeObserverFactory, this.#schedule = options.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs)), this.#cancel = options.cancel ?? ((handle) => clearTimeout(handle)), this.scope.add(() => this.clear());
	  }
	  highlight(target) {
	    if (this.scope.destroyed || !target.isConnected) return;
	    this.clear();
	    const token = String(++this.#token);
	    target.classList.remove("ldp-jump-highlight"), target.style.removeProperty("--ldp-first-post-jump-highlight-height");
	    const anchor = this.#syncFirstPostRegion(target);
	    target.offsetWidth, target.dataset.jumpHighlightToken = token, target.classList.add("ldp-jump-highlight");
	    const observer = anchor ? this.#createResizeObserver(() => {
	      target.isConnected && target.dataset.jumpHighlightToken === token && this.#syncFirstPostRegion(target);
	    }) : null;
	    observer?.observe(target), anchor && observer?.observe(anchor);
	    const lifetime = this.#prefersReducedMotion() ? 1e3 : Math.max(1, finiteNonNegative(this.#readLifetimeMs(), 2500)), timer = this.#schedule(() => {
	      target.dataset.jumpHighlightToken === token && this.#clearTarget(target), observer?.disconnect(), this.#active?.token === token && (this.#active = null);
	    }, lifetime);
	    this.#active = Object.freeze({ target, token, timer, observer });
	  }
	  clear() {
	    const active = this.#active;
	    active && (this.#active = null, this.#cancel(active.timer), active.observer?.disconnect(), active.target.dataset.jumpHighlightToken === active.token && this.#clearTarget(active.target));
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #syncFirstPostRegion(target) {
	    if (target.dataset.postNumber !== "1") return null;
	    const actions = target.querySelector(
	      ":scope > .ldp-post-body .ldp-reactions, :scope > .ldp-reactions"
	    ), targetRect = target.getBoundingClientRect();
	    if (actions) {
	      const actionsRect = actions.getBoundingClientRect();
	      if (actionsRect.height > 0 && actionsRect.bottom > targetRect.top)
	        return this.#setFirstPostRegion(target, targetRect, actionsRect), actions;
	    }
	    const body = target.querySelector(
	      ":scope > .ldp-post-body"
	    );
	    return body ? (this.#setFirstPostRegion(
	      target,
	      targetRect,
	      body.getBoundingClientRect()
	    ), body) : null;
	  }
	  #setFirstPostRegion(target, targetRect, boundaryRect) {
	    const height = Math.min(
	      targetRect.height,
	      Math.max(0, boundaryRect.bottom - targetRect.top)
	    );
	    target.style.setProperty(
	      "--ldp-first-post-jump-highlight-height",
	      `${Math.ceil(height)}px`
	    );
	  }
	  #clearTarget(target) {
	    target.classList.remove("ldp-jump-highlight"), target.style.removeProperty("--ldp-first-post-jump-highlight-height"), delete target.dataset.jumpHighlightToken;
	  }
	}
	const SCROLLING_KEYS = /* @__PURE__ */ new Set([
	  "ArrowDown",
	  "ArrowUp",
	  "End",
	  "Home",
	  "PageDown",
	  "PageUp",
	  " "
	]), USER_SCROLL_SESSION_GAP_MS = 500;
	function isEditableScrollTarget(target) {
	  const candidate = target;
	  return !candidate || typeof candidate.closest != "function" ? !1 : !!candidate.closest(
	    'input, textarea, select, [contenteditable=""], [contenteditable="true"]'
	  );
	}
	class ReaderTopicScrollAdapter {
	  scope;
	  highlight;
	  #scrollRoot;
	  #readOverscan;
	  #readMaxMountedPostCount;
	  #readTopInset;
	  #requestFrame;
	  #cancelFrame;
	  #now;
	  #observesViewportSize;
	  #windowChangeListeners = /* @__PURE__ */ new Set();
	  #userScrollIntentListeners = /* @__PURE__ */ new Set();
	  #viewportSize = 1;
	  #viewportSizeDirty = !1;
	  #scrollOffset = 0;
	  #pendingScrollOffset = null;
	  #scrollOffsetDirty = !1;
	  #scrollFrame = 0;
	  #lastUserScrollAt = 0;
	  #userScrollSessionActive = !1;
	  #stationaryAnchor = null;
	  #stationaryScrollWriteOffset = null;
	  #stationaryMutationObserver = null;
	  #stationaryResizeObserver = null;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#scrollRoot = options.scrollRoot, this.#readOverscan = options.readOverscan ?? (() => ({})), this.#readMaxMountedPostCount = options.readMaxMountedPostCount ?? (() => {
	    }), this.#readTopInset = options.readTopInset ?? (() => 0), this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.#now = options.now ?? (() => performance.now()), this.#scrollRoot.classList.remove("ldp-stream-viewport-anchor");
	    const createMutationObserver = options.createMutationObserver ?? ((callback) => {
	      const NativeMutationObserver = this.#scrollRoot.ownerDocument.defaultView?.MutationObserver;
	      return NativeMutationObserver ? new NativeMutationObserver(callback) : null;
	    });
	    this.#stationaryMutationObserver = createMutationObserver(() => {
	      this.#observeStationaryContentSize(), this.#restoreStationaryViewport();
	    });
	    const NativeResizeObserver = this.#scrollRoot.ownerDocument.defaultView?.ResizeObserver;
	    NativeResizeObserver && (this.#stationaryResizeObserver = new NativeResizeObserver(() => {
	      this.#restoreStationaryViewport();
	    })), this.scope.add(() => this.#releaseStationaryViewport());
	    const createResizeObserver = options.createResizeObserver ?? defaultResizeObserverFactory, viewportObserver = createResizeObserver((entries) => {
	      const entry = entries.find(({ target }) => target === this.#scrollRoot), viewportSize = Math.max(
	        1,
	        finiteNonNegative(entry?.blockSize ?? 0) || this.#measureViewportSize()
	      ), scrollOffset = finiteNonNegative(this.#scrollRoot.scrollTop);
	      viewportSize === this.#viewportSize && scrollOffset === this.#scrollOffset || (this.#viewportSize = viewportSize, this.#scrollOffset = scrollOffset, this.#pendingScrollOffset = scrollOffset, this.#scrollOffsetDirty = !1, this.#notifyWindowChange());
	    });
	    this.#observesViewportSize = viewportObserver !== null, viewportObserver || (this.#viewportSize = this.#measureViewportSize(), this.#scrollOffset = finiteNonNegative(this.#scrollRoot.scrollTop)), viewportObserver?.observe(this.#scrollRoot), viewportObserver && this.scope.add(() => viewportObserver.disconnect()), options.viewportChangeTarget && this.scope.listen(
	      options.viewportChangeTarget,
	      "ldp-reader-window-change",
	      () => {
	        this.#viewportSizeDirty = !0, this.#notifyWindowChange();
	      }
	    ), this.highlight = new ReaderTopicJumpHighlightController({
	      ...options.readLifetimeMs ? { readLifetimeMs: options.readLifetimeMs } : {},
	      ...options.prefersReducedMotion ? { prefersReducedMotion: options.prefersReducedMotion } : {},
	      createResizeObserver,
	      ...options.schedule ? { schedule: options.schedule } : {},
	      ...options.cancel ? { cancel: options.cancel } : {},
	      parentScope: this.scope
	    }), this.scope.listen(
	      this.#scrollRoot,
	      "scroll",
	      () => {
	        this.#claimScrollOnlyUserInput(), this.#markUserScrollProgress(), this.#scrollOffsetDirty = !0, this.#scheduleScrollCommit();
	      },
	      { passive: !0 }
	    ), this.scope.listen(this.#scrollRoot, "scrollend", () => {
	      this.#finishUserScrollSession();
	    }, { passive: !0 }), this.scope.listen(this.#scrollRoot, "wheel", (event) => {
	      event.ctrlKey || this.#markUserScrollIntent();
	    }, { passive: !0 });
	    for (const type of ["touchstart", "touchmove"])
	      this.scope.listen(this.#scrollRoot, type, () => {
	        this.#markUserScrollIntent();
	      }, { passive: !0 });
	    this.scope.listen(this.#scrollRoot, "keydown", (event) => {
	      const keyboard = event;
	      keyboard.defaultPrevented || keyboard.altKey || keyboard.ctrlKey || keyboard.metaKey || !SCROLLING_KEYS.has(keyboard.key) || isEditableScrollTarget(keyboard.target) || this.#markUserScrollIntent();
	    }), this.scope.add(() => {
	      this.#scrollFrame && (this.#cancelFrame(this.#scrollFrame), this.#scrollFrame = 0, this.#pendingScrollOffset = null, this.#scrollOffsetDirty = !1);
	    }), this.scope.add(() => this.#windowChangeListeners.clear()), this.scope.add(() => this.#userScrollIntentListeners.clear());
	  }
	  readWindowInput() {
	    const overscan = this.#readOverscan(), maxMountedPostCount = this.#readMaxMountedPostCount(), scrollOffset = this.#refreshPendingScrollOffset(), bootstrapViewportSize = this.#observesViewportSize && this.#viewportSize <= 1, viewportSize = !this.#observesViewportSize || this.#viewportSizeDirty || bootstrapViewportSize ? this.#measureViewportSize() : this.#viewportSize;
	    return (this.#viewportSizeDirty || bootstrapViewportSize) && (this.#viewportSize = viewportSize, this.#viewportSizeDirty = !1), Object.freeze({
	      scrollOffset,
	      viewportSize,
	      ...this.#stationaryAnchor === null ? {} : { preservePostNumber: this.#stationaryAnchor.postNumber },
	      /*
	       * overscan 是用户/性能策略的稳定窗口契约。滚动方向只改变 offset;
	       * 若在反向瞬间翻转前后边界,会额外整批卸载/挂载树节点并制造尖峰。
	       */
	      overscanBeforeScreens: finiteNonNegative(
	        Number(overscan.beforeScreens),
	        1
	      ),
	      overscanAfterScreens: finiteNonNegative(
	        Number(overscan.afterScreens),
	        1
	      ),
	      ...maxMountedPostCount === void 0 ? {} : { maxMountedPostCount }
	    });
	  }
	  lastUserScrollAt() {
	    return this.#lastUserScrollAt;
	  }
	  remainingUserIdleMs(minimumIdleMs) {
	    const idleMs = finiteNonNegative(Number(minimumIdleMs));
	    return this.#lastUserScrollAt <= 0 ? 0 : Math.max(0, idleMs - (this.#now() - this.#lastUserScrollAt));
	  }
	  readVisibleViewportAnchor(elements) {
	    const visible = this.#findTopVisiblePost(elements);
	    return visible ? Object.freeze({
	      postNumber: visible.postNumber,
	      postOffset: visible.ownerOffset,
	      scrollTop: finiteNonNegative(this.#scrollRoot.scrollTop)
	    }) : null;
	  }
	  #measureViewportSize() {
	    const clientHeight = finiteNonNegative(this.#scrollRoot.clientHeight), rectHeight = clientHeight > 0 ? 0 : finiteNonNegative(
	      this.#scrollRoot.getBoundingClientRect().height
	    );
	    return Math.max(1, clientHeight || rectHeight || 1);
	  }
	  applyScrollCompensation(delta) {
	    !Number.isFinite(delta) || delta === 0 || (this.#refreshPendingScrollOffset(), this.#scrollOffset = Math.max(0, this.#scrollOffset + delta), this.#pendingScrollOffset !== null && (this.#pendingScrollOffset = Math.max(
	      0,
	      this.#pendingScrollOffset + delta
	    )), this.#writeScrollRootOffset(
	      this.#pendingScrollOffset ?? this.#scrollOffset
	    ), this.#restoreStationaryViewport());
	  }
	  listenScroll(listener) {
	    return this.scope.destroyed ? () => {
	    } : (this.#windowChangeListeners.add(listener), this.scope.add(() => {
	      this.#windowChangeListeners.delete(listener);
	    }));
	  }
	  listenUserScrollIntent(listener) {
	    return this.scope.destroyed ? () => {
	    } : (this.#userScrollIntentListeners.add(listener), this.scope.add(() => {
	      this.#userScrollIntentListeners.delete(listener);
	    }));
	  }
	  writeScrollOffset(offset) {
	    const normalized = finiteNonNegative(offset), maxOffset = Math.max(
	      0,
	      finiteNonNegative(this.#scrollRoot.scrollHeight) - finiteNonNegative(this.#scrollRoot.clientHeight)
	    );
	    this.#setScrollOffset(maxOffset > 0 ? Math.min(normalized, maxOffset) : normalized);
	  }
	  alignPost(target, options) {
	    if (!target.isConnected) return;
	    const rootRect = this.#scrollRoot.getBoundingClientRect(), targetRect = target.getBoundingClientRect(), topInset = Math.min(
	      rootRect.height,
	      finiteNonNegative(this.#readTopInset())
	    ), configuredVisibleTop = rootRect.top + topInset, frozenHeaderBottom = options.viewportOffset === void 0 ? configuredVisibleTop : this.#scrollRoot.closest(".ldp-modal")?.querySelector(":scope > .ldp-header")?.getBoundingClientRect().bottom ?? configuredVisibleTop, visibleTop = Math.min(
	      rootRect.bottom,
	      Math.max(configuredVisibleTop, frozenHeaderBottom) + finiteNonNegative(options.viewportOffset ?? 0)
	    ), visibleBottom = rootRect.bottom, visibleHeight = Math.max(1, visibleBottom - visibleTop), fits = targetRect.height <= visibleHeight, alignment = options.alignment ?? (target.dataset.postNumber === "1" ? "start" : "center");
	    let correction = 0;
	    alignment === "nearest" ? targetRect.top < visibleTop && targetRect.bottom > visibleBottom ? correction = 0 : targetRect.top < visibleTop ? correction = targetRect.top - visibleTop : targetRect.bottom > visibleBottom && (correction = targetRect.bottom - visibleBottom) : alignment === "center" && fits ? correction = targetRect.top + targetRect.height / 2 - (visibleTop + visibleHeight / 2) : correction = targetRect.top - visibleTop, Math.abs(correction) >= 1 && this.writeScrollOffset(this.#scrollOffset + correction), options.focus && this.#focus(target), options.highlight !== !1 && this.highlight.highlight(target);
	  }
	  highlightPost(target) {
	    target.isConnected && this.highlight.highlight(target);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #setScrollOffset(offset) {
	    this.#releaseStationaryViewport(), this.#scrollOffset = Math.max(0, finiteNonNegative(offset)), this.#pendingScrollOffset = this.#scrollOffset, this.#scrollOffsetDirty = !1, this.#lastUserScrollAt = 0, this.#userScrollSessionActive = !1, this.#writeScrollRootOffset(this.#scrollOffset);
	  }
	  #focus(target) {
	    const previousTabIndex = target.getAttribute("tabindex");
	    previousTabIndex === null && target.setAttribute("tabindex", "-1");
	    try {
	      target.focus({ preventScroll: !0 });
	    } catch {
	      target.focus();
	    }
	    previousTabIndex === null ? target.removeAttribute("tabindex") : target.setAttribute("tabindex", previousTabIndex);
	  }
	  #findTopVisiblePost(elements) {
	    const rootRect = this.#scrollRoot.getBoundingClientRect(), visibleTop = rootRect.top + Math.min(
	      rootRect.height,
	      finiteNonNegative(this.#readTopInset())
	    ), visibleBottom = rootRect.bottom, candidates = elements.flatMap((owner) => {
	      const postNumber = Number(owner.dataset.postNumber);
	      if (!Number.isSafeInteger(postNumber) || postNumber <= 0 || !owner.isConnected || !this.#scrollRoot.contains(owner) || owner.hidden) return [];
	      const marker2 = owner.querySelector(":scope > .ldp-post-head") ?? owner, markerRect = marker2.getBoundingClientRect(), ownerRect = owner.getBoundingClientRect();
	      return [{ postNumber, marker: marker2, markerRect, owner, ownerRect }];
	    }), visibleHeaders = candidates.filter(
	      ({ markerRect }) => markerRect.bottom > visibleTop + 1 && markerRect.top < visibleBottom - 1
	    ).sort(
	      (left, right) => Math.max(0, left.markerRect.top - visibleTop) - Math.max(0, right.markerRect.top - visibleTop) || left.markerRect.top - right.markerRect.top
	    ), fallback = candidates.filter(
	      ({ ownerRect }) => ownerRect.bottom > visibleTop + 1 && ownerRect.top < visibleBottom - 1
	    ).sort(
	      (left, right) => Math.abs(left.ownerRect.top - visibleTop) - Math.abs(right.ownerRect.top - visibleTop) || left.ownerRect.height - right.ownerRect.height
	    ), visibleHeader = visibleHeaders[0], best = visibleHeader ?? fallback[0];
	    if (!best) return null;
	    const marker = visibleHeader ? best.marker : best.owner, markerOffset = visibleHeader ? best.markerRect.top - visibleTop : best.ownerRect.top - visibleTop;
	    return Object.freeze({
	      postNumber: best.postNumber,
	      marker,
	      markerOffset,
	      owner: best.owner,
	      ownerOffset: best.ownerRect.top - visibleTop
	    });
	  }
	  #notifyWindowChange() {
	    for (const listener of [...this.#windowChangeListeners]) listener();
	  }
	  #markUserScrollIntent() {
	    this.#releaseStationaryViewport(), this.#userScrollSessionActive = !0, this.#lastUserScrollAt = Math.max(
	      Number.EPSILON,
	      finiteNonNegative(this.#now())
	    );
	    for (const listener of [...this.#userScrollIntentListeners]) listener();
	  }
	  #markUserScrollProgress() {
	    if (!this.#userScrollSessionActive) return;
	    const now = finiteNonNegative(this.#now());
	    if (this.#lastUserScrollAt <= 0 || now - this.#lastUserScrollAt > USER_SCROLL_SESSION_GAP_MS) {
	      this.#userScrollSessionActive = !1;
	      return;
	    }
	    this.#lastUserScrollAt = Math.max(Number.EPSILON, now);
	  }
	  #finishUserScrollSession() {
	    this.#userScrollSessionActive && (this.#userScrollSessionActive = !1, this.#lastUserScrollAt = Math.max(
	      Number.EPSILON,
	      finiteNonNegative(this.#now())
	    ), this.#lockStationaryViewport());
	  }
	  #lockStationaryViewport() {
	    this.#releaseStationaryViewport();
	    const visible = this.#findTopVisiblePost(
	      Array.from(
	        this.#scrollRoot.querySelectorAll(
	          ".ldp-post[data-post-number]"
	        )
	      )
	    );
	    visible && (this.#stationaryAnchor = Object.freeze({
	      postNumber: visible.postNumber,
	      markerRole: visible.marker === visible.owner ? "owner" : "header",
	      markerOffset: visible.markerOffset,
	      ownerOffset: visible.ownerOffset
	    }), this.#scrollRoot.classList.add("ldp-stream-viewport-anchor"), this.#stationaryMutationObserver?.observe(this.#scrollRoot, {
	      attributes: !0,
	      attributeFilter: ["class", "hidden", "style"],
	      characterData: !0,
	      childList: !0,
	      subtree: !0
	    }), this.#observeStationaryContentSize());
	  }
	  #releaseStationaryViewport() {
	    this.#stationaryAnchor = null, this.#stationaryScrollWriteOffset = null, this.#stationaryMutationObserver?.disconnect(), this.#stationaryResizeObserver?.disconnect(), this.#scrollRoot.classList.remove("ldp-stream-viewport-anchor");
	  }
	  #observeStationaryContentSize() {
	    const observer = this.#stationaryResizeObserver;
	    if (!observer || !this.#stationaryAnchor) return;
	    observer.disconnect();
	    const stream = this.#scrollRoot.querySelector(
	      ".ldp-virtual-stream"
	    );
	    stream && observer.observe(stream);
	  }
	  #restoreStationaryViewport() {
	    const anchor = this.#stationaryAnchor;
	    if (!anchor || this.#userScrollSessionActive) return;
	    const owner = this.#scrollRoot.querySelector(
	      `.ldp-post[data-post-number="${anchor.postNumber}"]`
	    );
	    if (!owner || owner.hidden) return;
	    const header = anchor.markerRole === "header" ? owner.querySelector(":scope > .ldp-post-head") : null, marker = header ?? owner, expectedOffset = header ? anchor.markerOffset : anchor.ownerOffset, rootRect = this.#scrollRoot.getBoundingClientRect(), visibleTop = rootRect.top + Math.min(
	      rootRect.height,
	      finiteNonNegative(this.#readTopInset())
	    ), correction = marker.getBoundingClientRect().top - visibleTop - expectedOffset;
	    if (Math.abs(correction) < 0.5) return;
	    const nextOffset = Math.max(
	      0,
	      finiteNonNegative(this.#scrollRoot.scrollTop) + correction
	    );
	    this.#scrollOffset = nextOffset, this.#pendingScrollOffset = nextOffset, this.#scrollOffsetDirty = !1, this.#writeScrollRootOffset(nextOffset);
	  }
	  #writeScrollRootOffset(offset) {
	    this.#stationaryAnchor && (this.#stationaryScrollWriteOffset = offset), this.#scrollRoot.scrollTop = offset;
	  }
	  #claimScrollOnlyUserInput() {
	    if (!this.#stationaryAnchor || this.#userScrollSessionActive) return;
	    const actualOffset = finiteNonNegative(this.#scrollRoot.scrollTop), internalOffset = this.#stationaryScrollWriteOffset;
	    this.#stationaryScrollWriteOffset = null, !(internalOffset !== null && Math.abs(actualOffset - internalOffset) < 0.5) && this.#markUserScrollIntent();
	  }
	  #scheduleScrollCommit() {
	    if (this.#scrollFrame || this.scope.destroyed) return;
	    let completed = !1;
	    const handle = this.#requestFrame(() => {
	      if (completed = !0, this.#scrollFrame = 0, this.scope.destroyed) return;
	      const scrollOffset = this.#refreshPendingScrollOffset();
	      this.#pendingScrollOffset = null, scrollOffset !== this.#scrollOffset && (this.#scrollOffset = scrollOffset, this.#notifyWindowChange());
	    });
	    completed || (this.#scrollFrame = handle);
	  }
	  #refreshPendingScrollOffset() {
	    return this.#scrollOffsetDirty && (this.#pendingScrollOffset = finiteNonNegative(
	      this.#scrollRoot.scrollTop
	    ), this.#scrollOffsetDirty = !1), this.#pendingScrollOffset ?? this.#scrollOffset;
	  }
	}
}, "62099ec9c2b6aec3830d06b1a57f6f8f888cb5129bef70fc5cda435212626ae5");

/* Source: lite/src/topic/reader-topic-scroll-lifecycle.ts */
runtime.register("src/topic/reader-topic-scroll-lifecycle.js", function(module, exports, require) {
	var reader_topic_scroll_lifecycle_exports = {};
	__export(reader_topic_scroll_lifecycle_exports, {
	  ReaderTopicScrollLifecycle: () => ReaderTopicScrollLifecycle
	});
	module.exports = __toCommonJS(reader_topic_scroll_lifecycle_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	class ReaderTopicScrollLifecycle {
	  scope;
	  #readLastUserScrollAt;
	  #readIdleMs;
	  #scheduler;
	  #now;
	  #idleHandle = null;
	  #idlePromise = null;
	  #idleResolve = null;
	  #minimumIdleMs = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#readLastUserScrollAt = options.readLastUserScrollAt, this.#readIdleMs = options.readIdleMs, this.#scheduler = options.scheduler, this.#now = options.now ?? (() => performance.now()), this.scope.add(() => {
	      this.#idleHandle !== null && this.#scheduler.cancel(this.#idleHandle), this.#idleHandle = null;
	      const resolve = this.#idleResolve;
	      this.#idleResolve = null, this.#idlePromise = null, this.#minimumIdleMs = 0, resolve?.();
	    });
	  }
	  lastUserScrollAt() {
	    const value = Number(this.#readLastUserScrollAt());
	    return Number.isFinite(value) && value > 0 ? value : 0;
	  }
	  remainingIdleMs(minimumIdleMs = 120) {
	    const configuredIdleMs = Number(this.#readIdleMs()), idleMs = Math.max(
	      Math.max(0, Number(minimumIdleMs) || 0),
	      Number.isFinite(configuredIdleMs) ? configuredIdleMs : 0
	    ), lastUserScrollAt = this.lastUserScrollAt();
	    return lastUserScrollAt <= 0 ? 0 : Math.max(0, idleMs - (this.#now() - lastUserScrollAt));
	  }
	  isIdle(minimumIdleMs = 120) {
	    return this.remainingIdleMs(minimumIdleMs) <= 0;
	  }
	  waitForIdle(minimumIdleMs = 120) {
	    if (this.scope.destroyed) return Promise.resolve();
	    this.#minimumIdleMs = Math.max(
	      this.#minimumIdleMs,
	      Math.max(0, Number(minimumIdleMs) || 0)
	    );
	    const remainingMs = this.remainingIdleMs(this.#minimumIdleMs);
	    return remainingMs <= 0 ? (this.#minimumIdleMs = 0, Promise.resolve()) : (this.#idlePromise || (this.#idlePromise = new Promise((resolve) => {
	      this.#idleResolve = resolve;
	    })), this.#scheduleIdleCheck(remainingMs), this.#idlePromise);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #scheduleIdleCheck(delayMs) {
	    this.#idleHandle !== null && this.#scheduler.cancel(this.#idleHandle), this.#idleHandle = this.#scheduler.schedule(() => {
	      this.#idleHandle = null;
	      const remainingMs = this.remainingIdleMs(this.#minimumIdleMs);
	      if (!this.scope.destroyed && remainingMs > 0) {
	        this.#scheduleIdleCheck(remainingMs);
	        return;
	      }
	      const resolve = this.#idleResolve;
	      this.#idleResolve = null, this.#idlePromise = null, this.#minimumIdleMs = 0, resolve?.();
	    }, Math.max(0, delayMs));
	  }
	}
}, "78b9e8af68ce9f98c3d2d31a24b01cdee20e2bc67ef8989adee2ad6c4cf6ca2d");

/* Source: lite/src/topic/reader-topic-special-content-feature.ts */
runtime.register("src/topic/reader-topic-special-content-feature.js", function(module, exports, require) {
	var reader_topic_special_content_feature_exports = {};
	__export(reader_topic_special_content_feature_exports, {
	  ReaderTopicSpecialContentFeature: () => ReaderTopicSpecialContentFeature,
	  normalizeReaderSolvedAnswers: () => normalizeReaderSolvedAnswers
	});
	module.exports = __toCommonJS(reader_topic_special_content_feature_exports);
	var import_html_element = require("../dom/html-element.js"), import_reader_icon = require("../components/reader-icon.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_value_record = require("../kernel/value-record.js");
	const EMPTY_RECORD = Object.freeze({});
	function text(value) {
	  return String(value ?? "").trim();
	}
	function postNumber(value) {
	  const numeric = Number((0, import_value_record.valueRecord)(value)?.post_number ?? value);
	  return Number.isSafeInteger(numeric) && numeric > 1 ? numeric : null;
	}
	function acceptedCandidates(topic, posts) {
	  const byPostNumber = /* @__PURE__ */ new Map(), add = (value) => {
	    const number = postNumber(value);
	    number !== null && byPostNumber.set(number, Object.freeze({
	      ...byPostNumber.get(number) ?? {},
	      ...(0, import_value_record.valueRecord)(value) ?? {},
	      post_number: number
	    }));
	  };
	  if (Array.isArray(topic.accepted_answers))
	    for (const answer of topic.accepted_answers) add(answer);
	  add(topic.accepted_answer);
	  for (const postValue of posts) {
	    const post = (0, import_value_record.valueRecord)(postValue);
	    post?.accepted_answer === !0 && add(post);
	  }
	  return Object.freeze([...byPostNumber.values()].sort((left, right) => Number(left.post_number) - Number(right.post_number)));
	}
	function normalizeReaderSolvedAnswers(topicValue, posts, presentation) {
	  const topic = (0, import_value_record.valueRecord)(topicValue) ?? EMPTY_RECORD, postsByNumber = /* @__PURE__ */ new Map();
	  for (const postValue of posts) {
	    const post = (0, import_value_record.valueRecord)(postValue), number = Number(post?.post_number);
	    post && Number.isSafeInteger(number) && number > 0 && postsByNumber.set(number, post);
	  }
	  return Object.freeze(
	    acceptedCandidates(topic, posts).map((candidate) => {
	      const number = Number(candidate.post_number), canonical = postsByNumber.get(number) ?? EMPTY_RECORD, username = text(candidate.username ?? canonical.username), name = text(
	        candidate.name ?? canonical.name ?? username
	      ) || "已解决回复", avatarTemplate = text(
	        candidate.avatar_template ?? canonical.avatar_template
	      );
	      return Object.freeze({
	        postNumber: number,
	        username,
	        name,
	        avatarSource: presentation.avatarSource(avatarTemplate, 32),
	        createdAt: text(
	          candidate.created_at ?? canonical.created_at
	        ),
	        cooked: text(candidate.cooked ?? canonical.cooked),
	        excerpt: text(candidate.excerpt ?? canonical.excerpt)
	      });
	    })
	  );
	}
	function specialBadges(post) {
	  const badges = [], add = (label, tone = "", title = "", icon = "") => {
	    badges.push(Object.freeze({ label, tone, title, icon }));
	  }, postType = Number(post.post_type);
	  return postType === 4 && add("私信回复"), postType === 2 && add("管理操作", "warn"), post.wiki === !0 && add("Wiki"), post.hidden === !0 && add("已隐藏", "warn"), post.deleted_at && add("已删除", "danger"), post.locked === !0 && add("已锁定", "warn"), Object.freeze(badges);
	}
	function postIdentityBadge(post) {
	  const username = text(post.username).toLocaleLowerCase();
	  if (Number(post.post_type) === 3 || username === "system")
	    return Object.freeze({
	      label: "系统",
	      title: "系统账户",
	      icon: "settings"
	    });
	  if (post.moderator === !0 || post.group_moderator === !0)
	    return Object.freeze({
	      label: "版主",
	      title: "版主",
	      icon: "shield-halved"
	    });
	  const notice = (0, import_value_record.valueRecord)(post.notice), noticeType = text(notice?.type);
	  if (noticeType === "new_user")
	    return Object.freeze({
	      label: "新用户",
	      title: "新用户,首次发帖",
	      icon: "user-plus"
	    });
	  if (noticeType === "returning_user")
	    return Object.freeze({
	      label: "回归用户",
	      title: "回归用户,久未发帖",
	      icon: "rotate-ccw"
	    });
	  if (noticeType !== "custom") return null;
	  const source = `${text(notice?.raw)} ${text(notice?.cooked)}`;
	  return /\bpremium\b/i.test(source) ? Object.freeze({ label: "Premium", title: "Premium", icon: "check" }) : Object.freeze({ label: "富可敌国", title: "富可敌国", icon: "tag" });
	}
	const SYSTEM_ACTION_LABELS = Object.freeze({
	  closed: "主题已关闭",
	  opened: "主题已重新开放",
	  archived: "主题已归档",
	  unarchived: "主题已取消归档",
	  pinned: "主题已置顶",
	  unpinned: "主题已取消置顶",
	  autoclosed: "主题已自动关闭",
	  "autoclosed.enabled": "主题已自动关闭",
	  "autoclosed.disabled": "主题已自动重新开放",
	  split_topic: "帖子已拆分到新主题",
	  merged: "主题已合并",
	  moved: "帖子已移动",
	  visible: "主题已公开",
	  invisible: "主题已隐藏",
	  "visible.enabled": "主题已公开",
	  "visible.disabled": "主题已取消公开",
	  renamed: "主题标题已修改",
	  assigned: "已指定"
	}), SYSTEM_ACTION_ICONS = Object.freeze({
	  visible: "check",
	  invisible: "eye-off",
	  "visible.enabled": "check",
	  "visible.disabled": "eye-off",
	  assigned: "user-plus"
	});
	class ReaderTopicSpecialContentFeature {
	  scope;
	  #document;
	  #session;
	  #presentation;
	  #relativeTime;
	  #navigate;
	  #renderIcon;
	  #actions;
	  #commands;
	  #descriptors;
	  #models;
	  #loadPostVotingComments;
	  #onBodyLayerChanged;
	  #onError;
	  #boundViews = /* @__PURE__ */ new WeakSet();
	  #starterViews = /* @__PURE__ */ new Set();
	  #views = /* @__PURE__ */ new Map();
	  #expandedComments = /* @__PURE__ */ new WeakSet();
	  #pendingPostIds = /* @__PURE__ */ new Map();
	  #loadingCommentPostIds = /* @__PURE__ */ new Set();
	  constructor(options) {
	    this.#document = options.document, this.#session = options.session, this.#presentation = options.presentation, this.#relativeTime = options.relativeTime, this.#navigate = options.navigate, this.#renderIcon = options.renderIcon ?? null, this.#actions = options.actions ?? null, this.#commands = options.commands ?? null, this.#descriptors = options.descriptors ?? null, this.#models = options.models ?? null, this.#loadPostVotingComments = options.loadPostVotingComments ?? null, this.#onBodyLayerChanged = options.onBodyLayerChanged ?? null, this.#onError = options.onError ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), (options.presentationChanges ?? this.#session.changes).subscribe(() => {
	      for (const view of [...this.#starterViews]) {
	        if (view.scope.destroyed) {
	          this.#starterViews.delete(view);
	          continue;
	        }
	        this.#renderSolved(view) && this.#notifyBodyLayerChanged(view);
	      }
	    }, this.scope), this.#actions?.events.subscribe((event) => {
	      const postIds = event.presentation?.postIds ?? [];
	      if (event.phase === "pending")
	        for (const postId of postIds)
	          this.#pendingPostIds.set(
	            postId,
	            (this.#pendingPostIds.get(postId) ?? 0) + 1
	          );
	      else if (event.phase === "settled")
	        for (const postId of postIds) {
	          const next = (this.#pendingPostIds.get(postId) ?? 1) - 1;
	          next > 0 ? this.#pendingPostIds.set(postId, next) : this.#pendingPostIds.delete(postId);
	        }
	      else
	        return;
	      this.#refreshInteractiveViews(new Set(postIds));
	    }, this.scope), this.scope.add(() => {
	      this.#starterViews.clear(), this.#views.clear(), this.#pendingPostIds.clear(), this.#loadingCommentPostIds.clear();
	    });
	  }
	  afterRender(postValue, view) {
	    const post = (0, import_value_record.valueRecord)(postValue) ?? EMPTY_RECORD;
	    this.#views.set(view, Number(post.post_number)), this.#bindView(view), this.#renderRootState(post, view), this.#renderSpecialBadges(post, view), this.#renderNotice(post, view), this.#renderSystemAction(post, view), this.#renderPostEvent(post, view), Number(post.post_number) === 1 && (this.#starterViews.add(view), this.#renderSolved(view)), this.#renderPostVoting(post, view);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #bindView(view) {
	    this.#boundViews.has(view) || (this.#boundViews.add(view), view.scope.listen(view.slots.root, "click", (event) => {
	      this.#onClick(view, event);
	    }), view.scope.listen(view.slots.root, "submit", (event) => {
	      this.#onSubmit(view, event);
	    }), view.scope.add(() => {
	      this.#starterViews.delete(view), this.#views.delete(view);
	    }));
	  }
	  #refreshInteractiveViews(postIds) {
	    for (const [view, number] of [...this.#views]) {
	      if (view.scope.destroyed) {
	        this.#views.delete(view);
	        continue;
	      }
	      const post = this.#session.postByNumber(number);
	      if (!post) continue;
	      const postId = Number(post.id);
	      if (postIds && !postIds.has(postId)) continue;
	      const source = (0, import_value_record.valueRecord)(post) ?? EMPTY_RECORD;
	      this.#renderInteractiveBody(source, view) && this.#notifyBodyLayerChanged(view);
	    }
	  }
	  #currentPost(view) {
	    const number = this.#views.get(view) ?? view.postNumber;
	    return this.#session.postByNumber(number) ?? null;
	  }
	  #onClick(view, event) {
	    const target = event.target, solved = target?.closest(
	      "[data-reader-solved-post-number]"
	    );
	    if (solved && view.slots.bodyLayer.contains(solved)) {
	      const number = Number(solved.dataset.readerSolvedPostNumber);
	      if (!Number.isSafeInteger(number) || number < 2) return;
	      event.preventDefault(), new Promise((resolve) => {
	        resolve(this.#navigate(number));
	      }).catch((error) => {
	        !this.scope.destroyed && !view.scope.destroyed && this.#onError(error);
	      });
	      return;
	    }
	    const post = this.#currentPost(view);
	    if (!post) return;
	    const source = (0, import_value_record.valueRecord)(post) ?? EMPTY_RECORD, postId = Number(source.id);
	    if (!Number.isSafeInteger(postId) || postId < 1) return;
	    if (target?.closest(
	      "[data-pv-comments-toggle]"
	    )) {
	      event.preventDefault(), this.#expandedComments.has(view) ? this.#expandedComments.delete(view) : this.#expandedComments.add(view), this.#renderPostVoting(source, view), this.#notifyBodyLayerChanged(view);
	      return;
	    }
	    if (target?.closest(
	      "[data-pv-comments-more]"
	    )) {
	      event.preventDefault(), this.#loadMorePostVotingComments(post, view);
	      return;
	    }
	    const vote = target?.closest("[data-pv-vote]");
	    if (vote) {
	      event.preventDefault();
	      const direction = text(vote.dataset.pvVote);
	      if (direction !== "up" && direction !== "down") return;
	      const current = text(source.post_voting_user_voted_direction);
	      this.#runAction(() => {
	        if (!this.#descriptors || !this.#commands)
	          throw new Error("Post Voting 动作端口尚未就绪");
	        const mutation = this.#descriptors.postVotingVote({
	          postId,
	          direction,
	          remove: current === direction
	        });
	        return this.#commands.postVotingVote(postId, mutation);
	      });
	      return;
	    }
	    const commentVote = target?.closest(
	      "[data-pv-comment-vote]"
	    );
	    if (commentVote) {
	      event.preventDefault();
	      const commentNode = commentVote.closest(
	        "[data-pv-comment-id]"
	      ), commentId = Number(commentNode?.dataset.pvCommentId);
	      if (!Number.isSafeInteger(commentId) || commentId < 1) return;
	      const comment = this.#postVotingComments(source).find((entry) => Number(entry.id) === commentId);
	      this.#runAction(() => {
	        if (!this.#descriptors || !this.#commands)
	          throw new Error("Post Voting 评论动作端口尚未就绪");
	        const remove = comment?.user_voted === !0, mutation = this.#descriptors.postVotingCommentVote({
	          commentId,
	          remove
	        });
	        return this.#commands.postVotingCommentVote(
	          postId,
	          commentId,
	          remove,
	          mutation
	        );
	      });
	      return;
	    }
	    const attendance = target?.closest(
	      "[data-event-status]"
	    );
	    if (!attendance) return;
	    event.preventDefault();
	    const status = text(attendance.dataset.eventStatus), eventData = (0, import_value_record.valueRecord)(source.event), eventId = Number(eventData?.id) || postId;
	    !eventData || !Number.isSafeInteger(eventId) || eventId < 1 || this.#runAction(() => {
	      if (!this.#descriptors || !this.#commands || !this.#models)
	        throw new Error("Discourse 活动动作端口尚未就绪");
	      const invitee = (0, import_value_record.valueRecord)(eventData.watching_invitee), eventModel = this.#models.createPostEvent(eventData, postId), mutation = this.#descriptors.eventAttendance({
	        eventId,
	        event: eventModel,
	        status,
	        alreadyInvited: Number(invitee?.id) > 0
	      });
	      return this.#commands.eventAttendance(postId, mutation);
	    });
	  }
	  #onSubmit(view, event) {
	    const form = event.target?.closest(
	      ".ldp-pv-comment-form"
	    );
	    if (!form || !view.slots.root.contains(form)) return;
	    event.preventDefault();
	    const post = this.#currentPost(view), source = (0, import_value_record.valueRecord)(post), postId = Number(source?.id);
	    if (!post || !Number.isSafeInteger(postId) || postId < 1) return;
	    const input = form.querySelector(
	      ".ldp-pv-comment-input"
	    ), raw = text(input?.value);
	    raw && this.#runAction(() => {
	      if (!this.#descriptors || !this.#commands)
	        throw new Error("Post Voting 评论动作端口尚未就绪");
	      const mutation = this.#descriptors.postVotingCommentCreate({
	        postId,
	        raw
	      });
	      return this.#commands.postVotingCommentCreate(postId, mutation);
	    }).then(() => {
	      !this.scope.destroyed && !view.scope.destroyed && input && (input.value = "");
	    });
	  }
	  async #runAction(create) {
	    try {
	      if (!this.#actions) throw new Error("楼层动作控制器尚未就绪");
	      await this.#actions.dispatch(create());
	    } catch (error) {
	      this.scope.destroyed || this.#onError(error);
	    }
	  }
	  async #loadMorePostVotingComments(post, view) {
	    const source = (0, import_value_record.valueRecord)(post) ?? EMPTY_RECORD, postId = Number(source.id);
	    if (!this.#loadPostVotingComments || !Number.isSafeInteger(postId) || postId < 1 || this.#loadingCommentPostIds.has(postId)) return;
	    const loaded = this.#postVotingComments(source), afterCommentId = Number(loaded.at(-1)?.id) || 0;
	    this.#loadingCommentPostIds.add(postId), this.#renderPostVoting(source, view), this.#notifyBodyLayerChanged(view);
	    try {
	      const payload = await this.#loadPostVotingComments(
	        postId,
	        afterCommentId
	      ), payloadRecord = (0, import_value_record.valueRecord)(payload), incomingSource = Array.isArray(payload) ? payload : Array.isArray(payloadRecord?.comments) ? payloadRecord.comments : [], merged = /* @__PURE__ */ new Map();
	      for (const comment of [...loaded, ...incomingSource]) {
	        const candidate = (0, import_value_record.valueRecord)(comment), id = Number(candidate?.id);
	        candidate && Number.isSafeInteger(id) && id > 0 && merged.set(id, Object.freeze({ ...candidate }));
	      }
	      this.#session.ingestPosts([
	        {
	          ...source,
	          post_voting_comments: Object.freeze([...merged.values()])
	        }
	      ], "action-response");
	    } catch (error) {
	      !this.scope.destroyed && !view.scope.destroyed && this.#onError(error);
	    } finally {
	      this.#loadingCommentPostIds.delete(postId);
	      const current = this.#session.postByNumber(Number(source.post_number));
	      current && !view.scope.destroyed && (this.#renderPostVoting((0, import_value_record.valueRecord)(current) ?? EMPTY_RECORD, view), this.#notifyBodyLayerChanged(view));
	    }
	  }
	  #renderInteractiveBody(post, view) {
	    const selector = ":scope > :is(.ldp-pv-votes,.ldp-pv-comments,.ldp-event-card)", hadInteractiveBody = !!view.slots.bodyLayer.querySelector(selector);
	    return this.#renderPostEvent(post, view), this.#renderPostVoting(post, view), hadInteractiveBody || !!view.slots.bodyLayer.querySelector(selector);
	  }
	  #notifyBodyLayerChanged(view) {
	    if (!(this.scope.destroyed || view.scope.destroyed))
	      try {
	        this.#onBodyLayerChanged?.(view);
	      } catch (error) {
	        this.#onError(error);
	      }
	  }
	  #postVotingComments(post) {
	    const source = Array.isArray(post.post_voting_comments) ? post.post_voting_comments : Array.isArray(post.comments) ? post.comments : [];
	    return Object.freeze(source.map((value) => (0, import_value_record.valueRecord)(value)).filter((value) => value !== null));
	  }
	  #renderSolved(view) {
	    if (view.scope.destroyed) return !1;
	    const answers = normalizeReaderSolvedAnswers(
	      this.#session.topic,
	      this.#session.cachedPosts(),
	      this.#presentation
	    ), previous = view.slots.bodyLayer.querySelector(
	      ":scope > .ldp-solved-card"
	    );
	    if (!answers.length)
	      return previous?.remove(), previous !== null;
	    const card = (0, import_html_element.htmlElement)(this.#document, "section", "ldp-solved-card"), heading = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-solved-head"), icon = this.#icon("check");
	    icon && heading.append(icon);
	    const headingLabel = answers.length > 1 ? `✓ 已解决 · ${answers.length} 个答案` : "✓ 已解决", headingText = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-solved-label",
	      icon ? headingLabel.replace(/^✓\s*/, "") : headingLabel
	    );
	    heading.append(headingText), card.append(heading);
	    for (const answer of answers)
	      card.append(this.#answerNode(answer));
	    return previous?.isEqualNode(card) ? !1 : (previous ? previous.replaceWith(card) : view.slots.bodyLayer.append(card), !0);
	  }
	  #answerNode(answer) {
	    const body = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-solved-body");
	    body.dataset.solvedPostNumber = String(answer.postNumber);
	    const authorRow = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-solved-author-row"
	    ), profileHref = this.#presentation.userHref(answer.username);
	    if (answer.avatarSource) {
	      const avatarLink = this.#document.createElement(
	        profileHref ? "a" : "span"
	      );
	      avatarLink.className = "ldp-user-link", profileHref && avatarLink.setAttribute("href", profileHref), answer.username && (avatarLink.dataset.userCard = answer.username);
	      const avatar = (0, import_html_element.htmlElement)(this.#document, "img", "ldp-solved-avatar");
	      avatar.src = answer.avatarSource, avatar.alt = "", avatar.loading = "lazy", avatar.decoding = "async", avatarLink.append(avatar), authorRow.append(avatarLink);
	    }
	    const author = this.#document.createElement(
	      profileHref ? "a" : "span"
	    );
	    if (author.className = "ldp-user-link ldp-solved-author", author.textContent = answer.name, profileHref && author.setAttribute("href", profileHref), answer.username && (author.dataset.userCard = answer.username), authorRow.append(author), answer.username) {
	      const username = (0, import_html_element.htmlElement)(
	        this.#document,
	        "span",
	        "ldp-solved-username",
	        `@${answer.username}`
	      );
	      authorRow.append(username);
	    }
	    const relative = answer.createdAt ? this.#relativeTime(answer.createdAt) : "";
	    if (relative) {
	      const time = (0, import_html_element.htmlElement)(this.#document, "span", "", `· ${relative}`);
	      authorRow.append(time);
	    }
	    const floor = (0, import_html_element.htmlElement)(
	      this.#document,
	      "button",
	      "ldp-solved-floor ldp-jump-self",
	      `#${answer.postNumber}`
	    );
	    floor.type = "button", floor.dataset.readerSolvedPostNumber = String(answer.postNumber), floor.setAttribute("aria-label", `跳到楼层 #${answer.postNumber}`), authorRow.append(floor), body.append(authorRow);
	    const excerpt = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-solved-excerpt ldp-content cooked"
	    );
	    answer.cooked ? excerpt.innerHTML = answer.cooked : excerpt.textContent = answer.excerpt || "查看被采纳的完整回复。", body.append(excerpt);
	    const jump = (0, import_html_element.htmlElement)(
	      this.#document,
	      "button",
	      "ldp-solved-jump ldp-jump-self",
	      "阅读更多"
	    );
	    return jump.type = "button", jump.dataset.readerSolvedPostNumber = String(answer.postNumber), body.append(jump), body;
	  }
	  #renderPostVoting(post, view) {
	    view.slots.bodyLayer.querySelector(":scope > .ldp-pv-votes")?.remove(), view.slots.bodyLayer.querySelector(":scope > .ldp-pv-comments")?.remove();
	    const isAnswer = ((0, import_value_record.valueRecord)(this.#session.topic) ?? EMPTY_RECORD).is_post_voting === !0 && Number(post.post_number) !== 1;
	    if (view.slots.root.classList.toggle("ldp-post-voting-answer", isAnswer), !isAnswer) return;
	    const postId = Number(post.id), pending = this.#pendingPostIds.has(postId), votes = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-pv-votes"), direction = text(post.post_voting_user_voted_direction);
	    for (const [value, label, iconName] of [
	      ["up", "赞同", "chevron-up"],
	      ["down", "反对", "chevron-down"]
	    ]) {
	      const button = this.#document.createElement("button");
	      button.type = "button", button.className = `ldp-pv-vote${direction === value ? " on" : ""}`, button.dataset.pvVote = value, button.setAttribute("aria-label", label), button.disabled = pending || !this.#actions;
	      const icon = this.#icon(iconName);
	      if (icon ? button.append(icon) : button.textContent = value === "up" ? "↑" : "↓", votes.append(button), value === "up") {
	        const score = (0, import_html_element.htmlElement)(
	          this.#document,
	          "span",
	          "ldp-pv-score",
	          String(Math.max(0, Number(post.post_voting_vote_count) || 0))
	        );
	        votes.append(score);
	      }
	    }
	    view.slots.bodyLayer.prepend(votes);
	    const comments = this.#postVotingComments(post), count = Math.max(
	      0,
	      Number(post.comments_count) || comments.length
	    ), host = (0, import_html_element.htmlElement)(this.#document, "section", "ldp-pv-comments"), toggle = (0, import_html_element.htmlElement)(
	      this.#document,
	      "button",
	      "ldp-pv-comments-toggle"
	    );
	    toggle.type = "button", toggle.dataset.pvCommentsToggle = "";
	    const expanded = this.#expandedComments.has(view);
	    toggle.setAttribute("aria-expanded", String(expanded)), toggle.setAttribute(
	      "aria-label",
	      expanded ? "收起评论" : "展开评论"
	    );
	    const toggleIcon = this.#icon("message-square");
	    toggleIcon && toggle.append(toggleIcon);
	    const toggleCount = (0, import_html_element.htmlElement)(this.#document, "span", "", String(count));
	    toggle.append(toggleCount), host.append(toggle);
	    const body = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-pv-comments-body");
	    body.hidden = !expanded;
	    for (const comment of comments)
	      body.append(this.#postVotingCommentNode(comment, pending));
	    if (comments.length < count && this.#loadPostVotingComments) {
	      const more = (0, import_html_element.htmlElement)(
	        this.#document,
	        "button",
	        "ldp-pv-comments-more"
	      );
	      more.type = "button", more.dataset.pvCommentsMore = "", more.disabled = this.#loadingCommentPostIds.has(postId), more.textContent = more.disabled ? "正在加载…" : "加载更多评论", body.append(more);
	    }
	    if (this.#models?.currentUser()) {
	      const form = (0, import_html_element.htmlElement)(this.#document, "form", "ldp-pv-comment-form"), input = (0, import_html_element.htmlElement)(this.#document, "input", "ldp-pv-comment-input");
	      input.name = "raw", input.autocomplete = "off", input.placeholder = "写评论…", input.required = !0, input.disabled = pending;
	      const submit = (0, import_html_element.htmlElement)(
	        this.#document,
	        "button",
	        "ldp-pv-comment-submit",
	        "发送"
	      );
	      submit.type = "submit", submit.disabled = pending, form.append(input, submit), body.append(form);
	    }
	    host.append(body), view.slots.bodyLayer.append(host);
	  }
	  #postVotingCommentNode(comment, pending) {
	    const user = (0, import_value_record.valueRecord)(comment.user) ?? EMPTY_RECORD, username = text(comment.username ?? user.username), name = text(comment.name ?? user.name ?? username) || "用户", node = (0, import_html_element.htmlElement)(this.#document, "article", "ldp-pv-comment"), commentId = Number(comment.id);
	    Number.isSafeInteger(commentId) && commentId > 0 && (node.dataset.pvCommentId = String(commentId));
	    const avatarTemplate = text(
	      user.avatar_template ?? comment.avatar_template
	    ), avatarSource = this.#presentation.avatarSource(avatarTemplate, 28), avatarHost = this.#document.createElement(
	      username ? "a" : "span"
	    );
	    if (avatarHost.className = "ldp-user-link", username) {
	      avatarHost.dataset.userCard = username;
	      const href = this.#presentation.userHref(username);
	      href && avatarHost.setAttribute("href", href);
	    }
	    if (avatarSource) {
	      const avatar = (0, import_html_element.htmlElement)(
	        this.#document,
	        "img",
	        "ldp-pv-comment-avatar"
	      );
	      avatar.src = avatarSource, avatar.alt = "", avatar.loading = "lazy", avatar.decoding = "async", avatarHost.append(avatar);
	    }
	    node.append(avatarHost);
	    const content = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-pv-comment-body"), meta = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-pv-comment-meta",
	      [
	        name,
	        username ? `@${username}` : "",
	        text(comment.created_at) ? this.#relativeTime(text(comment.created_at)) : ""
	      ].filter(Boolean).join(" · ")
	    ), cooked = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-content cooked"), cookedValue = text(comment.cooked);
	    cookedValue ? cooked.innerHTML = cookedValue : cooked.textContent = text(comment.raw), content.append(meta, cooked), node.append(content);
	    const vote = this.#document.createElement("button");
	    vote.type = "button", vote.className = `ldp-pv-comment-vote${comment.user_voted === !0 ? " on" : ""}`, vote.dataset.pvCommentVote = "", vote.setAttribute("aria-label", "赞同评论"), vote.disabled = pending || !this.#actions || !Number.isSafeInteger(commentId) || commentId < 1;
	    const icon = this.#icon("chevron-up");
	    icon && vote.append(icon);
	    const count = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "",
	      String(Math.max(0, Number(comment.post_voting_vote_count) || 0))
	    );
	    return vote.append(count), node.append(vote), node;
	  }
	  #renderPostEvent(post, view) {
	    view.slots.bodyLayer.querySelector(":scope > .ldp-event-card")?.remove();
	    const event = (0, import_value_record.valueRecord)(post.event);
	    if (!event) return;
	    const postId = Number(post.id), pending = this.#pendingPostIds.has(postId), card = (0, import_html_element.htmlElement)(this.#document, "section", "ldp-event-card"), title = (0, import_html_element.htmlElement)(
	      this.#document,
	      "h3",
	      "ldp-event-title",
	      text(event.name) || "活动"
	    );
	    card.append(title);
	    const grid = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-event-grid"), dateLabel = this.#eventDateLabel(event);
	    if (dateLabel) {
	      const date = this.#document.createElement("div"), strong = (0, import_html_element.htmlElement)(this.#document, "b", "", dateLabel);
	      date.append(strong);
	      const timezone = text(event.timezone);
	      if (timezone) {
	        const zone = (0, import_html_element.htmlElement)(
	          this.#document,
	          "span",
	          "ldp-event-meta",
	          ` ${timezone}`
	        );
	        date.append(zone);
	      }
	      grid.append(date);
	    }
	    const locationRecord = (0, import_value_record.valueRecord)(event.location), location = typeof event.location == "string" ? text(event.location) : text(
	      locationRecord?.name ?? locationRecord?.address ?? locationRecord?.display
	    );
	    if (location) {
	      const place = (0, import_html_element.htmlElement)(
	        this.#document,
	        "div",
	        "",
	        `地点:${location}`
	      );
	      grid.append(place);
	    }
	    const description = text(event.description_html);
	    if (description) {
	      const detail = (0, import_html_element.htmlElement)(this.#document, "div", "cooked");
	      detail.innerHTML = description, grid.append(detail);
	    }
	    const stats = (0, import_value_record.valueRecord)(event.stats) ?? EMPTY_RECORD, statsNode = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-event-meta"), going = Math.max(0, Number(stats.going) || 0), interested = Math.max(0, Number(stats.interested) || 0), maxAttendees = Math.max(0, Number(event.max_attendees) || 0);
	    statsNode.textContent = [
	      `参加 ${going}`,
	      `感兴趣 ${interested}`,
	      maxAttendees ? `名额 ${going}/${maxAttendees}` : "",
	      event.is_ongoing === !0 ? "进行中" : "",
	      event.is_expired === !0 ? "已结束" : ""
	    ].filter(Boolean).join(" · "), grid.append(statsNode), card.append(grid);
	    const actions = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-event-actions"), watching = (0, import_value_record.valueRecord)(event.watching_invitee) ?? EMPTY_RECORD, currentStatus = text(watching.status), closed = event.is_closed === !0 || event.is_expired === !0 || event.can_update_attendance === !1;
	    for (const [status, label] of [
	      ["going", "参加"],
	      ["interested", "感兴趣"],
	      ["not_going", "不参加"]
	    ]) {
	      const button = (0, import_html_element.htmlElement)(
	        this.#document,
	        "button",
	        `ldp-event-action${currentStatus === status ? " on" : ""}`,
	        label
	      );
	      button.type = "button", button.dataset.eventStatus = status, button.disabled = closed || pending || !this.#actions || !this.#models, actions.append(button);
	    }
	    const calendarUrl = text(event.ics_url ?? event.calendar_url);
	    if (/^(?:https?:\/\/|\/)/i.test(calendarUrl)) {
	      const calendar = (0, import_html_element.htmlElement)(
	        this.#document,
	        "a",
	        "ldp-event-ics",
	        "下载日历"
	      );
	      calendar.href = calendarUrl, calendar.download = "", actions.append(calendar);
	    }
	    card.append(actions), view.slots.bodyLayer.append(card);
	  }
	  #eventDateLabel(event) {
	    const options = {
	      year: "numeric",
	      month: "short",
	      day: "numeric",
	      ...event.all_day === !0 ? {} : { hour: "2-digit", minute: "2-digit" }
	    }, format = (value) => {
	      const date = new Date(text(value));
	      return Number.isFinite(date.getTime()) ? date.toLocaleString("zh-CN", options) : "";
	    };
	    return [format(event.starts_at), format(event.ends_at)].filter(Boolean).join(" — ");
	  }
	  #icon(name) {
	    return (0, import_reader_icon.renderReaderIcon)(this.#document, name, this.#renderIcon);
	  }
	  #renderRootState(post, view) {
	    const postType = Number(post.post_type), actionCode = text(post.action_code);
	    view.slots.root.classList.toggle("ldp-whisper", postType === 4), view.slots.root.classList.toggle(
	      "ldp-system-post",
	      postType === 2 || postType === 3
	    ), view.slots.root.classList.toggle(
	      "ldp-system-action-compact",
	      (postType === 2 || postType === 3) && actionCode === "assigned"
	    ), this.#renderIdentityBadge(post, view);
	  }
	  #renderIdentityBadge(post, view) {
	    view.slots.header.querySelector(":scope > .ldp-new-user-badge")?.remove();
	    const noticeType = text((0, import_value_record.valueRecord)(post.notice)?.type);
	    view.slots.root.classList.toggle(
	      "ldp-new-user",
	      noticeType === "new_user"
	    );
	    const identity = postIdentityBadge(post);
	    if (!identity) return;
	    const badge = (0, import_html_element.htmlElement)(
	      this.#document,
	      "span",
	      "ldp-new-user-badge"
	    );
	    badge.title = identity.title, badge.setAttribute("role", "img"), badge.setAttribute("aria-label", identity.title), badge.append(
	      this.#icon(identity.icon),
	      (0, import_html_element.htmlElement)(this.#document, "span", "", identity.label)
	    );
	    const username = view.slots.header.querySelector(":scope > .ldp-user");
	    username ? username.after(badge) : view.slots.header.append(badge);
	  }
	  #renderSpecialBadges(post, view) {
	    view.slots.bodyLayer.querySelector(":scope > .ldp-special-badges")?.remove();
	    const badges = specialBadges(post);
	    if (!badges.length) return;
	    const host = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-special-badges");
	    for (const badge of badges) {
	      const node = this.#document.createElement("span");
	      node.className = `ldp-special-badge ${badge.tone}`.trim(), badge.title && (node.title = badge.title), badge.icon && node.append(this.#icon(badge.icon));
	      const label = (0, import_html_element.htmlElement)(this.#document, "span", "", badge.label);
	      node.append(label), host.append(node);
	    }
	    view.slots.bodyLayer.prepend(host);
	  }
	  #renderNotice(post, view) {
	    view.slots.bodyLayer.querySelector(":scope > .ldp-special-notice")?.remove();
	    const notice = (0, import_value_record.valueRecord)(post.notice), noticeType = text(notice?.type);
	    if (["new_user", "returning_user", "custom"].includes(noticeType))
	      return;
	    const label = typeof post.notice == "string" ? text(post.notice) : text(notice?.text ?? noticeType);
	    if (!label) return;
	    const node = (0, import_html_element.htmlElement)(
	      this.#document,
	      "div",
	      "ldp-special-notice",
	      label
	    );
	    view.slots.bodyLayer.prepend(node);
	  }
	  #renderSystemAction(post, view) {
	    view.slots.bodyLayer.querySelector(":scope > .ldp-system-action")?.remove();
	    const postType = Number(post.post_type), actionCode = text(post.action_code);
	    if (postType !== 2 && postType !== 3 || !actionCode) return;
	    const node = (0, import_html_element.htmlElement)(this.#document, "div", "ldp-system-action");
	    node.append(this.#icon(
	      SYSTEM_ACTION_ICONS[actionCode] ?? "settings"
	    ));
	    const label = SYSTEM_ACTION_LABELS[actionCode] ?? `系统操作:${actionCode.replace(/[._]/g, " ")}`, content = this.#document.createElement("span");
	    if (content.append(this.#document.createTextNode(label)), actionCode === "assigned") {
	      const who = (0, import_value_record.valueRecord)(post.action_code_who), username = text(who?.username ?? post.action_code_who).replace(/^@/, "");
	      if (username) {
	        content.append(this.#document.createTextNode("给 "));
	        const user = (0, import_html_element.htmlElement)(
	          this.#document,
	          "a",
	          "ldp-user-link ldp-system-action-user",
	          `@${username}`
	        );
	        user.dataset.userCard = username;
	        const href = this.#presentation.userHref(username);
	        href && (user.href = href), content.append(user);
	      }
	    }
	    node.append(content), view.slots.bodyLayer.prepend(node);
	  }
	}
}, "a25a0e582f7c7356abc805a738c93b073a947f345738805c07ae16c7c486dfc4");

/* Source: lite/src/topic/reader-topic-timeline-controller.ts */
runtime.register("src/topic/reader-topic-timeline-controller.js", function(module, exports, require) {
	var reader_topic_timeline_controller_exports = {};
	__export(reader_topic_timeline_controller_exports, {
	  ReaderTopicTimelineController: () => ReaderTopicTimelineController
	});
	module.exports = __toCommonJS(reader_topic_timeline_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function normalizedTotal(value) {
	  const numeric = Math.floor(Number(value));
	  return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : 1;
	}
	function normalizedNavigablePosts(values, totalPostCount) {
	  if (!values?.length) return null;
	  const postNumbers = /* @__PURE__ */ new Set();
	  for (const value of values) {
	    const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(value);
	    postNumber !== null && postNumber <= totalPostCount && postNumbers.add(postNumber);
	  }
	  return postNumbers.size ? Object.freeze([...postNumbers].sort((left, right) => left - right)) : null;
	}
	function clampedPostNumber(value, totalPostCount) {
	  return (0, import_identifiers.discoursePostReference)({
	    post_number: Math.max(1, Math.min(totalPostCount, Math.floor(value) || 1))
	  }).postNumber;
	}
	function snapshotsEqual(left, right) {
	  if (left.currentPostNumber !== right.currentPostNumber || left.totalPostCount !== right.totalPostCount || left.progress !== right.progress || left.pendingPostNumber !== right.pendingPostNumber)
	    return !1;
	  const leftPosts = left.navigablePostNumbers, rightPosts = right.navigablePostNumbers;
	  return leftPosts === rightPosts || leftPosts !== null && rightPosts !== null && leftPosts.length === rightPosts.length && leftPosts.every((postNumber, index) => postNumber === rightPosts[index]);
	}
	class ReaderTopicTimelineController {
	  scope;
	  changes = new import_signal.Signal();
	  #navigation;
	  #readTotalPostCount;
	  #readNavigablePostNumbers;
	  #onError;
	  #snapshot;
	  #jumpEpoch = 0;
	  constructor(options) {
	    this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#navigation = options.navigation, this.#readTotalPostCount = options.readTotalPostCount, this.#readNavigablePostNumbers = options.readNavigablePostNumbers ?? (() => null), this.#onError = options.onError ?? (() => {
	    }), this.#snapshot = this.#derive(
	      options.initialPostNumber ?? 1,
	      null
	    ), this.#navigation.changes.subscribe((result) => {
	      result.status === "revealed" && this.#commit(
	        result.rootPostNumber ?? result.postNumber,
	        this.#snapshot.pendingPostNumber
	      );
	    }, this.scope), this.scope.add(() => {
	      this.#jumpEpoch += 1, this.changes.clear();
	    });
	  }
	  get snapshot() {
	    return this.#snapshot;
	  }
	  refresh() {
	    return this.#commit(
	      this.#snapshot.currentPostNumber,
	      this.#snapshot.pendingPostNumber
	    );
	  }
	  syncVisiblePost(postNumber, options = {}) {
	    const navigable = this.#snapshot.navigablePostNumbers, boundaryPostNumber = options.atStart ? navigable?.[0] ?? 1 : options.atEnd ? navigable?.at(-1) ?? this.#snapshot.totalPostCount : postNumber;
	    return this.#commitCachedSources(
	      boundaryPostNumber,
	      this.#snapshot.pendingPostNumber
	    );
	  }
	  progressFor(postNumber) {
	    const target = clampedPostNumber(postNumber, this.#snapshot.totalPostCount), posts = this.#snapshot.navigablePostNumbers;
	    if (posts) {
	      const index = posts.indexOf(target);
	      if (index >= 0) return posts.length > 1 ? index / (posts.length - 1) : 0;
	    }
	    return this.#snapshot.totalPostCount > 1 ? (target - 1) / (this.#snapshot.totalPostCount - 1) : 0;
	  }
	  targetAtRatio(ratio) {
	    const normalizedRatio = Number.isFinite(ratio) ? Math.max(0, Math.min(1, ratio)) : 0, posts = this.#snapshot.navigablePostNumbers;
	    return posts ? posts[Math.round(normalizedRatio * (posts.length - 1))] : clampedPostNumber(
	      Math.round(1 + normalizedRatio * (this.#snapshot.totalPostCount - 1)),
	      this.#snapshot.totalPostCount
	    );
	  }
	  targetByStep(currentPostNumber, delta) {
	    const posts = this.#snapshot.navigablePostNumbers;
	    if (posts) {
	      const current = (0, import_identifiers.tryDiscoursePostNumber)(currentPostNumber), currentIndex = current === null ? -1 : posts.indexOf(current), baseIndex = currentIndex >= 0 ? currentIndex : 0, nextIndex = Number.isFinite(delta) ? Math.max(
	        0,
	        Math.min(posts.length - 1, baseIndex + Math.trunc(delta))
	      ) : delta < 0 ? 0 : posts.length - 1;
	      return posts[nextIndex];
	    }
	    const numericDelta = Number.isFinite(delta) ? Math.trunc(delta) : delta < 0 ? -this.#snapshot.totalPostCount : this.#snapshot.totalPostCount;
	    return clampedPostNumber(
	      currentPostNumber + numericDelta,
	      this.#snapshot.totalPostCount
	    );
	  }
	  validateInput(rawValue) {
	    const total = this.#snapshot.totalPostCount;
	    if (!rawValue)
	      return Object.freeze({
	        postNumber: null,
	        message: `请输入楼层号(1–${total})`
	      });
	    if (!/^\d+$/.test(rawValue))
	      return Object.freeze({
	        postNumber: null,
	        message: "仅支持十进制整数"
	      });
	    const value = Number(rawValue);
	    return Number.isSafeInteger(value) ? value < 1 || value > total ? Object.freeze({
	      postNumber: null,
	      message: `超出范围,请输入 1–${total}`
	    }) : Object.freeze({
	      postNumber: (0, import_identifiers.discoursePostReference)({ post_number: value }).postNumber,
	      message: ""
	    }) : Object.freeze({
	      postNumber: null,
	      message: "楼层号数值过大"
	    });
	  }
	  async jumpTo(postNumber, options = {}) {
	    if (this.scope.destroyed)
	      throw new Error("ReaderTopicTimelineController 已销毁");
	    const target = (0, import_identifiers.discoursePostReference)({ post_number: postNumber }).postNumber;
	    if (target > this.#snapshot.totalPostCount)
	      throw new RangeError(`目标楼层超出范围:1–${this.#snapshot.totalPostCount}`);
	    const epoch = ++this.#jumpEpoch;
	    this.#commit(this.#snapshot.currentPostNumber, target);
	    try {
	      return await this.#navigation.navigate({
	        postNumber: target,
	        source: "timeline",
	        ...options.alignment === void 0 ? {} : { alignment: options.alignment },
	        ...options.focus === void 0 ? {} : { focus: options.focus },
	        ...options.highlight === void 0 ? {} : { highlight: options.highlight }
	      });
	    } finally {
	      epoch === this.#jumpEpoch && !this.scope.destroyed && this.#commit(this.#snapshot.currentPostNumber, null);
	    }
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #derive(currentPostNumber, pendingPostNumber) {
	    const totalPostCount = normalizedTotal(this.#readTotalPostCount()), navigablePostNumbers = normalizedNavigablePosts(
	      this.#readNavigablePostNumbers(),
	      totalPostCount
	    );
	    return this.#deriveFromSources(
	      currentPostNumber,
	      pendingPostNumber,
	      totalPostCount,
	      navigablePostNumbers
	    );
	  }
	  #deriveFromSources(currentPostNumber, pendingPostNumber, totalPostCount, navigablePostNumbers) {
	    const current = clampedPostNumber(currentPostNumber, totalPostCount), progress = navigablePostNumbers?.includes(current) ? navigablePostNumbers.length > 1 ? navigablePostNumbers.indexOf(current) / (navigablePostNumbers.length - 1) : 0 : totalPostCount > 1 ? (current - 1) / (totalPostCount - 1) : 0;
	    return Object.freeze({
	      currentPostNumber: current,
	      totalPostCount,
	      progress,
	      pendingPostNumber: pendingPostNumber !== null && pendingPostNumber <= totalPostCount ? pendingPostNumber : null,
	      navigablePostNumbers
	    });
	  }
	  #commitCachedSources(currentPostNumber, pendingPostNumber) {
	    return this.#accept(this.#deriveFromSources(
	      currentPostNumber,
	      pendingPostNumber,
	      this.#snapshot.totalPostCount,
	      this.#snapshot.navigablePostNumbers
	    ));
	  }
	  #commit(currentPostNumber, pendingPostNumber) {
	    return this.#accept(this.#derive(currentPostNumber, pendingPostNumber));
	  }
	  #accept(next) {
	    if (snapshotsEqual(this.#snapshot, next)) return this.#snapshot;
	    this.#snapshot = next;
	    for (const error of this.changes.emit(next)) this.#onError(error);
	    return next;
	  }
	}
}, "723fab465feaadd0631af235a22c01453df3f315f1af2dec57f07641496dca90");

/* Source: lite/src/topic/reader-topic-timeline-view.ts */
runtime.register("src/topic/reader-topic-timeline-view.js", function(module, exports, require) {
	var reader_topic_timeline_view_exports = {};
	__export(reader_topic_timeline_view_exports, {
	  ReaderTopicTimelineView: () => ReaderTopicTimelineView,
	  readerTimelineDateLabel: () => readerTimelineDateLabel
	});
	module.exports = __toCommonJS(reader_topic_timeline_view_exports);
	var import_lifecycle = require("../kernel/lifecycle.js");
	function normalizedPreferences(value) {
	  const numeric = Math.floor(Number(value.pageStep));
	  return Object.freeze({
	    pageStep: Number.isSafeInteger(numeric) && numeric > 0 ? Math.min(64, numeric) : 16
	  });
	}
	function normalizedInset(value) {
	  const numeric = Number(value);
	  return Number.isFinite(numeric) && numeric >= 0 ? numeric : 10;
	}
	function readerTimelineDateLabel(timestamp) {
	  if (!timestamp) return "";
	  const date = new Date(timestamp);
	  return Number.isFinite(date.getTime()) ? `${date.getFullYear()} 年
${date.getMonth() + 1} 月 ${date.getDate()} 日` : "";
	}
	function pointerCoordinate(event) {
	  const value = Number(event.clientY);
	  return Number.isFinite(value) ? value : 0;
	}
	function pointerIdentifier(event) {
	  const value = Number(event.pointerId);
	  return Number.isFinite(value) ? value : 0;
	}
	function eventPathIncludes(event, node) {
	  const composedPath = event.composedPath;
	  if (typeof composedPath == "function")
	    return composedPath.call(event).includes(node);
	  const target = event.target;
	  return target !== null && typeof target == "object" && "nodeType" in target && node.contains(target);
	}
	class ReaderTopicTimelineView {
	  scope;
	  #controller;
	  #elements;
	  #readCreatedAt;
	  #readLatestReplyAt;
	  #formatRelative;
	  #frameScheduler;
	  #animationFrameScheduler;
	  #scheduleTimer;
	  #cancelTimer;
	  #now;
	  #prefersReducedMotion;
	  #trackTopInset;
	  #trackBottomInset;
	  #notify;
	  #onError;
	  #lensElements = /* @__PURE__ */ new Map();
	  #spareLensElements = [];
	  #preferences;
	  #previewFrame = 0;
	  #jumpAnimationFrame = 0;
	  #jumpAnimationTimer = 0;
	  #jumpAnimationEpoch = 0;
	  #previewClientY = 0;
	  #previewVisible = !1;
	  #pointerInside = !1;
	  #dragging = !1;
	  #activePointerId = 0;
	  #trackRect = null;
	  constructor(options) {
	    this.#controller = options.controller, this.#elements = options.elements, this.#readCreatedAt = options.readCreatedAt, this.#readLatestReplyAt = options.readLatestReplyAt, this.#formatRelative = options.formatRelative, this.#trackTopInset = normalizedInset(options.trackTopInset), this.#trackBottomInset = normalizedInset(options.trackBottomInset), this.#notify = options.notify ?? (() => {
	    }), this.#onError = options.onError ?? (() => {
	    }), this.#preferences = normalizedPreferences(options.preferences);
	    const view = this.#elements.timeline.ownerDocument.defaultView;
	    this.#frameScheduler = options.frameScheduler ?? Object.freeze({
	      request(callback) {
	        return typeof view?.requestAnimationFrame == "function" ? view.requestAnimationFrame(callback) : (callback(0), 0);
	      },
	      cancel(frameId) {
	        view?.cancelAnimationFrame?.(frameId);
	      }
	    }), this.#animationFrameScheduler = options.animationFrameScheduler ?? Object.freeze({
	      request(callback) {
	        return typeof view?.requestAnimationFrame == "function" ? view.requestAnimationFrame(callback) : view?.setTimeout(
	          () => callback(Date.now()),
	          16
	        ) ?? globalThis.setTimeout(
	          () => callback(Date.now()),
	          16
	        );
	      },
	      cancel(frameId) {
	        typeof view?.cancelAnimationFrame == "function" ? view.cancelAnimationFrame(frameId) : view ? view.clearTimeout(frameId) : globalThis.clearTimeout(frameId);
	      }
	    }), this.#scheduleTimer = options.scheduleTimer ?? ((callback, delayMs) => view?.setTimeout(callback, delayMs) ?? globalThis.setTimeout(callback, delayMs)), this.#cancelTimer = options.cancelTimer ?? ((timerId) => {
	      view ? view.clearTimeout(timerId) : globalThis.clearTimeout(timerId);
	    }), this.#now = options.now ?? Date.now, this.#prefersReducedMotion = options.prefersReducedMotion ?? (() => view?.matchMedia?.("(prefers-reduced-motion: reduce)").matches === !0), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const timelineDocument = this.#elements.timeline.ownerDocument, timelineWindow = timelineDocument.defaultView;
	    let relativeTimer = null;
	    const stopRelativeTimer = () => {
	      relativeTimer !== null && (timelineWindow?.clearInterval(relativeTimer), relativeTimer = null);
	    }, syncRelativeTimer = () => {
	      stopRelativeTimer(), !(!timelineWindow || timelineDocument.visibilityState === "hidden") && (relativeTimer = timelineWindow.setInterval(() => {
	        this.scope.destroyed || this.#syncRelativeTime();
	      }, 3e4));
	    };
	    this.#listen(
	      timelineDocument,
	      "visibilitychange",
	      syncRelativeTimer
	    ), this.scope.add(stopRelativeTimer), syncRelativeTimer();
	    const {
	      root,
	      date,
	      track,
	      relative,
	      jump,
	      top,
	      jumpForm,
	      jumpInput
	    } = this.#elements;
	    this.#listen(date, "click", () => {
	      this.#submitJump(1);
	    }), this.#listen(top, "click", () => {
	      this.#submitJump(1);
	    }), this.#listen(relative, "click", () => {
	      const target = this.#controller.targetByStep(
	        this.#controller.snapshot.currentPostNumber,
	        Number.POSITIVE_INFINITY
	      );
	      this.#submitJump(target);
	    }), this.#listen(jump, "click", () => {
	      jumpForm.hidden ? this.#openJumpForm() : this.#closeJumpForm(!0);
	    }), this.#listen(jumpInput, "input", () => {
	      this.#validateJumpInput();
	    }), this.#listen(jumpForm, "submit", (event) => {
	      event.preventDefault();
	      const validation = this.#validateJumpInput();
	      validation !== null && (this.#closeJumpForm(), this.#submitJump(validation));
	    }), this.#listen(jumpForm, "keydown", (event) => {
	      const key = event.key;
	      key === "Enter" && this.#elements.jumpSubmit.disabled ? (event.preventDefault(), event.stopPropagation()) : key === "Escape" && (event.preventDefault(), event.stopPropagation(), this.#closeJumpForm(!0));
	    }), this.#listen(root.getRootNode(), "pointerdown", (event) => {
	      !jumpForm.hidden && !eventPathIncludes(event, jumpForm) && !eventPathIncludes(event, jump) && this.#closeJumpForm();
	    }, !0), this.#listen(track, "pointerenter", (event) => {
	      this.#pointerInside = !0, this.#refreshTrackRect(), this.#showPointerPreview(pointerCoordinate(event));
	    }), this.#listen(track, "pointerleave", () => {
	      this.#pointerInside = !1, this.#dragging || this.#hidePointerPreview();
	    }), this.#listen(track, "pointerdown", (event) => {
	      this.#dragging || (event.preventDefault(), this.#dragging = !0, this.#activePointerId = pointerIdentifier(event), this.#refreshTrackRect(), this.#capturePointer(this.#activePointerId), this.#showPointerPreview(pointerCoordinate(event)));
	    }), this.#listen(track, "pointermove", (event) => {
	      this.#previewVisible && this.#showPointerPreview(pointerCoordinate(event));
	    }), this.#listen(track, "pointerup", (event) => {
	      const pointerId = pointerIdentifier(event);
	      if (!this.#dragging || pointerId !== this.#activePointerId) return;
	      this.#dragging = !1, this.#releasePointer(pointerId), this.#activePointerId = 0;
	      const target = this.#controller.targetAtRatio(
	        this.#pointerPosition(pointerCoordinate(event)).ratio
	      );
	      this.#pointerInside || this.#hidePointerPreview(), this.#submitJump(target);
	    }), this.#listen(track, "pointercancel", (event) => {
	      !this.#dragging || pointerIdentifier(event) !== this.#activePointerId || (this.#dragging = !1, this.#releasePointer(this.#activePointerId), this.#activePointerId = 0, this.#hidePointerPreview(), this.#sync(this.#controller.snapshot));
	    }), this.#listen(track, "keydown", (event) => {
	      this.#onTrackKeyDown(event);
	    }), this.#controller.changes.subscribe((snapshot) => {
	      this.#sync(snapshot);
	    }, this.scope), this.scope.add(() => {
	      this.#finishJumpAnimation(!1), this.#elements.track.classList.remove("ldp-timeline-pending"), this.#previewFrame && (this.#frameScheduler.cancel(this.#previewFrame), this.#previewFrame = 0), this.#releasePointer(this.#activePointerId), this.#closeJumpForm(), this.#hidePointerPreview(), this.#elements.cursor.replaceChildren(), this.#lensElements.clear(), this.#spareLensElements.length = 0, this.#elements.timeline.hidden = !0;
	    }), this.#sync(this.#controller.snapshot);
	  }
	  applyPreferences(value) {
	    this.#assertActive(), this.#preferences = normalizedPreferences(value);
	  }
	  refresh() {
	    this.#assertActive(), this.#sync(this.#controller.snapshot);
	  }
	  focusJump() {
	    this.#assertActive(), this.#openJumpForm();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #sync(snapshot) {
	    const {
	      timeline,
	      date,
	      track,
	      current,
	      total,
	      jump,
	      top,
	      jumpInput
	    } = this.#elements, hidden = snapshot.totalPostCount <= 1;
	    timeline.hidden !== hidden && (timeline.hidden = hidden);
	    const progress = String(snapshot.progress);
	    timeline.style.getPropertyValue("--ldp-timeline-progress") !== progress && timeline.style.setProperty("--ldp-timeline-progress", progress), this.#setText(current, String(snapshot.currentPostNumber)), this.#setText(total, String(snapshot.totalPostCount)), this.#setAttribute(track, "aria-valuemin", "1"), this.#setAttribute(track, "aria-valuemax", String(snapshot.totalPostCount)), this.#setAttribute(track, "aria-valuenow", String(snapshot.currentPostNumber)), this.#setAttribute(
	      track,
	      "aria-valuetext",
	      `第 ${snapshot.currentPostNumber} 楼,共 ${snapshot.totalPostCount} 楼`
	    ), this.#setAttribute(
	      jump,
	      "aria-label",
	      `跳到指定楼层,当前第 ${snapshot.currentPostNumber} 楼,共 ${snapshot.totalPostCount} 楼`
	    );
	    const maxLength = String(snapshot.totalPostCount).length + 1;
	    jumpInput.maxLength !== maxLength && (jumpInput.maxLength = maxLength), this.#setAttribute(
	      jumpInput,
	      "aria-label",
	      `楼层号,范围 1 到 ${snapshot.totalPostCount}`
	    );
	    const createdAt = this.#readCreatedAt(snapshot.currentPostNumber), dateText = readerTimelineDateLabel(createdAt);
	    this.#setText(date, dateText);
	    const pending = snapshot.pendingPostNumber !== null;
	    this.#syncRelativeTime(pending), track.classList.contains("ldp-timeline-pending") !== pending && track.classList.toggle("ldp-timeline-pending", pending), this.#setAttribute(timeline, "aria-busy", String(pending)), this.#setDisabled(track, pending), this.#setDisabled(date, pending || !dateText), this.#setDisabled(jump, pending), this.#setDisabled(top, pending), this.#setDisabled(jumpInput, pending), this.#elements.jumpForm.hidden || this.#validateJumpInput();
	  }
	  #syncRelativeTime(pending = this.#controller.snapshot.pendingPostNumber !== null) {
	    const latestReplyAt = this.#readLatestReplyAt() ?? "";
	    let relativeText = "";
	    if (latestReplyAt)
	      try {
	        relativeText = this.#formatRelative(latestReplyAt);
	      } catch (error) {
	        this.#report(error);
	      }
	    this.#setText(this.#elements.relative, relativeText), this.#setDisabled(
	      this.#elements.relative,
	      pending || !relativeText
	    );
	  }
	  #setAttribute(element, name, value) {
	    element.getAttribute(name) !== value && element.setAttribute(name, value);
	  }
	  #setText(element, value) {
	    element.textContent !== value && (element.textContent = value);
	  }
	  #setDisabled(element, disabled) {
	    element.disabled !== disabled && (element.disabled = disabled);
	  }
	  #onTrackKeyDown(event) {
	    const current = this.#controller.snapshot.currentPostNumber;
	    let delta;
	    if (event.key === "ArrowUp" || event.key === "ArrowLeft") delta = -1;
	    else if (event.key === "ArrowDown" || event.key === "ArrowRight") delta = 1;
	    else if (event.key === "PageUp") delta = -this.#preferences.pageStep;
	    else if (event.key === "PageDown") delta = this.#preferences.pageStep;
	    else if (event.key === "Home") delta = Number.NEGATIVE_INFINITY;
	    else if (event.key === "End") delta = Number.POSITIVE_INFINITY;
	    else return;
	    event.preventDefault(), this.#submitJump(this.#controller.targetByStep(current, delta));
	  }
	  #openJumpForm() {
	    this.#hidePointerPreview();
	    const { jump, jumpForm, jumpInput } = this.#elements;
	    jumpInput.value = String(this.#controller.snapshot.currentPostNumber), jumpForm.hidden = !1, jump.setAttribute("aria-expanded", "true"), this.#validateJumpInput(), jumpInput.focus(), jumpInput.select?.();
	  }
	  #closeJumpForm(restoreFocus = !1) {
	    const { jump, jumpForm } = this.#elements;
	    jumpForm.hidden || (jumpForm.hidden = !0, jump.setAttribute("aria-expanded", "false"), restoreFocus && jump.focus());
	  }
	  #validateJumpInput() {
	    const {
	      jumpForm,
	      jumpInput,
	      jumpSubmit,
	      jumpHint
	    } = this.#elements, validation = this.#controller.validateInput(jumpInput.value), invalid = validation.postNumber === null;
	    return jumpInput.setAttribute("aria-invalid", String(invalid)), jumpForm.classList.toggle("is-invalid", invalid), jumpHint.textContent = invalid ? validation.message : `有效范围:1–${this.#controller.snapshot.totalPostCount}`, jumpSubmit.disabled = invalid || this.#controller.snapshot.pendingPostNumber !== null, validation.postNumber;
	  }
	  #submitJump(postNumber) {
	    this.scope.destroyed || this.#controller.snapshot.pendingPostNumber !== null || (this.#beginJumpAnimation(postNumber), this.#controller.jumpTo(postNumber).then((result) => {
	      this.scope.destroyed || result.status !== "revealed" && result.status !== "superseded" && this.#notify(`暂时无法定位到楼层 #${postNumber},可重试`);
	    }).catch((error) => {
	      this.scope.destroyed || (this.#report(error), this.#notify(`楼层 #${postNumber} 加载失败,请重试`));
	    }));
	  }
	  #beginJumpAnimation(postNumber) {
	    if (this.#finishJumpAnimation(!1), this.#prefersReducedMotion() || this.scope.destroyed) return;
	    const snapshot = this.#controller.snapshot, currentRatio = this.#ratioForPost(
	      snapshot.currentPostNumber,
	      snapshot
	    ), targetRatio = this.#ratioForPost(postNumber, snapshot), startRatio = this.#previewVisible && this.#lensElements.get(postNumber)?.classList.contains(
	      "ldp-timeline-lens-selected"
	    ) === !0 ? targetRatio : currentRatio;
	    this.#renderLens(startRatio, this.#topForRatio(startRatio));
	    const { track, cursor } = this.#elements;
	    track.classList.remove("ldp-timeline-jumping"), cursor.offsetWidth, track.classList.add("ldp-timeline-jumping");
	    const epoch = ++this.#jumpAnimationEpoch, durationMs = 360;
	    let startedAt = null;
	    const settle = () => {
	      this.scope.destroyed || epoch !== this.#jumpAnimationEpoch || (this.#jumpAnimationFrame = 0, this.#renderLens(targetRatio, this.#topForRatio(targetRatio)), this.#jumpAnimationTimer = this.#scheduleTimer(() => {
	        epoch === this.#jumpAnimationEpoch && this.#finishJumpAnimation(!0);
	      }, 460));
	    };
	    if (Math.abs(targetRatio - startRatio) <= 1e-4) {
	      this.#jumpAnimationFrame = this.#animationFrameScheduler.request(settle);
	      return;
	    }
	    const animate = (timestamp) => {
	      if (this.scope.destroyed || epoch !== this.#jumpAnimationEpoch) return;
	      const currentTime = Number.isFinite(timestamp) ? timestamp : this.#now();
	      startedAt === null && (startedAt = currentTime);
	      const elapsedRatio = Math.min(
	        1,
	        Math.max(0, (currentTime - startedAt) / durationMs)
	      ), easedRatio = 1 - Math.pow(1 - elapsedRatio, 3), ratio = startRatio + (targetRatio - startRatio) * easedRatio;
	      if (this.#renderLens(ratio, this.#topForRatio(ratio)), elapsedRatio >= 1) {
	        settle();
	        return;
	      }
	      this.#jumpAnimationFrame = this.#animationFrameScheduler.request(animate);
	    };
	    this.#jumpAnimationFrame = this.#animationFrameScheduler.request(animate);
	  }
	  #finishJumpAnimation(resumePreview) {
	    this.#jumpAnimationEpoch += 1, this.#jumpAnimationFrame && (this.#animationFrameScheduler.cancel(this.#jumpAnimationFrame), this.#jumpAnimationFrame = 0), this.#jumpAnimationTimer && (this.#cancelTimer(this.#jumpAnimationTimer), this.#jumpAnimationTimer = 0), this.#elements.track.classList.remove("ldp-timeline-jumping"), resumePreview && this.#previewVisible && this.#showPointerPreview(this.#previewClientY);
	  }
	  #ratioForPost(postNumber, snapshot) {
	    const navigable = snapshot.navigablePostNumbers;
	    if (navigable?.length) {
	      const index = navigable.findIndex(
	        (candidate) => Number(candidate) === postNumber
	      );
	      if (index >= 0) return navigable.length === 1 ? 0 : index / (navigable.length - 1);
	    }
	    return snapshot.totalPostCount <= 1 ? 0 : Math.max(0, Math.min(
	      1,
	      (postNumber - 1) / (snapshot.totalPostCount - 1)
	    ));
	  }
	  #topForRatio(ratio) {
	    const rect = this.#trackRect ?? this.#refreshTrackRect(), usableHeight = Math.max(
	      1,
	      rect.height - this.#trackTopInset - this.#trackBottomInset
	    );
	    return this.#trackTopInset + Math.max(0, Math.min(1, ratio)) * usableHeight;
	  }
	  #showPointerPreview(clientY) {
	    this.#previewVisible = !0, this.#previewClientY = clientY, !this.#previewFrame && (this.#previewFrame = this.#frameScheduler.request(() => {
	      if (this.#previewFrame = 0, !this.#previewVisible || this.scope.destroyed) return;
	      const position = this.#pointerPosition(this.#previewClientY), target = this.#controller.targetAtRatio(position.ratio);
	      this.#elements.timeline.style.setProperty(
	        "--ldp-timeline-preview-progress",
	        String(position.ratio)
	      ), this.#elements.preview.textContent = `#${target}`, this.#elements.track.classList.toggle(
	        "ldp-timeline-previewing",
	        target !== this.#controller.snapshot.currentPostNumber
	      ), this.#elements.track.classList.add("ldp-timeline-hovering"), this.#renderLens(position.ratio, position.top);
	    }));
	  }
	  #hidePointerPreview() {
	    this.#previewVisible = !1, this.#previewFrame && (this.#frameScheduler.cancel(this.#previewFrame), this.#previewFrame = 0), this.#trackRect = null, this.#elements.track.classList.remove(
	      "ldp-timeline-previewing",
	      "ldp-timeline-hovering"
	    );
	  }
	  #renderLens(ratio, top) {
	    const snapshot = this.#controller.snapshot, navigablePosts = snapshot.navigablePostNumbers, floorCount = navigablePosts?.length ?? snapshot.totalPostCount, floorAt = (index) => navigablePosts?.[index] ?? index + 1, continuousIndex = Math.max(
	      0,
	      Math.min(floorCount - 1, ratio * (floorCount - 1))
	    ), nearestIndex = Math.round(continuousIndex), selectedFloor = floorAt(nearestIndex), firstIndex = Math.max(0, nearestIndex - 3), lastIndex = Math.min(floorCount - 1, nearestIndex + 3), desired = /* @__PURE__ */ new Map();
	    for (let index = firstIndex; index <= lastIndex; index += 1)
	      desired.set(floorAt(index), index - continuousIndex);
	    for (const [floor, element] of this.#lensElements)
	      desired.has(floor) || (element.hidden = !0, this.#lensElements.delete(floor), this.#spareLensElements.push(element));
	    for (const [floor, offset] of desired) {
	      let element = this.#lensElements.get(floor);
	      element || (element = this.#spareLensElements.pop() ?? this.#elements.timeline.ownerDocument.createElement("span"), element.textContent = `#${floor}`, element.hidden = !1, this.#lensElements.set(floor, element), element.isConnected || this.#elements.cursor.append(element));
	      const distance = Math.abs(offset), focus = Math.exp(-0.72 * distance * distance), opacity = Math.pow(Math.max(0, 1 - distance / 3), 1.25), scale = 0.46 + 0.54 * focus, shiftX = -Math.min(6, distance * 2);
	      element.style.setProperty("--ldp-lens-offset", String(offset)), element.style.setProperty("--ldp-lens-scale", scale.toFixed(4)), element.style.setProperty("--ldp-lens-opacity", opacity.toFixed(4)), element.style.setProperty(
	        "--ldp-lens-shift-x",
	        `${shiftX.toFixed(2)}px`
	      ), element.classList.toggle(
	        "ldp-timeline-lens-selected",
	        floor === selectedFloor
	      ), element.classList.toggle(
	        "ldp-timeline-lens-focus",
	        Math.abs(offset) < 1e-3
	      );
	    }
	    this.#elements.cursor.style.setProperty(
	      "--ldp-timeline-lens-top",
	      `${top.toFixed(2)}px`
	    );
	  }
	  #pointerPosition(clientY) {
	    const rect = this.#trackRect ?? this.#refreshTrackRect(), usableHeight = Math.max(
	      1,
	      rect.height - this.#trackTopInset - this.#trackBottomInset
	    ), localTop = Math.max(
	      this.#trackTopInset,
	      Math.min(
	        Math.max(this.#trackTopInset, rect.height - this.#trackBottomInset),
	        clientY - rect.top
	      )
	    );
	    return Object.freeze({
	      top: localTop,
	      ratio: Math.max(
	        0,
	        Math.min(1, (localTop - this.#trackTopInset) / usableHeight)
	      )
	    });
	  }
	  #refreshTrackRect() {
	    return this.#trackRect = this.#elements.track.getBoundingClientRect(), this.#trackRect;
	  }
	  #capturePointer(pointerId) {
	    const track = this.#elements.track;
	    if (!(!pointerId || typeof track.setPointerCapture != "function"))
	      try {
	        track.setPointerCapture(pointerId);
	      } catch {
	      }
	  }
	  #releasePointer(pointerId) {
	    const track = this.#elements.track;
	    if (!(!pointerId || typeof track.releasePointerCapture != "function"))
	      try {
	        (typeof track.hasPointerCapture != "function" || track.hasPointerCapture(pointerId)) && track.releasePointerCapture(pointerId);
	      } catch {
	      }
	  }
	  #listen(target, type, listener, options) {
	    this.scope.listen(target, type, listener, options);
	  }
	  #report(error) {
	    try {
	      this.#onError(error);
	    } catch {
	    }
	  }
	  #assertActive() {
	    if (this.scope.destroyed)
	      throw new Error("ReaderTopicTimelineView 已销毁");
	  }
	}
}, "b90805592fe59d521b7c704ace802709fdce614c1dbc4aba584fa114aeb8a2d3");

/* Source: lite/src/topic/topic-read-request-adapter.ts */
runtime.register("src/topic/topic-read-request-adapter.js", function(module, exports, require) {
	var topic_read_request_adapter_exports = {};
	__export(topic_read_request_adapter_exports, {
	  TopicReadRequestAdapter: () => TopicReadRequestAdapter
	});
	module.exports = __toCommonJS(topic_read_request_adapter_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_native_request_descriptors = require("../discourse/native-request-descriptors.js");
	function cacheWithPostIds(cache, postIds) {
	  return Object.freeze({
	    ...cache,
	    tags: Object.freeze(
	      [.../* @__PURE__ */ new Set([
	        ...cache.tags,
	        ...postIds.map((postId) => `post:${(0, import_identifiers.discoursePostId)(postId)}`)
	      ])].sort()
	    )
	  });
	}
	function cacheForTopic(cache, topicId) {
	  return Object.freeze({
	    ...cache,
	    tags: Object.freeze([
	      ...cache.tags.filter((tag) => !tag.startsWith("topic:")),
	      `topic:${topicId}`
	    ])
	  });
	}
	function linkedRequestSignal(topicSignal, requestSignal) {
	  if (!requestSignal || requestSignal === topicSignal)
	    return Object.freeze({ signal: topicSignal, dispose() {
	    } });
	  const controller = new AbortController(), abortFromTopic = () => controller.abort(topicSignal.reason), abortFromRequest = () => controller.abort(requestSignal.reason);
	  return topicSignal.aborted ? abortFromTopic() : requestSignal.aborted ? abortFromRequest() : (topicSignal.addEventListener("abort", abortFromTopic, { once: !0 }), requestSignal.addEventListener("abort", abortFromRequest, { once: !0 })), Object.freeze({
	    signal: controller.signal,
	    dispose() {
	      topicSignal.removeEventListener("abort", abortFromTopic), requestSignal.removeEventListener("abort", abortFromRequest);
	    }
	  });
	}
	class TopicReadRequestAdapter {
	  topicId;
	  authScope;
	  #gateway;
	  #transport;
	  #signal;
	  #caches;
	  #basePath;
	  constructor(options) {
	    this.#gateway = options.gateway, this.#transport = options.transport, this.authScope = (0, import_identifiers.discourseAuthScope)(options.authScope), this.topicId = (0, import_identifiers.discourseTopicId)(options.topicId), this.#signal = options.signal, this.#caches = options.caches, this.#basePath = (0, import_native_request_descriptors.discourseBasePath)(options.basePath);
	  }
	  loadTopic(options = {}) {
	    const descriptor = import_native_request_descriptors.DiscourseNativeRequests.topic({
	      basePath: this.#basePath,
	      topicId: this.topicId
	    });
	    return this.#gateway.loadTopicTarget({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      operation: "topic-refresh",
	      profile: options.background ? "background-prefetch" : "topic-visible",
	      input: descriptor.path,
	      signal: this.#signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: options.refresh === !0 ? "refresh" : "default",
	      cache: this.#caches.topic,
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    });
	  }
	  loadPostsByIds(rawPostIds, options = {}) {
	    const postIds = (0, import_identifiers.discoursePostIds)(rawPostIds), descriptor = import_native_request_descriptors.DiscourseNativeRequests.postsById({
	      basePath: this.#basePath,
	      topicId: this.topicId,
	      postIds
	    });
	    return this.#gateway.loadTopicPosts({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      postIds,
	      profile: options.background ? "background-prefetch" : options.priority === "nested" ? "nested-visible" : "topic-visible",
	      input: descriptor.path,
	      signal: this.#signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: options.refresh ? "refresh" : "default",
	      cache: cacheWithPostIds(this.#caches.posts, postIds),
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    });
	  }
	  promotePostsByIds(rawPostIds, options = {}) {
	    const postIds = (0, import_identifiers.discoursePostIds)(rawPostIds);
	    return this.#gateway.promoteTopicPosts?.({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      postIds,
	      profile: options.background ? "background-prefetch" : options.priority === "nested" ? "nested-visible" : "topic-visible",
	      cacheMode: options.refresh ? "refresh" : "default"
	    }) ?? !1;
	  }
	  loadPostById(rawPostId, options = {}) {
	    const postId = (0, import_identifiers.discoursePostId)(rawPostId), descriptor = import_native_request_descriptors.DiscourseNativeRequests.postById({
	      basePath: this.#basePath,
	      postId
	    });
	    return this.#gateway.loadTopicTarget({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      operation: "post-by-id-refresh",
	      postId,
	      profile: options.background ? "background-prefetch" : "topic-visible",
	      input: descriptor.path,
	      signal: this.#signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: "refresh",
	      cache: cacheWithPostIds(this.#caches.posts, [postId]),
	      allowStaleOnError: !1,
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    });
	  }
	  loadPostVotingComments(rawPostId, options = {}) {
	    const postId = (0, import_identifiers.discoursePostId)(rawPostId), afterCommentId = Number(options.afterCommentId ?? 0);
	    if (!Number.isSafeInteger(afterCommentId) || afterCommentId < 0)
	      throw new RangeError("afterCommentId 必须是非负安全整数");
	    const descriptor = import_native_request_descriptors.DiscourseNativeRequests.postVotingComments({
	      basePath: this.#basePath,
	      postId,
	      afterCommentId
	    });
	    return this.#gateway.loadCollectionPage({
	      authScope: this.authScope,
	      collection: `post-voting-comments:${postId}`,
	      page: afterCommentId,
	      cursor: afterCommentId,
	      profile: options.background ? "background-prefetch" : "collection-visible",
	      input: descriptor.path,
	      signal: this.#signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: options.refresh ? "refresh" : "default",
	      cache: cacheWithPostIds(this.#caches.posts, [postId]),
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    });
	  }
	  targetCandidates(rawPostNumber, options, rawTopicId = this.topicId) {
	    const postNumber = (0, import_identifiers.discoursePostNumber)(rawPostNumber), topicId = (0, import_identifiers.discourseTopicId)(rawTopicId);
	    return Object.freeze(import_native_request_descriptors.DiscourseNativeRequests.targetCandidates({
	      basePath: this.#basePath,
	      topicId,
	      postNumber,
	      scope: options.scope,
	      ...options.slug === void 0 ? {} : { slug: options.slug },
	      ...options.refresh === void 0 ? {} : { refresh: options.refresh }
	    }).map(({ endpoint, url }) => Object.freeze({ endpoint, url })));
	  }
	  loadTargetCandidate(candidate, rawPostNumber, options, rawTopicId = this.topicId) {
	    const postNumber = (0, import_identifiers.discoursePostNumber)(rawPostNumber), topicId = (0, import_identifiers.discourseTopicId)(rawTopicId), refresh = options.refresh === !0, catalogCandidate = import_native_request_descriptors.DiscourseNativeRequests.targetCandidates({
	      basePath: this.#basePath,
	      topicId,
	      postNumber,
	      scope: options.scope,
	      ...options.slug === void 0 ? {} : { slug: options.slug },
	      refresh
	    }).find((entry) => entry.endpoint === candidate.endpoint && entry.url === candidate.url);
	    if (!catalogCandidate)
	      throw new Error("目标楼层请求不属于 Discourse 原生目录");
	    const descriptor = catalogCandidate.descriptor;
	    return this.#gateway.loadTopicTarget({
	      authScope: this.authScope,
	      topicId,
	      operation: `target:${options.scope}:${candidate.endpoint}`,
	      postNumber,
	      profile: options.background ? "background-prefetch" : "topic-visible",
	      input: candidate.url,
	      signal: this.#signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: refresh ? "refresh" : "default",
	      cache: cacheForTopic(this.#caches.posts, topicId),
	      allowStaleOnError: !refresh,
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    });
	  }
	  loadNestedReplies(rawParentPostNumber, options = {}) {
	    const parentPostNumber = (0, import_identifiers.discoursePostNumber)(rawParentPostNumber), parentPostId = options.parentPostId === void 0 ? void 0 : (0, import_identifiers.discoursePostId)(options.parentPostId), after = (0, import_identifiers.discourseReplyCursor)(options.after);
	    if (parentPostId === void 0)
	      throw new Error("直属回复 endpoint 需要 parentPostId");
	    const descriptor = import_native_request_descriptors.DiscourseNativeRequests.directReplies({
	      basePath: this.#basePath,
	      parentPostId,
	      after
	    }), requestLifetime = linkedRequestSignal(
	      this.#signal,
	      options.signal
	    );
	    return this.#gateway.loadNestedReplies({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      parentPostNumber,
	      parentPostId,
	      after,
	      profile: options.background ? "background-prefetch" : "nested-visible",
	      input: descriptor.path,
	      signal: requestLifetime.signal,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	      cacheMode: options.refresh ? "refresh" : "default",
	      cache: cacheWithPostIds(this.#caches.nested, [parentPostId]),
	      transport: (input) => this.#transport.request({
	        descriptor,
	        signal: input.signal,
	        attempt: input.attempt
	      })
	    }).finally(() => requestLifetime.dispose());
	  }
	  promoteNestedReplies(rawParentPostNumber, options = {}) {
	    const parentPostNumber = (0, import_identifiers.discoursePostNumber)(rawParentPostNumber), parentPostId = options.parentPostId === void 0 ? void 0 : (0, import_identifiers.discoursePostId)(options.parentPostId);
	    return parentPostId === void 0 ? !1 : this.#gateway.promoteNestedReplies?.({
	      authScope: this.authScope,
	      topicId: this.topicId,
	      parentPostNumber,
	      parentPostId,
	      after: (0, import_identifiers.discourseReplyCursor)(options.after),
	      profile: options.background ? "background-prefetch" : "nested-visible",
	      cacheMode: options.refresh ? "refresh" : "default"
	    }) ?? !1;
	  }
	}
}, "a4d4661b418d7855a2bfde8c5f5cb70167cfce87728bf5aa411ce9f80cf9abce");

/* Source: lite/src/topic/topic-session.ts */
runtime.register("src/topic/topic-session.js", function(module, exports, require) {
	var topic_session_exports = {};
	__export(topic_session_exports, {
	  TopicSession: () => TopicSession,
	  discoursePostsFromPayload: () => discoursePostsFromPayload
	});
	module.exports = __toCommonJS(topic_session_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_native_request_descriptors = require("../discourse/native-request-descriptors.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_signal = require("../kernel/signal.js");
	function positiveInteger(value, name) {
	  const numeric = Number(value);
	  if (!Number.isSafeInteger(numeric) || numeric < 1)
	    throw new RangeError(`${name} 必须是正安全整数`);
	  return numeric;
	}
	function nonNegativeInteger(value) {
	  const numeric = Number(value ?? 0);
	  return Number.isSafeInteger(numeric) && numeric >= 0 ? numeric : 0;
	}
	function directReplyPost(post, parentPostNumber) {
	  const rawParent = post.reply_to_post_number;
	  if (rawParent != null && rawParent !== "") {
	    let explicitParent;
	    try {
	      explicitParent = (0, import_identifiers.discoursePostReference)({
	        post_number: rawParent
	      }).postNumber;
	    } catch {
	      return null;
	    }
	    return explicitParent === parentPostNumber ? post : null;
	  }
	  return Object.freeze({
	    ...post,
	    reply_to_post_number: parentPostNumber
	  });
	}
	function scopedReplyPost(post, parentPostNumber) {
	  try {
	    (0, import_identifiers.discoursePostReference)(post);
	  } catch {
	    return null;
	  }
	  const direct = directReplyPost(post, parentPostNumber);
	  if (direct) return direct;
	  try {
	    return (0, import_identifiers.discoursePostReference)({
	      post_number: post.reply_to_post_number
	    }), post;
	  } catch {
	    return null;
	  }
	}
	function replyPageCursor(posts) {
	  let cursor = 0;
	  for (const post of posts)
	    try {
	      cursor = Math.max(cursor, (0, import_identifiers.discoursePostReference)(post).postNumber);
	    } catch {
	    }
	  return cursor;
	}
	function errorStatus(error) {
	  return Number(error?.status ?? 0);
	}
	function isAuthFailure(error) {
	  return [401, 403].includes(errorStatus(error));
	}
	function isThrottleFailure(error) {
	  return errorStatus(error) === 429 || error?.cloudflareMitigated === !0;
	}
	function isAbortFailure(error) {
	  return error instanceof DOMException && error.name === "AbortError" || String(error?.name ?? "") === "AbortError";
	}
	function throwIfAborted(signal) {
	  if (signal?.aborted)
	    throw signal.reason ?? new DOMException("Aborted", "AbortError");
	}
	function awaitWithSignal(operation, signal) {
	  return signal ? signal.aborted ? Promise.reject(
	    signal.reason ?? new DOMException("Aborted", "AbortError")
	  ) : new Promise((resolve, reject) => {
	    let settled = !1;
	    const finish = (callback) => {
	      settled || (settled = !0, signal.removeEventListener("abort", onAbort), callback());
	    }, onAbort = () => finish(() => reject(
	      signal.reason ?? new DOMException("Aborted", "AbortError")
	    ));
	    signal.addEventListener("abort", onAbort, { once: !0 }), Promise.resolve(operation).then(
	      (value) => finish(() => resolve(value)),
	      (error) => finish(() => reject(error))
	    );
	  }) : Promise.resolve(operation);
	}
	function linkedAbortSignals(first, second) {
	  if (!first || first === second) {
	    const signal = second ?? first;
	    return Object.freeze(signal ? { signal, dispose() {
	    } } : { dispose() {
	    } });
	  }
	  if (!second) return Object.freeze({ signal: first, dispose() {
	  } });
	  const controller = new AbortController(), abortFromFirst = () => controller.abort(first.reason), abortFromSecond = () => controller.abort(second.reason);
	  return first.aborted ? abortFromFirst() : second.aborted ? abortFromSecond() : (first.addEventListener("abort", abortFromFirst, { once: !0 }), second.addEventListener("abort", abortFromSecond, { once: !0 })), Object.freeze({
	    signal: controller.signal,
	    dispose() {
	      first.removeEventListener("abort", abortFromFirst), second.removeEventListener("abort", abortFromSecond);
	    }
	  });
	}
	function shouldSplit(error) {
	  return [400, 404, 413, 414, 422].includes(errorStatus(error));
	}
	function freezeNumbers(values) {
	  return Object.freeze([...values]);
	}
	function discoursePostsFromPayload(payload) {
	  if (Array.isArray(payload)) return Object.freeze(payload.filter(Boolean));
	  if (!payload || typeof payload != "object") return Object.freeze([]);
	  const candidate = payload;
	  return Array.isArray(candidate.post_stream?.posts) ? Object.freeze(candidate.post_stream.posts.filter(Boolean)) : candidate.post_number === void 0 ? Object.freeze([]) : Object.freeze([payload]);
	}
	class TopicSession {
	  topicId;
	  scope;
	  changes = new import_signal.Signal();
	  archiveChanges = new import_signal.Signal();
	  #requests;
	  #snapshots;
	  #replies;
	  #pageSize;
	  #refreshCachedInBackground;
	  #now;
	  #wait;
	  #signal;
	  #onError;
	  #onInitializeSource;
	  #postById = /* @__PURE__ */ new Map();
	  #postByNumber = /* @__PURE__ */ new Map();
	  #cachedPostsSnapshot = null;
	  #pendingByPostId = /* @__PURE__ */ new Map();
	  #pendingDirectReplies = /* @__PURE__ */ new Map();
	  #unavailablePostNumbers = /* @__PURE__ */ new Set();
	  #streamPostIds = Object.freeze([]);
	  #topic = null;
	  #cursor = 0;
	  #sequentialLoadStarted = !1;
	  #initializedFromCache = !1;
	  #initPromise = null;
	  #refreshPromise = null;
	  #postStreamPromise = null;
	  #postStreamExecution = null;
	  #closed = !1;
	  #archiveStateKey = "";
	  constructor(options) {
	    if (this.topicId = (0, import_identifiers.discourseTopicId)(options.topicId), options.snapshots.topicId !== String(this.topicId))
	      throw new Error("TopicSession 与 TopicSnapshotRepository topicId 不一致");
	    if (options.replies.topicId !== String(this.topicId))
	      throw new Error("TopicSession 与 ReplyTreeRepository topicId 不一致");
	    this.#requests = options.requests, this.#snapshots = options.snapshots, this.#replies = options.replies, this.#pageSize = positiveInteger(options.pageSize, "pageSize"), this.#refreshCachedInBackground = options.refreshCachedInBackground !== !1, this.#now = options.now ?? Date.now, this.#wait = options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))), this.#signal = options.signal, this.#onError = options.onError ?? (() => {
	    }), this.#onInitializeSource = options.onInitializeSource ?? (() => {
	    }), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.scope), this.scope.add(() => {
	      this.#closed = !0, this.#cachedPostsSnapshot = null, this.#pendingByPostId.clear();
	      for (const pending of this.#pendingDirectReplies.values())
	        pending.controller.signal.aborted || pending.controller.abort(new DOMException(
	          "Topic 已关闭",
	          "AbortError"
	        ));
	      this.#pendingDirectReplies.clear(), this.#postStreamPromise = null, this.#postStreamExecution = null;
	    });
	  }
	  get topic() {
	    return this.#topic;
	  }
	  get initializedFromCache() {
	    return this.#initializedFromCache;
	  }
	  get loadDone() {
	    return this.#cursor >= this.#streamPostIds.length;
	  }
	  streamPostIds() {
	    return this.#streamPostIds;
	  }
	  postStreamCoverage() {
	    const expectedPostCount = this.#snapshots.snapshot().expectedPostCount, missingPostCount = this.#streamPostIds.reduce(
	      (total, postId) => total + (this.#postById.has(postId) ? 0 : 1),
	      0
	    );
	    return Object.freeze({
	      complete: expectedPostCount > 0 && expectedPostCount <= this.#streamPostIds.length && missingPostCount === 0,
	      expectedPostCount,
	      streamPostCount: this.#streamPostIds.length,
	      missingPostCount
	    });
	  }
	  unavailablePostNumbers() {
	    return Object.freeze(
	      [...this.#unavailablePostNumbers].sort((left, right) => left - right)
	    );
	  }
	  localArchiveState() {
	    return this.#snapshots.localArchiveState();
	  }
	  get pageSize() {
	    return this.#pageSize;
	  }
	  /**
	   * 原地切换后续 loader 批次大小;不移动 cursor、不清 pending,也不重建 Topic。
	   */
	  applyPageSize(pageSize) {
	    this.#assertActive(), this.#pageSize = positiveInteger(pageSize, "pageSize");
	  }
	  cachedPosts() {
	    return this.#cachedPostsSnapshot ??= Object.freeze([...this.#postByNumber.entries()].sort(([left], [right]) => left - right).map(([, post]) => post)), this.#cachedPostsSnapshot;
	  }
	  postById(rawPostId) {
	    return this.#postById.get((0, import_identifiers.discoursePostId)(rawPostId));
	  }
	  postByNumber(rawPostNumber) {
	    return this.#postByNumber.get((0, import_identifiers.discoursePostNumber)(rawPostNumber));
	  }
	  init(options = {}) {
	    if (this.#assertActive(), this.#topic) return Promise.resolve(this.#topic);
	    if (this.#initPromise) return this.#initPromise;
	    const promise = this.#initialize(options);
	    return this.#initPromise = promise, promise.finally(() => {
	      this.#initPromise === promise && (this.#initPromise = null);
	    }).catch(() => {
	    }), promise;
	  }
	  refresh(options = {}) {
	    return this.#assertActive(), this.#loadTopic(options, !0);
	  }
	  #loadTopic(options, refresh) {
	    if (this.#refreshPromise) return this.#refreshPromise;
	    const observedAt = this.#now(), promise = this.#requests.loadTopic({
	      ...options,
	      refresh
	    }).then((topic) => (this.#assertActive(), this.#commitTopic(topic, "topic-json", observedAt), topic)).catch((error) => {
	      this.#assertActive();
	      const status = errorStatus(error), cached = this.#snapshots.topic();
	      if (![403, 404, 410].includes(status) || cached === null || this.#snapshots.posts().length === 0) throw error;
	      return this.#snapshots.markTopicUnavailable(status, observedAt), this.#restoreIndexes(), this.#initializedFromCache = !0, this.#syncLocalArchiveState(), cached;
	    });
	    return this.#refreshPromise = promise, promise.finally(() => {
	      this.#refreshPromise === promise && (this.#refreshPromise = null);
	    }).catch(() => {
	    }), promise;
	  }
	  async next(options = {}) {
	    this.#assertActive(), this.#sequentialLoadStarted = !0;
	    const position = this.#cursor;
	    if (position >= this.#streamPostIds.length)
	      return this.#batchResult([], !0, [], !1, !1);
	    const ids = this.#streamPostIds.slice(position, position + this.#pageSize), missingBefore = ids.filter((postId) => !this.#postById.has(postId));
	    options.onSource?.(missingBefore.length ? "network" : "cache", Object.freeze({
	      cachedCount: ids.length - missingBefore.length,
	      missingCount: missingBefore.length,
	      totalCount: ids.length
	    }));
	    let loadError;
	    if (missingBefore.length)
	      try {
	        await this.loadPostsByIds(ids, {
	          ...options.background === void 0 ? {} : { background: options.background },
	          ...options.priority === void 0 ? {} : { priority: options.priority },
	          ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork },
	          ...options.maxAttempts === void 0 ? {} : { maxAttempts: options.maxAttempts },
	          ...options.beforeCommit === void 0 ? {} : { beforeCommit: options.beforeCommit }
	        });
	      } catch (error) {
	        if (isThrottleFailure(error)) throw error;
	        loadError = error;
	      }
	    const missing = ids.filter((postId) => !this.#postById.has(postId));
	    if (missing.length) {
	      const fatal = loadError !== void 0 && isAuthFailure(loadError);
	      return this.#batchResult(
	        [],
	        !1,
	        missing,
	        !fatal,
	        fatal,
	        loadError
	      );
	    }
	    const posts = ids.map((postId) => this.#postById.get(postId)).filter((post) => post !== void 0);
	    return this.#cursor += ids.length, this.#batchResult(
	      posts,
	      this.#cursor >= this.#streamPostIds.length,
	      [],
	      !1,
	      !1
	    );
	  }
	  async loadPostsByIds(rawPostIds, options = {}) {
	    this.#assertActive();
	    const postIds = (0, import_identifiers.discoursePostIdStream)(rawPostIds), maxAttempts = positiveInteger(options.maxAttempts ?? 2, "maxAttempts");
	    let lastError;
	    for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
	      const pending = attempt === 0 && options.refresh ? postIds : postIds.filter((postId) => !this.#postById.has(postId));
	      if (!pending.length) break;
	      try {
	        await this.#ensurePostIds(pending, options), lastError = void 0;
	      } catch (error) {
	        if (lastError = error, isAuthFailure(error) || isThrottleFailure(error)) throw error;
	      }
	      postIds.some((postId) => !this.#postById.has(postId)) && attempt + 1 < maxAttempts && await this.#wait(240);
	    }
	    const missingPostIds = postIds.filter((postId) => !this.#postById.has(postId));
	    if (missingPostIds.length && lastError !== void 0) throw lastError;
	    return Object.freeze({
	      posts: Object.freeze(
	        postIds.map((postId) => this.#postById.get(postId)).filter((post) => post !== void 0)
	      ),
	      missingPostIds: freezeNumbers(missingPostIds)
	    });
	  }
	  /**
	   * 用 Discourse post_ids[] 批量端点预热当前顺序流之后最近仍缺正文的完整批次。
	   *
	   * 顺序流尚未启动时保留“当前批次之后”的显式预热语义;next() 已启动后则从当前
	   * cursor 向前扫描,跳过完整缓存和整批在途请求。这样缓存命中同步推进 cursor 时不会
	   * 留下最近批次冷缺口,网络批次又仍可通过 pendingByPostId 保持单飞。
	   */
	  async prefetchAhead(rawBatchCount, options = {}) {
	    this.#assertActive();
	    const batchCount = Math.min(
	      2,
	      positiveInteger(rawBatchCount, "batchCount")
	    ), firstOffset = this.#sequentialLoadStarted ? this.#cursor : this.#cursor + this.#pageSize, batches = [];
	    for (let offset = firstOffset; offset < this.#streamPostIds.length && batches.length < batchCount; offset += this.#pageSize) {
	      const batch = this.#streamPostIds.slice(offset, offset + this.#pageSize), missing = batch.filter((postId) => !this.#postById.has(postId));
	      !missing.length || missing.every((postId) => this.#pendingByPostId.has(postId)) || batches.push([...batch]);
	    }
	    return Object.freeze(await Promise.all(batches.map((batch) => this.loadPostsByIds(batch, options))));
	  }
	  /**
	   * 补齐当前 Topic 的 canonical post.id stream。
	   *
	   * 需要全帖投影的功能(例如图片关联评论、全帖媒体索引)共用本入口;调用方不得再复制
	   * stream 分批、single-flight、缺口判断或第二份 post Map。失败批次不会阻断其余批次,
	   * 最终以 missingPostIds/complete 明确报告覆盖率。
	   */
	  ensurePostStream(options = {}) {
	    if (this.#assertActive(), this.#postStreamPromise)
	      return this.#upgradePostStreamExecution(options), this.#postStreamPromise;
	    const execution = {
	      background: options.background === !0,
	      priority: options.priority,
	      maxAttempts: positiveInteger(options.maxAttempts ?? 2, "maxAttempts"),
	      refresh: options.refresh === !0
	    };
	    this.#postStreamExecution = execution;
	    const request = this.#loadPostStream(options, execution).finally(() => {
	      this.#postStreamPromise === request && (this.#postStreamPromise = null, this.#postStreamExecution === execution && (this.#postStreamExecution = null));
	    });
	    return this.#postStreamPromise = request, request;
	  }
	  #upgradePostStreamExecution(options) {
	    const execution = this.#postStreamExecution;
	    if (!execution) return;
	    const wasBackground = execution.background, previousPriority = execution.priority;
	    options.background !== !0 && (execution.background = !1), options.priority === "nested" ? execution.priority = "nested" : execution.priority === void 0 && options.priority === "visible" && (execution.priority = "visible"), options.maxAttempts !== void 0 && (execution.maxAttempts = Math.max(
	      execution.maxAttempts,
	      positiveInteger(options.maxAttempts, "maxAttempts")
	    )), !(wasBackground === execution.background && previousPriority === execution.priority) && this.#promotePendingPostBatches({
	      background: execution.background,
	      ...execution.priority === void 0 ? {} : { priority: execution.priority },
	      ...execution.refresh ? { refresh: !0 } : {}
	    });
	  }
	  async #loadPostStream(options, execution) {
	    let loadedCount = 0;
	    const report = () => {
	      options.onProgress?.(Object.freeze({
	        loadedCount,
	        totalCount: this.#streamPostIds.length,
	        missingCount: this.#streamPostIds.length - loadedCount
	      }));
	    };
	    let failedBatchCount = 0;
	    if (this.#snapshots.snapshot().expectedPostCount > this.#streamPostIds.length)
	      try {
	        await this.refresh({
	          background: execution.background,
	          ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork }
	        });
	      } catch (error) {
	        if (this.#closed || this.scope.destroyed || isThrottleFailure(error)) throw error;
	        failedBatchCount += 1, this.#onError(error);
	      }
	    loadedCount = this.#streamPostIds.reduce(
	      (total, postId) => total + Number(this.#postById.has(postId)),
	      0
	    ), report();
	    for (let offset = 0; offset < this.#streamPostIds.length; ) {
	      const batch = this.#streamPostIds.slice(
	        offset,
	        offset + this.#pageSize
	      );
	      if (!batch.length) break;
	      offset += batch.length;
	      const loadedBefore = batch.reduce(
	        (total, postId) => total + Number(this.#postById.has(postId)),
	        0
	      );
	      if (loadedBefore === batch.length) continue;
	      try {
	        await options.beforeBatch?.(), await this.loadPostsByIds(batch, {
	          ...options,
	          background: execution.background,
	          maxAttempts: execution.maxAttempts,
	          ...execution.priority === void 0 ? {} : { priority: execution.priority }
	        });
	      } catch (error) {
	        if (this.#closed || this.scope.destroyed || isThrottleFailure(error)) throw error;
	        failedBatchCount += 1, this.#onError(error);
	      }
	      const loadedAfter = batch.reduce(
	        (total, postId) => total + Number(this.#postById.has(postId)),
	        0
	      );
	      loadedAfter !== loadedBefore && (loadedCount += loadedAfter - loadedBefore, report());
	    }
	    const missingPostIds = this.#streamPostIds.filter(
	      (postId) => !this.#postById.has(postId)
	    ), coverage = this.postStreamCoverage();
	    return Object.freeze({
	      posts: this.cachedPosts(),
	      missingPostIds: freezeNumbers(missingPostIds),
	      complete: coverage.complete,
	      failedBatchCount
	    });
	  }
	  async loadPostById(rawPostId, options = {}) {
	    this.#assertActive();
	    const postId = (0, import_identifiers.discoursePostId)(rawPostId), observedAt = this.#now(), payload = await this.#requests.loadPostById(postId, options), matchingPost = discoursePostsFromPayload(payload).find((post) => {
	      try {
	        return (0, import_identifiers.discoursePostReference)(post).postId === postId;
	      } catch {
	        return !1;
	      }
	    });
	    return matchingPost ? (options.created === !0 || !this.#streamPostIds.includes(postId) ? this.ingestCreatedPost(matchingPost, "target-refresh", observedAt) : this.#commit({ posts: [matchingPost] }, "target-refresh", observedAt), this.#postById.get(postId) ?? null) : null;
	  }
	  /**
	   * Discourse 直属回复 endpoint 的唯一 canonical 入口。
	   *
	   * endpoint 已由 parent post.id 限定;部分站点会省略 reply_to_post_number,
	   * 因而在提交快照/树之前必须补回父级提示。显式指向其他父级的帖子按自身 canonical
	   * 关系提交,但不混入当前直属集合;调用者可通过 scopedPosts 保留 endpoint 讨论语义。
	   */
	  loadDirectReplies(rawParentPostNumber, options = {}) {
	    this.#assertActive(), throwIfAborted(options.signal);
	    const parentPostNumber = (0, import_identifiers.discoursePostReference)({
	      post_number: rawParentPostNumber
	    }).postNumber, refresh = options.refresh === !0, key = `${parentPostNumber}:${refresh ? "refresh" : "default"}`, pending = this.#pendingDirectReplies.get(key);
	    if (pending)
	      return options.background !== !0 && (pending.profile.background = !1, pending.profile.activeRequest && this.#requests.promoteNestedReplies?.(
	        parentPostNumber,
	        {
	          ...pending.profile.activeRequest,
	          background: !1
	        }
	      )), this.#joinDirectReplies(pending, options.signal);
	    const controller = new AbortController(), requestLifetime = linkedAbortSignals(
	      this.#signal,
	      controller.signal
	    ), profile = {
	      background: options.background === !0,
	      activeRequest: null
	    };
	    let created;
	    return created = {
	      task: this.#loadDirectReplies(parentPostNumber, {
	        ...options,
	        signal: requestLifetime.signal ?? controller.signal
	      }, profile).finally(() => {
	        created.settled = !0, requestLifetime.dispose(), this.#pendingDirectReplies.get(key) === created && this.#pendingDirectReplies.delete(key);
	      }),
	      controller,
	      consumers: /* @__PURE__ */ new Set(),
	      profile,
	      unabortableConsumer: !1,
	      settled: !1
	    }, this.#pendingDirectReplies.set(key, created), this.#joinDirectReplies(created, options.signal);
	  }
	  #joinDirectReplies(pending, signal) {
	    if (throwIfAborted(signal), !signal)
	      return pending.unabortableConsumer = !0, pending.task;
	    const consumer = Symbol("direct-replies-consumer");
	    return pending.consumers.add(consumer), awaitWithSignal(pending.task, signal).finally(() => {
	      pending.consumers.delete(consumer), !(pending.settled || pending.unabortableConsumer || pending.consumers.size > 0 || pending.controller.signal.aborted) && pending.controller.abort(
	        signal.reason ?? new DOMException(
	          "直属回复已无消费者",
	          "AbortError"
	        )
	      );
	    });
	  }
	  /**
	   * 从一个或多个已加载根楼层递归补齐子孙分支。
	   *
	   * 完整讨论、阅读队列等上层只提交根集合;遍历、直属分页、partial 统计与错误保留均由
	   * TopicSession 统一完成,避免每个 surface 维护自己的树扫描和请求循环。
	   */
	  async loadReplyBranches(rawRootPostNumbers, options = {}) {
	    this.#assertActive();
	    const rootPostNumbers = Object.freeze([...new Set(
	      rawRootPostNumbers.map((postNumber) => (0, import_identifiers.discoursePostReference)({
	        post_number: postNumber
	      }).postNumber)
	    )]), pending = [...rootPostNumbers], queued = new Set(pending), enqueue = (postNumber) => {
	      queued.has(postNumber) || (queued.add(postNumber), pending.push(postNumber));
	    }, seen = /* @__PURE__ */ new Set(), postNumbers = [], parentPostNumbers = [], contextualReplyRelations = [], contextualRelationKeys = /* @__PURE__ */ new Set(), errors = [];
	    let expectedReplyCount = 0, loadedReplyCount = 0, complete = !0;
	    const report = (processedCount) => {
	      options.onProgress?.(Object.freeze({
	        processedCount,
	        totalCount: pending.length,
	        loadedReplyCount,
	        expectedReplyCount
	      }));
	    };
	    report(0);
	    for (let index = 0; index < pending.length; index += 1) {
	      this.#assertActive();
	      const postNumber = pending[index];
	      if (seen.has(postNumber)) {
	        report(index + 1);
	        continue;
	      }
	      seen.add(postNumber);
	      const post = this.#postByNumber.get(postNumber);
	      if (!post) {
	        complete = !1, report(index + 1);
	        continue;
	      }
	      postNumbers.push(postNumber);
	      const expectedCount = Math.max(
	        nonNegativeInteger(post.reply_count),
	        this.#replies.topology.childrenOf(postNumber).length
	      );
	      if (expectedCount > 0) {
	        parentPostNumbers.push(postNumber);
	        let directPosts = this.#knownDirectReplies(postNumber), scopedPosts = directPosts;
	        if (directPosts.length < expectedCount)
	          try {
	            const result = await this.loadDirectReplies(postNumber, {
	              ...options,
	              expectedCount
	            });
	            directPosts = result.posts, scopedPosts = result.scopedPosts, complete = complete && result.complete;
	          } catch (error) {
	            let finalError = error;
	            if (isAbortFailure(error) && !options.signal?.aborted && !this.scope.destroyed)
	              try {
	                const recovered = await this.loadDirectReplies(postNumber, {
	                  ...options,
	                  expectedCount,
	                  background: !1
	                });
	                directPosts = recovered.posts, scopedPosts = recovered.scopedPosts, complete = complete && recovered.complete, finalError = null;
	              } catch (recoveryError) {
	                finalError = recoveryError;
	              }
	            if (finalError !== null) {
	              if (this.scope.destroyed || isAbortFailure(finalError) || isThrottleFailure(finalError)) throw finalError;
	              errors.push(finalError), complete = !1;
	            }
	          }
	        const scopedPostNumbers = /* @__PURE__ */ new Set();
	        for (const scopedPost of scopedPosts) {
	          const childPostNumber = (0, import_identifiers.discoursePostReference)(scopedPost).postNumber;
	          if (childPostNumber === postNumber || (scopedPostNumbers.add(childPostNumber), enqueue(childPostNumber), this.#canonicalBranchContains(postNumber, childPostNumber))) continue;
	          const key = `${postNumber}:${childPostNumber}`;
	          contextualRelationKeys.has(key) || (contextualRelationKeys.add(key), contextualReplyRelations.push(Object.freeze({
	            parentPostNumber: postNumber,
	            postNumber: childPostNumber
	          })));
	        }
	        for (const directPost of directPosts)
	          scopedPostNumbers.add((0, import_identifiers.discoursePostReference)(directPost).postNumber);
	        expectedReplyCount += expectedCount, loadedReplyCount += scopedPostNumbers.size, complete = complete && scopedPostNumbers.size >= expectedCount;
	      }
	      for (const child of this.#replies.topology.childrenOf(postNumber))
	        enqueue((0, import_identifiers.discoursePostReference)({
	          post_number: child
	        }).postNumber);
	      report(index + 1);
	    }
	    return Object.freeze({
	      rootPostNumbers,
	      postNumbers: Object.freeze(postNumbers),
	      parentPostNumbers: Object.freeze(parentPostNumbers),
	      expectedReplyCount,
	      loadedReplyCount,
	      complete,
	      contextualReplyRelations: Object.freeze(contextualReplyRelations),
	      errors: Object.freeze(errors)
	    });
	  }
	  /**
	   * 用户 reply/create 与 MessageBus created 共用的原子 ingress。
	   *
	   * 新 post 在同一次 commit 中进入 post.id stream、正文索引、快照和回复拓扑;已知 post
	   * 只更新正文/关系,不重复增加 stream 或 expected count。
	   */
	  ingestCreatedPost(post, source, observedAt = this.#now()) {
	    this.#assertActive();
	    const reference = (0, import_identifiers.discoursePostReference)(post);
	    if (reference.postId === null) throw new Error("created 楼层缺少 post.id");
	    const nextStream = this.#streamWithCreatedPost(
	      reference.postId,
	      reference.postNumber
	    ), expectedPostCount = nextStream === void 0 ? void 0 : Math.max(
	      nextStream.length,
	      this.#snapshots.snapshot().expectedPostCount
	    ), repairedChildren = [...this.#postByNumber.values()].filter((candidate) => {
	      try {
	        return (0, import_identifiers.discoursePostReference)(candidate).replyToPostNumber === reference.postNumber;
	      } catch {
	        return !1;
	      }
	    });
	    return this.#commit({
	      posts: [post, ...repairedChildren],
	      ...nextStream === void 0 ? {} : { streamPostIds: nextStream },
	      ...expectedPostCount === void 0 ? {} : { expectedPostCount }
	    }, source, observedAt);
	  }
	  removePostById(rawPostId, source = "action-response", observedAt = this.#now()) {
	    this.#assertActive();
	    const postId = (0, import_identifiers.discoursePostId)(rawPostId), post = this.#postById.get(postId);
	    if (!post) throw new Error(`canonical post.id ${postId} 尚未加载`);
	    const reference = (0, import_identifiers.discoursePostReference)(post), snapshotResult = this.#snapshots.removePost(
	      reference.postNumber,
	      postId,
	      source,
	      observedAt
	    ), treeEvent = this.#replies.remove(
	      reference.postNumber,
	      source,
	      { observedAt }
	    );
	    this.#restoreIndexes();
	    const changedPostNumbers = Object.freeze(
	      [.../* @__PURE__ */ new Set([
	        reference.postNumber,
	        ...treeEvent?.change.changedPostNumbers ?? []
	      ])].sort((left, right) => left - right)
	    ), result = Object.freeze({
	      source,
	      observedAt,
	      acceptedPosts: 0,
	      ignoredPosts: snapshotResult.removed ? 0 : 1,
	      changedPostNumbers,
	      removedPostNumbers: snapshotResult.removed ? Object.freeze([reference.postNumber]) : Object.freeze([]),
	      topicChanged: !1,
	      streamChanged: snapshotResult.streamChanged
	    });
	    for (const error of this.changes.emit(result)) this.#onError(error);
	    return result;
	  }
	  async loadTarget(rawPostNumber, options = {}) {
	    this.#assertActive();
	    const postNumber = (0, import_identifiers.discoursePostReference)({ post_number: rawPostNumber }).postNumber, scope = options.scope ?? "single", shouldAdvance = options.advanceCursor ?? scope === "around", cachedBeforeRequest = this.#postByNumber.get(postNumber);
	    if (!options.forceRefresh && this.#unavailablePostNumbers.has(postNumber))
	      return Object.freeze(cachedBeforeRequest ? [cachedBeforeRequest] : []);
	    const cached = scope === "single" && !options.forceRefresh ? this.#postByNumber.get(postNumber) : void 0;
	    if (cached)
	      return shouldAdvance && this.#advanceCursorPast([cached], postNumber), Object.freeze([cached]);
	    const targetOptions = {
	      scope,
	      slug: String(this.#topic?.slug ?? "topic"),
	      refresh: options.forceRefresh === !0,
	      ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork }
	    };
	    let fallback = Object.freeze([]), definitiveStatus = null;
	    for (const candidate of this.#requests.targetCandidates(postNumber, targetOptions)) {
	      const observedAt = this.#now();
	      try {
	        const payload = await this.#requests.loadTargetCandidate(
	          candidate,
	          postNumber,
	          targetOptions
	        ), posts = discoursePostsFromPayload(payload);
	        this.ingestPosts(posts, "target-refresh", observedAt);
	        const committedPosts = posts.map((post) => {
	          try {
	            return this.#postByNumber.get(
	              (0, import_identifiers.discoursePostReference)(post).postNumber
	            );
	          } catch {
	            return;
	          }
	        }).filter((post) => post !== void 0), found = posts.some((post) => {
	          try {
	            return (0, import_identifiers.discoursePostReference)(post).postNumber === postNumber;
	          } catch {
	            return !1;
	          }
	        }) ? this.#postByNumber.get(postNumber) : void 0;
	        if (found) {
	          const result = scope === "around" ? committedPosts : [found];
	          return shouldAdvance && this.#advanceCursorPast(result, postNumber), Object.freeze([...result]);
	        }
	        scope === "around" && committedPosts.length && !fallback.length && (fallback = committedPosts);
	      } catch (error) {
	        const status = errorStatus(error);
	        if ((0, import_native_request_descriptors.discourseNativeTargetFailureIsDefinitive)({
	          endpoint: candidate.endpoint,
	          scope,
	          status
	        })) {
	          definitiveStatus = status;
	          break;
	        }
	        if (scope === "single" && candidate.endpoint === "post-by-number" && status === 403 && cachedBeforeRequest !== void 0) {
	          definitiveStatus = 403;
	          continue;
	        }
	        if (isAuthFailure(error) || isThrottleFailure(error)) throw error;
	      }
	    }
	    if (scope === "around" && this.#streamPostIds.length) {
	      const center = this.#streamIndexForPostNumber(postNumber), start = Math.max(
	        0,
	        Math.min(
	          Math.max(0, this.#streamPostIds.length - this.#pageSize),
	          center - Math.floor(this.#pageSize / 2)
	        )
	      ), result = await this.loadPostsByIds(
	        this.#streamPostIds.slice(start, start + this.#pageSize)
	      );
	      if (result.posts.length)
	        return this.#advanceCursorPast(result.posts, postNumber), result.posts;
	    }
	    return scope === "single" && definitiveStatus !== null && (this.#unavailablePostNumbers.add(postNumber), cachedBeforeRequest) ? (this.#snapshots.markPostUnavailable(
	      postNumber,
	      definitiveStatus,
	      this.#now()
	    ), this.#syncLocalArchiveState(), shouldAdvance && this.#advanceCursorPast([cachedBeforeRequest], postNumber), Object.freeze([cachedBeforeRequest])) : (fallback.length && shouldAdvance && this.#advanceCursorPast(fallback, postNumber), Object.freeze([...fallback]));
	  }
	  loadBeforePost(postNumber, options = {}) {
	    return this.#loadRelative(postNumber, "before", options);
	  }
	  loadAfterPost(postNumber, options = {}) {
	    return this.#loadRelative(postNumber, "after", options);
	  }
	  loadLastPost(options = {}) {
	    return this.#loadRelative(1, "last", options);
	  }
	  ingestPosts(posts, source, observedAt = this.#now()) {
	    return this.#assertActive(), this.#commit({ posts }, source, observedAt);
	  }
	  ingestTopic(topic, source, observedAt = this.#now()) {
	    return this.#assertActive(), this.#commitTopic(topic, source, observedAt);
	  }
	  async flush() {
	    await this.#replies.flush(), await this.#snapshots.flush();
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #loadDirectReplies(parentPostNumber, options, profile) {
	    const request = this.#requests.loadNestedReplies;
	    if (typeof request != "function")
	      throw new Error("TopicSession 请求端口未提供 Discourse 直属回复能力");
	    const parentPost = this.#postByNumber.get(parentPostNumber);
	    if (!parentPost) throw new Error(`父楼层 #${parentPostNumber} 尚未加载`);
	    const parentPostId = (0, import_identifiers.discoursePostReference)(parentPost).postId;
	    if (parentPostId === null)
	      throw new Error(`父楼层 #${parentPostNumber} 缺少 post.id`);
	    const configuredExpected = nonNegativeInteger(options.expectedCount), declaredExpected = nonNegativeInteger(parentPost.reply_count);
	    let posts = this.#knownDirectReplies(parentPostNumber);
	    const scopedPostsByNumber = new Map(
	      posts.map((post) => [(0, import_identifiers.discoursePostReference)(post).postNumber, post])
	    ), knownRelationCount = this.#replies.topology.childrenOf(parentPostNumber).length, expectedCount = Math.max(
	      configuredExpected,
	      declaredExpected,
	      knownRelationCount,
	      posts.length
	    );
	    if (!options.refresh && expectedCount <= posts.length)
	      return Object.freeze({
	        parentPostNumber,
	        posts,
	        scopedPosts: posts,
	        expectedCount,
	        complete: !0,
	        endpointExhausted: !1,
	        pageCount: 0,
	        nextAfter: 0
	      });
	    const maxPages = Math.max(
	      1,
	      Math.min(100, positiveInteger(options.maxPages ?? 32, "maxPages"))
	    ), configuredMaxAttempts = options.maxAttempts === void 0 ? null : Math.max(
	      1,
	      Math.min(
	        4,
	        positiveInteger(options.maxAttempts, "maxAttempts")
	      )
	    ), maxAttempts = configuredMaxAttempts ?? 2;
	    let after = 0, pageCount = 0, endpointExhausted = !1;
	    const seenCursors = /* @__PURE__ */ new Set();
	    for (; pageCount < maxPages; ) {
	      this.#assertActive(), throwIfAborted(options.signal);
	      let payload, observedAt = this.#now(), lastError;
	      for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
	        throwIfAborted(options.signal), await awaitWithSignal(options.beforePage?.(), options.signal), this.#assertActive(), throwIfAborted(options.signal), observedAt = this.#now();
	        const activeRequest = Object.freeze({
	          parentPostId,
	          after,
	          background: profile.background,
	          ...options.refresh === void 0 ? {} : { refresh: options.refresh },
	          ...options.signal === void 0 ? {} : { signal: options.signal },
	          ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork }
	        });
	        profile.activeRequest = activeRequest;
	        try {
	          payload = await request.call(
	            this.#requests,
	            parentPostNumber,
	            activeRequest
	          ), lastError = void 0;
	          break;
	        } catch (error) {
	          if (lastError = error, options.signal?.aborted || isAbortFailure(error) || isAuthFailure(error) || isThrottleFailure(error)) throw error;
	          const allowedAttempts = configuredMaxAttempts ?? (profile.background ? 1 : 2);
	          if (attempt + 1 < allowedAttempts)
	            await awaitWithSignal(this.#wait(240), options.signal);
	          else
	            break;
	        } finally {
	          profile.activeRequest === activeRequest && (profile.activeRequest = null);
	        }
	      }
	      if (lastError !== void 0) throw lastError;
	      this.#assertActive(), pageCount += 1;
	      const pagePosts = discoursePostsFromPayload(payload), scopedPagePosts = pagePosts.map((post) => scopedReplyPost(post, parentPostNumber)).filter((post) => post !== null);
	      if (scopedPagePosts.length) {
	        throwIfAborted(options.signal), await awaitWithSignal(options.beforeCommit?.(), options.signal), this.#assertActive(), throwIfAborted(options.signal), this.ingestPosts(scopedPagePosts, "loader-batch", observedAt);
	        for (const post of scopedPagePosts)
	          scopedPostsByNumber.set(
	            (0, import_identifiers.discoursePostReference)(post).postNumber,
	            post
	          );
	        posts = this.#knownDirectReplies(parentPostNumber);
	      }
	      const nextAfter = replyPageCursor(pagePosts);
	      if (endpointExhausted = pagePosts.length === 0 || pagePosts.length < import_native_request_descriptors.DISCOURSE_DIRECT_REPLIES_PAGE_SIZE || nextAfter <= after || seenCursors.has(nextAfter), expectedCount <= scopedPostsByNumber.size || endpointExhausted) {
	        after = Math.max(after, nextAfter);
	        break;
	      }
	      seenCursors.add(nextAfter), after = nextAfter;
	    }
	    return Object.freeze({
	      parentPostNumber,
	      posts,
	      scopedPosts: Object.freeze([...scopedPostsByNumber.values()]),
	      expectedCount,
	      complete: expectedCount <= scopedPostsByNumber.size,
	      endpointExhausted,
	      pageCount,
	      nextAfter: after
	    });
	  }
	  #canonicalBranchContains(parentPostNumber, childPostNumber) {
	    const seen = /* @__PURE__ */ new Set();
	    let current = childPostNumber;
	    for (; current !== null && !seen.has(current); ) {
	      if (current === parentPostNumber) return !0;
	      seen.add(current);
	      const parent = this.#replies.topology.parentOf(current);
	      current = parent == null ? null : (0, import_identifiers.discoursePostReference)({ post_number: parent }).postNumber;
	    }
	    return !1;
	  }
	  #knownDirectReplies(parentPostNumber) {
	    return Object.freeze(
	      this.#replies.topology.childrenOf(parentPostNumber).map((postNumber) => this.#postByNumber.get(
	        (0, import_identifiers.discoursePostReference)({ post_number: postNumber }).postNumber
	      )).filter((post) => post !== void 0)
	    );
	  }
	  async #initialize(options) {
	    this.#assertActive(), await Promise.all([
	      this.#snapshots.restore(),
	      this.#replies.restore()
	    ]), this.#restoreIndexes();
	    const snapshot = this.#snapshots.snapshot();
	    this.#replies.setExpectedPostCount(snapshot.expectedPostCount);
	    const cachedPosts = this.#snapshots.posts();
	    if (this.#replies.coverage().knownPostCount < cachedPosts.length) {
	      const repairedTree = this.#replies.ingest(
	        cachedPosts,
	        "loader-batch",
	        { observedAt: snapshot.updatedAt }
	      );
	      for (const error of repairedTree.listenerErrors) this.#onError(error);
	    }
	    const restoredStreamCount = this.#streamPostIds.reduce(
	      (count, postId) => count + (this.#postById.has(postId) ? 1 : 0),
	      0
	    ), archivedTopic = this.#snapshots.localArchiveState().topic !== null;
	    return this.#topic !== null && this.#streamPostIds.length > 0 && (archivedTopic || snapshot.expectedPostCount <= this.#streamPostIds.length) ? (this.#initializedFromCache = !0, this.#syncLocalArchiveState(), this.#onInitializeSource("cache", Object.freeze({
	      cachedCount: restoredStreamCount,
	      missingCount: 0,
	      totalCount: this.#streamPostIds.length
	    })), this.#refreshCachedInBackground && !this.#snapshots.isFresh() && this.refresh({ background: !0 }).then(() => this.#snapshots.localArchiveState().topic ? null : this.loadPostById(this.#streamPostIds.at(-1), {
	      background: !0
	    })).catch(this.#onError), this.#topic) : (this.#initializedFromCache = !1, this.#onInitializeSource("network", Object.freeze({
	      cachedCount: restoredStreamCount,
	      missingCount: Math.max(
	        1,
	        snapshot.expectedPostCount - restoredStreamCount
	      ),
	      totalCount: Math.max(
	        1,
	        snapshot.expectedPostCount,
	        this.#streamPostIds.length
	      )
	    })), this.#loadTopic(options, !1));
	  }
	  #commitTopic(topic, source, observedAt) {
	    const payloadTopicId = topic.id === void 0 ? this.topicId : (0, import_identifiers.discourseTopicId)(topic.id);
	    if (payloadTopicId !== this.topicId)
	      throw new Error(`Topic 响应 ${payloadTopicId} 与会话 ${this.topicId} 不一致`);
	    const streamPostIds = this.#mergeStreamPostIds(
	      (0, import_identifiers.discoursePostIdStream)(topic.post_stream?.stream ?? [])
	    ), posts = discoursePostsFromPayload(topic), expectedPostCount = nonNegativeInteger(topic.posts_count);
	    return this.#commit(
	      { topic, posts, streamPostIds, expectedPostCount },
	      source,
	      observedAt
	    );
	  }
	  #commit(input, source, observedAt) {
	    const normalized = [];
	    let ignoredPosts = 0;
	    const byId = /* @__PURE__ */ new Map(), byNumber = /* @__PURE__ */ new Map();
	    for (const post of input.posts)
	      try {
	        const reference = (0, import_identifiers.discoursePostReference)(post);
	        if (reference.postId === null) throw new Error("Topic 楼层缺少 post.id");
	        if (reference.topicId !== null && reference.topicId !== this.topicId)
	          throw new Error(`楼层 #${reference.postNumber} 属于其他 Topic`);
	        const entry = Object.freeze({
	          post,
	          postId: reference.postId,
	          postNumber: reference.postNumber
	        }), previousById = byId.get(entry.postId);
	        previousById && (previousById.postNumber !== entry.postNumber && this.#onError(new Error(
	          `post.id ${entry.postId} 同时映射楼层 #${previousById.postNumber} 与 #${entry.postNumber}`
	        )), byNumber.delete(previousById.postNumber));
	        const previousByNumber = byNumber.get(entry.postNumber);
	        previousByNumber && (previousByNumber.postId !== entry.postId && this.#onError(new Error(
	          `楼层 #${entry.postNumber} 同时映射 post.id ${previousByNumber.postId} 与 ${entry.postId}`
	        )), byId.delete(previousByNumber.postId)), byId.set(entry.postId, entry), byNumber.set(entry.postNumber, entry);
	      } catch (error) {
	        ignoredPosts += 1, this.#onError(error);
	      }
	    normalized.push(...byId.values());
	    const posts = normalized.map((entry) => entry.post), treeResult = this.#replies.ingest(posts, source, { observedAt });
	    input.expectedPostCount !== void 0 && this.#replies.setExpectedPostCount(input.expectedPostCount);
	    const snapshotResult = this.#snapshots.ingest({
	      source,
	      observedAt,
	      ...input.topic === void 0 ? {} : { topic: input.topic },
	      ...input.streamPostIds === void 0 ? {} : { streamPostIds: input.streamPostIds },
	      ...input.expectedPostCount === void 0 ? {} : { expectedPostCount: input.expectedPostCount },
	      posts
	    });
	    this.#applySnapshotChanges(snapshotResult.changedPostNumbers), this.#syncLocalArchiveState();
	    const result = Object.freeze({
	      source,
	      observedAt,
	      acceptedPosts: snapshotResult.acceptedPosts,
	      ignoredPosts: ignoredPosts + snapshotResult.ignoredPosts,
	      changedPostNumbers: snapshotResult.changedPostNumbers,
	      topicChanged: snapshotResult.topicChanged,
	      streamChanged: snapshotResult.streamChanged
	    });
	    for (const error of treeResult.listenerErrors) this.#onError(error);
	    for (const error of this.changes.emit(result)) this.#onError(error);
	    return result;
	  }
	  /**
	   * 常规网络批次只会新增或刷新少量楼层;按仓储裁决后的 winner 增量更新索引,
	   * 避免全帖补流时每批重扫此前所有帖子。身份漂移或冲突属于异常输入,回退到
	   * 完整重建以保留既有冲突诊断与确定性 winner 语义。
	   */
	  #applySnapshotChanges(changedPostNumbers) {
	    if (this.#topic = this.#snapshots.topic(), this.#streamPostIds = this.#snapshots.streamPostIds(), !changedPostNumbers.length) return;
	    const entries = [], changedIds = /* @__PURE__ */ new Set();
	    for (const rawPostNumber of changedPostNumbers) {
	      const postNumber = (0, import_identifiers.discoursePostNumber)(rawPostNumber), post = this.#snapshots.post(postNumber);
	      if (!post) {
	        this.#restoreIndexes();
	        return;
	      }
	      try {
	        const reference = (0, import_identifiers.discoursePostReference)(post);
	        if (reference.postId === null || reference.postNumber !== postNumber || changedIds.has(reference.postId)) {
	          this.#restoreIndexes();
	          return;
	        }
	        const previousAtNumber = this.#postByNumber.get(postNumber);
	        if (previousAtNumber && (0, import_identifiers.discoursePostReference)(previousAtNumber).postId !== reference.postId) {
	          this.#restoreIndexes();
	          return;
	        }
	        const previousAtId = this.#postById.get(reference.postId);
	        if (previousAtId && (0, import_identifiers.discoursePostReference)(previousAtId).postNumber !== postNumber) {
	          this.#restoreIndexes();
	          return;
	        }
	        changedIds.add(reference.postId), entries.push(Object.freeze({
	          post,
	          postId: reference.postId,
	          postNumber
	        }));
	      } catch (error) {
	        this.#onError(error), this.#restoreIndexes();
	        return;
	      }
	    }
	    this.#cachedPostsSnapshot = null;
	    const archivedPostNumbers = new Set(
	      this.#snapshots.localArchiveState().posts.map((entry) => (0, import_identifiers.discoursePostNumber)(entry.postNumber))
	    );
	    for (const entry of entries)
	      this.#postById.set(entry.postId, entry.post), this.#postByNumber.set(entry.postNumber, entry.post), archivedPostNumbers.has(entry.postNumber) ? this.#unavailablePostNumbers.add(entry.postNumber) : this.#unavailablePostNumbers.delete(entry.postNumber);
	  }
	  #restoreIndexes() {
	    this.#cachedPostsSnapshot = null, this.#topic = this.#snapshots.topic(), this.#streamPostIds = this.#snapshots.streamPostIds(), this.#postById.clear(), this.#postByNumber.clear();
	    const normalizedById = /* @__PURE__ */ new Map(), normalizedByNumber = /* @__PURE__ */ new Map();
	    for (const post of this.#snapshots.posts())
	      try {
	        const reference = (0, import_identifiers.discoursePostReference)(post);
	        if (reference.postId === null) continue;
	        const entry = Object.freeze({
	          post,
	          postId: reference.postId,
	          postNumber: reference.postNumber
	        }), previousById = normalizedById.get(entry.postId);
	        previousById && (previousById.postNumber !== entry.postNumber && this.#onError(new Error(
	          `快照 post.id ${entry.postId} 同时映射楼层 #${previousById.postNumber} 与 #${entry.postNumber}`
	        )), normalizedByNumber.delete(previousById.postNumber));
	        const previousByNumber = normalizedByNumber.get(entry.postNumber);
	        previousByNumber && (previousByNumber.postId !== entry.postId && this.#onError(new Error(
	          `快照楼层 #${entry.postNumber} 同时映射 post.id ${previousByNumber.postId} 与 ${entry.postId}`
	        )), normalizedById.delete(previousByNumber.postId)), normalizedById.set(entry.postId, entry), normalizedByNumber.set(entry.postNumber, entry);
	      } catch (error) {
	        this.#onError(error);
	      }
	    const archivedPostNumbers = new Set(
	      this.#snapshots.localArchiveState().posts.map((entry) => (0, import_identifiers.discoursePostReference)({ post_number: entry.postNumber }).postNumber)
	    );
	    for (const entry of normalizedById.values()) this.#postById.set(entry.postId, entry.post);
	    for (const entry of normalizedByNumber.values())
	      this.#postByNumber.set(entry.postNumber, entry.post);
	    for (const postNumber of this.#unavailablePostNumbers)
	      this.#postByNumber.has(postNumber) && !archivedPostNumbers.has(postNumber) && this.#unavailablePostNumbers.delete(postNumber);
	    for (const postNumber of archivedPostNumbers)
	      this.#unavailablePostNumbers.add(postNumber);
	  }
	  #syncLocalArchiveState() {
	    const snapshot = this.#snapshots.localArchiveState(), key = JSON.stringify([
	      snapshot.topic?.status ?? 0,
	      snapshot.topic?.confirmedAt ?? 0,
	      snapshot.posts.map((entry) => [
	        entry.postNumber,
	        entry.status,
	        entry.confirmedAt
	      ])
	    ]);
	    if (key !== this.#archiveStateKey) {
	      this.#archiveStateKey = key;
	      for (const error of this.archiveChanges.emit(snapshot)) this.#onError(error);
	    }
	  }
	  #mergeStreamPostIds(incoming) {
	    if (!this.#streamPostIds.length) return incoming;
	    const merged = [...incoming], known = new Set(merged);
	    for (const postId of this.#streamPostIds) {
	      if (known.has(postId)) continue;
	      const post = this.#postById.get(postId);
	      if (!post) {
	        merged.push(postId), known.add(postId);
	        continue;
	      }
	      const postNumber = (0, import_identifiers.discoursePostReference)(post).postNumber;
	      let insertAt = merged.length;
	      for (let index = 0; index < merged.length; index += 1) {
	        const candidate = this.#postById.get(merged[index]);
	        if (candidate && (0, import_identifiers.discoursePostReference)(candidate).postNumber > postNumber) {
	          insertAt = index;
	          break;
	        }
	      }
	      merged.splice(insertAt, 0, postId), known.add(postId);
	    }
	    return Object.freeze(merged);
	  }
	  #streamWithCreatedPost(postId, postNumber) {
	    if (this.#streamPostIds.includes(postId)) return;
	    const mutable = [...this.#streamPostIds];
	    let insertAt = mutable.length;
	    for (; insertAt > 0; ) {
	      const previous = this.#postById.get(mutable[insertAt - 1]);
	      if (!previous || (0, import_identifiers.discoursePostReference)(previous).postNumber <= postNumber) break;
	      insertAt -= 1;
	    }
	    return mutable.splice(insertAt, 0, postId), Object.freeze(mutable);
	  }
	  async #ensurePostIds(missing, options) {
	    options.background !== !0 && this.#promotePendingPostBatches(options);
	    const unclaimed = missing.filter((postId) => !this.#pendingByPostId.has(postId));
	    if (unclaimed.length) {
	      const request = this.#fetchPostBatch(unclaimed, options, 0).finally(() => {
	        for (const postId of unclaimed)
	          this.#pendingByPostId.get(postId) === request && this.#pendingByPostId.delete(postId);
	      });
	      for (const postId of unclaimed) this.#pendingByPostId.set(postId, request);
	    }
	    await Promise.all([...new Set(
	      missing.map((postId) => this.#pendingByPostId.get(postId)).filter(
	        (request) => request !== void 0
	      )
	    )]);
	  }
	  #promotePendingPostBatches(options) {
	    if (!this.#requests.promotePostsByIds) return;
	    const claimed = new Set(this.#pendingByPostId.values());
	    for (const request of claimed) {
	      const batch = [...this.#pendingByPostId.entries()].filter(([, pending]) => pending === request).map(([postId]) => postId);
	      batch.length && this.#requests.promotePostsByIds(batch, options);
	    }
	  }
	  async #fetchPostBatch(postIds, options, splitDepth) {
	    const observedAt = this.#now();
	    try {
	      const payload = await this.#requests.loadPostsByIds(postIds, {
	        ...options.background === void 0 ? {} : { background: options.background },
	        ...options.refresh === void 0 ? {} : { refresh: options.refresh },
	        ...options.priority === void 0 ? {} : { priority: options.priority },
	        ...options.beforeNetwork === void 0 ? {} : { beforeNetwork: options.beforeNetwork }
	      }), posts = discoursePostsFromPayload(payload);
	      await options.beforeCommit?.(), this.#assertActive(), this.ingestPosts(posts, "loader-batch", observedAt);
	    } catch (error) {
	      if (shouldSplit(error) && postIds.length > 1 && splitDepth < 1) {
	        const middle = Math.ceil(postIds.length / 2), failure = (await Promise.allSettled([
	          this.#fetchPostBatch(postIds.slice(0, middle), options, splitDepth + 1),
	          this.#fetchPostBatch(postIds.slice(middle), options, splitDepth + 1)
	        ])).find((result) => result.status === "rejected");
	        if (failure) throw failure.reason;
	        return;
	      }
	      throw error;
	    }
	  }
	  async #loadRelative(rawPostNumber, direction, options) {
	    if (this.#assertActive(), !this.#streamPostIds.length) return Object.freeze([]);
	    const postNumber = (0, import_identifiers.discoursePostReference)({ post_number: rawPostNumber }).postNumber, last = direction === "last", targetIndex = last ? this.#streamPostIds.length - 1 : this.#streamIndexForPostNumber(postNumber);
	    if (direction === "before" && targetIndex <= 0) return Object.freeze([]);
	    const start = last ? targetIndex : direction === "before" ? Math.max(0, targetIndex - this.#pageSize) : Math.min(this.#streamPostIds.length, targetIndex + 1), end = last ? this.#streamPostIds.length : direction === "before" ? targetIndex : start + this.#pageSize, result = await this.loadPostsByIds(this.#streamPostIds.slice(start, end), options);
	    return last && result.posts.length && this.#advanceCursorPast(result.posts), result.posts;
	  }
	  #streamIndexForPostNumber(postNumber) {
	    const post = this.#postByNumber.get(postNumber);
	    if (post) {
	      const reference = (0, import_identifiers.discoursePostReference)(post);
	      if (reference.postId !== null) {
	        const exact = this.#streamPostIds.indexOf(reference.postId);
	        if (exact >= 0) return exact;
	      }
	    }
	    return Math.min(this.#streamPostIds.length - 1, Math.max(0, postNumber - 1));
	  }
	  #advanceCursorPast(posts, targetPostNumber) {
	    let maxIndex = -1;
	    for (const post of posts)
	      try {
	        const reference = (0, import_identifiers.discoursePostReference)(post);
	        reference.postId !== null && (maxIndex = Math.max(maxIndex, this.#streamPostIds.indexOf(reference.postId)));
	      } catch {
	      }
	    maxIndex < 0 && targetPostNumber !== void 0 && (maxIndex = Math.min(
	      this.#streamPostIds.length - 1,
	      Math.max(0, targetPostNumber - 1)
	    )), maxIndex >= this.#cursor && (this.#cursor = maxIndex + 1);
	  }
	  #batchResult(posts, done, missingPostIds, retry, fatal, error) {
	    return Object.freeze({
	      posts: Object.freeze([...posts]),
	      done,
	      retry,
	      fatal,
	      ...error === void 0 ? {} : { error },
	      missingPostIds: freezeNumbers(missingPostIds)
	    });
	  }
	  #assertActive() {
	    if (this.#closed || this.scope.destroyed || this.#signal?.aborted)
	      throw this.#signal?.aborted ? this.#signal.reason ?? new DOMException("Topic session closed", "AbortError") : new DOMException("Topic session closed", "AbortError");
	  }
	}
}, "4cd7a5453578b0f1444c14df497571329e73b192e2c6f6f845219f925b02fdc2");

/* Source: lite/src/userscript/browser-share-surface.ts */
runtime.register("src/userscript/browser-share-surface.js", function(module, exports, require) {
	var browser_share_surface_exports = {};
	__export(browser_share_surface_exports, {
	  BrowserReaderShareSurface: () => BrowserReaderShareSurface
	});
	module.exports = __toCommonJS(browser_share_surface_exports);
	var import_value_record = require("../kernel/value-record.js");
	class BrowserReaderShareSurface {
	  #navigator;
	  constructor(pageWindow) {
	    const page = (0, import_value_record.valueRecord)(pageWindow);
	    this.#navigator = (0, import_value_record.valueRecord)(page?.navigator) ?? Object.freeze({});
	  }
	  async share(input) {
	    const share = this.#navigator.share;
	    if (typeof share != "function") return "unsupported";
	    try {
	      return await share.call(this.#navigator, input), "shared";
	    } catch (cause) {
	      if ((0, import_value_record.valueRecord)(cause)?.name === "AbortError") return "cancelled";
	      throw cause;
	    }
	  }
	  async copyText(text) {
	    const clipboard = (0, import_value_record.valueRecord)(this.#navigator.clipboard), writeText = clipboard?.writeText;
	    if (typeof writeText != "function")
	      throw new Error("浏览器剪贴板不可用");
	    await writeText.call(clipboard, text);
	  }
	}
}, "ea7818f3373ea5d34e4251de1fa84d0f8d0f136ac4484637bb53967e9486b9d8");

/* Source: lite/src/userscript/browser-userscript-environment.ts */
runtime.register("src/userscript/browser-userscript-environment.js", function(module, exports, require) {
	var browser_userscript_environment_exports = {};
	__export(browser_userscript_environment_exports, {
	  BrowserUserscriptEnvironment: () => BrowserUserscriptEnvironment
	});
	module.exports = __toCommonJS(browser_userscript_environment_exports);
	var import_native_host_api = require("../discourse/native-host-api.js"), import_coordinated_request_client = require("../network/coordinated-request-client.js"), import_discourse_native_read_transport = require("../network/discourse-native-read-transport.js"), import_public_resource_request_adapter = require("../network/public-resource-request-adapter.js"), import_reader_search = require("../search/reader-search.js"), import_translation_request_adapter = require("../translation/translation-request-adapter.js"), import_browser_share_surface = require("./browser-share-surface.js"), import_browser_discourse_site_probe = require("../site/browser-discourse-site-probe.js"), import_value_record = require("../kernel/value-record.js"), import_reader_webdav_client = require("../sync/reader-webdav-client.js");
	class BrowserUserscriptEnvironment {
	  #userscriptGlobal;
	  pageWindow;
	  discourseHost;
	  constructor(options) {
	    const userscriptGlobal = (0, import_value_record.objectRecord)(options.userscriptGlobal);
	    if (!userscriptGlobal) throw new Error("userscript global 不可用");
	    const pageWindow = userscriptGlobal.unsafeWindow ?? userscriptGlobal.window;
	    if (!(0, import_value_record.objectRecord)(pageWindow))
	      throw new Error("userscript page window 不可用");
	    this.#userscriptGlobal = userscriptGlobal, this.pageWindow = pageWindow, this.discourseHost = new import_native_host_api.BrowserDiscourseHostApiPort({ pageWindow });
	  }
	  async waitForDiscourseRuntime(signal, options = {}) {
	    const timeoutMs = Math.max(1, Math.floor(options.timeoutMs ?? 15e3)), pollIntervalMs = Math.max(
	      1,
	      Math.floor(options.pollIntervalMs ?? 50)
	    ), now = options.now ?? Date.now, delay = options.delay ?? import_coordinated_request_client.abortableDelay, startedAt = now();
	    for (; !(0, import_discourse_native_read_transport.discourseNativeAjaxAvailable)(this.discourseHost) || !(0, import_native_host_api.discourseNativeCurrentUserBindingAvailable)(this.discourseHost); ) {
	      if (signal.aborted) throw signal.reason;
	      const remaining = timeoutMs - (now() - startedAt);
	      if (remaining <= 0)
	        throw new Error(
	          "Discourse 原生 Ajax/current-user 在启动期限内未就绪"
	        );
	      await delay(Math.min(pollIntervalMs, remaining), signal);
	    }
	  }
	  createExternalHttp(options = {}) {
	    const rawRequest = this.#userscriptGlobal.GM_xmlhttpRequest;
	    if (typeof rawRequest != "function")
	      throw new Error("GM_xmlhttpRequest 不可用,无法访问登记的外部服务");
	    const request = (requestOptions) => rawRequest.call(this.#userscriptGlobal, requestOptions);
	    return new import_translation_request_adapter.BrowserUserscriptExternalHttpPort({
	      ...options,
	      request
	    });
	  }
	  createDiscourseSiteProbe() {
	    const rawRequest = this.#userscriptGlobal.GM_xmlhttpRequest;
	    if (typeof rawRequest != "function")
	      throw new Error("GM_xmlhttpRequest 不可用,无法检测自定义站点");
	    const request = (requestOptions) => rawRequest.call(this.#userscriptGlobal, requestOptions);
	    return new import_browser_discourse_site_probe.BrowserDiscourseSiteProbe({ request });
	  }
	  createWebDavClient() {
	    const rawRequest = this.#userscriptGlobal.GM_xmlhttpRequest;
	    if (typeof rawRequest != "function")
	      throw new Error("GM_xmlhttpRequest 不可用,无法访问 WebDAV");
	    const request = (requestOptions) => rawRequest.call(this.#userscriptGlobal, requestOptions);
	    return new import_reader_webdav_client.ReaderWebDavClient({ request });
	  }
	  createPublicResourceHttp() {
	    const rawRequest = (0, import_value_record.objectRecord)(this.pageWindow)?.fetch;
	    if (typeof rawRequest != "function")
	      throw new Error("page window fetch 不可用,无法访问公共图片资源");
	    const request = (input, init) => rawRequest.call(this.pageWindow, input, init);
	    return new import_public_resource_request_adapter.BrowserPublicResourceHttpPort({ request });
	  }
	  createCreditBridgeHttp() {
	    const rawRequest = (0, import_value_record.objectRecord)(this.pageWindow)?.fetch;
	    if (typeof rawRequest != "function")
	      throw new Error("page window fetch 不可用,无法读取 LDC 账户摘要");
	    return Object.freeze({
	      loadUserInfo: async (signal) => {
	        const response = await rawRequest.call(
	          this.pageWindow,
	          "/api/v1/oauth/user-info",
	          {
	            signal,
	            credentials: "include",
	            cache: "no-store",
	            headers: { Accept: "application/json" }
	          }
	        );
	        if (!response.ok) throw new Error(`LDC HTTP ${response.status}`);
	        return response.json();
	      }
	    });
	  }
	  createObjectUrlPort() {
	    const urlOwner = (0, import_value_record.objectRecord)(this.pageWindow)?.URL, create = urlOwner?.createObjectURL, revoke = urlOwner?.revokeObjectURL;
	    if (typeof create != "function" || typeof revoke != "function")
	      throw new Error("page window URL.createObjectURL/revokeObjectURL 不可用");
	    return Object.freeze({
	      createObjectURL: (blob) => String(create.call(urlOwner, blob)),
	      revokeObjectURL: (source) => {
	        revoke.call(urlOwner, source);
	      }
	    });
	  }
	  createAssetCacheStorage() {
	    const page = (0, import_value_record.objectRecord)(this.pageWindow), storage = (0, import_value_record.objectRecord)(page?.caches), keys = storage?.keys, open = storage?.open, remove = storage?.delete;
	    return typeof keys != "function" || typeof open != "function" || typeof remove != "function" ? null : Object.freeze({
	      keys: () => keys.call(storage),
	      open: (name) => open.call(storage, name),
	      delete: (name) => remove.call(storage, name)
	    });
	  }
	  createValueStorage() {
	    const modern = (0, import_value_record.objectRecord)(this.#userscriptGlobal.GM), modernGet = modern?.getValue, modernSet = modern?.setValue;
	    if (typeof modernGet == "function" && typeof modernSet == "function")
	      return Object.freeze({
	        getValue: (key) => modernGet.call(modern, key, null),
	        setValue: (key, value) => modernSet.call(modern, key, value)
	      });
	    const legacyGet = this.#userscriptGlobal.GM_getValue, legacySet = this.#userscriptGlobal.GM_setValue;
	    return typeof legacyGet != "function" || typeof legacySet != "function" ? null : Object.freeze({
	      getValue: (key) => legacyGet.call(this.#userscriptGlobal, key, null),
	      setValue: (key, value) => legacySet.call(this.#userscriptGlobal, key, value)
	    });
	  }
	  createShareSurface() {
	    return new import_browser_share_surface.BrowserReaderShareSurface(this.pageWindow);
	  }
	  createKatexPort() {
	    const page = (0, import_value_record.objectRecord)(this.pageWindow), owner = (0, import_value_record.objectRecord)(this.#userscriptGlobal.katex) ?? (0, import_value_record.objectRecord)(page?.katex), render = owner?.render;
	    return typeof render != "function" ? null : Object.freeze({
	      render: (tex, target, options) => {
	        render.call(owner, tex, target, options);
	      }
	    });
	  }
	  createHlsPort() {
	    const page = (0, import_value_record.objectRecord)(this.pageWindow), candidate = this.#userscriptGlobal.Hls ?? page?.Hls, isSupported = candidate?.isSupported;
	    return typeof candidate != "function" || typeof isSupported != "function" ? null : Object.freeze({
	      isSupported: () => !!isSupported.call(candidate),
	      create: () => {
	        const player = new candidate(), loadSource = player.loadSource, attachMedia = player.attachMedia, destroy = player.destroy;
	        if (typeof loadSource != "function" || typeof attachMedia != "function" || typeof destroy != "function")
	          throw new Error("Hls player 缺少标准生命周期方法");
	        return Object.freeze({
	          loadSource: (source) => {
	            loadSource.call(player, source);
	          },
	          attachMedia: (video) => {
	            attachMedia.call(player, video);
	          },
	          destroy: () => {
	            destroy.call(player);
	          }
	        });
	      }
	    });
	  }
	  readScriptVersion() {
	    const modern = (0, import_value_record.objectRecord)(this.#userscriptGlobal.GM), info = (0, import_value_record.objectRecord)(modern?.info) ?? (0, import_value_record.objectRecord)(this.#userscriptGlobal.GM_info), script = (0, import_value_record.objectRecord)(info?.script);
	    return String(script?.version ?? "").trim() || null;
	  }
	  async readTextResource(name) {
	    const resourceName = String(name).trim();
	    if (!resourceName) throw new Error("userscript resource name 不能为空");
	    const modern = (0, import_value_record.objectRecord)(this.#userscriptGlobal.GM), modernRead = modern?.getResourceText, legacyRead = this.#userscriptGlobal.GM_getResourceText, value = typeof modernRead == "function" ? await modernRead.call(modern, resourceName) : typeof legacyRead == "function" ? await legacyRead.call(this.#userscriptGlobal, resourceName) : null;
	    if (typeof value != "string" || !value.trim())
	      throw new Error(
	        `userscript 文本资源 ${resourceName} 不可用`
	      );
	    return value;
	  }
	  createPinyinSearchForms(maxEntries = 512) {
	    const limit = Math.floor(Number(maxEntries));
	    if (!Number.isSafeInteger(limit) || limit <= 0)
	      throw new RangeError("拼音搜索缓存上限必须是正整数");
	    const page = (0, import_value_record.objectRecord)(this.pageWindow), pinyinOwner = (0, import_value_record.objectRecord)(this.#userscriptGlobal.pinyinPro) ?? (0, import_value_record.objectRecord)(page?.pinyinPro), pinyin = pinyinOwner?.pinyin, cache = /* @__PURE__ */ new Map();
	    return (value) => {
	      const source = String(value ?? "");
	      if (!source) return Object.freeze([]);
	      const cached = cache.get(source);
	      if (cached)
	        return cache.delete(source), cache.set(source, cached), cached;
	      const forms = [(0, import_reader_search.normalizeReaderSearchText)(source)];
	      if (typeof pinyin == "function")
	        try {
	          forms.push((0, import_reader_search.normalizeReaderSearchText)(pinyin.call(pinyinOwner, source, {
	            toneType: "none",
	            nonZh: "consecutive"
	          }))), forms.push((0, import_reader_search.normalizeReaderSearchText)(pinyin.call(pinyinOwner, source, {
	            pattern: "first",
	            toneType: "none",
	            nonZh: "consecutive"
	          })));
	        } catch {
	        }
	      const result = Object.freeze([...new Set(forms.filter(Boolean))]);
	      for (cache.set(source, result); cache.size > limit; ) {
	        const oldest = cache.keys().next().value;
	        if (oldest === void 0) break;
	        cache.delete(oldest);
	      }
	      return result;
	    };
	  }
	}
}, "6f3de165942d7bf43142e7f3b938030155c244dcf1c8514c80a5917e0fb3329a");

/* Source: lite/src/userscript/main-lite-bootstrap.ts */
runtime.register("src/userscript/main-lite-bootstrap.js", function(module, exports, require) {
	var main_lite_bootstrap_exports = {};
	__export(main_lite_bootstrap_exports, {
	  startMainLiteUserscript: () => startMainLiteUserscript,
	  startMianLiteUserscript: () => startMianLiteUserscript
	});
	module.exports = __toCommonJS(main_lite_bootstrap_exports);
	var import_reader_application_stages = require("../app/reader-application-stages.js"), import_native_host_api = require("../discourse/native-host-api.js"), import_reader_native_composer_window = require("../discourse/reader-native-composer-window.js"), import_reader_icon = require("../components/reader-icon.js"), import_reader_katex_controller = require("../media/reader-katex-controller.js"), import_reader_image_preferences = require("../media/reader-image-preferences.js"), import_reader_appearance_style_controller = require("../appearance/reader-appearance-style-controller.js"), import_reader_theme_controller = require("../appearance/reader-theme-controller.js"), import_reader_font_style_controller = require("../font/reader-font-style-controller.js"), import_reader_layout_style_controller = require("../layout/reader-layout-style-controller.js"), import_reader_shell_template = require("../shell/reader-shell-template.js"), import_reader_surface_portal = require("../shell/reader-surface-portal.js"), import_reader_shortcut_controller = require("../shell/reader-shortcut-controller.js"), import_embedded_host_topic_card_enhancement = require("../shell/embedded-host-topic-card-enhancement.js"), import_reader_motion_settings_form = require("../settings/reader-motion-settings-form.js"), import_boost_copy_rule = require("../post/boost-copy-rule.js"), import_reader_topic_action_rail = require("../post/reader-topic-action-rail.js"), import_reader_performance_settings_form = require("../settings/reader-performance-settings-form.js"), import_reader_reading_settings_form = require("../settings/reader-reading-settings-form.js"), import_reader_settings_reset_reminder = require("../settings/reader-settings-reset-reminder.js"), import_read_viewport_adapter = require("../reading/read-viewport-adapter.js"), import_reader_preferences_schema = require("../state/reader-preferences-schema.js"), import_reader_post_presentation = require("../topic/reader-post-presentation.js"), import_translation_text = require("../translation/translation-text.js"), import_reader_translation_config = require("../translation/reader-translation-config.js"), import_reader_reply_tree_preferences = require("../topic/reader-reply-tree-preferences.js"), import_browser_userscript_environment = require("./browser-userscript-environment.js"), import_reader_userscript_application = require("./reader-userscript-application.js"), import_reader_userscript_target_adapter = require("./reader-userscript-target-adapter.js"), import_reader_native_topic_route = require("../topic/reader-native-topic-route.js"), import_reader_credit_account_bridge = require("../user/reader-credit-account-bridge.js"), import_reader_custom_site_repository = require("../site/reader-custom-site-repository.js"), import_reader_embedded_reload_coordinator = require("./reader-embedded-reload-coordinator.js"), import_browser_shared_request_permit = require("../network/browser-shared-request-permit.js"), import_reader_webdav_config_repository = require("../sync/reader-webdav-config-repository.js");
	const DEBUG_HANDLE_KEY = "__LDP_MAIN_LITE__", LEGACY_DEBUG_HANDLE_KEY = "__LDP_MIAN_LITE__", STYLE_ID = "ldp-mian-lite-styles", STYLE_RESOURCE = "ldpReaderStyles", KATEX_STYLE_RESOURCE = "ldpKatexStyles", KATEX_STYLESHEET_URL = "https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css";
	function pageRecord(value) {
	  if (value === null || typeof value != "object" && typeof value != "function")
	    throw new Error("main-lite page window 不可用");
	  return value;
	}
	function theme(document, window) {
	  return document.documentElement.classList.contains("dark") || document.documentElement.dataset.colorScheme === "dark" || window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
	}
	function sourceId(window) {
	  const value = window.crypto?.randomUUID?.();
	  return value ? `main-lite:${value}` : `main-lite:${Date.now()}`;
	}
	function createStyleStage(environment, document, state) {
	  return Object.freeze({
	    name: "userscript-styles",
	    required: !0,
	    async setup() {
	      const [css, katexCss] = await Promise.all([
	        environment.readTextResource(STYLE_RESOURCE),
	        environment.readTextResource(KATEX_STYLE_RESOURCE)
	      ]);
	      document.getElementById(STYLE_ID)?.remove();
	      const style = document.createElement("style");
	      style.id = STYLE_ID;
	      const stylesheet = `${css}
${(0, import_reader_katex_controller.readerKatexStylesheet)(
	        katexCss,
	        KATEX_STYLESHEET_URL
	      )}`;
	      style.textContent = stylesheet;
	      let portal = null;
	      try {
	        portal = new import_reader_surface_portal.ReaderSurfacePortal(document, stylesheet), (document.head ?? document.documentElement).append(style);
	      } catch (cause) {
	        throw portal?.destroy(), style.remove(), cause;
	      }
	      return state.portal = portal, () => {
	        portal.destroy(), state.portal === portal && (state.portal = null), style.remove();
	      };
	    }
	  });
	}
	function requestedMode(preferences, routeKind) {
	  return routeKind === "direct-topic" ? preferences.topicReaderMode : preferences.listReaderMode;
	}
	function createRuntimeStage(environment, document, window, state, suppressInitialTopicOpen, customSites) {
	  let template = null;
	  const origin = document.location.origin, siteName = (0, import_reader_custom_site_repository.readerDiscourseSiteDisplayName)(document.location.hostname), routeKind = (0, import_reader_userscript_target_adapter.readerUserscriptRouteKind)(
	    document.location.href,
	    origin
	  );
	  return Object.freeze({
	    name: "reader-userscript-runtime",
	    required: !0,
	    async setup(scope, applicationContext) {
	      const readiness = scope.abortController(
	        new DOMException("main-lite runtime 启动已取消", "AbortError")
	      );
	      await environment.waitForDiscourseRuntime(readiness.signal);
	      const initialPreferences = applicationContext.readPreferences(), replyTreePreferences = new import_reader_reply_tree_preferences.ReaderReplyTreePreferencesPreview(
	        import_reader_reply_tree_preferences.readerPreferencesReplyTreeAdapter.read(initialPreferences),
	        (error) => {
	          console.error("[main-lite:reply-tree-preview]", error);
	        }
	      );
	      scope.add(() => replyTreePreferences.destroy());
	      const preferencesEnvironment = Object.freeze({
	        viewportWidth: window.innerWidth,
	        viewportHeight: window.innerHeight
	      }), scriptVersion = environment.readScriptVersion() ?? "development", preferencesCodec = (0, import_reader_preferences_schema.createReaderPreferencesConfigCodec)({
	        environment: preferencesEnvironment,
	        scriptVersion
	      }), preferencesDefaults = (0, import_reader_preferences_schema.createReaderPreferencesDefaults)(
	        preferencesEnvironment
	      ), currentUsername = (0, import_native_host_api.discourseNativeCurrentUsername)(
	        environment.discourseHost
	      ), renderNativeIcon = (0, import_native_host_api.discourseNativeIconRenderer)(
	        environment.discourseHost
	      ), siteLogoCandidates = [
	        document.querySelector(
	          ".d-header #site-logo,.d-header img.logo-big,.d-header img.logo-small,.custom-logo-link img"
	        )?.currentSrc,
	        document.querySelector(
	          'link[rel~="apple-touch-icon"]'
	        )?.href,
	        document.querySelector(
	          'link[rel~="icon"]'
	        )?.href
	      ].filter((value) => !!value), siteLogoUrl = (0, import_native_host_api.discourseNativeSiteLogoUrl)(
	        environment.discourseHost,
	        origin,
	        siteLogoCandidates
	      ), renderIcon = (name, iconDocument) => {
	        const nativeIcon = renderNativeIcon(name, iconDocument);
	        return (0, import_reader_icon.resolveReaderIcon)(iconDocument, name, nativeIcon);
	      }, katex = environment.createKatexPort(), hls = environment.createHlsPort(), bodyTranslationAllowed = (0, import_reader_custom_site_repository.readerDiscourseSiteAllowsBodyTranslation)(
	        document.location.hostname
	      );
	      let persistTranslationMode = null;
	      return (0, import_reader_userscript_application.createReaderUserscriptRuntimeStage)({
	        environment,
	        shell: {
	          compatibilityKey: () => `${origin}:mian-lite:v1`,
	          createView: () => {
	            const portal = state.portal;
	            if (!portal) throw new Error("main-lite Shadow Portal 未就绪");
	            return template = (0, import_reader_shell_template.createReaderShellTemplate)({
	              document,
	              mount: portal.root,
	              listModeAllowed: routeKind === "list",
	              siteName,
	              homeUrl: `${origin}/`,
	              logoUrl: siteLogoUrl,
	              renderIcon
	            }), template.view;
	          },
	          createWorkspaceOptions: (_shell, context) => {
	            if (!template) throw new Error("main-lite Shell template 未创建");
	            const readPreferences = context.readPreferences, scrolling = () => document.scrollingElement ?? document.documentElement;
	            return {
	              document,
	              routeKind,
	              requestedMode: requestedMode(
	                readPreferences(),
	                routeKind
	              ),
	              embedWidth: readPreferences().listReaderEmbedWidth,
	              windowPreferences: readPreferences(),
	              elements: template.workspaceElements,
	              viewportTarget: window,
	              pointerTarget: document,
	              scrollTarget: window,
	              readViewport: () => ({
	                width: window.innerWidth,
	                height: window.innerHeight
	              }),
	              hostScroll: {
	                read: () => ({
	                  viewportHeight: window.innerHeight,
	                  scrollHeight: scrolling().scrollHeight,
	                  scrollTop: scrolling().scrollTop
	                }),
	                scrollTo: (top) => window.scrollTo({
	                  top,
	                  behavior: "auto"
	                })
	              },
	              enhancements: new import_embedded_host_topic_card_enhancement.EmbeddedHostTopicCardEnhancement(
	                document,
	                environment.discourseHost,
	                {
	                  notify: (message) => state.runtime?.feedback.show(message),
	                  onError: (cause) => {
	                    console.error(
	                      "[main-lite:host-topic-notification]",
	                      cause
	                    );
	                  }
	                }
	              ),
	              readAppearance: () => {
	                const preferences = readPreferences(), activeTheme = theme(document, window);
	                return {
	                  profile: preferences.appearanceProfile,
	                  theme: activeTheme,
	                  defaultDividerLineColor: activeTheme === "dark" ? "#343b44" : "#e5e5e5",
	                  defaultDividerLineWidth: 0.5
	                };
	              },
	              onPersistMode: (mode) => {
	                context.updatePreferences?.(
	                  routeKind === "direct-topic" ? {
	                    topicReaderMode: mode === "fullpage" ? "fullpage" : "floating"
	                  } : { listReaderMode: mode }
	                );
	              },
	              onPersistEmbedWidth: (listReaderEmbedWidth) => {
	                context.updatePreferences?.({ listReaderEmbedWidth });
	              },
	              onPersistWindow: (preferences) => {
	                context.updatePreferences?.({
	                  readerWindowWidth: preferences.readerWindowWidth,
	                  readerWindowHeight: preferences.readerWindowHeight,
	                  readerWindowX: preferences.readerWindowX,
	                  readerWindowY: preferences.readerWindowY,
	                  readerWindowLocked: preferences.readerWindowLocked,
	                  readerWindowPinned: preferences.readerWindowPinned
	                });
	              },
	              createMutationObserver: (callback) => new MutationObserver(callback),
	              ...typeof ResizeObserver == "function" ? {
	                createResizeObserver: (callback) => new ResizeObserver(callback)
	              } : {},
	              requestFrame: (callback) => window.requestAnimationFrame(callback),
	              cancelFrame: (id) => window.cancelAnimationFrame(id)
	            };
	          }
	        },
	        runtime: {
	          document,
	          renderIcon,
	          translationView: bodyTranslationAllowed ? customSites.translation ? {
	            initialAnimation: (0, import_reader_translation_config.readerTranslationActiveProfile)(
	              customSites.translation.snapshot.config
	            ).animation,
	            subscribeAnimation: (listener, animationScope) => {
	              customSites.translation.changes.subscribe(
	                (snapshot) => listener((0, import_reader_translation_config.readerTranslationActiveProfile)(
	                  snapshot.config
	                ).animation),
	                animationScope
	              );
	            }
	          } : {} : !1,
	          storage: window.localStorage,
	          sourceId: sourceId(window),
	          locks: window.navigator.locks ?? null,
	          indexedDb: window.indexedDB ?? null,
	          storageEvents: window,
	          broadcastChannelFactory: typeof BroadcastChannel == "function" ? (name) => new BroadcastChannel(name) : null,
	          permit: {
	            shortWindowMs: 1e4,
	            longWindowMs: 6e4,
	            shortBudget: 50,
	            longBudget: 200,
	            minIntervalMs: initialPreferences.performanceRequestInterval,
	            maxConcurrent: initialPreferences.performanceRequestConcurrency
	          },
	          data: {
	            scheduler: {
	              maxConcurrent: initialPreferences.performanceRequestConcurrency,
	              queueLimit: 160,
	              defaultTimeoutMs: 15e3
	            },
	            rateLimit: {
	              evidenceWindowMs: 4e3,
	              maxEndpointEntries: 128,
	              retryAfterFallbackMs: 1500,
	              baseUrl: origin
	            },
	            responseMemoryMaxEntries: 96,
	            responseMemoryMaxBytes: 24 * 1024 * 1024,
	            responsePersistentMaxEntries: 600,
	            responsePersistentMaxBytes: 96 * 1024 * 1024,
	            responseOperationTimeoutMs: 5e3,
	            cacheFlightTtlMs: 3e4,
	            cacheFlightStaleMs: 45e3
	          },
	          topic: {
	            authScope: currentUsername ? `account:${currentUsername}` : `anonymous:${origin}`,
	            origin,
	            pageSize: initialPreferences.performancePageSize,
	            caches: {
	              topic: {
	                freshForMs: 30 * 6e4,
	                retainForMs: 10080 * 6e4,
	                persist: !0
	              },
	              posts: {
	                freshForMs: 30 * 6e4,
	                retainForMs: 10080 * 6e4,
	                persist: !0
	              },
	              nested: {
	                freshForMs: 30 * 6e4,
	                retainForMs: 10080 * 6e4,
	                persist: !0
	              },
	              snapshot: {
	                freshForMs: 30 * 6e4,
	                retainForMs: 720 * 60 * 6e4
	              }
	            }
	          },
	          timelineView: {
	            preferences: {
	              pageStep: initialPreferences.performancePageSize
	            }
	          },
	          history: {
	            panelView: {
	              preferences: {
	                sortMode: initialPreferences.historySortMode
	              },
	              topicHref: (entry) => `${origin}/t/${entry.topicId}/${entry.postNumber}`,
	              changeSortMode: (historySortMode) => {
	                applicationContext.updatePreferences?.({
	                  historySortMode
	                });
	              }
	            }
	          },
	          media: {
	            ...katex ? { katex } : {},
	            ...hls ? { hls } : {},
	            hasManagedMediaSource: "ManagedMediaSource" in window
	          },
	          lightbox: {
	            mount: () => template?.view.surfaceHost ?? document.body
	          },
	          topicFactory: {
	            createDomOptions: (bundle, context, _root, services) => {
	              const presentation = (0, import_reader_post_presentation.createReaderPostPresentation)({
	                document,
	                presentation: services.presentation,
	                relativeTime: services.relativeTime,
	                exactTime: services.exactTime,
	                readTopic: () => bundle.services.session.topic,
	                currentUsername: services.currentUsername,
	                renderIcon
	              }), readState = (0, import_reader_post_presentation.createReaderPostReadStateFeature)({
	                readState: bundle.services.read,
	                parentScope: context.scope,
	                renderIcon,
	                prefersReducedMotion: () => !!window.matchMedia?.(
	                  "(prefers-reduced-motion: reduce)"
	                ).matches,
	                isVisible: (view) => {
	                  const postRoot = view.slots.root;
	                  if (!postRoot.isConnected || !postRoot.getClientRects().length)
	                    return !1;
	                  const rect = postRoot.getBoundingClientRect(), viewport = (postRoot.closest(
	                    ".ldp-descendant-replies-list"
	                  ) ?? template?.view.body)?.getBoundingClientRect() ?? {
	                    top: 0,
	                    right: window.innerWidth,
	                    bottom: window.innerHeight,
	                    left: 0
	                  };
	                  return rect.bottom > viewport.top && rect.top < viewport.bottom && rect.right > viewport.left && rect.left < viewport.right;
	                }
	              }), readViewport = new import_read_viewport_adapter.ReaderPostReadViewportFeature({
	                controller: bundle.services.read,
	                document,
	                parentScope: context.scope,
	                rootFor: (postRoot) => {
	                  const discussion = postRoot.closest(
	                    ".ldp-descendant-replies-list"
	                  );
	                  return discussion || (postRoot.closest(
	                    ".ldp-lb-comment-list,.ldp-topic-action-rail"
	                  ) ? !1 : template?.view.body ?? null);
	                }
	              });
	              return {
	                estimatedRootSize: 360,
	                identity: presentation.identity,
	                render: presentation.render,
	                postFeatures: Object.freeze([readViewport, readState]),
	                replyTreePreferences: {
	                  read: () => replyTreePreferences.read(),
	                  subscribe: (listener, preferenceScope) => replyTreePreferences.subscribe(
	                    listener,
	                    preferenceScope
	                  )
	                }
	              };
	            }
	          },
	          onTopicFeatureError: (diagnostic) => {
	            console.error(
	              `[main-lite:${diagnostic.feature}]`,
	              diagnostic.cause
	            );
	          }
	        },
	        translation: {
	          ...customSites.translation ? {
	            readConfig: async () => (await customSites.translation.load()).config
	          } : {},
	          fingerprint: (texts) => {
	            const subtle = window.crypto?.subtle;
	            return subtle ? (0, import_translation_text.translationTextFingerprint)(texts, subtle) : Promise.reject(new Error("浏览器缺少 SubtleCrypto"));
	          },
	          translationCache: {
	            kind: "translations",
	            tags: ["translation:zh-CN"],
	            freshForMs: 720 * 60 * 6e4,
	            retainForMs: 4320 * 60 * 6e4,
	            persist: !0
	          },
	          credentialCache: {
	            kind: "translation-credentials",
	            tags: ["translation:credential"],
	            freshForMs: 8 * 6e4,
	            retainForMs: 8 * 6e4,
	            persist: !1
	          }
	        },
	        resources: {
	          baseUrl: origin,
	          cache: {
	            kind: "images",
	            tags: ["images"],
	            freshForMs: 1440 * 6e4,
	            retainForMs: 720 * 60 * 6e4,
	            persist: !0
	          }
	        },
	        selectPerformancePreferences: (preferences) => preferences,
	        performanceBudgetCeilings: { short: 50, long: 200 },
	        layout: import_reader_layout_style_controller.readerPreferencesLayoutAdapter,
	        appearance: import_reader_appearance_style_controller.readerPreferencesAppearanceAdapter,
	        theme: {
	          preferences: import_reader_theme_controller.readerPreferencesThemeAdapter,
	          hostTheme: (0, import_native_host_api.discourseNativeTheme)(environment.discourseHost),
	          system: {
	            readDark: () => !!window.matchMedia?.(
	              "(prefers-color-scheme: dark)"
	            ).matches,
	            subscribe: (listener, scope2) => {
	              const query = window.matchMedia?.(
	                "(prefers-color-scheme: dark)"
	              );
	              if (!query) return () => {
	              };
	              const onChange = () => listener(query.matches);
	              query.addEventListener("change", onChange);
	              const cleanup = () => query.removeEventListener("change", onChange);
	              return scope2.add(cleanup), cleanup;
	            }
	          }
	        },
	        font: import_reader_font_style_controller.readerPreferencesFontAdapter,
	        motion: {
	          ...import_reader_motion_settings_form.readerPreferencesMotionAdapter,
	          siteName
	        },
	        image: import_reader_image_preferences.readerPreferencesImageAdapter,
	        boostCopy: import_boost_copy_rule.readerPreferencesBoostCopyAdapter,
	        topicActionRail: import_reader_topic_action_rail.readerPreferencesTopicActionRailAdapter,
	        openQueue: {
	          read: (preferences) => Object.freeze({
	            openTopicsAtFirstPost: preferences.openTopicsAtFirstPost,
	            readerQueueAlwaysVisibleWhenEmpty: preferences.readerQueueAlwaysVisibleWhenEmpty,
	            doubleEscapeToCloseReader: preferences.doubleEscapeToCloseReader,
	            confirmNativeComposerClose: preferences.confirmNativeComposerClose
	          }),
	          createPatch: (preferences) => preferences
	        },
	        shortcuts: import_reader_shortcut_controller.readerPreferencesShortcutAdapter,
	        settings: {
	          view: { brandName: "Awesome LinuxDo Reader" },
	          sitesForm: customSites,
	          ...customSites.webDav ? {
	            webDav: {
	              ...customSites.webDav,
	              customSites: customSites.repository
	            }
	          } : {},
	          aboutContent: {
	            version: scriptVersion
	          },
	          configuration: {
	            codec: preferencesCodec,
	            defaults: preferencesDefaults,
	            customSites: customSites.repository,
	            translation: customSites.translation,
	            webDav: customSites.webDav?.repository ?? null
	          },
	          performanceForm: import_reader_performance_settings_form.readerPreferencesPerformanceSettingsAdapter,
	          ...customSites.translation ? {
	            translationForm: {
	              repository: customSites.translation
	            }
	          } : {},
	          imageForm: import_reader_image_preferences.readerPreferencesImageAdapter,
	          readingForm: import_reader_reading_settings_form.readerPreferencesReadingSettingsAdapter,
	          interactionForm: {
	            boostCopy: import_boost_copy_rule.readerPreferencesBoostCopyAdapter,
	            topicActionRail: import_reader_topic_action_rail.readerPreferencesTopicActionRailAdapter,
	            replyTree: import_reader_reply_tree_preferences.readerPreferencesReplyTreeAdapter,
	            replyTreePreview: replyTreePreferences,
	            boostsAvailable: (0, import_native_host_api.discourseNativeBoostsAvailable)(
	              environment.discourseHost
	            )
	          }
	        },
	        selectHistoryNavigationPreferences: (preferences) => ({
	          edgeTriggerPercent: preferences.historyEdgeTriggerPercent,
	          buttonsAlwaysVisible: preferences.historyButtonsAlwaysVisible
	        }),
	        selectHistoryPanelPreferences: (preferences) => ({
	          sortMode: preferences.historySortMode
	        }),
	        selectBookmarkPreferences: (preferences) => ({
	          tabOrder: preferences.bookmarkTabOrder
	        }),
	        selectTimelineViewPreferences: (preferences) => ({
	          pageStep: preferences.performancePageSize
	        }),
	        ...bodyTranslationAllowed ? {
	          selectTranslationMode: (preferences) => preferences.translationMode,
	          persistTranslationMode: (translationMode) => persistTranslationMode?.(translationMode)
	        } : {},
	        targets: {
	          openInitialRoute: !suppressInitialTopicOpen,
	          selectOpenTopicsAtFirstPost: (preferences) => preferences.openTopicsAtFirstPost,
	          onError: (error) => {
	            console.error("[main-lite:target]", error);
	          }
	        },
	        onReady(runtime, context, _settings, _settingsView, _layout, appearance, font) {
	          state.runtime = runtime, persistTranslationMode = (translationMode) => {
	            context.updatePreferences?.({ translationMode });
	          };
	          let settingsResetReminderChecked = !1;
	          const checkSettingsResetReminder = () => {
	            settingsResetReminderChecked || !["opening", "running", "failed"].includes(runtime.shell.state) || (settingsResetReminderChecked = !0, (0, import_reader_settings_reset_reminder.showReaderSettingsResetReminder)({
	              storage: window.localStorage,
	              preferencesStorageKey: import_reader_preferences_schema.READER_PREFERENCES_STORAGE_KEY,
	              defaults: preferencesDefaults,
	              update: (preferences) => {
	                if (!context.updatePreferences)
	                  throw new Error("偏好写端口不可用");
	                context.updatePreferences(preferences);
	              },
	              feedback: runtime.feedback,
	              isActive: () => !runtime.scope.destroyed,
	              onError: (error) => {
	                console.error("[main-lite:settings-reset-reminder]", error);
	              }
	            }));
	          };
	          runtime.shell.changes.subscribe(
	            checkSettingsResetReminder,
	            runtime.scope
	          ), checkSettingsResetReminder();
	          const portal = state.portal;
	          if (!portal) throw new Error("main-lite Shadow Portal 未就绪");
	          const embeddedReload = routeKind === "list" ? new import_reader_embedded_reload_coordinator.ReaderEmbeddedReloadCoordinator({
	            target: window,
	            storage: window.sessionStorage,
	            currentHostRoute: () => `${document.location.pathname}${document.location.search}${document.location.hash}`,
	            navigationType: () => {
	              const entry = window.performance.getEntriesByType?.("navigation")[0];
	              return entry?.type ? entry.type : window.performance.navigation?.type === 1 ? "reload" : null;
	            },
	            capture: () => {
	              const workspace = runtime.workspace.workspace.snapshot, topicId = runtime.shell.activeTopicId, active = runtime.shell.activeValue;
	              if (!workspace.presentation.embedded || topicId === null || !active) return null;
	              const anchor = runtime.historyNavigation.captureCurrent();
	              return anchor ? Object.freeze({
	                mode: workspace.presentation.mode,
	                topicId: Number(topicId),
	                anchor,
	                onlyOp: active.topicOnlyOp.snapshot.enabled
	              }) : null;
	            },
	            restore: async (reload) => {
	              if (!runtime.workspace.setMode(reload.mode)) return !1;
	              const opened = await runtime.openTarget({
	                topicId: reload.topicId,
	                postNumber: reload.anchor.viewport.postNumber,
	                source: "restore",
	                alignment: "nearest"
	              });
	              return opened.topic.status !== "opened" && opened.topic.status !== "reused" ? !1 : (reload.onlyOp && opened.topic.value.topicOnlyOp.setEnabled(!0), await runtime.historyNavigation.restore(
	                reload.topicId,
	                reload.anchor
	              ), !0);
	            },
	            parentScope: runtime.scope,
	            onError: (error) => {
	              console.error("[main-lite:embedded-reload]", error);
	            }
	          }) : null;
	          (async () => {
	            if (await embeddedReload?.restore() || runtime.shell.activeTopicId !== null || !["embed-left", "embed-right"].includes(
	              runtime.workspace.workspace.snapshot.requestedMode
	            ) || !runtime.workspace.workspace.snapshot.canEmbed) return;
	            const recent = runtime.history.ordered("recent-viewed")[0] ?? null;
	            if (!recent) return;
	            const anchor = runtime.historyNavigation.snapshot.states[String(recent.topicId)] ?? null, opened = await runtime.openTarget({
	              topicId: recent.topicId,
	              postNumber: anchor?.viewport.postNumber ?? recent.postNumber,
	              source: "restore",
	              alignment: "nearest"
	            });
	            anchor && (opened.topic.status === "opened" || opened.topic.status === "reused") && await runtime.historyNavigation.restore(recent.topicId, anchor);
	          })().catch((error) => {
	            console.error("[main-lite:embedded-default-topic]", error);
	          });
	          const composerWindow = new import_reader_native_composer_window.ReaderNativeComposerWindowController({
	            document,
	            window,
	            mount: portal.root,
	            pageRoot: document.documentElement,
	            readPreferences: context.readPreferences,
	            preferenceChanges: context.preferenceChanges,
	            updatePreferences: (patch) => {
	              context.updatePreferences?.(patch);
	            },
	            readFontProfile: () => font?.snapshot.settings.fontProfile ?? context.readPreferences().fontProfile,
	            ...font ? {
	              fontChanges: {
	                subscribe(listener, scope2) {
	                  return font.changes.subscribe((snapshot) => {
	                    listener(snapshot.settings.fontProfile);
	                  }, scope2);
	                }
	              }
	            } : {},
	            ...appearance ? {
	              readAppearance: () => appearance.snapshot.interaction,
	              appearanceChanges: {
	                subscribe(listener, scope2) {
	                  return appearance.changes.subscribe((snapshot) => {
	                    listener(snapshot.interaction);
	                  }, scope2);
	                }
	              }
	            } : {},
	            createMutationObserver: (callback) => new MutationObserver(callback),
	            requestFrame: (callback) => window.requestAnimationFrame(callback),
	            cancelFrame: (frameId) => window.cancelAnimationFrame(frameId),
	            parentScope: runtime.scope,
	            onError: (error) => {
	              console.error("[main-lite:native-composer-window]", error);
	            }
	          }), unbindComposerWindow = runtime.composer.bindWindow(
	            composerWindow
	          );
	          return runtime.composer.warmReply(), () => {
	            unbindComposerWindow(), composerWindow.destroy(), embeddedReload?.destroy(), persistTranslationMode = null, state.runtime === runtime && (state.runtime = null);
	          };
	        }
	      }).setup(scope, applicationContext);
	    }
	  });
	}
	function startMainLiteUserscript(userscriptGlobal = globalThis) {
	  const environment = new import_browser_userscript_environment.BrowserUserscriptEnvironment({
	    userscriptGlobal
	  }), page = pageRecord(environment.pageWindow), existing = page[DEBUG_HANDLE_KEY] ?? page[LEGACY_DEBUG_HANDLE_KEY], document = page.document, window = environment.pageWindow;
	  if (!document) throw new Error("main-lite document 不可用");
	  if ((0, import_browser_shared_request_permit.isReaderCloudflareChallengeWindow)(window))
	    return existing?.destroy?.(), null;
	  if (document.location.hostname === "credit.linux.do")
	    return (0, import_reader_credit_account_bridge.scheduleReaderCreditAccountBridge)(
	      window,
	      document,
	      environment.createValueStorage(),
	      environment.createCreditBridgeHttp(),
	      (cause) => console.warn("[main-lite] LDC 账户桥同步失败", cause)
	    ), null;
	  const bypassNativeTab = (0, import_reader_native_topic_route.consumeReaderNativeTabBypass)(window), bypassNativeUrl = (0, import_reader_native_topic_route.consumeReaderNativeBypass)(
	    document.location.href,
	    document.location.origin,
	    (cleanHref) => {
	      try {
	        window.history.replaceState(
	          window.history.state,
	          "",
	          cleanHref
	        );
	      } catch {
	      }
	    }
	  ), suppressInitialTopicOpen = bypassNativeTab || bypassNativeUrl;
	  if (suppressInitialTopicOpen && existing?.destroy?.(), existing && !suppressInitialTopicOpen)
	    return existing;
	  const valueStorage = environment.createValueStorage(), customSiteRepository = new import_reader_custom_site_repository.ReaderCustomSiteRepository({
	    storage: valueStorage
	  }), translation = valueStorage ? new import_reader_translation_config.ReaderTranslationConfigRepository({ storage: valueStorage }) : null;
	  let customSiteProbe = null;
	  try {
	    customSiteProbe = environment.createDiscourseSiteProbe();
	  } catch {
	  }
	  let webDav = null;
	  if (valueStorage)
	    try {
	      webDav = Object.freeze({
	        client: environment.createWebDavClient(),
	        repository: new import_reader_webdav_config_repository.ReaderWebDavConfigRepository({
	          storage: valueStorage
	        })
	      });
	    } catch {
	    }
	  const preferences = (0, import_reader_preferences_schema.createReaderPreferencesRepository)({
	    environment: {
	      viewportWidth: window.innerWidth,
	      viewportHeight: window.innerHeight
	    },
	    storage: window.localStorage
	  }), state = {
	    runtime: null,
	    portal: null,
	    diagnostics: []
	  }, onWindowKeyDown = (event) => {
	    state.runtime?.shell.activeValue?.topicContextSurface.handleEscape(event);
	  };
	  window.addEventListener("keydown", onWindowKeyDown, !0);
	  const application = (0, import_reader_userscript_application.createReaderUserscriptApplication)({
	    environment,
	    document,
	    window,
	    preferences,
	    isVerifiedHost: async (hostname, signal) => {
	      if ((0, import_reader_custom_site_repository.readerBuiltinDiscourseHost)(hostname)) return !0;
	      if (signal.aborted) return !1;
	      try {
	        const verified = await customSiteRepository.allows(hostname);
	        return !signal.aborted && verified;
	      } catch {
	        return !1;
	      }
	    },
	    stages: [
	      (0, import_reader_application_stages.createPreferencesStorageSyncStage)({
	        key: import_reader_preferences_schema.READER_PREFERENCES_STORAGE_KEY,
	        window,
	        repository: preferences,
	        onError: (cause) => {
	          console.error("[main-lite:preferences-sync]", cause);
	        }
	      }),
	      createStyleStage(environment, document, state),
	      createRuntimeStage(
	        environment,
	        document,
	        window,
	        state,
	        suppressInitialTopicOpen,
	        {
	          repository: customSiteRepository,
	          probe: customSiteProbe,
	          translation,
	          webDav
	        }
	      )
	    ]
	  });
	  application.diagnostics.subscribe((diagnostic) => {
	    state.diagnostics.push(diagnostic), console.error(
	      `[main-lite:${diagnostic.stage}]`,
	      diagnostic.cause
	    );
	  });
	  const started = application.start(), handle = Object.freeze({
	    application,
	    started,
	    get diagnostics() {
	      return Object.freeze([...state.diagnostics]);
	    },
	    get runtime() {
	      return state.runtime;
	    },
	    destroy() {
	      window.removeEventListener("keydown", onWindowKeyDown, !0), application.destroy(), page[DEBUG_HANDLE_KEY] === handle && delete page[DEBUG_HANDLE_KEY], page[LEGACY_DEBUG_HANDLE_KEY] === handle && delete page[LEGACY_DEBUG_HANDLE_KEY];
	    }
	  });
	  return Object.defineProperty(page, DEBUG_HANDLE_KEY, {
	    configurable: !0,
	    enumerable: !1,
	    value: handle,
	    writable: !1
	  }), Object.defineProperty(page, LEGACY_DEBUG_HANDLE_KEY, {
	    configurable: !0,
	    enumerable: !1,
	    value: handle,
	    writable: !1
	  }), handle;
	}
	const startMianLiteUserscript = startMainLiteUserscript;
}, "9df6ee0eaca4d58065c1fcabba906403e4b9c2b9204468e323fd9444a6ad407b");

/* Source: lite/src/userscript/main-lite-entry.ts */
runtime.register("src/userscript/main-lite-entry.js", function(module, exports, require) {
	var import_main_lite_bootstrap = require("./main-lite-bootstrap.js");
	(0, import_main_lite_bootstrap.startMainLiteUserscript)();
}, "9e50c00fd0262b242afcb1582b85bc77d9ea61386a6ca6dc6939dc68f814e1ff");

/* Source: lite/src/userscript/reader-embedded-reload-coordinator.ts */
runtime.register("src/userscript/reader-embedded-reload-coordinator.js", function(module, exports, require) {
	var reader_embedded_reload_coordinator_exports = {};
	__export(reader_embedded_reload_coordinator_exports, {
	  ReaderEmbeddedReloadCoordinator: () => ReaderEmbeddedReloadCoordinator
	});
	module.exports = __toCommonJS(reader_embedded_reload_coordinator_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_reader_history_model = require("../history/reader-history-model.js"), import_lifecycle = require("../kernel/lifecycle.js");
	const DEFAULT_STORAGE_KEY = "ldp:mian-lite:embedded-reload:v1", DEFAULT_TTL_MS = 3e4;
	function embeddedMode(value) {
	  return value === "embed-left" || value === "embed-right" ? value : null;
	}
	function stateRecord(value) {
	  return value !== null && typeof value == "object" && !Array.isArray(value) ? value : null;
	}
	class ReaderEmbeddedReloadCoordinator {
	  scope;
	  #options;
	  #storageKey;
	  #ttlMs;
	  #now;
	  #restorePromise = null;
	  constructor(options) {
	    this.#options = options, this.#storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY, this.#ttlMs = Math.max(0, options.ttlMs ?? DEFAULT_TTL_MS), this.#now = options.now ?? Date.now, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.listen(options.target, "pagehide", () => {
	      this.save();
	    });
	  }
	  save() {
	    if (this.scope.destroyed) return !1;
	    let capture;
	    try {
	      capture = this.#options.capture();
	    } catch (cause) {
	      return this.#report(cause), !1;
	    }
	    const mode = embeddedMode(capture?.mode);
	    if (!capture || !mode) return !1;
	    const anchor = (0, import_reader_history_model.normalizeReaderHistoryAnchorState)(capture.anchor);
	    if (!anchor) return !1;
	    let topicId;
	    try {
	      topicId = Number((0, import_identifiers.discourseTopicId)(capture.topicId));
	    } catch {
	      return !1;
	    }
	    try {
	      const state = Object.freeze({
	        savedAt: this.#now(),
	        hostRoute: String(this.#options.currentHostRoute()),
	        mode,
	        topicId,
	        anchor,
	        onlyOp: capture.onlyOp === !0
	      });
	      return this.#options.storage.setItem(
	        this.#storageKey,
	        JSON.stringify(state)
	      ), !0;
	    } catch (cause) {
	      return this.#report(cause), !1;
	    }
	  }
	  restore() {
	    if (this.#restorePromise) return this.#restorePromise;
	    const transaction = this.#restore();
	    return this.#restorePromise = transaction, transaction.finally(() => {
	      this.#restorePromise === transaction && (this.#restorePromise = null);
	    }), transaction;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  async #restore() {
	    if (this.scope.destroyed) return !1;
	    const state = this.#consume();
	    if (!state) return !1;
	    try {
	      return await this.#options.restore(state);
	    } catch (cause) {
	      return this.#report(cause), !1;
	    }
	  }
	  #consume() {
	    let raw = null;
	    try {
	      raw = this.#options.storage.getItem(this.#storageKey), this.#options.storage.removeItem(this.#storageKey);
	    } catch (cause) {
	      return this.#report(cause), null;
	    }
	    if (!raw || this.#options.navigationType() !== "reload") return null;
	    let source;
	    try {
	      source = stateRecord(JSON.parse(raw));
	    } catch {
	      return null;
	    }
	    const savedAt = Number(source?.savedAt), mode = embeddedMode(source?.mode), anchor = (0, import_reader_history_model.normalizeReaderHistoryAnchorState)(source?.anchor);
	    if (!source || !Number.isFinite(savedAt) || this.#now() - savedAt < 0 || this.#now() - savedAt > this.#ttlMs || String(source.hostRoute ?? "") !== String(this.#options.currentHostRoute()) || !mode || !anchor) return null;
	    let topicId;
	    try {
	      topicId = Number((0, import_identifiers.discourseTopicId)(source.topicId));
	    } catch {
	      return null;
	    }
	    return Object.freeze({
	      savedAt,
	      hostRoute: String(source.hostRoute),
	      mode,
	      topicId,
	      anchor,
	      onlyOp: source.onlyOp === !0
	    });
	  }
	  #report(cause) {
	    try {
	      this.#options.onError?.(cause);
	    } catch {
	    }
	  }
	}
}, "3140edd91b8288950ef4bc855c11df2651f0fccda7a2e4fa0adeef29bbf49d1d");

/* Source: lite/src/userscript/reader-floating-host-target-controller.ts */
runtime.register("src/userscript/reader-floating-host-target-controller.js", function(module, exports, require) {
	var reader_floating_host_target_controller_exports = {};
	__export(reader_floating_host_target_controller_exports, {
	  ReaderFloatingHostTargetController: () => ReaderFloatingHostTargetController
	});
	module.exports = __toCommonJS(reader_floating_host_target_controller_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_reader_workspace = require("../shell/reader-workspace.js"), import_reader_userscript_target_adapter = require("./reader-userscript-target-adapter.js");
	class ReaderFloatingHostTargetController {
	  scope;
	  #options;
	  #requestFrame;
	  #cancelFrame;
	  #target = null;
	  #frame = 0;
	  #clientX = 0;
	  #clientY = 0;
	  #destroyed = !1;
	  constructor(options) {
	    this.#options = options, this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)), this.#cancelFrame = options.cancelFrame ?? ((id) => cancelAnimationFrame(id)), this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.scope.listen(options.overlay, "pointermove", (event) => this.#onPointerMove(event)), this.scope.listen(options.overlay, "click", (event) => this.#onClick(event)), this.scope.add(() => this.#clear());
	  }
	  destroy() {
	    this.#destroyed || (this.#destroyed = !0, this.scope.destroy());
	  }
	  #canSwitch() {
	    return this.#options.workspace.snapshot.presentation.floating && !this.#options.window.snapshot.pinned && this.#options.window.snapshot.viewportWidth > import_reader_workspace.READER_COMPACT_MAX_WIDTH;
	  }
	  #onPointerMove(event) {
	    if (event.target !== this.#options.overlay || event.pointerType === "touch" || !this.#canSwitch()) {
	      this.#stop();
	      return;
	    }
	    this.#clientX = event.clientX, this.#clientY = event.clientY, this.#frame || (this.#frame = this.#requestFrame(() => this.#syncTarget()));
	  }
	  #onClick(event) {
	    if (event.target !== this.#options.overlay || !this.#canSwitch()) return;
	    const target = this.#targetAt(event.clientX, event.clientY);
	    if (this.#stop(), !target) {
	      this.#run(() => this.#options.closeReader());
	      return;
	    }
	    const openAtFirst = this.#readOpenAtFirstPost();
	    this.#run(() => this.#options.target.openTarget({
	      topicId: target.topicId,
	      ...openAtFirst ? { postNumber: (0, import_identifiers.tryDiscoursePostNumber)(1) } : target.postNumber === null ? {} : { postNumber: target.postNumber },
	      source: "link"
	    }));
	  }
	  #syncTarget() {
	    this.#frame = 0, this.#setTarget(
	      this.#canSwitch() ? this.#targetAt(this.#clientX, this.#clientY)?.anchor ?? null : null
	    );
	  }
	  #targetAt(clientX, clientY) {
	    let source = null;
	    try {
	      this.#options.overlay.classList.add("ldp-reader-hit-test-hidden"), source = this.#options.document.elementFromPoint(clientX, clientY);
	    } finally {
	      this.#options.overlay.classList.remove("ldp-reader-hit-test-hidden");
	    }
	    const anchor = source?.closest("a[href]") ?? null;
	    if (!anchor || anchor.classList.contains("ldp-open")) return null;
	    let currentUrl;
	    try {
	      currentUrl = this.#options.currentUrl();
	    } catch (error) {
	      return this.#report(error), null;
	    }
	    const route = (0, import_reader_userscript_target_adapter.parseReaderUserscriptTopicRoute)(
	      anchor.getAttribute("href") ?? anchor.href,
	      currentUrl
	    );
	    return !route || route.bypassReader ? null : Object.freeze({
	      anchor,
	      topicId: route.topicId,
	      postNumber: route.postNumber
	    });
	  }
	  #setTarget(target) {
	    target !== this.#target && (this.#target?.classList.remove("ldp-reader-switch-target"), this.#target = target, this.#target?.classList.add("ldp-reader-switch-target"), this.#options.overlay.classList.toggle(
	      "ldp-reader-switch-ready",
	      this.#target !== null
	    ));
	  }
	  #stop() {
	    this.#frame && this.#cancelFrame(this.#frame), this.#frame = 0, this.#setTarget(null);
	  }
	  #clear() {
	    this.#stop();
	  }
	  #readOpenAtFirstPost() {
	    try {
	      return this.#options.readOpenTopicsAtFirstPost?.() === !0;
	    } catch (error) {
	      return this.#report(error), !1;
	    }
	  }
	  #run(action) {
	    try {
	      Promise.resolve(action()).catch((error) => this.#report(error));
	    } catch (error) {
	      this.#report(error);
	    }
	  }
	  #report(error) {
	    try {
	      this.#options.onError?.(error);
	    } catch {
	    }
	  }
	}
}, "3ed6bdf9714346b7cbaa0b69040534a7b798c0def2a9ec57ccf47683f55144ec");

/* Source: lite/src/userscript/reader-host-topic-source-coordinator.ts */
runtime.register("src/userscript/reader-host-topic-source-coordinator.js", function(module, exports, require) {
	var reader_host_topic_source_coordinator_exports = {};
	__export(reader_host_topic_source_coordinator_exports, {
	  ReaderHostTopicSourceCoordinator: () => ReaderHostTopicSourceCoordinator
	});
	module.exports = __toCommonJS(reader_host_topic_source_coordinator_exports);
	var import_native_host_api = require("../discourse/native-host-api.js"), import_lifecycle = require("../kernel/lifecycle.js");
	const TOPIC_ROW_SELECTOR = "tr.topic-list-item,.topic-list-item,.latest-topic-list-item", SOURCE_SELECTOR = ".fk-d-menu,.menu-panel,.search-menu,.user-menu,.hamburger-panel,.sidebar-hamburger-dropdown,.chat-drawer", SURFACE_SELECTOR = ".fk-d-menu,.menu-panel,.chat-drawer", OWNED_SELECTOR = '.ldp-overlay,.ldp-reader-portal-host,[data-ldp-owned="true"]';
	function topicIdFromCard(card) {
	  const direct = String(
	    card.getAttribute("data-topic-id") ?? card.dataset.topicId ?? ""
	  ).trim();
	  return direct || ((card.querySelector(
	    'a.raw-topic-link[href*="/t/"],a.title[href*="/t/"],.link-top-line a[href*="/t/"],a[href*="/t/"]'
	  )?.getAttribute("href") ?? "").match(/\/t\/(?:[^/]+\/)?(\d+)(?:\/|$)/)?.[1] ?? "");
	}
	function sourceElement(target) {
	  const anchorSource = target.anchor.closest(SOURCE_SELECTOR), markerSource = target.sourceElement?.closest(
	    SOURCE_SELECTOR
	  ) ?? null;
	  return anchorSource ?? markerSource;
	}
	function sourceSurface(source) {
	  return !source || source.closest(OWNED_SELECTOR) ? null : source.matches(SURFACE_SELECTOR) ? source : source.closest(SURFACE_SELECTOR) ?? source.querySelector(SURFACE_SELECTOR) ?? source;
	}
	function surfaceClosed(surface) {
	  if (!surface.isConnected || surface.hidden === !0 || surface.getAttribute("aria-hidden") === "true") return !0;
	  const view = surface.ownerDocument.defaultView;
	  if (typeof view?.getComputedStyle != "function") return !1;
	  try {
	    const style = view.getComputedStyle(surface);
	    return style.display === "none" || style.visibility === "hidden";
	  } catch {
	    return !1;
	  }
	}
	function openSourceSurfaces(document) {
	  const seen = /* @__PURE__ */ new Set(), result = [];
	  for (const source of document.querySelectorAll(SOURCE_SELECTOR)) {
	    const surface = sourceSurface(source);
	    !surface || seen.has(surface) || surfaceClosed(surface) || (seen.add(surface), result.push(Object.freeze({ source, surface })));
	  }
	  return Object.freeze(result);
	}
	class ReaderHostTopicSourceCoordinator {
	  scope;
	  #document;
	  #host;
	  #anchors = /* @__PURE__ */ new WeakMap();
	  #restoringTargets = /* @__PURE__ */ new Set();
	  #closingOpenSurfaces = null;
	  constructor(options) {
	    this.#document = options.document, this.#host = options.host, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope);
	    const readerRoot = options.readerRoot, isEmbedded = options.isEmbedded;
	    readerRoot && isEmbedded && this.scope.listen(readerRoot, "pointerdown", () => {
	      this.scope.destroyed || !isEmbedded() || this.closeOpenSurfaces();
	    }, !0), this.scope.add(() => {
	      this.#restoringTargets.clear(), this.#closingOpenSurfaces = null, this.#document.documentElement.classList.remove(
	        "ldp-reader-host-anchor-restoring"
	      );
	    });
	  }
	  async prepare(target) {
	    if (this.scope.destroyed) return;
	    const pointerAnchor = this.#capturePointerAnchor(target);
	    if (pointerAnchor) {
	      this.#anchors.set(target, pointerAnchor), this.#restoringTargets.add(target), this.#document.documentElement.classList.add(
	        "ldp-reader-host-anchor-restoring"
	      );
	      const blur = target.anchor.blur;
	      typeof blur == "function" && blur.call(target.anchor);
	    }
	    await this.#closeSourceSurface(target);
	  }
	  closeOpenSurfaces() {
	    if (this.scope.destroyed) return Promise.resolve();
	    if (this.#closingOpenSurfaces) return this.#closingOpenSurfaces;
	    const closing = this.#closeOpenSourceSurfaces().catch(() => {
	    });
	    return this.#closingOpenSurfaces = closing, closing.then(() => {
	      this.#closingOpenSurfaces === closing && (this.#closingOpenSurfaces = null);
	    }), closing;
	  }
	  async settle(target, opened) {
	    const anchor = this.#anchors.get(target) ?? null;
	    this.#anchors.delete(target);
	    let restored = !1;
	    return opened && anchor && !this.scope.destroyed && (await this.#nextFrame(), this.scope.destroyed || (restored = this.#restorePointerAnchor(anchor))), await this.#finishAnchorRestoration(target), restored;
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #capturePointerAnchor(target) {
	    const clientY = target.pointer?.clientY;
	    if (target.pointer?.detail === 0 || !Number.isFinite(clientY))
	      return null;
	    const card = target.anchor.closest(TOPIC_ROW_SELECTOR);
	    if (!card || card.closest(OWNED_SELECTOR)) return null;
	    const rect = card.getBoundingClientRect();
	    return !(rect.width > 0) || !(rect.height > 0) ? null : Object.freeze({
	      card,
	      topicId: String(target.request.topicId),
	      clientY,
	      offsetY: Math.max(0, Math.min(rect.height, clientY - rect.top))
	    });
	  }
	  #restorePointerAnchor(anchor) {
	    let card = anchor.card.isConnected ? anchor.card : null;
	    if (card || (card = [...this.#document.querySelectorAll(
	      TOPIC_ROW_SELECTOR
	    )].find(
	      (candidate) => !candidate.closest(OWNED_SELECTOR) && topicIdFromCard(candidate) === anchor.topicId
	    ) ?? null), !card) return !1;
	    for (const candidate of this.#document.querySelectorAll(
	      '[data-ldp-reader-active-topic="true"]'
	    ))
	      candidate !== card && candidate.removeAttribute("data-ldp-reader-active-topic");
	    card.dataset.ldpReaderActiveTopic = "true";
	    const delta = card.getBoundingClientRect().top + anchor.offsetY - anchor.clientY, view = this.#document.defaultView;
	    return Math.abs(delta) >= 0.5 && typeof view?.scrollBy == "function" && view.scrollBy({ top: delta, behavior: "auto" }), !0;
	  }
	  async #finishAnchorRestoration(target) {
	    this.#restoringTargets.delete(target), !(this.#restoringTargets.size || this.scope.destroyed) && (await this.#nextFrame(), !(this.#restoringTargets.size || this.scope.destroyed) && this.#document.documentElement.classList.remove(
	      "ldp-reader-host-anchor-restoring"
	    ));
	  }
	  async #closeSourceSurface(target) {
	    const source = sourceElement(target), surface = sourceSurface(source);
	    !surface || surfaceClosed(surface) || await this.#closeSurface(surface, source);
	  }
	  async #closeOpenSourceSurfaces() {
	    for (const { source, surface } of openSourceSurfaces(this.#document)) {
	      if (this.scope.destroyed) return;
	      await this.#closeSurface(surface, source);
	    }
	  }
	  async #closeSurface(surface, source) {
	    const identifier = surface.matches(".fk-d-menu") ? String(surface.dataset.identifier ?? "").trim() : "";
	    if (identifier) {
	      const close = (0, import_native_host_api.discourseNativeMenuCloser)(this.#host);
	      if (close) {
	        await this.#invokeAndWait(
	          surface,
	          () => close(identifier)
	        );
	        return;
	      }
	    }
	    const chatClose = surface.matches(".chat-drawer") ? surface.querySelector(
	      ".chat-drawer-header__close-btn"
	    ) : null;
	    if (chatClose && typeof chatClose.click == "function") {
	      await this.#invokeAndWait(surface, () => chatClose.click());
	      return;
	    }
	    const controlledIds = [surface.id].filter(Boolean);
	    let trigger = [...this.#document.querySelectorAll(
	      '[aria-expanded="true"]'
	    )].find(
	      (candidate) => controlledIds.some(
	        (id) => [
	          candidate.getAttribute("aria-controls"),
	          candidate.getAttribute("aria-owns")
	        ].some((value) => String(value ?? "").split(/\s+/).includes(id))
	      )
	    ) ?? null;
	    if (!trigger) {
	      const selector = source?.classList.contains("search-menu") ? ".d-header-icons .search-dropdown" : source?.classList.contains("user-menu") ? ".d-header-icons .current-user" : source ? ".d-header-icons .hamburger-dropdown,.hamburger-dropdown" : "";
	      if (selector) {
	        const candidates = [
	          ...this.#document.querySelectorAll(selector)
	        ];
	        trigger = candidates.find(
	          (candidate) => candidate.getAttribute("aria-expanded") === "true"
	        ) ?? candidates[0] ?? null;
	      }
	    }
	    trigger && typeof trigger.click == "function" && await this.#invokeAndWait(surface, () => trigger.click());
	  }
	  async #invokeAndWait(surface, action) {
	    try {
	      await Promise.resolve(action());
	    } catch {
	      return;
	    }
	    surfaceClosed(surface) || this.scope.destroyed || await new Promise((resolve) => {
	      const view = this.#document.defaultView;
	      let settled = !1;
	      const finish = () => {
	        settled || (settled = !0, resolve());
	      }, deadline = Date.now() + 180, check = () => {
	        if (this.scope.destroyed || surfaceClosed(surface) || Date.now() >= deadline) {
	          finish();
	          return;
	        }
	        typeof view?.requestAnimationFrame == "function" ? view.requestAnimationFrame(check) : view?.setTimeout(check, 16);
	      };
	      check();
	    });
	  }
	  async #nextFrame() {
	    const view = this.#document.defaultView;
	    typeof view?.requestAnimationFrame == "function" && await new Promise((resolve) => {
	      view.requestAnimationFrame(() => resolve());
	    });
	  }
	}
}, "54da6eb46a64b56fbbc912bb38f329790d833b921800f746de735d514621f67a");

/* Source: lite/src/userscript/reader-userscript-application.ts */
runtime.register("src/userscript/reader-userscript-application.js", function(module, exports, require) {
	var reader_userscript_application_exports = {};
	__export(reader_userscript_application_exports, {
	  createReaderUserscriptApplication: () => createReaderUserscriptApplication,
	  createReaderUserscriptRouteChangePort: () => createReaderUserscriptRouteChangePort,
	  createReaderUserscriptRuntimeBindings: () => createReaderUserscriptRuntimeBindings,
	  createReaderUserscriptRuntimeStage: () => createReaderUserscriptRuntimeStage
	});
	module.exports = __toCommonJS(reader_userscript_application_exports);
	var import_reader_application = require("../app/reader-application.js"), import_reader_browser_runtime = require("../app/reader-browser-runtime.js"), import_value_record = require("../kernel/value-record.js"), import_reader_userscript_target_adapter = require("./reader-userscript-target-adapter.js"), import_reader_host_topic_source_coordinator = require("./reader-host-topic-source-coordinator.js"), import_reader_floating_host_target_controller = require("./reader-floating-host-target-controller.js");
	function createReaderUserscriptRouteChangePort(host) {
	  const module2 = (0, import_value_record.valueRecord)(host.lookupModule("discourse/lib/plugin-api")), defaultExport = (0, import_value_record.valueRecord)(module2?.default), owner = typeof module2?.withPluginApi == "function" ? module2 : defaultExport, withPluginApi = owner?.withPluginApi;
	  return typeof withPluginApi != "function" ? null : Object.freeze({
	    subscribe(handler) {
	      if (typeof handler != "function")
	        throw new TypeError("Discourse page-change handler 必须是函数");
	      let active = !0, pageCleanup = null, pluginCleanup = null;
	      const result = withPluginApi.call(owner, (apiValue) => {
	        if (!active) return;
	        const api = (0, import_value_record.valueRecord)(apiValue), onPageChange = api?.onPageChange;
	        if (typeof onPageChange != "function")
	          throw new Error("Discourse plugin API 缺少 onPageChange");
	        const cleanup = onPageChange.call(api, () => {
	          active && handler();
	        });
	        typeof cleanup == "function" && (pageCleanup = cleanup);
	      });
	      return typeof result == "function" && (pluginCleanup = result), () => {
	        if (active) {
	          active = !1;
	          try {
	            pageCleanup?.();
	          } finally {
	            pluginCleanup?.();
	          }
	        }
	      };
	    }
	  });
	}
	function createReaderUserscriptApplication(options) {
	  const detectedHost = new import_reader_application.BrowserDiscourseHostPort({
	    moduleLookup: (name) => options.environment.discourseHost.lookupModule(name),
	    document: options.document,
	    window: options.window,
	    ...options.hostTimeoutMs === void 0 ? {} : { timeoutMs: options.hostTimeoutMs },
	    ...options.createHostObserver === void 0 ? {} : { createObserver: options.createHostObserver }
	  }), host = options.isVerifiedHost ? Object.freeze({
	    async waitForHost(signal) {
	      const detected = await detectedHost.waitForHost(signal);
	      if (detected || signal.aborted) return detected;
	      const hostname = String(
	        options.document.location?.hostname ?? options.window.location?.hostname ?? ""
	      ).trim();
	      return !await options.isVerifiedHost(
	        hostname,
	        signal
	      ) || signal.aborted ? null : Object.freeze({ detection: "verified-site" });
	    }
	  }) : detectedHost;
	  return new import_reader_application.ReaderApplication({
	    bodyReady: (signal) => (0, import_reader_application.browserBodyReady)(options.document, signal),
	    host,
	    preferences: options.preferences,
	    stages: options.stages
	  });
	}
	function createReaderUserscriptRuntimeBindings(environment, translationOptions, resourceOptions) {
	  let externalHttp;
	  try {
	    externalHttp = environment.createExternalHttp(
	      translationOptions?.http
	    );
	  } catch (cause) {
	    if (translationOptions) throw cause;
	  }
	  let translation;
	  if (translationOptions) {
	    if (!externalHttp) throw new Error("外部翻译 HTTP capability 不可用");
	    const { http: _httpOptions, ...adapterOptions } = translationOptions;
	    translation = Object.freeze({ ...adapterOptions, http: externalHttp });
	  }
	  let resources;
	  if (resourceOptions) {
	    const http = environment.createPublicResourceHttp();
	    resources = Object.freeze({
	      ...resourceOptions,
	      http,
	      objectUrls: environment.createObjectUrlPort()
	    });
	  }
	  const valueStorage = environment.createValueStorage(), assetCacheStorage = environment.createAssetCacheStorage();
	  return Object.freeze({
	    host: environment.discourseHost,
	    share: environment.createShareSurface(),
	    ...externalHttp ? {
	      connect: Object.freeze({ http: externalHttp }),
	      credit: Object.freeze({
	        http: externalHttp,
	        ...valueStorage ? { storage: valueStorage } : {}
	      })
	    } : {},
	    ...translation === void 0 ? {} : { translation },
	    ...resources === void 0 ? {} : { resources },
	    ...assetCacheStorage ? { assetCacheStorage } : {}
	  });
	}
	function createReaderUserscriptRuntimeStage(options) {
	  const bindings = createReaderUserscriptRuntimeBindings(
	    options.environment,
	    options.translation,
	    options.resources
	  ), valueStorage = options.environment.createValueStorage(), onReady = options.onReady, targetOptions = options.targets === !1 ? null : options.targets ?? Object.freeze({});
	  return (0, import_reader_browser_runtime.createReaderBrowserRuntimeStage)({
	    shell: options.shell,
	    runtime: {
	      ...options.runtime,
	      searchForms: options.runtime.searchForms ?? options.environment.createPinyinSearchForms(),
	      host: bindings.host,
	      share: bindings.share,
	      ...bindings.connect ? { connect: bindings.connect } : {},
	      ...bindings.credit ? { credit: bindings.credit } : {},
	      ...bindings.translation === void 0 ? {} : { translation: bindings.translation },
	      ...bindings.resources === void 0 ? {} : { resources: bindings.resources },
	      ...bindings.assetCacheStorage === void 0 ? {} : { assetCacheStorage: bindings.assetCacheStorage },
	      ...options.runtime.threadContextStorage !== void 0 || !valueStorage ? {} : { threadContextStorage: valueStorage }
	    },
	    ...options.selectNavigationPreferences === void 0 ? {} : {
	      selectNavigationPreferences: options.selectNavigationPreferences
	    },
	    ...options.selectPerformancePreferences === void 0 ? {} : {
	      selectPerformancePreferences: options.selectPerformancePreferences
	    },
	    ...options.performanceBudgetCeilings === void 0 ? {} : {
	      performanceBudgetCeilings: options.performanceBudgetCeilings
	    },
	    ...options.layout === void 0 ? {} : { layout: options.layout },
	    ...options.appearance === void 0 ? {} : { appearance: options.appearance },
	    ...options.theme === void 0 ? {} : { theme: options.theme },
	    ...options.font === void 0 ? {} : { font: options.font },
	    ...options.motion === void 0 ? {} : { motion: options.motion },
	    ...options.image === void 0 ? {} : { image: options.image },
	    ...options.boostCopy === void 0 ? {} : { boostCopy: options.boostCopy },
	    ...options.topicActionRail === void 0 ? {} : { topicActionRail: options.topicActionRail },
	    ...options.openQueue === void 0 ? {} : { openQueue: options.openQueue },
	    ...options.shortcuts === void 0 ? {} : { shortcuts: options.shortcuts },
	    ...options.settings === void 0 ? {} : { settings: options.settings },
	    ...options.selectHistoryNavigationPreferences === void 0 ? {} : {
	      selectHistoryNavigationPreferences: options.selectHistoryNavigationPreferences
	    },
	    ...options.selectHistoryPanelPreferences === void 0 ? {} : {
	      selectHistoryPanelPreferences: options.selectHistoryPanelPreferences
	    },
	    ...options.selectBookmarkPreferences === void 0 ? {} : {
	      selectBookmarkPreferences: options.selectBookmarkPreferences
	    },
	    ...options.selectTimelineViewPreferences === void 0 ? {} : {
	      selectTimelineViewPreferences: options.selectTimelineViewPreferences
	    },
	    ...options.selectTranslationMode === void 0 ? {} : {
	      selectTranslationMode: options.selectTranslationMode
	    },
	    ...options.persistTranslationMode === void 0 ? {} : {
	      persistTranslationMode: options.persistTranslationMode
	    },
	    onReady(runtime, context, settings, settingsView, layout, appearance, font) {
	      let targetAdapter = null, floatingHostTarget = null, hostSource = null, readyCleanup;
	      try {
	        targetOptions && (floatingHostTarget = new import_reader_floating_host_target_controller.ReaderFloatingHostTargetController({
	          document: options.runtime.document,
	          overlay: runtime.shell.view.root,
	          workspace: runtime.workspace.workspace,
	          window: runtime.workspace.window,
	          currentUrl: () => options.runtime.document.location.href,
	          target: {
	            openTarget: (request) => runtime.openTarget(request)
	          },
	          closeReader: () => runtime.close(),
	          readOpenTopicsAtFirstPost: () => targetOptions.selectOpenTopicsAtFirstPost?.(
	            context.readPreferences()
	          ) === !0,
	          parentScope: runtime.scope,
	          ...targetOptions.onError === void 0 ? {} : { onError: targetOptions.onError }
	        }), hostSource = new import_reader_host_topic_source_coordinator.ReaderHostTopicSourceCoordinator({
	          document: options.runtime.document,
	          host: options.environment.discourseHost,
	          readerRoot: runtime.shell.view.root,
	          isEmbedded: () => runtime.workspace.workspace.snapshot.presentation.embedded,
	          parentScope: runtime.scope
	        }), targetAdapter = new import_reader_userscript_target_adapter.ReaderUserscriptTargetAdapter({
	          document: options.runtime.document,
	          currentUrl: () => options.runtime.document.location.href,
	          target: {
	            openTarget: (request) => runtime.openTarget(request)
	          },
	          routeChanges: createReaderUserscriptRouteChangePort(
	            options.environment.discourseHost
	          ),
	          serviceWorkerMessages: options.runtime.document.defaultView?.navigator.serviceWorker ?? null,
	          interceptServiceWorkerTopicTargets: () => runtime.workspace.workspace.snapshot.presentation.embedded,
	          readOpenTopicsAtFirstPost: () => targetOptions.selectOpenTopicsAtFirstPost?.(
	            context.readPreferences()
	          ) === !0,
	          ...targetOptions.openInitialRoute === void 0 ? {} : {
	            openInitialRoute: targetOptions.openInitialRoute
	          },
	          ...targetOptions.interceptTopicLinks === void 0 ? {} : {
	            interceptTopicLinks: targetOptions.interceptTopicLinks
	          },
	          beforeOpenTarget: async (target) => {
	            await hostSource.prepare(target), await targetOptions.beforeOpenTarget?.(target);
	          },
	          afterOpenTarget: async (target, opened) => {
	            await hostSource.settle(target, opened);
	          },
	          parentScope: runtime.scope,
	          ...targetOptions.onError === void 0 ? {} : { onError: targetOptions.onError }
	        })), readyCleanup = onReady?.(
	          runtime,
	          context,
	          settings,
	          settingsView,
	          layout,
	          appearance,
	          font
	        ) || void 0;
	      } catch (error) {
	        throw targetAdapter?.destroy(), floatingHostTarget?.destroy(), hostSource?.destroy(), error;
	      }
	      return () => {
	        try {
	          readyCleanup?.();
	        } finally {
	          targetAdapter?.destroy(), floatingHostTarget?.destroy(), hostSource?.destroy();
	        }
	      };
	    }
	  });
	}
}, "44997d4b9d0ed531b19ce5d8fff759494ecaf9577edd788c4b1d447252f6f2ff");

/* Source: lite/src/userscript/reader-userscript-target-adapter.ts */
runtime.register("src/userscript/reader-userscript-target-adapter.js", function(module, exports, require) {
	var reader_userscript_target_adapter_exports = {};
	__export(reader_userscript_target_adapter_exports, {
	  ReaderUserscriptTargetAdapter: () => ReaderUserscriptTargetAdapter,
	  parseReaderUserscriptTopicRoute: () => parseReaderUserscriptTopicRoute,
	  readerUserscriptRouteKind: () => readerUserscriptRouteKind
	});
	module.exports = __toCommonJS(reader_userscript_target_adapter_exports);
	var import_identifiers = require("../discourse/identifiers.js"), import_lifecycle = require("../kernel/lifecycle.js"), import_reader_native_topic_route = require("../topic/reader-native-topic-route.js");
	const SOURCE_SELECTOR = "[data-reader-target-source],.ldp-notification-item", BYPASS_SELECTOR = '[data-reader-target-interception="off"],[data-reader-target-source="history"],.ldp-open,.ldp-history-link';
	function element(value) {
	  return value === null || typeof value != "object" || value.nodeType !== 1 || typeof value.matches != "function" ? null : value;
	}
	function eventAnchor(event) {
	  const path = typeof event.composedPath == "function" ? event.composedPath() : [];
	  for (const value of path) {
	    const candidate = element(value);
	    if (candidate?.matches("a[href]")) return candidate;
	  }
	  return element(event.target)?.closest("a[href]") ?? null;
	}
	function linkSource(marker) {
	  const explicit = marker?.getAttribute("data-reader-target-source");
	  return explicit === "link" || explicit === "message" || explicit === "notification" || explicit === "restore" ? explicit : marker?.classList.contains("ldp-notification-message-item") || marker?.getAttribute("data-notification-mode") === "messages" ? "message" : marker?.classList.contains("ldp-notification-item") ? "notification" : "link";
	}
	function markerRoute(marker, fallback) {
	  if (!marker) return fallback;
	  const topicId = (0, import_identifiers.tryDiscourseTopicId)(
	    marker.getAttribute("data-reader-topic-id") ?? marker.getAttribute("data-notification-topic-id")
	  );
	  if (!topicId) return fallback;
	  const postNumber = (0, import_identifiers.tryDiscoursePostNumber)(
	    marker.getAttribute("data-reader-post-number") ?? marker.getAttribute("data-notification-post-number")
	  ) ?? (fallback?.topicId === topicId ? fallback.postNumber : null);
	  return Object.freeze({
	    topicId,
	    postNumber,
	    bypassReader: fallback?.bypassReader ?? !1,
	    href: fallback?.href ?? ""
	  });
	}
	function isPlainPrimaryClick(event) {
	  const pointer = event;
	  return event.type === "click" && !event.defaultPrevented && (pointer.button === void 0 || pointer.button === 0) && pointer.altKey !== !0 && pointer.ctrlKey !== !0 && pointer.metaKey !== !0 && pointer.shiftKey !== !0;
	}
	function truthyAttribute(node, name) {
	  const value = node?.getAttribute(name);
	  return value === "1" || value === "true";
	}
	function isSameOriginHttpTarget(value, baseValue) {
	  try {
	    const base = new URL(baseValue), url = new URL(value, base);
	    return /^https?:$/i.test(url.protocol) && url.origin === base.origin;
	  } catch {
	    return !1;
	  }
	}
	function parseReaderUserscriptTopicRoute(value, baseValue) {
	  let base, url;
	  try {
	    base = new URL(baseValue), url = new URL(value, base);
	  } catch {
	    return null;
	  }
	  if (!/^https?:$/i.test(url.protocol) || url.origin !== base.origin)
	    return null;
	  const segments = url.pathname.split("/").filter(Boolean);
	  if (segments[0] !== "t") return null;
	  const numericFirst = (0, import_identifiers.tryDiscourseTopicId)(segments[1]), topicId = numericFirst ?? (0, import_identifiers.tryDiscourseTopicId)(segments[2]);
	  if (!topicId) return null;
	  const postValue = segments[numericFirst ? 2 : 3], postNumber = postValue === void 0 ? null : (0, import_identifiers.tryDiscoursePostNumber)(postValue);
	  return postValue !== void 0 && !postNumber ? null : Object.freeze({
	    topicId,
	    postNumber,
	    bypassReader: url.searchParams.has(
	      import_reader_native_topic_route.READER_NATIVE_BYPASS_PARAMETER
	    ),
	    href: url.href
	  });
	}
	function readerUserscriptRouteKind(value, baseValue) {
	  return parseReaderUserscriptTopicRoute(value, baseValue) ? "direct-topic" : "list";
	}
	class ReaderUserscriptTargetAdapter {
	  scope;
	  ready;
	  #options;
	  #onClick;
	  #routeEpoch = 0;
	  #targetEpoch = 0;
	  #lastRouteKey = "";
	  constructor(options) {
	    if (this.#options = options, this.scope = import_lifecycle.LifecycleScope.ownedBy(options.parentScope), this.#onClick = (event) => {
	      this.#handleClick(event);
	    }, options.interceptTopicLinks !== !1 && (options.document.addEventListener("click", this.#onClick, !0), this.scope.add(() => {
	      options.document.removeEventListener("click", this.#onClick, !0);
	    })), options.routeChanges)
	      try {
	        this.scope.add(options.routeChanges.subscribe(() => {
	          this.syncCurrentRoute();
	        }));
	      } catch (error) {
	        this.#report(error);
	      }
	    if (options.serviceWorkerMessages) {
	      const listener = (event) => {
	        this.#handleServiceWorkerMessage(event);
	      };
	      options.serviceWorkerMessages.addEventListener(
	        "message",
	        listener,
	        !0
	      ), this.scope.add(() => {
	        options.serviceWorkerMessages?.removeEventListener(
	          "message",
	          listener,
	          !0
	        );
	      });
	    }
	    this.scope.add(() => {
	      this.#routeEpoch += 1, this.#targetEpoch += 1, this.#lastRouteKey = "";
	    }), options.openInitialRoute === !1 ? (this.#rememberCurrentRoute(), this.ready = Promise.resolve(!1)) : this.ready = this.syncCurrentRoute({ force: !0 });
	  }
	  async syncCurrentRoute(options = {}) {
	    if (this.scope.destroyed) return !1;
	    const currentUrl = this.#currentUrl();
	    if (!currentUrl) return !1;
	    const route = parseReaderUserscriptTopicRoute(
	      currentUrl,
	      currentUrl
	    );
	    if (!route || route.bypassReader)
	      return this.#lastRouteKey = "", !1;
	    const targetPostNumber = this.#ordinaryPostNumber(route), routeKey = `${route.topicId}:${targetPostNumber ?? 0}`;
	    if (options.force !== !0 && routeKey === this.#lastRouteKey)
	      return !1;
	    this.#lastRouteKey = routeKey;
	    const epoch = ++this.#routeEpoch, targetEpoch = ++this.#targetEpoch, opened = await this.#open({
	      topicId: route.topicId,
	      ...targetPostNumber === null ? {} : { postNumber: targetPostNumber },
	      source: "restore"
	    });
	    return epoch !== this.#routeEpoch || targetEpoch !== this.#targetEpoch || this.scope.destroyed ? !1 : (opened || (this.#lastRouteKey = ""), opened);
	  }
	  destroy() {
	    this.scope.destroy();
	  }
	  #handleClick(event) {
	    if (this.scope.destroyed || !isPlainPrimaryClick(event)) return;
	    const anchor = eventAnchor(event);
	    if (!anchor || anchor.closest(BYPASS_SELECTOR) || anchor.hasAttribute("download") || anchor.getAttribute("aria-disabled") === "true" || anchor.closest(".search-menu") && !anchor.closest(
	      ".search-result-topic,.search-result-post," + SOURCE_SELECTOR
	    ))
	      return;
	    const target = String(anchor.getAttribute("target") ?? "").toLowerCase();
	    if (target && target !== "_self") return;
	    const linkTarget = this.#linkTarget(anchor);
	    if (!linkTarget || linkTarget.route.bypassReader) return;
	    event.preventDefault(), event.stopPropagation(), event.stopImmediatePropagation();
	    const postNumber = linkTarget.source === "link" && !linkTarget.preservePostNumber ? this.#ordinaryPostNumber(linkTarget.route) : linkTarget.route.postNumber, request = {
	      topicId: linkTarget.route.topicId,
	      ...postNumber === null ? {} : { postNumber },
	      source: linkTarget.source
	    };
	    this.#openIntercepted({
	      request,
	      anchor,
	      sourceElement: linkTarget.sourceElement,
	      pointer: Number.isFinite(event.clientY) ? Object.freeze({
	        clientY: event.clientY,
	        detail: Number(event.detail) || 0
	      }) : null
	    });
	  }
	  #handleServiceWorkerMessage(event) {
	    if (this.scope.destroyed) return;
	    try {
	      if (this.#options.interceptServiceWorkerTopicTargets?.() !== !0)
	        return;
	    } catch (error) {
	      this.#report(error);
	      return;
	    }
	    const data = event.data;
	    if (data === null || typeof data != "object") return;
	    const targetUrl = String(
	      data.url ?? ""
	    ).trim();
	    if (!targetUrl) return;
	    const currentUrl = this.#currentUrl();
	    if (!currentUrl) return;
	    const route = parseReaderUserscriptTopicRoute(targetUrl, currentUrl);
	    !route || route.bypassReader || (event.preventDefault(), event.stopPropagation(), event.stopImmediatePropagation(), this.#targetEpoch += 1, this.#open({
	      topicId: route.topicId,
	      ...route.postNumber === null ? {} : { postNumber: route.postNumber },
	      source: "notification"
	    }));
	  }
	  #linkTarget(anchor) {
	    const currentUrl = this.#currentUrl();
	    if (!currentUrl) return null;
	    const href = anchor.getAttribute("href") ?? "";
	    if (!isSameOriginHttpTarget(href, currentUrl)) return null;
	    const fallback = parseReaderUserscriptTopicRoute(href, currentUrl), marker = anchor.closest(SOURCE_SELECTOR), route = markerRoute(marker, fallback);
	    if (!route) return null;
	    const source = linkSource(marker), preservePostNumber = source !== "link" || truthyAttribute(anchor, "data-reader-preserve-target") || truthyAttribute(marker, "data-reader-preserve-target") || truthyAttribute(anchor, "data-ldp-preserve-target-post") || truthyAttribute(marker, "data-ldp-preserve-target-post");
	    return Object.freeze({
	      route,
	      source,
	      preservePostNumber,
	      sourceElement: marker
	    });
	  }
	  #ordinaryPostNumber(route) {
	    try {
	      return this.#options.readOpenTopicsAtFirstPost?.() === !0 ? (0, import_identifiers.tryDiscoursePostNumber)(1) : route.postNumber;
	    } catch (error) {
	      return this.#report(error), route.postNumber;
	    }
	  }
	  #rememberCurrentRoute() {
	    const currentUrl = this.#currentUrl();
	    if (!currentUrl) return;
	    const route = parseReaderUserscriptTopicRoute(currentUrl, currentUrl);
	    !route || route.bypassReader || (this.#lastRouteKey = `${route.topicId}:${this.#ordinaryPostNumber(route) ?? 0}`);
	  }
	  #currentUrl() {
	    try {
	      const value = String(this.#options.currentUrl()).trim();
	      if (!value) throw new Error("当前页面 URL 为空");
	      return value;
	    } catch (error) {
	      return this.#report(error), null;
	    }
	  }
	  #report(error) {
	    try {
	      this.#options.onError?.(error);
	    } catch {
	    }
	  }
	  async #openIntercepted(target) {
	    const epoch = ++this.#targetEpoch;
	    if (this.#options.beforeOpenTarget)
	      try {
	        await this.#options.beforeOpenTarget(target);
	      } catch (error) {
	        this.#report(error);
	      }
	    if (this.scope.destroyed || epoch !== this.#targetEpoch)
	      return await this.#settleIntercepted(target, !1), !1;
	    const opened = await this.#open(target.request);
	    return this.scope.destroyed || epoch !== this.#targetEpoch ? (await this.#settleIntercepted(target, !1), !1) : (await this.#settleIntercepted(target, opened), opened);
	  }
	  async #settleIntercepted(target, opened) {
	    if (this.#options.afterOpenTarget)
	      try {
	        await this.#options.afterOpenTarget(target, opened);
	      } catch (error) {
	        this.#report(error);
	      }
	  }
	  async #open(request) {
	    try {
	      const result = await this.#options.target.openTarget(request);
	      if (result.topic.status === "opened" || result.topic.status === "reused")
	        return request.postNumber === void 0 || result.navigation?.status === "revealed";
	      if (result.topic.status === "failed")
	        throw result.topic.cause ?? new Error(`Reader 目标 Topic ${request.topicId} 打开失败`);
	      return !1;
	    } catch (error) {
	      return this.#report(error), !1;
	    }
	  }
	}
}, "b223121844ede7bf645cc153e0e5e3cd21742c8a3bfbb6b56b6e292aa2715c48");

	runtime.markLibrary("main-lite-core");
})();