From 366ed3eea12793f1ba1f9672b1612d8a112fde81 Mon Sep 17 00:00:00 2001 From: Hanblade <228376990+hanblade67-cloud@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:26:13 +0300 Subject: [PATCH] Delete main.js --- main.js | 6406 ------------------------------------------------------- 1 file changed, 6406 deletions(-) delete mode 100644 main.js diff --git a/main.js b/main.js deleted file mode 100644 index 9978719..0000000 --- a/main.js +++ /dev/null @@ -1,6406 +0,0 @@ -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: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__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: true }), mod); - -// main.ts -var main_exports = {}; -__export(main_exports, { - default: () => MediaSize -}); -module.exports = __toCommonJS(main_exports); -var import_obsidian = require("obsidian"); -var I18N = { - en: { - cloneSettingsTitle: "Clone customization", - animation: "Animation", - animationPopup: "Popup", - animationFade: "Fade", - backgroundOpacity: "Background opacity", - controlsOpacity: "Controls opacity", - showAudioHandles: "Show audio handles", - presets: "Sizes", - fit: "Fit to container", - natural: "Natural size", - add: "Add\u2026", - addPresetTitle: "Add width preset \u2039px\u203A", - placeholder: "e.g. 820", - ok: "OK", - cancel: "Cancel", - invalid: "Invalid value", - maxReached: "Maximum number of presets reached", - added: "Preset {n} added", - removed: "Preset {n} removed", - openLocation: "Open file location", - fileNotFound: "File not found in the vault", - shellDenied: "No access to shell", - localOnly: "Available for local vaults only", - restorePos: "Restore original position", - setSize: "Set size\u2026", - width: "Width", - height: "Height", - keepAR: "Keep aspect ratio", - sbReady: "ML \u2022 ready", - sbTitle: "Media Layout: click to rescan", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 plugin active", - posPresets: "Save clone positions", - resetAll: "Reset all", - saveLayout: "Save preset", - layoutMax: "Maximum number of presets reached", - saved: "Saved", - layoutN: "Preset {n}", - renameTitle: "Enter a name", - addNote: "Add note box", - copyLayout: "Copy page data", - pasteLayout: "Paste page data", - deleteNote: "Delete note box", - detach: "Detach", - bgColor: "Change background color", - duplicate: "Duplicate", - reset: "Reset", - removeBg: "Remove background", - deleteClone: "Delete", - lock: "Lock", - unlock: "Unlock", - localOnlyCheckboxLabel: "Only for this file", - makeInvisible: "Make invisible", - invalidLayoutData: "Invalid page data in clipboard.", - layoutPasted: "Page data and presets pasted.", - pasteLayoutFailed: "Failed to paste page data from clipboard.", - layoutCopied: "Page data copied to clipboard.", - makeVisible: "Make visible", - bringLayerUp: "Bring layer up", - sendLayerDown: "Send layer down", - showHandlesOnHoverLabel: "Show handles on hover", - hideDetachButtonLabel: "Hide \xABDetach\xBB button", - modalAnimationLabel: "Window animation", - cloneRestoreDelayLabel: "Clone appearance delay", - noteRestoreDelayLabel: "Note box appearance delay", - hideDelayLabel: "Panel hide delay", - changeTextColor: "Change text color", - cloneNearFile: "Clone next to the file", - addStroke: "Add stroke", - addLink: "Add link", - applyToHeader: "Apply to header", - deleteSelected: "Delete selected ({n})", - urlMediaClickToSave: "Click to save to vault", - urlMediaDownloadAndReplace: "Download and replace", - urlMediaDownloading: "Downloading image...", - urlMediaDownloadError: "Download error: {status}", - urlMediaFileSavedLinkFailed: "File saved, but failed to update link", - urlMediaSuccess: "Image saved and link updated", - urlMediaFileSavedPatternNotFound: "File saved, but link not found in text", - urlMediaError: "Error: {error}", - urlMediaUnknownError: "unknown error" - }, - ru: { - cloneSettingsTitle: "\u041A\u0430\u0441\u0442\u043E\u043C\u0438\u0437\u0430\u0446\u0438\u044F \u043A\u043B\u043E\u043D\u043E\u0432", - animation: "\u0410\u043D\u0438\u043C\u0430\u0446\u0438\u044F", - animationPopup: "\u0412\u0441\u043F\u043B\u044B\u0432\u0430\u044E\u0449\u0430\u044F", - animationFade: "\u041F\u0440\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u0435", - backgroundOpacity: "\u041F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C \u0444\u043E\u043D\u0430", - controlsOpacity: "\u041F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u0438", - showAudioHandles: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0440\u0443\u0447\u043A\u0438 \u0443 \u0430\u0443\u0434\u0438\u043E", - presets: "\u0420\u0430\u0437\u043C\u0435\u0440\u044B", - fit: "\u041F\u043E\u0434\u043E\u0433\u043D\u0430\u0442\u044C \u043F\u043E\u0434 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440", - natural: "\u041D\u0430\u0442\u0443\u0440\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440", - add: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C\u2026", - addPresetTitle: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u0435\u0441\u0435\u0442 \u0448\u0438\u0440\u0438\u043D\u044B \u2039px\u203A", - placeholder: "\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: 820", - ok: "\u041E\u041A", - cancel: "\u041E\u0442\u043C\u0435\u043D\u0430", - invalid: "\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435", - maxReached: "\u0414\u043E\u0441\u0442\u0438\u0433\u043D\u0443\u0442\u043E \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043F\u0440\u0435\u0441\u0435\u0442\u043E\u0432", - added: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u043F\u0440\u0435\u0441\u0435\u0442 {n}", - removed: "\u0423\u0434\u0430\u043B\u0451\u043D \u043F\u0440\u0435\u0441\u0435\u0442 {n}", - openLocation: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0440\u0430\u0441\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0444\u0430\u0439\u043B\u0430", - fileNotFound: "\u0424\u0430\u0439\u043B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0432\u0430\u0443\u043B\u0442\u0435", - shellDenied: "\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A shell", - localOnly: "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0445 \u0432\u0430\u0443\u043B\u0442\u043E\u0432", - restorePos: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0435 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435", - setSize: "\u0417\u0430\u0434\u0430\u0442\u044C \u0440\u0430\u0437\u043C\u0435\u0440\u2026", - width: "\u0428\u0438\u0440\u0438\u043D\u0430", - height: "\u0412\u044B\u0441\u043E\u0442\u0430", - keepAR: "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u043F\u0440\u043E\u043F\u043E\u0440\u0446\u0438\u0438", - sbReady: "ML \u2022 \u0433\u043E\u0442\u043E\u0432\u043E", - sbTitle: "Media Layout: \u043A\u043B\u0438\u043A \u2014 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 \u043F\u043B\u0430\u0433\u0438\u043D \u0430\u043A\u0442\u0438\u0432\u0435\u043D", - posPresets: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043A\u043B\u043E\u043D\u043E\u0432", - resetAll: "\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0432\u0441\u0451", - saveLayout: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0440\u0435\u0441\u0435\u0442", - layoutMax: "\u0414\u043E\u0441\u0442\u0438\u0433\u043D\u0443\u0442\u043E \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043F\u0440\u0435\u0441\u0435\u0442\u043E\u0432", - saved: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E", - layoutN: "\u041F\u0440\u0435\u0441\u0435\u0442 {n}", - renameTitle: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435", - addNote: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0442\u0430\u0431\u043B\u0438\u0447\u043A\u0443", - copyLayout: "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B", - pasteLayout: "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B", - deleteNote: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u0430\u0431\u043B\u0438\u0447\u043A\u0443", - detach: "\u041E\u0442\u043A\u0440\u0435\u043F\u0438\u0442\u044C", - bgColor: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0444\u043E\u043D\u0430", - duplicate: "\u041A\u043B\u043E\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C", - reset: "\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C", - removeBg: "\u0423\u0431\u0440\u0430\u0442\u044C \u0444\u043E\u043D", - deleteClone: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", - lock: "\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C", - unlock: "\u0420\u0430\u0437\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u0442\u044C", - localOnlyCheckboxLabel: "\u0422\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430", - makeInvisible: "\u0421\u0434\u0435\u043B\u0430\u0442\u044C \u043D\u0435\u0432\u0438\u0434\u0438\u043C\u044B\u043C", - invalidLayoutData: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0432 \u0431\u0443\u0444\u0435\u0440\u0435 \u043E\u0431\u043C\u0435\u043D\u0430.", - layoutPasted: "\u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0438 \u043F\u0440\u0435\u0441\u0435\u0442\u044B \u0432\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u044B.", - pasteLayoutFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0438\u0437 \u0431\u0443\u0444\u0435\u0440\u0430 \u043E\u0431\u043C\u0435\u043D\u0430.", - layoutCopied: "\u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0432 \u0431\u0443\u0444\u0435\u0440 \u043E\u0431\u043C\u0435\u043D\u0430.", - makeVisible: "\u0421\u0434\u0435\u043B\u0430\u0442\u044C \u0432\u0438\u0434\u0438\u043C\u044B\u043C", - bringLayerUp: "\u041F\u043E\u0434\u043D\u044F\u0442\u044C \u0441\u043B\u043E\u0439", - sendLayerDown: "\u041E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0441\u043B\u043E\u0439", - showHandlesOnHoverLabel: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0440\u0443\u0447\u043A\u0438 \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438", - hideDetachButtonLabel: "\u0421\u043A\u0440\u044B\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \xAB\u043E\u0442\u043A\u0440\u0435\u043F\u0438\u0442\u044C\xBB", - modalAnimationLabel: "\u0410\u043D\u0438\u043C\u0430\u0446\u0438\u044F \u043E\u043A\u043E\u043D", - cloneRestoreDelayLabel: "\u0417\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u043A\u043B\u043E\u043D\u043E\u0432", - noteRestoreDelayLabel: "\u0417\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0442\u0430\u0431\u043B\u0438\u0447\u0435\u043A", - hideDelayLabel: "\u0417\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u0441\u043A\u0440\u044B\u0442\u0438\u044F \u043F\u0430\u043D\u0435\u043B\u0438", - changeTextColor: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0442\u0435\u043A\u0441\u0442\u0430", - cloneNearFile: "\u041A\u043B\u043E\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0440\u044F\u0434\u043E\u043C \u0441 \u0444\u0430\u0439\u043B\u043E\u043C", - addStroke: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0432\u043E\u0434\u043A\u0443", - applyToHeader: "\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u043B\u044F \u0448\u0430\u043F\u043A\u0438", - addLink: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443", - deleteSelected: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043D\u043E\u0435 ({n})", - urlMediaClickToSave: "\u041A\u043B\u0438\u043A \u0434\u043B\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F \u0432 vault", - urlMediaDownloadAndReplace: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C \u0438 \u0437\u0430\u043C\u0435\u043D\u0438\u0442\u044C", - urlMediaDownloading: "\u0421\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F...", - urlMediaDownloadError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F: {status}", - urlMediaFileSavedLinkFailed: "\u0424\u0430\u0439\u043B \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D, \u043D\u043E \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443", - urlMediaSuccess: "\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E \u0438 \u0441\u0441\u044B\u043B\u043A\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430", - urlMediaFileSavedPatternNotFound: "\u0424\u0430\u0439\u043B \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D, \u043D\u043E \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0442\u0435\u043A\u0441\u0442\u0435", - urlMediaError: "\u041E\u0448\u0438\u0431\u043A\u0430: {error}", - urlMediaUnknownError: "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430" - }, - de: { - cloneSettingsTitle: "Klon-Anpassung", - animation: "Animation", - animationPopup: "Popup", - animationFade: "Einblenden", - backgroundOpacity: "Hintergrund-Deckkraft", - controlsOpacity: "Bedienfeld-Deckkraft", - showAudioHandles: "Audio-Griffe anzeigen", - presets: "Gr\xF6\xDFen", - fit: "An Container anpassen", - natural: "Nat\xFCrliche Gr\xF6\xDFe", - add: "Hinzuf\xFCgen\u2026", - addPresetTitle: "Breiten-Preset hinzuf\xFCgen \u2039px\u203A", - placeholder: "z. B. 820", - ok: "OK", - cancel: "Abbrechen", - invalid: "Ung\xFCltiger Wert", - maxReached: "Maximale Anzahl an Presets erreicht", - added: "Preset {n} hinzugef\xFCgt", - removed: "Preset {n} entfernt", - removeBg: "Hintergrund entfernen", - openLocation: "Dateispeicherort \xF6ffnen", - fileNotFound: "Datei im Vault nicht gefunden", - shellDenied: "Kein Shell-Zugriff", - localOnly: "Nur f\xFCr lokale Vaults verf\xFCgbar", - restorePos: "Ausgangsposition wiederherstellen", - setSize: "Gr\xF6\xDFe festlegen\u2026", - width: "Breite", - height: "H\xF6he", - keepAR: "Seitenverh\xE4ltnis beibehalten", - sbReady: "ML \u2022 bereit", - sbTitle: "Media Layout: Klicken zum Rescan", - cmdPing: "Media Layout: Ping", - pingOk: "ML: OK \u2014 Plugin aktiv", - posPresets: "Klonposition beibehalten", - resetAll: "Alles zur\xFCcksetzen", - saveLayout: "Preset speichern", - layoutMax: "Maximale Anzahl an Presets erreicht", - saved: "Gespeichert", - layoutN: "Preset {n}", - renameTitle: "Name eingeben", - addNote: "Notiz-Box hinzuf\xFCgen", - copyLayout: "Seitendaten kopieren", - pasteLayout: "Seitendaten einf\xFCgen", - deleteNote: "Notiz-Box l\xF6schen", - detach: "Abl\xF6sen", - bgColor: "Hintergrundfarbe \xE4ndern", - duplicate: "Duplizieren", - reset: "Zur\xFCcksetzen", - deleteClone: "l\xF6schen", - lock: "Sperren", - unlock: "Entsperren", - localOnlyCheckboxLabel: "Nur f\xFCr diese Datei", - makeInvisible: "Unsichtbar machen", - invalidLayoutData: "Ung\xFCltige Seitendaten in der Zwischenablage.", - layoutPasted: "Seitendaten und Presets eingef\xFCgt.", - pasteLayoutFailed: "Seitendaten konnten nicht aus der Zwischenablage eingef\xFCgt werden.", - layoutCopied: "Seitendaten in die Zwischenablage kopiert.", - makeVisible: "Sichtbar machen", - bringLayerUp: "Ebene nach oben", - sendLayerDown: "Ebene nach unten", - showHandlesOnHoverLabel: "Ziehpunkte bei Hover anzeigen", - hideDetachButtonLabel: "Schaltfl\xE4che \u201EAbl\xF6sen\u201C ausblenden", - modalAnimationLabel: "Modal-Animation", - cloneRestoreDelayLabel: "Verz\xF6gerung f\xFCr Klon-Einblendung", - noteRestoreDelayLabel: "Verz\xF6gerung f\xFCr Notizbox-Einblendung", - hideDelayLabel: "Verz\xF6gerung beim Ausblenden des Panels", - changeTextColor: "Textfarbe \xE4ndern", - cloneNearFile: "Neben der Datei klonen", - addStroke: "Kontur hinzuf\xFCgen", - applyToHeader: "Auf Kopfzeile anwenden", - addLink: "Link hinzuf\xFCgen", - deleteSelected: "Ausgew\xE4hlte l\xF6schen ({n})", - urlMediaClickToSave: "Klicken um im Vault zu speichern", - urlMediaDownloadAndReplace: "Herunterladen und ersetzen", - urlMediaDownloading: "Bild wird heruntergeladen...", - urlMediaDownloadError: "Download-Fehler: {status}", - urlMediaFileSavedLinkFailed: "Datei gespeichert, aber Link-Aktualisierung fehlgeschlagen", - urlMediaSuccess: "Bild gespeichert und Link aktualisiert", - urlMediaFileSavedPatternNotFound: "Datei gespeichert, aber Link nicht im Text gefunden", - urlMediaError: "Fehler: {error}", - urlMediaUnknownError: "unbekannter Fehler" - }, - pl: { - cloneSettingsTitle: "Dostosowanie klon\xF3w", - animation: "Animacja", - animationPopup: "Wyskakuj\u0105ca", - animationFade: "Wygaszanie", - backgroundOpacity: "Przezroczysto\u015B\u0107 t\u0142a", - controlsOpacity: "Przezroczysto\u015B\u0107 panelu", - showAudioHandles: "Poka\u017C uchwyty audio", - presets: "Rozmiary", - fit: "Dopasuj do kontenera", - natural: "Naturalny rozmiar", - add: "Dodaj\u2026", - addPresetTitle: "Dodaj preset szeroko\u015Bci \u2039px\u203A", - placeholder: "np. 820", - ok: "OK", - removeBg: "Usu\u0144 t\u0142o", - cancel: "Anuluj", - invalid: "Nieprawid\u0142owa warto\u015B\u0107", - maxReached: "Osi\u0105gni\u0119to maksymaln\u0105 liczb\u0119 preset\xF3w", - added: "Dodano preset {n}", - removed: "Usuni\u0119to preset {n}", - openLocation: "Otw\xF3rz lokalizacj\u0119 pliku", - fileNotFound: "Pliku nie znaleziono w vault", - shellDenied: "Brak dost\u0119pu do shell", - localOnly: "Dost\u0119pne tylko dla lokalnych vault\xF3w", - restorePos: "Przywr\xF3\u0107 pozycj\u0119", - setSize: "Ustaw rozmiar\u2026", - width: "Szeroko\u015B\u0107", - height: "Wysoko\u015B\u0107", - keepAR: "Zachowaj proporcje", - sbReady: "ML \u2022 gotowe", - sbTitle: "Media Layout: kliknij, aby przeskanowa\u0107", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 wtyczka aktywna", - posPresets: "Zapisz pozycje klon\xF3w", - resetAll: "Zresetuj wszystko", - saveLayout: "Zapisz preset", - layoutMax: "Osi\u0105gni\u0119to maksymaln\u0105 liczb\u0119 preset\xF3w", - saved: "Zapisano", - layoutN: "Preset {n}", - renameTitle: "Wpisz nazw\u0119", - addNote: "Dodaj ramk\u0119 notatki", - copyLayout: "Kopiuj dane strony", - pasteLayout: "Wklej dane strony", - deleteNote: "Usu\u0144 ramk\u0119 notatki", - detach: "Od\u0142\u0105cz", - bgColor: "Zmie\u0144 kolor t\u0142a", - duplicate: "Duplikuj", - reset: "Resetuj", - deleteClone: "Usu\u0144", - lock: "Zablokuj", - unlock: "Odblokuj", - localOnlyCheckboxLabel: "Tylko dla tego pliku", - makeInvisible: "Uczy\u0144 niewidocznym", - invalidLayoutData: "Nieprawid\u0142owe dane strony w schowku.", - layoutPasted: "Wklejono dane strony i presety.", - pasteLayoutFailed: "Nie uda\u0142o si\u0119 wklei\u0107 danych strony ze schowka.", - layoutCopied: "Skopiowano dane strony do schowka.", - makeVisible: "Uczy\u0144 widocznym", - bringLayerUp: "Podnie\u015B warstw\u0119", - sendLayerDown: "Opu\u015B\u0107 warstw\u0119", - showHandlesOnHoverLabel: "Poka\u017C uchwyty po najechaniu", - hideDetachButtonLabel: "Ukryj przycisk \u201EOdepnij\u201D", - modalAnimationLabel: "Animacja okien modalnych", - cloneRestoreDelayLabel: "Op\xF3\u017Anienie pojawiania si\u0119 klon\xF3w", - noteRestoreDelayLabel: "Op\xF3\u017Anienie pojawiania si\u0119 okienek notatek", - hideDelayLabel: "Op\xF3\u017Anienie ukrywania panelu", - changeTextColor: "Zmie\u0144 kolor tekstu", - cloneNearFile: "Klonuj obok pliku", - addStroke: "Dodaj obrys", - applyToHeader: "Zastosuj do nag\u0142\xF3wka", - addLink: "Dodaj link", - deleteSelected: "Usu\u0144 zaznaczone ({n})", - urlMediaClickToSave: "Kliknij aby zapisa\u0107 do vault", - urlMediaDownloadAndReplace: "Pobierz i zamie\u0144", - urlMediaDownloading: "Pobieranie obrazu...", - urlMediaDownloadError: "B\u0142\u0105d pobierania: {status}", - urlMediaFileSavedLinkFailed: "Plik zapisany, ale nie uda\u0142o si\u0119 zaktualizowa\u0107 linku", - urlMediaSuccess: "Obraz zapisany i link zaktualizowany", - urlMediaFileSavedPatternNotFound: "Plik zapisany, ale link nie znaleziony w tek\u015Bcie", - urlMediaError: "B\u0142\u0105d: {error}", - urlMediaUnknownError: "nieznany b\u0142\u0105d" - }, - es: { - cloneSettingsTitle: "Personalizar clones", - animation: "Animaci\xF3n", - removeBg: "Eliminar fondo", - animationPopup: "Emergente", - animationFade: "Fundido", - backgroundOpacity: "Opacidad del fondo", - controlsOpacity: "Opacidad del panel", - showAudioHandles: "Mostrar asas de audio", - presets: "Tama\xF1os", - fit: "Ajustar al contenedor", - natural: "Tama\xF1o natural", - add: "A\xF1adir\u2026", - addPresetTitle: "A\xF1adir preset de ancho \u2039px\u203A", - placeholder: "p. ej., 820", - ok: "OK", - cancel: "Cancelar", - invalid: "Valor no v\xE1lido", - maxReached: "Se alcanz\xF3 el n\xFAmero m\xE1ximo de presets", - added: "Preset {n} a\xF1adido", - removed: "Preset {n} eliminado", - openLocation: "Abrir ubicaci\xF3n del archivo", - fileNotFound: "Archivo no encontrado en el vault", - shellDenied: "Sin acceso a shell", - localOnly: "Disponible solo para vaults locales", - restorePos: "Restaurar posici\xF3n original", - setSize: "Establecer tama\xF1o\u2026", - width: "Ancho", - height: "Alto", - keepAR: "Mantener proporci\xF3n", - sbReady: "ML \u2022 listo", - sbTitle: "Media Layout: clic para reescanear", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 complemento activo", - posPresets: "Guardar posiciones de clones", - resetAll: "Restablecer todo", - saveLayout: "Guardar preset", - layoutMax: "Se alcanz\xF3 el n\xFAmero m\xE1ximo de presets", - saved: "Guardado", - layoutN: "Preset {n}", - renameTitle: "Introduce un nombre", - addNote: "A\xF1adir tabla/nota", - copyLayout: "Copiar datos de la p\xE1gina", - pasteLayout: "Pegar datos de la p\xE1gina", - deleteNote: "Eliminar tabla/nota", - detach: "Desanclar", - bgColor: "Cambiar color de fondo", - duplicate: "Duplicar", - reset: "Restablecer", - deleteClone: "Eliminar", - lock: "Bloquear", - unlock: "Desbloquear", - localOnlyCheckboxLabel: "Solo para este archivo", - makeInvisible: "Hacer invisible", - invalidLayoutData: "Datos de p\xE1gina no v\xE1lidos en el portapapeles.", - layoutPasted: "Se pegaron los datos y presets de la p\xE1gina.", - pasteLayoutFailed: "No se pudieron pegar los datos de la p\xE1gina desde el portapapeles.", - layoutCopied: "Datos de la p\xE1gina copiados al portapapeles.", - makeVisible: "Hacer visible", - bringLayerUp: "Subir capa", - sendLayerDown: "Bajar capa", - showHandlesOnHoverLabel: "Mostrar tiradores al pasar el cursor", - hideDetachButtonLabel: "Ocultar el bot\xF3n \xABDesanclar\xBB", - modalAnimationLabel: "Animaci\xF3n de la ventana modal", - cloneRestoreDelayLabel: "Retraso en la aparici\xF3n de los clones", - noteRestoreDelayLabel: "Retraso en la aparici\xF3n de los cuadros de notas", - hideDelayLabel: "Retraso al ocultar el panel", - changeTextColor: "Cambiar color del texto", - cloneNearFile: "Clonar junto al archivo", - addStroke: "A\xF1adir contorno", - applyToHeader: "Aplicar al encabezado", - addLink: "A\xF1adir enlace", - deleteSelected: "Eliminar seleccionados ({n})", - urlMediaClickToSave: "Haga clic para guardar en vault", - urlMediaDownloadAndReplace: "Descargar y reemplazar", - urlMediaDownloading: "Descargando imagen...", - urlMediaDownloadError: "Error de descarga: {status}", - urlMediaFileSavedLinkFailed: "Archivo guardado, pero no se pudo actualizar el enlace", - urlMediaSuccess: "Imagen guardada y enlace actualizado", - urlMediaFileSavedPatternNotFound: "Archivo guardado, pero enlace no encontrado en el texto", - urlMediaError: "Error: {error}", - urlMediaUnknownError: "error desconocido" - }, - fr: { - cloneSettingsTitle: "Personnalisation des clones", - removeBg: "Supprimer l\u2019arri\xE8re-plan", - animation: "Animation", - animationPopup: "Popup", - animationFade: "Fondu", - backgroundOpacity: "Opacit\xE9 du fond", - controlsOpacity: "Opacit\xE9 du panneau", - showAudioHandles: "Afficher les poign\xE9es audio", - presets: "Tailles", - fit: "Ajuster au conteneur", - natural: "Taille naturelle", - add: "Ajouter\u2026", - addPresetTitle: "Ajouter un preset de largeur \u2039px\u203A", - placeholder: "ex. : 820", - ok: "OK", - cancel: "Annuler", - invalid: "Valeur invalide", - maxReached: "Nombre maximal de presets atteint", - added: "Preset {n} ajout\xE9", - removed: "Preset {n} supprim\xE9", - openLocation: "Ouvrir l\u2019emplacement du fichier", - fileNotFound: "Fichier introuvable dans le vault", - shellDenied: "Acc\xE8s au shell refus\xE9", - localOnly: "Disponible uniquement pour les vaults locaux", - restorePos: "Restaurer la position d\u2019origine", - setSize: "D\xE9finir la taille\u2026", - width: "Largeur", - height: "Hauteur", - keepAR: "Conserver le ratio", - sbReady: "ML \u2022 pr\xEAt", - sbTitle: "Media Layout : cliquer pour rescanner", - cmdPing: "Media Layout : ping", - pingOk: "ML : OK \u2014 plugin actif", - posPresets: "Enregistrer les positions des clones", - resetAll: "Tout r\xE9initialiser", - saveLayout: "Enregistrer le preset", - layoutMax: "Nombre maximal de presets atteint", - saved: "Enregistr\xE9", - layoutN: "Preset {n}", - renameTitle: "Entrez un nom", - addNote: "Ajouter un encart", - copyLayout: "Copier les donn\xE9es de la page", - pasteLayout: "Coller les donn\xE9es de la page", - deleteNote: "Supprimer l\u2019encart", - detach: "D\xE9tacher", - bgColor: "Changer la couleur du fond", - duplicate: "Dupliquer", - reset: "R\xE9initialiser", - deleteClone: "Supprimer le clone", - lock: "Verrouiller", - unlock: "D\xE9verrouiller", - localOnlyCheckboxLabel: "Uniquement pour ce fichier", - makeInvisible: "Rendre invisible", - invalidLayoutData: "Donn\xE9es de page invalides dans le presse-papiers.", - layoutPasted: "Donn\xE9es et presets de page coll\xE9s.", - pasteLayoutFailed: "Impossible de coller les donn\xE9es de la page depuis le presse-papiers.", - layoutCopied: "Donn\xE9es de page copi\xE9es dans le presse-papiers.", - makeVisible: "Rendre visible", - bringLayerUp: "Monter le calque", - sendLayerDown: "Descendre le calque", - showHandlesOnHoverLabel: "Afficher les poign\xE9es au survol", - hideDetachButtonLabel: "Masquer le bouton \xAB D\xE9tacher \xBB", - modalAnimationLabel: "Animation des fen\xEAtres modales", - cloneRestoreDelayLabel: "D\xE9lai d\u2019apparition des clones", - noteRestoreDelayLabel: "D\xE9lai d\u2019apparition des bo\xEEtes de notes", - hideDelayLabel: "D\xE9lai avant masquage du panneau", - changeTextColor: "Changer la couleur du texte", - cloneNearFile: "Cloner \xE0 c\xF4t\xE9 du fichier", - addStroke: "Ajouter un contour", - applyToHeader: "Appliquer \xE0 l'en-t\xEAte", - addLink: "Ajouter un lien", - deleteSelected: "Supprimer les \xE9l\xE9ments s\xE9lectionn\xE9s ({n})", - urlMediaClickToSave: "Cliquez pour enregistrer dans le vault", - urlMediaDownloadAndReplace: "T\xE9l\xE9charger et remplacer", - urlMediaDownloading: "T\xE9l\xE9chargement de l'image...", - urlMediaDownloadError: "Erreur de t\xE9l\xE9chargement: {status}", - urlMediaFileSavedLinkFailed: "Fichier enregistr\xE9, mais \xE9chec de mise \xE0 jour du lien", - urlMediaSuccess: "Image enregistr\xE9e et lien mis \xE0 jour", - urlMediaFileSavedPatternNotFound: "Fichier enregistr\xE9, mais lien introuvable dans le texte", - urlMediaError: "Erreur: {error}", - urlMediaUnknownError: "erreur inconnue" - }, - it: { - cloneSettingsTitle: "Personalizzazione cloni", - removeBg: "Rimuovi sfondo", - animation: "Animazione", - animationPopup: "Popup", - animationFade: "Dissolvenza", - backgroundOpacity: "Opacit\xE0 sfondo", - controlsOpacity: "Opacit\xE0 pannello", - showAudioHandles: "Mostra maniglie audio", - presets: "Dimensioni", - fit: "Adatta al contenitore", - natural: "Dimensione naturale", - add: "Aggiungi\u2026", - addPresetTitle: "Aggiungi preset larghezza \u2039px\u203A", - placeholder: "es. 820", - ok: "OK", - cancel: "Annulla", - invalid: "Valore non valido", - maxReached: "Numero massimo di preset raggiunto", - added: "Preset {n} aggiunto", - removed: "Preset {n} rimosso", - openLocation: "Apri posizione del file", - fileNotFound: "File non trovato nel vault", - shellDenied: "Nessun accesso alla shell", - localOnly: "Disponibile solo per vault locali", - restorePos: "Ripristina posizione iniziale", - setSize: "Imposta dimensione\u2026", - width: "Larghezza", - height: "Altezza", - keepAR: "Mantieni proporzioni", - sbReady: "ML \u2022 pronto", - sbTitle: "Media Layout: clic per ripetere la scansione", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 plugin attivo", - posPresets: "Salva posizioni dei cloni", - resetAll: "Reimposta tutto", - saveLayout: "Salva preset", - layoutMax: "Numero massimo di preset raggiunto", - saved: "Salvato", - layoutN: "Preset {n}", - renameTitle: "Inserisci un nome", - addNote: "Aggiungi riquadro", - copyLayout: "Copia dati della pagina", - pasteLayout: "Incolla dati della pagina", - deleteNote: "Elimina riquadro", - detach: "Stacca", - bgColor: "Cambia colore sfondo", - duplicate: "Duplica", - reset: "Reset", - deleteClone: "Elimina", - lock: "Blocca", - unlock: "Sblocca", - localOnlyCheckboxLabel: "Solo per questo file", - makeInvisible: "Rendi invisibile", - invalidLayoutData: "Dati pagina non validi negli appunti.", - layoutPasted: "Dati e preset della pagina incollati.", - pasteLayoutFailed: "Impossibile incollare i dati della pagina dagli appunti.", - layoutCopied: "Dati della pagina copiati negli appunti.", - makeVisible: "Rendi visibile", - bringLayerUp: "Porta livello in alto", - sendLayerDown: "Manda livello in basso", - showHandlesOnHoverLabel: "Mostra le maniglie al passaggio del mouse", - hideDetachButtonLabel: "Nascondi il pulsante \xABSgancia\xBB", - modalAnimationLabel: "Animazione delle finestre modali", - cloneRestoreDelayLabel: "Ritardo nella comparsa dei cloni", - noteRestoreDelayLabel: "Ritardo nella comparsa dei riquadri nota", - hideDelayLabel: "Ritardo nel nascondere il pannello", - changeTextColor: "Cambia colore del testo", - cloneNearFile: "Clona accanto al file", - addStroke: "Aggiungi contorno", - applyToHeader: "Applica all'intestazione", - addLink: "Aggiungi link", - deleteSelected: "Elimina selezionati ({n})", - urlMediaClickToSave: "Clicca per salvare nel vault", - urlMediaDownloadAndReplace: "Scarica e sostituisci", - urlMediaDownloading: "Download immagine...", - urlMediaDownloadError: "Errore download: {status}", - urlMediaFileSavedLinkFailed: "File salvato, ma aggiornamento link fallito", - urlMediaSuccess: "Immagine salvata e link aggiornato", - urlMediaFileSavedPatternNotFound: "File salvato, ma link non trovato nel testo", - urlMediaError: "Errore: {error}", - urlMediaUnknownError: "errore sconosciuto" - }, - be: { - cloneSettingsTitle: "\u041D\u0430\u043B\u0430\u0434\u043A\u0430 \u043A\u043B\u043E\u043D\u0430\u045E", - removeBg: "\u0412\u044B\u0434\u0430\u043B\u0456\u0446\u044C \u0444\u043E\u043D", - animation: "\u0410\u043D\u0456\u043C\u0430\u0446\u044B\u044F", - animationPopup: "\u041F\u0430\u043F\u0430\u043F", - animationFade: "\u0417\u2019\u044F\u045E\u043B\u0435\u043D\u043D\u0435", - backgroundOpacity: "\u041F\u0440\u0430\u0437\u0440\u044B\u0441\u0442\u0430\u0441\u0446\u044C \u0444\u043E\u043D\u0443", - controlsOpacity: "\u041F\u0440\u0430\u0437\u0440\u044B\u0441\u0442\u0430\u0441\u0446\u044C \u043F\u0430\u043D\u044D\u043B\u0456", - showAudioHandles: "\u041F\u0430\u043A\u0430\u0437\u0432\u0430\u0446\u044C \u0440\u0443\u0447\u043A\u0456 \u0430\u045E\u0434\u044B\u0451", - presets: "\u041F\u0430\u043C\u0435\u0440\u044B", - fit: "\u041F\u0430\u0434\u0430\u0433\u043D\u0430\u0446\u044C \u0434\u0430 \u043A\u0430\u043D\u0442\u044D\u0439\u043D\u0435\u0440\u0430", - natural: "\u041D\u0430\u0442\u0443\u0440\u0430\u043B\u044C\u043D\u044B \u043F\u0430\u043C\u0435\u0440", - add: "\u0414\u0430\u0434\u0430\u0446\u044C\u2026", - addPresetTitle: "\u0414\u0430\u0434\u0430\u0446\u044C \u043F\u0440\u044D\u0441\u0435\u0442 \u0448\u044B\u0440\u044B\u043D\u0456 \u2039px\u203A", - placeholder: "\u043D\u0430\u043F\u0440\u044B\u043A\u043B\u0430\u0434: 820", - ok: "OK", - cancel: "\u0421\u043A\u0430\u0441\u0430\u0432\u0430\u0446\u044C", - invalid: "\u041D\u044F\u043F\u0440\u0430\u0432\u0456\u043B\u044C\u043D\u0430\u0435 \u0437\u043D\u0430\u0447\u044D\u043D\u043D\u0435", - maxReached: "\u0414\u0430\u0441\u0442\u0456\u0433\u043D\u0443\u0442\u044B \u043C\u0430\u043A\u0441\u0456\u043C\u0430\u043B\u044C\u043D\u044B \u043B\u0456\u043A \u043F\u0440\u044D\u0441\u0435\u0442\u0430\u045E", - added: "\u0414\u0430\u0434\u0430\u0434\u0437\u0435\u043D\u044B \u043F\u0440\u044D\u0441\u0435\u0442 {n}", - removed: "\u0412\u044B\u0434\u0430\u043B\u0435\u043D\u044B \u043F\u0440\u044D\u0441\u0435\u0442 {n}", - openLocation: "\u0410\u0434\u043A\u0440\u044B\u0446\u044C \u0440\u0430\u0437\u043C\u044F\u0448\u0447\u044D\u043D\u043D\u0435 \u0444\u0430\u0439\u043B\u0430", - fileNotFound: "\u0424\u0430\u0439\u043B \u0443 \u0441\u0445\u043E\u0432\u0456\u0448\u0447\u044B (vault) \u043D\u0435 \u0437\u043D\u043E\u0439\u0434\u0437\u0435\u043D\u044B", - shellDenied: "\u041D\u044F\u043C\u0430 \u0434\u043E\u0441\u0442\u0443\u043F\u0443 \u0434\u0430 shell", - localOnly: "\u0414\u0430\u0441\u0442\u0443\u043F\u043D\u0430 \u0442\u043E\u043B\u044C\u043A\u0456 \u0434\u043B\u044F \u043B\u0430\u043A\u0430\u043B\u044C\u043D\u044B\u0445 \u0441\u0445\u043E\u0432\u0456\u0448\u0447\u0430\u045E", - restorePos: "\u0412\u044F\u0440\u043D\u0443\u0446\u044C \u043F\u0430\u0447\u0430\u0442\u043A\u043E\u0432\u0430\u0435 \u0441\u0442\u0430\u043D\u043E\u0432\u0456\u0448\u0447\u0430", - setSize: "\u0417\u0430\u0434\u0430\u0446\u044C \u043F\u0430\u043C\u0435\u0440\u2026", - width: "\u0428\u044B\u0440\u044B\u043D\u044F", - height: "\u0412\u044B\u0448\u044B\u043D\u044F", - keepAR: "\u0417\u0430\u0445\u043E\u045E\u0432\u0430\u0446\u044C \u043F\u0440\u0430\u043F\u043E\u0440\u0446\u044B\u0456", - sbReady: "ML \u2022 \u0433\u0430\u0442\u043E\u0432\u0430", - sbTitle: "Media Layout: \u043A\u043B\u0456\u043A \u2014 \u043F\u0435\u0440\u0430\u0441\u043A\u0430\u043D\u0430\u0432\u0430\u0446\u044C", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 \u0443\u0431\u0443\u0434\u043E\u0432\u0430 \u0430\u043A\u0442\u044B\u045E\u043D\u0430\u044F", - posPresets: "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044C \u0441\u0442\u0430\u043D\u043E\u0432\u0456\u0448\u0447\u044B \u043A\u043B\u043E\u043D\u0430\u045E", - resetAll: "\u0421\u043A\u0456\u0434 \u0443\u0441\u0456\u0445 \u043D\u0430\u043B\u0430\u0434", - saveLayout: "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044C \u043F\u0440\u044D\u0441\u0435\u0442", - layoutMax: "\u0414\u0430\u0441\u0442\u0456\u0433\u043D\u0443\u0442\u044B \u043C\u0430\u043A\u0441\u0456\u043C\u0430\u043B\u044C\u043D\u044B \u043B\u0456\u043A \u043F\u0440\u044D\u0441\u0435\u0442\u0430\u045E", - saved: "\u0417\u0430\u0445\u0430\u0432\u0430\u043D\u0430", - layoutN: "\u041F\u0440\u044D\u0441\u0435\u0442 {n}", - renameTitle: "\u0423\u0432\u044F\u0434\u0437\u0456\u0446\u0435 \u043D\u0430\u0437\u0432\u0443", - addNote: "\u0414\u0430\u0434\u0430\u0446\u044C \u0442\u0430\u0431\u043B\u0456\u0447\u043A\u0443", - copyLayout: "\u0421\u043A\u0430\u043F\u0456\u0440\u0430\u0432\u0430\u0446\u044C \u0434\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456", - pasteLayout: "\u0423\u0441\u0442\u0430\u0432\u0456\u0446\u044C \u0434\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456", - deleteNote: "\u0412\u044B\u0434\u0430\u043B\u0456\u0446\u044C \u0442\u0430\u0431\u043B\u0456\u0447\u043A\u0443", - detach: "\u0410\u0434\u0447\u0430\u043F\u0456\u0446\u044C", - bgColor: "\u0417\u043C\u044F\u043D\u0456\u0446\u044C \u043A\u043E\u043B\u0435\u0440 \u0444\u043E\u043D\u0443", - duplicate: "\u0414\u0443\u0431\u043B\u044F\u0432\u0430\u0446\u044C", - reset: "\u0421\u043A\u0456\u0434", - deleteClone: "\u0412\u044B\u0434\u0430\u043B\u0456\u0446\u044C", - lock: "\u0417\u0430\u0444\u0456\u043A\u0441\u0430\u0432\u0430\u0446\u044C", - unlock: "\u0420\u0430\u0437\u0431\u043B\u0430\u043A\u0456\u0440\u0430\u0432\u0430\u0446\u044C", - localOnlyCheckboxLabel: "\u0422\u043E\u043B\u044C\u043A\u0456 \u0434\u043B\u044F \u0433\u044D\u0442\u0430\u0433\u0430 \u0444\u0430\u0439\u043B\u0430", - makeInvisible: "\u0417\u0440\u0430\u0431\u0456\u0446\u044C \u043D\u044F\u0431\u0430\u0447\u043D\u044B\u043C", - invalidLayoutData: "\u041D\u044F\u0441\u043B\u0443\u0448\u043D\u044B\u044F \u0434\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456 \u045E \u0431\u0443\u0444\u0435\u0440\u044B \u0430\u0431\u043C\u0435\u043D\u0443.", - layoutPasted: "\u0414\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456 \u0456 \u043F\u0440\u044D\u0441\u0435\u0442\u044B \u045E\u0441\u0442\u0430\u045E\u043B\u0435\u043D\u044B.", - pasteLayoutFailed: "\u041D\u0435 \u045E\u0434\u0430\u043B\u043E\u0441\u044F \u045E\u0441\u0442\u0430\u0432\u0456\u0446\u044C \u0434\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456 \u0437 \u0431\u0443\u0444\u0435\u0440\u0430 \u0430\u0431\u043C\u0435\u043D\u0443.", - layoutCopied: "\u0414\u0430\u0434\u0437\u0435\u043D\u044B\u044F \u0441\u0442\u0430\u0440\u043E\u043D\u043A\u0456 \u0441\u043A\u0430\u043F\u0456\u0440\u0430\u0432\u0430\u043D\u044B \u045E \u0431\u0443\u0444\u0435\u0440 \u0430\u0431\u043C\u0435\u043D\u0443.", - makeVisible: "\u0417\u0440\u0430\u0431\u0456\u0446\u044C \u0431\u0430\u0447\u043D\u044B\u043C", - bringLayerUp: "\u041F\u0430\u0434\u043D\u044F\u0446\u044C \u0441\u043B\u043E\u0439", - sendLayerDown: "\u0410\u043F\u0443\u0441\u0446\u0456\u0446\u044C \u0441\u043B\u043E\u0439", - showHandlesOnHoverLabel: "\u041F\u0430\u043A\u0430\u0437\u0432\u0430\u0446\u044C \u0440\u0443\u0447\u043A\u0456 \u043F\u0440\u044B \u043D\u0430\u0432\u044F\u0434\u0437\u0435\u043D\u043D\u0456 \u043A\u0443\u0440\u0441\u043E\u0440\u0430", - hideDetachButtonLabel: "\u0421\u0445\u0430\u0432\u0430\u0446\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \xAB\u0430\u0434\u043C\u0430\u0446\u0430\u0432\u0430\u0446\u044C\xBB", - modalAnimationLabel: "\u0410\u043D\u0456\u043C\u0430\u0446\u044B\u044F \u043C\u0430\u0434\u0430\u043B\u044C\u043D\u044B\u0445 \u0432\u043E\u043A\u043D\u0430\u045E", - cloneRestoreDelayLabel: "\u0417\u0430\u0442\u0440\u044B\u043C\u043A\u0430 \u0437\u2019\u044F\u045E\u043B\u0435\u043D\u043D\u044F \u043A\u043B\u043E\u043D\u0430\u045E", - noteRestoreDelayLabel: "\u0417\u0430\u0442\u0440\u044B\u043C\u043A\u0430 \u0437\u2019\u044F\u045E\u043B\u0435\u043D\u043D\u044F \u0431\u043B\u043E\u043A\u0430\u045E \u043D\u0430\u0442\u0430\u0442\u0430\u043A", - hideDelayLabel: "\u0417\u0430\u0442\u0440\u044B\u043C\u043A\u0430 \u0441\u043A\u0440\u044B\u0446\u0446\u044F \u043F\u0430\u043D\u044D\u043B\u0456", - changeTextColor: "\u0417\u043C\u044F\u043D\u0456\u0446\u044C \u043A\u043E\u043B\u0435\u0440 \u0442\u044D\u043A\u0441\u0442\u0443", - cloneNearFile: "\u041A\u043B\u0430\u043D\u0456\u0440\u0430\u0432\u0430\u0446\u044C \u043F\u043E\u0431\u0430\u0447 \u0437 \u0444\u0430\u0439\u043B\u0430\u043C", - addStroke: "\u0414\u0430\u0434\u0430\u0446\u044C \u0430\u0431\u0432\u043E\u0434\u043A\u0443", - applyToHeader: "\u041F\u0440\u044B\u043C\u044F\u043D\u0456\u0446\u044C \u0434\u043B\u044F \u0448\u0430\u043F\u043A\u0456", - addLink: "\u0414\u0430\u0434\u0430\u0446\u044C \u0441\u043F\u0430\u0441\u044B\u043B\u043A\u0443", - deleteSelected: "\u0412\u044B\u0434\u0430\u043B\u0456\u0446\u044C \u0432\u044B\u0431\u0440\u0430\u043D\u044B\u044F ({n})", - urlMediaClickToSave: "\u041A\u043B\u0456\u043A \u0434\u043B\u044F \u0437\u0430\u0445\u0430\u0432\u0430\u043D\u043D\u044F \u045E vault", - urlMediaDownloadAndReplace: "\u0421\u043F\u0430\u043C\u043F\u0430\u0432\u0430\u0446\u044C \u0456 \u0437\u0430\u043C\u044F\u043D\u0456\u0446\u044C", - urlMediaDownloading: "\u0421\u043F\u0430\u043C\u043F\u043E\u045E\u0432\u0430\u043D\u043D\u0435 \u0432\u044B\u044F\u0432\u044B...", - urlMediaDownloadError: "\u041F\u0430\u043C\u044B\u043B\u043A\u0430 \u0441\u043F\u0430\u043C\u043F\u043E\u045E\u0432\u0430\u043D\u043D\u044F: {status}", - urlMediaFileSavedLinkFailed: "\u0424\u0430\u0439\u043B \u0437\u0430\u0445\u0430\u0432\u0430\u043D\u044B, \u0430\u043B\u0435 \u043D\u0435 \u0430\u0442\u0440\u044B\u043C\u0430\u043B\u0430\u0441\u044F \u0430\u0431\u043D\u0430\u0432\u0456\u0446\u044C \u0441\u043F\u0430\u0441\u044B\u043B\u043A\u0443", - urlMediaSuccess: "\u0412\u044B\u044F\u0432\u0430 \u0437\u0430\u0445\u0430\u0432\u0430\u043D\u0430 \u0456 \u0441\u043F\u0430\u0441\u044B\u043B\u043A\u0430 \u0430\u0431\u043D\u043E\u045E\u043B\u0435\u043D\u0430", - urlMediaFileSavedPatternNotFound: "\u0424\u0430\u0439\u043B \u0437\u0430\u0445\u0430\u0432\u0430\u043D\u044B, \u0430\u043B\u0435 \u0441\u043F\u0430\u0441\u044B\u043B\u043A\u0430 \u043D\u0435 \u0437\u043D\u043E\u0439\u0434\u0437\u0435\u043D\u0430 \u045E \u0442\u044D\u043A\u0441\u0446\u0435", - urlMediaError: "\u041F\u0430\u043C\u044B\u043B\u043A\u0430: {error}", - urlMediaUnknownError: "\u043D\u0435\u0432\u044F\u0434\u043E\u043C\u0430\u044F \u043F\u0430\u043C\u044B\u043B\u043A\u0430" - }, - uk: { - cloneSettingsTitle: "\u041D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F \u043A\u043B\u043E\u043D\u0456\u0432", - removeBg: "\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438 \u0442\u043B\u043E", - animation: "\u0410\u043D\u0456\u043C\u0430\u0446\u0456\u044F", - animationPopup: "\u0421\u043F\u043B\u0438\u0432\u0430\u044E\u0447\u0430", - animationFade: "\u041F\u043E\u0441\u0442\u0443\u043F\u043E\u0432\u0430", - backgroundOpacity: "\u041F\u0440\u043E\u0437\u043E\u0440\u0456\u0441\u0442\u044C \u0444\u043E\u043D\u0443", - controlsOpacity: "\u041F\u0440\u043E\u0437\u043E\u0440\u0456\u0441\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u0456", - showAudioHandles: "\u041F\u043E\u043A\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0440\u0443\u0447\u043A\u0438 \u0443 \u0430\u0443\u0434\u0456\u043E", - presets: "\u0420\u043E\u0437\u043C\u0456\u0440\u0438", - fit: "\u041F\u0456\u0434\u0456\u0433\u043D\u0430\u0442\u0438 \u043F\u0456\u0434 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440", - natural: "\u041F\u0440\u0438\u0440\u043E\u0434\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440", - add: "\u0414\u043E\u0434\u0430\u0442\u0438\u2026", - addPresetTitle: "\u0414\u043E\u0434\u0430\u0442\u0438 \u043F\u0440\u0435\u0441\u0435\u0442 \u0448\u0438\u0440\u0438\u043D\u0438 \u2039px\u203A", - placeholder: "\u043D\u0430\u043F\u0440\u0438\u043A\u043B\u0430\u0434: 820", - ok: "OK", - cancel: "\u0421\u043A\u0430\u0441\u0443\u0432\u0430\u0442\u0438", - invalid: "\u041D\u0435\u0432\u0456\u0440\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F", - maxReached: "\u0414\u043E\u0441\u044F\u0433\u043D\u0443\u0442\u043E \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C \u043F\u0440\u0435\u0441\u0435\u0442\u0456\u0432", - added: "\u0414\u043E\u0434\u0430\u043D\u043E \u043F\u0440\u0435\u0441\u0435\u0442 {n}", - removed: "\u0412\u0438\u043B\u0443\u0447\u0435\u043D\u043E \u043F\u0440\u0435\u0441\u0435\u0442 {n}", - openLocation: "\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u0440\u043E\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043D\u043D\u044F \u0444\u0430\u0439\u043B\u0443", - fileNotFound: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0443 \u0441\u0445\u043E\u0432\u0438\u0449\u0456 (vault)", - shellDenied: "\u041D\u0435\u043C\u0430\u0454 \u0434\u043E\u0441\u0442\u0443\u043F\u0443 \u0434\u043E shell", - localOnly: "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u043B\u0438\u0448\u0435 \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0438\u0445 \u0441\u0445\u043E\u0432\u0438\u0449", - restorePos: "\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u0438 \u043F\u043E\u0447\u0430\u0442\u043A\u043E\u0432\u0435 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u043D\u044F", - setSize: "\u0417\u0430\u0434\u0430\u0442\u0438 \u0440\u043E\u0437\u043C\u0456\u0440\u2026", - width: "\u0428\u0438\u0440\u0438\u043D\u0430", - height: "\u0412\u0438\u0441\u043E\u0442\u0430", - keepAR: "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u043F\u0440\u043E\u043F\u043E\u0440\u0446\u0456\u0457", - sbReady: "ML \u2022 \u0433\u043E\u0442\u043E\u0432\u043E", - sbTitle: "Media Layout: \u043A\u043B\u0456\u043A \u2014 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u043D\u0443\u0432\u0430\u0442\u0438", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 \u043F\u043B\u0430\u0433\u0456\u043D \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439", - posPresets: "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u043D\u044F \u043A\u043B\u043E\u043D\u0456\u0432", - resetAll: "\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u0432\u0441\u0435", - saveLayout: "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043F\u0440\u0435\u0441\u0435\u0442", - layoutMax: "\u0414\u043E\u0441\u044F\u0433\u043D\u0443\u0442\u043E \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C \u043F\u0440\u0435\u0441\u0435\u0442\u0456\u0432", - saved: "\u0417\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043E", - layoutN: "\u041F\u0440\u0435\u0441\u0435\u0442 {n}", - renameTitle: "\u0412\u0432\u0435\u0434\u0456\u0442\u044C \u043D\u0430\u0437\u0432\u0443", - addNote: "\u0414\u043E\u0434\u0430\u0442\u0438 \u0442\u0430\u0431\u043B\u0438\u0447\u043A\u0443", - copyLayout: "\u041A\u043E\u043F\u0456\u044E\u0432\u0430\u0442\u0438 \u0434\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438", - pasteLayout: "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0434\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438", - deleteNote: "\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438 \u0442\u0430\u0431\u043B\u0438\u0447\u043A\u0443", - detach: "\u0412\u0456\u0434\u0447\u0435\u043F\u0438\u0442\u0438", - bgColor: "\u0417\u043C\u0456\u043D\u0438\u0442\u0438 \u043A\u043E\u043B\u0456\u0440 \u0444\u043E\u043D\u0443", - duplicate: "\u0414\u0443\u0431\u043B\u044E\u0432\u0430\u0442\u0438", - reset: "\u0421\u043A\u0438\u043D\u0443\u0442\u0438", - deleteClone: "\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438", - lock: "\u0417\u0430\u043A\u0440\u0456\u043F\u0438\u0442\u0438", - unlock: "\u0420\u043E\u0437\u0431\u043B\u043E\u043A\u0443\u0432\u0430\u0442\u0438", - localOnlyCheckboxLabel: "\u041B\u0438\u0448\u0435 \u0434\u043B\u044F \u0446\u044C\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0443", - makeInvisible: "\u0417\u0440\u043E\u0431\u0438\u0442\u0438 \u043D\u0435\u0432\u0438\u0434\u0438\u043C\u0438\u043C", - invalidLayoutData: "\u041D\u0435\u043A\u043E\u0440\u0435\u043A\u0442\u043D\u0456 \u0434\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438 \u0432 \u0431\u0443\u0444\u0435\u0440\u0456 \u043E\u0431\u043C\u0456\u043D\u0443.", - layoutPasted: "\u0414\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438 \u0442\u0430 \u043F\u0440\u0435\u0441\u0435\u0442\u0438 \u0432\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E.", - pasteLayoutFailed: "\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0434\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438 \u0437 \u0431\u0443\u0444\u0435\u0440\u0430 \u043E\u0431\u043C\u0456\u043D\u0443.", - layoutCopied: "\u0414\u0430\u043D\u0456 \u0441\u0442\u043E\u0440\u0456\u043D\u043A\u0438 \u0441\u043A\u043E\u043F\u0456\u0439\u043E\u0432\u0430\u043D\u043E \u0432 \u0431\u0443\u0444\u0435\u0440 \u043E\u0431\u043C\u0456\u043D\u0443.", - makeVisible: "\u0417\u0440\u043E\u0431\u0438\u0442\u0438 \u0432\u0438\u0434\u0438\u043C\u0438\u043C", - bringLayerUp: "\u041F\u0456\u0434\u043D\u044F\u0442\u0438 \u0448\u0430\u0440", - sendLayerDown: "\u041E\u043F\u0443\u0441\u0442\u0438\u0442\u0438 \u0448\u0430\u0440", - showHandlesOnHoverLabel: "\u041F\u043E\u043A\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0440\u0443\u0447\u043A\u0438 \u043F\u0456\u0434 \u0447\u0430\u0441 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u043D\u044F", - hideDetachButtonLabel: "\u041F\u0440\u0438\u0445\u043E\u0432\u0430\u0442\u0438 \u043A\u043D\u043E\u043F\u043A\u0443 \xAB\u0432\u0456\u0434\u043A\u0440\u0456\u043F\u0438\u0442\u0438\xBB", - modalAnimationLabel: "\u0410\u043D\u0456\u043C\u0430\u0446\u0456\u044F \u043C\u043E\u0434\u0430\u043B\u044C\u043D\u0438\u0445 \u0432\u0456\u043A\u043E\u043D", - cloneRestoreDelayLabel: "\u0417\u0430\u0442\u0440\u0438\u043C\u043A\u0430 \u043F\u043E\u044F\u0432\u0438 \u043A\u043B\u043E\u043D\u0456\u0432", - noteRestoreDelayLabel: "\u0417\u0430\u0442\u0440\u0438\u043C\u043A\u0430 \u043F\u043E\u044F\u0432\u0438 \u0431\u043B\u043E\u043A\u0456\u0432 \u043D\u043E\u0442\u0430\u0442\u043E\u043A", - hideDelayLabel: "\u0417\u0430\u0442\u0440\u0438\u043C\u043A\u0430 \u043F\u0440\u0438\u0445\u043E\u0432\u0443\u0432\u0430\u043D\u043D\u044F \u043F\u0430\u043D\u0435\u043B\u0456", - changeTextColor: "\u0417\u043C\u0456\u043D\u0438\u0442\u0438 \u043A\u043E\u043B\u0456\u0440 \u0442\u0435\u043A\u0441\u0442\u0443", - cloneNearFile: "\u041A\u043B\u043E\u043D\u0443\u0432\u0430\u0442\u0438 \u043F\u043E\u0440\u0443\u0447 \u0456\u0437 \u0444\u0430\u0439\u043B\u043E\u043C", - addStroke: "\u0414\u043E\u0434\u0430\u0442\u0438 \u043E\u0431\u0432\u0435\u0434\u0435\u043D\u043D\u044F", - applyToHeader: "\u0417\u0430\u0441\u0442\u043E\u0441\u0443\u0432\u0430\u0442\u0438 \u0434\u043E \u0448\u0430\u043F\u043A\u0438", - addLink: "\u0414\u043E\u0434\u0430\u0442\u0438 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F", - deleteSelected: "\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0440\u0430\u043D\u0456 ({n})", - urlMediaClickToSave: "\u041A\u043B\u0456\u043A \u0434\u043B\u044F \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043D\u044F \u0432 vault", - urlMediaDownloadAndReplace: "\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0456 \u0437\u0430\u043C\u0456\u043D\u0438\u0442\u0438", - urlMediaDownloading: "\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F...", - urlMediaDownloadError: "\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F: {status}", - urlMediaFileSavedLinkFailed: "\u0424\u0430\u0439\u043B \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043E, \u0430\u043B\u0435 \u043D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u043E\u043D\u043E\u0432\u0438\u0442\u0438 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F", - urlMediaSuccess: "\u0417\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043E \u0456 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043E\u043D\u043E\u0432\u043B\u0435\u043D\u043E", - urlMediaFileSavedPatternNotFound: "\u0424\u0430\u0439\u043B \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043E, \u0430\u043B\u0435 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0432 \u0442\u0435\u043A\u0441\u0442\u0456", - urlMediaError: "\u041F\u043E\u043C\u0438\u043B\u043A\u0430: {error}", - urlMediaUnknownError: "\u043D\u0435\u0432\u0456\u0434\u043E\u043C\u0430 \u043F\u043E\u043C\u0438\u043B\u043A\u0430" - }, - uz: { - cloneSettingsTitle: "Klon sozlamalari", - removeBg: "Fonni olib tashlash", - animation: "Animatsiya", - animationPopup: "Popup", - animationFade: "So\u2018nish", - backgroundOpacity: "Fon shaffofligi", - controlsOpacity: "Panel shaffofligi", - showAudioHandles: "Audio qo\u2018llarini ko\u2018rsatish", - presets: "O\u2018lchamlar", - fit: "Konteynerga moslashtir", - natural: "Tabiiy o\u2018lcham", - add: "Qo\u2018shish\u2026", - addPresetTitle: "Kenglik preseti qo\u2018shish \u2039px\u203A", - placeholder: "masalan: 820", - ok: "OK", - cancel: "Bekor qilish", - invalid: "Noto\u2018g\u2018ri qiymat", - maxReached: "Presetlar sonining chegarasi", - added: "Preset {n} qo\u2018shildi", - removed: "Preset {n} o\u2018chirildi", - openLocation: "Fayl joyini ochish", - fileNotFound: "Fayl vault ichida topilmadi", - shellDenied: "Shell\u2019ga ruxsat yo\u2018q", - localOnly: "Faqat lokal vaultlar uchun", - restorePos: "Boshlang\u2018ich joyni tiklash", - setSize: "O\u2018lchamni o\u2018rnatish\u2026", - width: "Kenglik", - height: "Balandlik", - keepAR: "Nisbatni saqlash", - sbReady: "ML \u2022 tayyor", - sbTitle: "Media Layout: qayta skan qilish uchun bosing", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 plagin faol", - posPresets: "Klon joylashuvlarini saqlash", - resetAll: "Hammasini tiklash", - saveLayout: "Presetni saqlash", - layoutMax: "Presetlar sonining chegarasi", - saved: "Saqlandi", - layoutN: "Preset {n}", - renameTitle: "Nom kiriting", - addNote: "Yorliq qo\u2018shish", - copyLayout: "Sahifa ma\u2019lumotlarini nusxalash", - pasteLayout: "Sahifa ma\u2019lumotlarini joylash", - deleteNote: "Yorliqni o\u2018chirish", - detach: "Ajratish", - bgColor: "Fon rangini o\u2018zgartirish", - duplicate: "Nusxa olish", - reset: "Qayta o\u2018rnatish", - deleteClone: "Klonni o\u2018chirish", - lock: "Qulflash", - unlock: "Qulfni ochish", - localOnlyCheckboxLabel: "Faqat ushbu fayl uchun", - makeInvisible: "Ko\u2018rinmas qilish", - invalidLayoutData: "Buferdagi sahifa ma\u2019lumotlari noto\u2018g\u2018ri.", - layoutPasted: "Sahifa ma\u2019lumotlari va presetlar joylandi.", - pasteLayoutFailed: "Sahifa ma\u2019lumotlarini buferdan joylab bo\u2018lmadi.", - layoutCopied: "Sahifa ma\u2019lumotlari buferga nusxalandi.", - makeVisible: "Ko\u2018rinadigan qilish", - bringLayerUp: "Qatlamni yuqoriga", - sendLayerDown: "Qatlamni pastga", - showHandlesOnHoverLabel: "Kursorni olib o\u2018tganda tutqichlarni ko\u2018rsatish", - hideDetachButtonLabel: "\u201CBiriktirishni yechish\u201D tugmasini yashirish", - modalAnimationLabel: "Modal oynalar animatsiyasi", - cloneRestoreDelayLabel: "Klonlarning paydo bo\u2018lish kechikishi", - noteRestoreDelayLabel: "Eslatma bloklarining paydo bo\u2018lish kechikishi", - hideDelayLabel: "Panelni yashirish kechikishi", - changeTextColor: "Matn rangini o'zgartirish", - cloneNearFile: "Fayl yoniga klon yaratish", - addStroke: "Chiziq qo'shish", - applyToHeader: "Sarlavhaga qo'llash", - addLink: "Havola qo\u02BBshish", - deleteSelected: "Tanlanganlarni o\u02BBchirish ({n})", - urlMediaClickToSave: "Vaultga saqlash uchun bosing", - urlMediaDownloadAndReplace: "Yuklab olish va almashtirish", - urlMediaDownloading: "Rasm yuklanmoqda...", - urlMediaDownloadError: "Yuklash xatosi: {status}", - urlMediaFileSavedLinkFailed: "Fayl saqlandi, lekin havolani yangilab bo'lmadi", - urlMediaSuccess: "Rasm saqlandi va havola yangilandi", - urlMediaFileSavedPatternNotFound: "Fayl saqlandi, lekin matnda havola topilmadi", - urlMediaError: "Xato: {error}", - urlMediaUnknownError: "noma'lum xato" - }, - vi: { - cloneSettingsTitle: "T\xF9y ch\u1EC9nh b\u1EA3n sao", - removeBg: "X\xF3a n\u1EC1n", - animation: "Hi\u1EC7u \u1EE9ng", - animationPopup: "Popup", - animationFade: "M\u1EDD d\u1EA7n", - backgroundOpacity: "\u0110\u1ED9 m\u1EDD n\u1EC1n", - controlsOpacity: "\u0110\u1ED9 m\u1EDD b\u1EA3ng \u0111i\u1EC1u khi\u1EC3n", - showAudioHandles: "Hi\u1EC7n tay n\u1EAFm audio", - presets: "K\xEDch th\u01B0\u1EDBc", - fit: "V\u1EEBa v\u1EDBi khung", - natural: "K\xEDch th\u01B0\u1EDBc g\u1ED1c", - add: "Th\xEAm\u2026", - addPresetTitle: "Th\xEAm preset chi\u1EC1u r\u1ED9ng \u2039px\u203A", - placeholder: "vd: 820", - ok: "OK", - cancel: "Hu\u1EF7", - invalid: "Gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7", - maxReached: "\u0110\xE3 \u0111\u1EA1t s\u1ED1 l\u01B0\u1EE3ng preset t\u1ED1i \u0111a", - added: "\u0110\xE3 th\xEAm preset {n}", - removed: "\u0110\xE3 xo\xE1 preset {n}", - openLocation: "M\u1EDF v\u1ECB tr\xED t\u1EC7p", - fileNotFound: "Kh\xF4ng t\xECm th\u1EA5y t\u1EC7p trong vault", - shellDenied: "Kh\xF4ng c\xF3 quy\u1EC1n shell", - localOnly: "Ch\u1EC9 d\xF9ng cho vault c\u1EE5c b\u1ED9", - restorePos: "Kh\xF4i ph\u1EE5c v\u1ECB tr\xED g\u1ED1c", - setSize: "\u0110\u1EB7t k\xEDch th\u01B0\u1EDBc\u2026", - width: "R\u1ED9ng", - height: "Cao", - keepAR: "Gi\u1EEF t\u1EC9 l\u1EC7", - sbReady: "ML \u2022 s\u1EB5n s\xE0ng", - sbTitle: "Media Layout: nh\u1EA5p \u0111\u1EC3 qu\xE9t l\u1EA1i", - cmdPing: "Media Layout: ping", - pingOk: "ML: OK \u2014 plugin ho\u1EA1t \u0111\u1ED9ng", - posPresets: "L\u01B0u v\u1ECB tr\xED c\u1EE7a c\xE1c b\u1EA3n sao", - resetAll: "\u0110\u1EB7t l\u1EA1i t\u1EA5t c\u1EA3", - saveLayout: "L\u01B0u preset", - layoutMax: "\u0110\xE3 \u0111\u1EA1t s\u1ED1 l\u01B0\u1EE3ng preset t\u1ED1i \u0111a", - saved: "\u0110\xE3 l\u01B0u", - layoutN: "Preset {n}", - renameTitle: "Nh\u1EADp t\xEAn", - addNote: "Th\xEAm h\u1ED9p ghi ch\xFA", - copyLayout: "Sao ch\xE9p d\u1EEF li\u1EC7u trang", - pasteLayout: "D\xE1n d\u1EEF li\u1EC7u trang", - deleteNote: "Xo\xE1 h\u1ED9p ghi ch\xFA", - detach: "T\xE1ch ra", - bgColor: "\u0110\u1ED5i m\xE0u n\u1EC1n", - duplicate: "Nh\xE2n b\u1EA3n", - reset: "\u0110\u1EB7t l\u1EA1i", - deleteClone: "Xo\xE1", - lock: "Kho\xE1", - unlock: "M\u1EDF kho\xE1", - localOnlyCheckboxLabel: "Ch\u1EC9 cho t\u1EC7p n\xE0y", - makeInvisible: "\u1EA8n \u0111i", - invalidLayoutData: "D\u1EEF li\u1EC7u trang trong clipboard kh\xF4ng h\u1EE3p l\u1EC7.", - layoutPasted: "\u0110\xE3 d\xE1n d\u1EEF li\u1EC7u v\xE0 preset c\u1EE7a trang.", - pasteLayoutFailed: "Kh\xF4ng th\u1EC3 d\xE1n d\u1EEF li\u1EC7u trang t\u1EEB clipboard.", - layoutCopied: "\u0110\xE3 sao ch\xE9p d\u1EEF li\u1EC7u trang v\xE0o clipboard.", - makeVisible: "Hi\u1EC7n l\xEAn", - bringLayerUp: "\u0110\u01B0a l\u1EDBp l\xEAn", - sendLayerDown: "\u0110\u01B0a l\u1EDBp xu\u1ED1ng", - showHandlesOnHoverLabel: "Hi\u1EC7n tay c\u1EA7m khi r\xEA chu\u1ED9t", - hideDetachButtonLabel: "\u1EA8n n\xFAt \u201CG\u1EE1 ghim\u201D", - modalAnimationLabel: "Hi\u1EC7u \u1EE9ng cho c\u1EEDa s\u1ED5 modal", - cloneRestoreDelayLabel: "\u0110\u1ED9 tr\u1EC5 xu\u1EA5t hi\u1EC7n c\u1EE7a b\u1EA3n sao", - noteRestoreDelayLabel: "\u0110\u1ED9 tr\u1EC5 xu\u1EA5t hi\u1EC7n c\u1EE7a h\u1ED9p ghi ch\xFA", - hideDelayLabel: "\u0110\u1ED9 tr\u1EC5 \u1EA9n b\u1EA3ng \u0111i\u1EC1u khi\u1EC3n", - changeTextColor: "\u0110\u1ED5i m\xE0u ch\u1EEF", - cloneNearFile: "Nh\xE2n b\u1EA3n b\xEAn c\u1EA1nh t\u1EC7p", - addStroke: "Th\xEAm vi\u1EC1n", - applyToHeader: "\xC1p d\u1EE5ng cho ti\xEAu \u0111\u1EC1", - addLink: "Th\xEAm li\xEAn k\u1EBFt", - deleteSelected: "X\xF3a m\u1EE5c \u0111\xE3 ch\u1ECDn ({n})", - urlMediaClickToSave: "Nh\u1EA5p \u0111\u1EC3 l\u01B0u v\xE0o vault", - urlMediaDownloadAndReplace: "T\u1EA3i xu\u1ED1ng v\xE0 thay th\u1EBF", - urlMediaDownloading: "\u0110ang t\u1EA3i h\xECnh \u1EA3nh...", - urlMediaDownloadError: "L\u1ED7i t\u1EA3i xu\u1ED1ng: {status}", - urlMediaFileSavedLinkFailed: "T\u1EC7p \u0111\xE3 l\u01B0u, nh\u01B0ng kh\xF4ng th\u1EC3 c\u1EADp nh\u1EADt li\xEAn k\u1EBFt", - urlMediaSuccess: "H\xECnh \u1EA3nh \u0111\xE3 l\u01B0u v\xE0 li\xEAn k\u1EBFt \u0111\xE3 c\u1EADp nh\u1EADt", - urlMediaFileSavedPatternNotFound: "T\u1EC7p \u0111\xE3 l\u01B0u, nh\u01B0ng kh\xF4ng t\xECm th\u1EA5y li\xEAn k\u1EBFt trong v\u0103n b\u1EA3n", - urlMediaError: "L\u1ED7i: {error}", - urlMediaUnknownError: "l\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh" - }, - "zh-CN": { - cloneSettingsTitle: "\u514B\u9686\u81EA\u5B9A\u4E49", - removeBg: "\u79FB\u9664\u80CC\u666F", - animation: "\u52A8\u753B", - animationPopup: "\u5F39\u51FA", - animationFade: "\u6DE1\u5165", - backgroundOpacity: "\u80CC\u666F\u4E0D\u900F\u660E\u5EA6", - controlsOpacity: "\u9762\u677F\u4E0D\u900F\u660E\u5EA6", - showAudioHandles: "\u663E\u793A\u97F3\u9891\u624B\u67C4", - presets: "\u5C3A\u5BF8", - fit: "\u9002\u914D\u5BB9\u5668", - natural: "\u539F\u59CB\u5C3A\u5BF8", - add: "\u6DFB\u52A0\u2026", - addPresetTitle: "\u6DFB\u52A0\u5BBD\u5EA6\u9884\u8BBE \u2039px\u203A", - placeholder: "\u4F8B\u5982\uFF1A820", - ok: "\u786E\u5B9A", - cancel: "\u53D6\u6D88", - invalid: "\u65E0\u6548\u7684\u503C", - maxReached: "\u5DF2\u8FBE\u5230\u9884\u8BBE\u7684\u6700\u5927\u6570\u91CF", - added: "\u5DF2\u6DFB\u52A0\u9884\u8BBE {n}", - removed: "\u5DF2\u5220\u9664\u9884\u8BBE {n}", - openLocation: "\u6253\u5F00\u6587\u4EF6\u4F4D\u7F6E", - fileNotFound: "\u5728 Vault \u4E2D\u672A\u627E\u5230\u6587\u4EF6", - shellDenied: "\u65E0\u6743\u8BBF\u95EE shell", - localOnly: "\u4EC5\u9002\u7528\u4E8E\u672C\u5730 Vault", - restorePos: "\u6062\u590D\u521D\u59CB\u4F4D\u7F6E", - setSize: "\u8BBE\u7F6E\u5C3A\u5BF8\u2026", - width: "\u5BBD\u5EA6", - height: "\u9AD8\u5EA6", - keepAR: "\u4FDD\u6301\u7EB5\u6A2A\u6BD4", - sbReady: "ML \u2022 \u5C31\u7EEA", - sbTitle: "Media Layout\uFF1A\u70B9\u51FB\u91CD\u65B0\u626B\u63CF", - cmdPing: "Media Layout\uFF1Aping", - pingOk: "ML\uFF1AOK \u2014 \u63D2\u4EF6\u5DF2\u6FC0\u6D3B", - posPresets: "\u4FDD\u5B58\u514B\u9686\u7684\u4F4D\u7F6E", - resetAll: "\u5168\u90E8\u91CD\u7F6E", - saveLayout: "\u4FDD\u5B58\u9884\u8BBE", - layoutMax: "\u5DF2\u8FBE\u5230\u9884\u8BBE\u7684\u6700\u5927\u6570\u91CF", - saved: "\u5DF2\u4FDD\u5B58", - layoutN: "\u9884\u8BBE {n}", - renameTitle: "\u8F93\u5165\u540D\u79F0", - addNote: "\u6DFB\u52A0\u6807\u6CE8\u6846", - copyLayout: "\u590D\u5236\u9875\u9762\u6570\u636E", - pasteLayout: "\u7C98\u8D34\u9875\u9762\u6570\u636E", - deleteNote: "\u5220\u9664\u6807\u6CE8\u6846", - detach: "\u5206\u79BB", - bgColor: "\u66F4\u6539\u80CC\u666F\u989C\u8272", - duplicate: "\u590D\u5236", - reset: "\u91CD\u7F6E", - deleteClone: "\u79FB\u8D70", - lock: "\u9501\u5B9A", - unlock: "\u89E3\u9501", - localOnlyCheckboxLabel: "\u4EC5\u5BF9\u6B64\u6587\u4EF6", - makeInvisible: "\u8BBE\u4E3A\u4E0D\u53EF\u89C1", - invalidLayoutData: "\u526A\u8D34\u677F\u4E2D\u7684\u9875\u9762\u6570\u636E\u65E0\u6548\u3002", - layoutPasted: "\u5DF2\u7C98\u8D34\u9875\u9762\u6570\u636E\u4E0E\u9884\u8BBE\u3002", - pasteLayoutFailed: "\u65E0\u6CD5\u4ECE\u526A\u8D34\u677F\u7C98\u8D34\u9875\u9762\u6570\u636E\u3002", - layoutCopied: "\u9875\u9762\u6570\u636E\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\u3002", - makeVisible: "\u8BBE\u4E3A\u53EF\u89C1", - bringLayerUp: "\u4E0A\u79FB\u56FE\u5C42", - sendLayerDown: "\u4E0B\u79FB\u56FE\u5C42", - showHandlesOnHoverLabel: "\u60AC\u505C\u65F6\u663E\u793A\u63A7\u5236\u624B\u67C4", - hideDetachButtonLabel: "\u9690\u85CF\u201C\u53D6\u6D88\u56FA\u5B9A\u201D\u6309\u94AE", - modalAnimationLabel: "\u6A21\u6001\u7A97\u53E3\u52A8\u753B", - cloneRestoreDelayLabel: "\u514B\u9686\u663E\u793A\u5EF6\u8FDF", - noteRestoreDelayLabel: "\u4FBF\u7B3A\u6846\u663E\u793A\u5EF6\u8FDF", - hideDelayLabel: "\u9762\u677F\u9690\u85CF\u5EF6\u8FDF", - changeTextColor: "\u66F4\u6539\u6587\u672C\u989C\u8272", - cloneNearFile: "\u5728\u6587\u4EF6\u65C1\u8FB9\u514B\u9686", - addStroke: "\u6DFB\u52A0\u63CF\u8FB9", - applyToHeader: "\u5E94\u7528\u4E8E\u6807\u9898", - addLink: "\u6DFB\u52A0\u94FE\u63A5", - deleteSelected: "\u5220\u9664\u6240\u9009 ({n})", - urlMediaClickToSave: "\u70B9\u51FB\u4FDD\u5B58\u5230 vault", - urlMediaDownloadAndReplace: "\u4E0B\u8F7D\u5E76\u66FF\u6362", - urlMediaDownloading: "\u6B63\u5728\u4E0B\u8F7D\u56FE\u7247...", - urlMediaDownloadError: "\u4E0B\u8F7D\u9519\u8BEF: {status}", - urlMediaFileSavedLinkFailed: "\u6587\u4EF6\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u65E0\u6CD5\u66F4\u65B0\u94FE\u63A5", - urlMediaSuccess: "\u56FE\u7247\u5DF2\u4FDD\u5B58\u5E76\u66F4\u65B0\u94FE\u63A5", - urlMediaFileSavedPatternNotFound: "\u6587\u4EF6\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u5728\u6587\u672C\u4E2D\u627E\u4E0D\u5230\u94FE\u63A5", - urlMediaError: "\u9519\u8BEF: {error}", - urlMediaUnknownError: "\u672A\u77E5\u9519\u8BEF" - }, - "zh-TW": { - cloneSettingsTitle: "\u8907\u88FD\u9805\u81EA\u8A02", - removeBg: "\u79FB\u9664\u80CC\u666F", - animation: "\u52D5\u756B", - animationPopup: "\u5F48\u51FA", - animationFade: "\u6DE1\u5165", - backgroundOpacity: "\u80CC\u666F\u4E0D\u900F\u660E\u5EA6", - controlsOpacity: "\u9762\u677F\u4E0D\u900F\u660E\u5EA6", - showAudioHandles: "\u986F\u793A\u97F3\u8A0A\u63A7\u5236\u9EDE", - presets: "\u5C3A\u5BF8", - fit: "\u9069\u914D\u5BB9\u5668", - natural: "\u539F\u59CB\u5C3A\u5BF8", - add: "\u65B0\u589E\u2026", - addPresetTitle: "\u65B0\u589E\u5BEC\u5EA6\u9810\u8A2D \u2039px\u203A", - placeholder: "\u4F8B\u5982\uFF1A820", - ok: "\u78BA\u5B9A", - cancel: "\u53D6\u6D88", - invalid: "\u7121\u6548\u7684\u503C", - maxReached: "\u5DF2\u9054\u5230\u9810\u8A2D\u7684\u6700\u5927\u6578\u91CF", - added: "\u5DF2\u65B0\u589E\u9810\u8A2D {n}", - removed: "\u5DF2\u522A\u9664\u9810\u8A2D {n}", - openLocation: "\u958B\u555F\u6A94\u6848\u4F4D\u7F6E", - fileNotFound: "\u5728 Vault \u4E2D\u627E\u4E0D\u5230\u6A94\u6848", - shellDenied: "\u7121\u6CD5\u5B58\u53D6 shell", - localOnly: "\u50C5\u9069\u7528\u65BC\u672C\u6A5F Vault", - restorePos: "\u9084\u539F\u521D\u59CB\u4F4D\u7F6E", - setSize: "\u8A2D\u5B9A\u5C3A\u5BF8\u2026", - width: "\u5BEC\u5EA6", - height: "\u9AD8\u5EA6", - keepAR: "\u4FDD\u6301\u9577\u5BEC\u6BD4", - sbReady: "ML \u2022 \u5C31\u7DD2", - sbTitle: "Media Layout\uFF1A\u9EDE\u64CA\u91CD\u65B0\u6383\u63CF", - cmdPing: "Media Layout\uFF1Aping", - pingOk: "ML\uFF1AOK \u2014 \u5916\u639B\u5DF2\u555F\u7528", - posPresets: "\u4FDD\u5B58\u514B\u9686\u7684\u4F4D\u7F6E", - resetAll: "\u5168\u90E8\u91CD\u8A2D", - saveLayout: "\u5132\u5B58\u9810\u8A2D", - layoutMax: "\u5DF2\u9054\u5230\u9810\u8A2D\u7684\u6700\u5927\u6578\u91CF", - saved: "\u5DF2\u5132\u5B58", - layoutN: "\u9810\u8A2D {n}", - renameTitle: "\u8F38\u5165\u540D\u7A31", - addNote: "\u65B0\u589E\u6A19\u8A3B\u6846", - copyLayout: "\u8907\u88FD\u9801\u9762\u8CC7\u6599", - pasteLayout: "\u8CBC\u4E0A\u9801\u9762\u8CC7\u6599", - deleteNote: "\u522A\u9664\u6A19\u8A3B\u6846", - detach: "\u5206\u96E2", - bgColor: "\u8B8A\u66F4\u80CC\u666F\u984F\u8272", - duplicate: "\u91CD\u88FD", - reset: "\u91CD\u8A2D", - deleteClone: "\u79FB\u8D70", - lock: "\u9396\u5B9A", - unlock: "\u89E3\u9664\u9396\u5B9A", - localOnlyCheckboxLabel: "\u50C5\u6B64\u6A94\u6848", - makeInvisible: "\u8A2D\u70BA\u4E0D\u53EF\u898B", - invalidLayoutData: "\u526A\u8CBC\u7C3F\u4E2D\u7684\u9801\u9762\u8CC7\u6599\u7121\u6548\u3002", - layoutPasted: "\u5DF2\u8CBC\u4E0A\u9801\u9762\u8CC7\u6599\u8207\u9810\u8A2D\u3002", - pasteLayoutFailed: "\u7121\u6CD5\u5F9E\u526A\u8CBC\u7C3F\u8CBC\u4E0A\u9801\u9762\u8CC7\u6599\u3002", - layoutCopied: "\u9801\u9762\u8CC7\u6599\u5DF2\u8907\u88FD\u5230\u526A\u8CBC\u7C3F\u3002", - makeVisible: "\u8A2D\u70BA\u53EF\u898B", - bringLayerUp: "\u4E0A\u79FB\u5716\u5C64", - sendLayerDown: "\u4E0B\u79FB\u5716\u5C64", - showHandlesOnHoverLabel: "\u6ED1\u9F20\u61F8\u505C\u6642\u986F\u793A\u63A7\u5236\u628A\u624B", - hideDetachButtonLabel: "\u96B1\u85CF\u300C\u53D6\u6D88\u91D8\u9078\u300D\u6309\u9215", - modalAnimationLabel: "\u6A21\u614B\u8996\u7A97\u52D5\u756B", - cloneRestoreDelayLabel: "\u8907\u672C\u986F\u793A\u5EF6\u9072", - noteRestoreDelayLabel: "\u5099\u8A3B\u65B9\u584A\u986F\u793A\u5EF6\u9072", - hideDelayLabel: "\u9762\u677F\u96B1\u85CF\u5EF6\u9072", - changeTextColor: "\u8B8A\u66F4\u6587\u5B57\u984F\u8272", - cloneNearFile: "\u5728\u6A94\u6848\u65C1\u908A\u514B\u9686", - addStroke: "\u65B0\u589E\u5916\u6846", - applyToHeader: "\u5957\u7528\u5230\u6A19\u984C", - addLink: "\u65B0\u589E\u9023\u7D50", - deleteSelected: "\u522A\u9664\u6240\u9078 ({n})", - urlMediaClickToSave: "\u9EDE\u64CA\u5132\u5B58\u5230 vault", - urlMediaDownloadAndReplace: "\u4E0B\u8F09\u4E26\u66FF\u63DB", - urlMediaDownloading: "\u6B63\u5728\u4E0B\u8F09\u5716\u7247...", - urlMediaDownloadError: "\u4E0B\u8F09\u932F\u8AA4: {status}", - urlMediaFileSavedLinkFailed: "\u6A94\u6848\u5DF2\u5132\u5B58\uFF0C\u4F46\u7121\u6CD5\u66F4\u65B0\u9023\u7D50", - urlMediaSuccess: "\u5716\u7247\u5DF2\u5132\u5B58\u4E26\u66F4\u65B0\u9023\u7D50", - urlMediaFileSavedPatternNotFound: "\u6A94\u6848\u5DF2\u5132\u5B58\uFF0C\u4F46\u5728\u6587\u5B57\u4E2D\u627E\u4E0D\u5230\u9023\u7D50", - urlMediaError: "\u932F\u8AA4: {error}", - urlMediaUnknownError: "\u672A\u77E5\u932F\u8AA4" - } -}; -function t(app, key, vars) { - const locale = app.getLanguage?.() ?? "en"; - const loc = String(locale).toLowerCase(); - const dict = I18N[loc] ?? I18N[loc.split("-")[0]] ?? I18N.en; - let s = dict[key] ?? I18N.en[key]; - if (vars) for (const k in vars) s = s.replace(new RegExp(`\\{${k}\\}`, "g"), String(vars[k])); - return s; -} -function findContentSizer(root) { - if (!root) return null; - let readingSizer = root.querySelector(":scope > .markdown-preview-sizer"); - if (readingSizer) return readingSizer; - readingSizer = root.querySelector(".markdown-preview-view > .markdown-preview-sizer"); - if (readingSizer) return readingSizer; - readingSizer = root.querySelector(".markdown-preview-sizer"); - if (readingSizer) return readingSizer; - const editorSizer = root.querySelector(".cm-sizer"); - if (editorSizer) return editorSizer; - if (root.classList.contains("markdown-preview-sizer") || root.classList.contains("cm-sizer")) { - return root; - } - console.warn("[Media Layout] Could not find content sizer in root:", root); - return null; -} -var MAX_WIDTH_PRESETS = 15; -var MAX_LAYOUT_PRESETS = 150; -var clamp = (n, min, max) => Math.max(min, Math.min(max, n)); -var snap = (n, grid = 20) => Math.round(n / grid) * grid; -var WidthPresetModal = class extends import_obsidian.Modal { - onSubmit; - constructor(app, onSubmit) { - super(app); - this.onSubmit = onSubmit; - } - onOpen() { - const { contentEl } = this; - this.modalEl.addClass("ms-modal-width-355"); - contentEl.addClass("ms-bgcolor-modal-content"); - contentEl.addClass("ms-width-preset-modal-content"); - contentEl.empty(); - contentEl.createEl("h3", { text: t(this.app, "addPresetTitle") }); - let val = ""; - const input = contentEl.createEl("input", { - type: "number", - placeholder: t(this.app, "placeholder"), - attr: { min: "64", max: "8192", step: "1" }, - cls: "ms-input-width-300" - }); - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") this.closeWithValue(); - }); - input.addEventListener("input", () => val = input.value); - input.focus(); - const row = contentEl.createDiv({ cls: "modal-button-container" }); - const ok = row.createEl("button", { text: t(this.app, "ok") }); - const cancel = row.createEl("button", { text: t(this.app, "cancel") }); - ok.addEventListener("click", () => this.closeWithValue()); - cancel.addEventListener("click", () => { - this.onSubmit(null); - this.close(); - }); - this.closeWithValue = () => { - const n = Number.parseInt(val || input.value, 10); - if (!Number.isFinite(n) || n < 64 || n > 8192) { - new import_obsidian.Notice(t(this.app, "invalid")); - return; - } - this.onSubmit(n); - this.close(); - }; - } - onClose() { - this.contentEl.empty(); - } - closeWithValue; -}; -var RenamePresetModal = class extends import_obsidian.Modal { - onSubmit; - init; - constructor(app, init, onSubmit) { - super(app); - this.init = init || ""; - this.onSubmit = onSubmit; - } - onOpen() { - const { contentEl } = this; - contentEl.empty(); - contentEl.addClass("ms-rename-modal"); - contentEl.createEl("h3", { text: t(this.app, "renameTitle") }); - const wrap = contentEl.createDiv({ cls: "ms-rename-form" }); - const inp = wrap.createEl("input", { type: "text", value: this.init, attr: { spellcheck: "false" } }); - const row = contentEl.createDiv({ cls: "ms-rename-actions" }); - const ok = row.createEl("button", { text: t(this.app, "ok") }); - const cancel = row.createEl("button", { text: t(this.app, "cancel") }); - const submit = () => { - this.onSubmit(inp.value.trim() || null); - this.close(); - }; - ok.onclick = submit; - cancel.onclick = () => { - this.onSubmit(null); - this.close(); - }; - inp.onkeydown = (e) => { - if (e.key === "Enter") submit(); - if (e.key === "Escape") { - this.onSubmit(null); - this.close(); - } - }; - setTimeout(() => { - inp.focus(); - inp.select(); - }, 0); - } - onClose() { - this.contentEl.empty(); - } -}; -var LinkModal = class extends import_obsidian.Modal { - onSubmit; - init; - constructor(app, init, onSubmit) { - super(app); - this.init = init || ""; - this.onSubmit = onSubmit; - } - onOpen() { - const { contentEl } = this; - contentEl.empty(); - contentEl.addClass("ms-rename-modal"); - contentEl.createEl("h3", { text: t(this.app, "addLink") }); - const wrap = contentEl.createDiv({ cls: "ms-rename-form" }); - const inp = wrap.createEl("input", { type: "text", value: this.init, attr: { spellcheck: "false" } }); - inp.placeholder = "https://..."; - const row = contentEl.createDiv({ cls: "ms-rename-actions" }); - const ok = row.createEl("button", { text: t(this.app, "ok") }); - const cancel = row.createEl("button", { text: t(this.app, "cancel") }); - const submit = () => { - this.onSubmit(inp.value.trim()); - this.close(); - }; - ok.onclick = submit; - cancel.onclick = () => { - this.onSubmit(null); - this.close(); - }; - inp.onkeydown = (e) => { - if (e.key === "Enter") submit(); - if (e.key === "Escape") { - this.onSubmit(null); - this.close(); - } - }; - setTimeout(() => { - inp.focus(); - inp.select(); - }, 0); - } - onClose() { - this.contentEl.empty(); - } -}; -var ImageSizeModal = class extends import_obsidian.Modal { - onSubmit; - initW; - initH; - constructor(app, w, h, onSubmit) { - super(app); - this.onSubmit = onSubmit; - this.initW = w; - this.initH = h; - } - onOpen() { - const { contentEl } = this; - contentEl.empty(); - const form = contentEl.createDiv({ cls: "ms-imgsz-form" }); - form.createEl("h3", { text: t(this.app, "setSize") }); - const rowW = form.createDiv({ cls: "ms-row" }); - rowW.createEl("label", { text: t(this.app, "width") }); - const inW = rowW.createEl("input", { type: "number", value: String(Math.round(this.initW)), attr: { min: "1", step: "1" } }); - const rowH = form.createDiv({ cls: "ms-row" }); - rowH.createEl("label", { text: t(this.app, "height") }); - const inH = rowH.createEl("input", { type: "number", value: String(Math.round(this.initH)), attr: { min: "1", step: "1" } }); - const keep = form.createDiv({ cls: "ms-row" }); - const keepCb = keep.createEl("input", { type: "checkbox" }); - keepCb.checked = true; - keep.createEl("span", { text: t(this.app, "keepAR") }); - const ar = this.initW / Math.max(1, this.initH); - const syncFrom = (src) => { - if (!keepCb.checked) return; - if (src === "w") { - const w = Number(inW.value); - if (Number.isFinite(w) && w > 0) inH.value = String(Math.round(w / ar)); - } else { - const h = Number(inH.value); - if (Number.isFinite(h) && h > 0) inW.value = String(Math.round(h * ar)); - } - }; - inW.addEventListener("input", () => syncFrom("w")); - inH.addEventListener("input", () => syncFrom("h")); - keepCb.addEventListener("change", () => syncFrom("w")); - const buttons = contentEl.createDiv({ cls: "modal-button-container" }); - const ok = buttons.createEl("button", { text: t(this.app, "ok") }); - const cancel = buttons.createEl("button", { text: t(this.app, "cancel") }); - ok.addEventListener("click", () => { - const w = Number(inW.value); - const h = Number(inH.value); - if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) { - new import_obsidian.Notice(t(this.app, "invalid")); - return; - } - this.onSubmit(w, h, keepCb.checked); - this.close(); - }); - cancel.addEventListener("click", () => { - this.onSubmit(null, null, true); - this.close(); - }); - } - onClose() { - this.contentEl.empty(); - } -}; -var BgColorModal = class extends import_obsidian.Modal { - value; - onOk; - t; - isClone; - isNoteBox; - isTextColor; - initialStrokeState; - constructor(app, initial, onOk, t2, options) { - super(app); - this.value = initial || "#242424"; - this.onOk = onOk; - this.t = t2; - this.isClone = !!options.isClone; - this.isNoteBox = !!options.isNoteBox; - this.isTextColor = !!options.isTextColor; - this.initialStrokeState = !!options.initialStrokeState; - } - onOpen() { - const { contentEl } = this; - contentEl.addClass("ms-bgcolor-modal-content"); - contentEl.empty(); - contentEl.createEl("h3", { text: this.isTextColor ? this.t("changeTextColor") : this.t("bgColor") }); - const wrap = contentEl.createDiv({ cls: "ms-bgcolor-wrap" }); - const colorInp = wrap.createEl("input", { type: "color" }); - colorInp.value = this.coerceHex(this.value); - const textInp = wrap.createEl("input", { type: "text" }); - textInp.placeholder = "#242424"; - textInp.value = this.coerceHex(this.value); - colorInp.addEventListener("input", () => { - textInp.value = colorInp.value; - }); - textInp.addEventListener("input", () => { - const v = this.coerceHex(textInp.value); - if (v) colorInp.value = v; - }); - let localOnlyCb = null; - let applyToHeaderCb = null; - let removeBgCb = null; - let addStrokeCb = null; - const optionsContainer = contentEl.createDiv({ attr: { style: "display: flex; flex-direction: column; align-items: flex-start;" } }); - const createCheckbox = (text) => { - const row = optionsContainer.createEl("label", { cls: "ms-row ms-flex-align-center ms-margin-bottom-8" }); - row.style.cursor = "pointer"; - const cb = row.createEl("input", { type: "checkbox", cls: "ms-margin-right-6" }); - const span = row.createEl("span", { text, cls: "ms-position-relative-bottom-0" }); - return [row, cb]; - }; - if (!this.isTextColor) { - if (this.isClone) { - [, localOnlyCb] = createCheckbox(this.t("localOnlyCheckboxLabel")); - } - if (this.isNoteBox) { - [, applyToHeaderCb] = createCheckbox(this.t("applyToHeader")); - } - if (this.isClone || this.isNoteBox) { - [, removeBgCb] = createCheckbox(this.t("removeBg")); - } - } else { - [, addStrokeCb] = createCheckbox(this.t("addStroke")); - if (addStrokeCb) addStrokeCb.checked = this.initialStrokeState; - } - if (optionsContainer.hasChildNodes()) { - optionsContainer.addClass("ms-margin-bottom-12"); - } - const actions = contentEl.createDiv({ cls: "modal-button-container" }); - const okBtn = actions.createEl("button", { text: this.t("ok") }); - const cancelBtn = actions.createEl("button", { text: this.t("cancel") }); - const resetBtn = actions.createEl("button", { text: this.t("reset") }); - actions.insertBefore(resetBtn, cancelBtn); - resetBtn.addEventListener("click", () => { - const resetColor = this.isTextColor ? "#FFFFFF" : this.isNoteBox ? "#262626" : "#242424"; - colorInp.value = resetColor; - textInp.value = resetColor; - }); - okBtn.addEventListener("click", () => { - const v = this.coerceHex(textInp.value) || "#242424"; - this.onOk({ - hex: v, - isLocal: localOnlyCb?.checked ?? false, - removeBg: removeBgCb?.checked ?? false, - applyToHeader: applyToHeaderCb?.checked ?? false, - addStroke: addStrokeCb?.checked ?? false - }); - this.close(); - }); - cancelBtn.addEventListener("click", () => this.close()); - } - coerceHex(v) { - v = (v || "").trim(); - if (!v) return ""; - if (v[0] !== "#") v = "#" + v; - if (/^#([0-9a-f]{6})$/i.test(v)) return v.toUpperCase(); - return ""; - } -}; -var CloneSettingsModal = class _CloneSettingsModal extends import_obsidian.Modal { - onSubmit; - initial; - constructor(app, initial, onSubmit) { - super(app); - this.initial = { ...initial }; - this.onSubmit = onSubmit; - } - onOpen() { - const { contentEl } = this; - contentEl.empty(); - contentEl.addClass("ms-clone-settings-modal"); - contentEl.createEl("h3", { text: t(this.app, "cloneSettingsTitle") }); - new import_obsidian.Setting(contentEl).setName(t(this.app, "animation")).addButton((btn) => { - btn.setButtonText(this.initial.animation === "popup" ? t(this.app, "animationPopup") : t(this.app, "animationFade")).onClick(() => { - this.initial.animation = this.initial.animation === "popup" ? "fade" : "popup"; - btn.setButtonText(this.initial.animation === "popup" ? t(this.app, "animationPopup") : t(this.app, "animationFade")); - }); - }); - new import_obsidian.Setting(contentEl).setName(t(this.app, "backgroundOpacity")).addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.initial.bgOpacity * 100).setDynamicTooltip().onChange((value) => { - this.initial.bgOpacity = value / 100; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "controlsOpacity")).addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.initial.controlsOpacity * 100).setDynamicTooltip().onChange((value) => { - this.initial.controlsOpacity = value / 100; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "hideDelayLabel")).addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.initial.hideDelay * 10).setDynamicTooltip().onChange((value) => { - this.initial.hideDelay = value / 10; - })); - const buttons = contentEl.createDiv({ cls: "modal-button-container" }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "ok")).setCta().onClick(() => { - this.onSubmit(this.initial); - this.close(); - }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "reset")).onClick(() => { - this.initial = { animation: "popup", bgOpacity: 1, controlsOpacity: 0.35, hideDelay: 2, showAudioHandles: true }; - this.close(); - const newModal = new _CloneSettingsModal( - this.app, - this.initial, - this.onSubmit - ); - newModal.open(); - }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "cancel")).onClick(() => { - this.onSubmit(null); - this.close(); - }); - } - onClose() { - this.contentEl.empty(); - } -}; -var MediaLayoutSettingsModal = class _MediaLayoutSettingsModal extends import_obsidian.Modal { - plugin; - constructor(app, plugin) { - super(app); - this.plugin = plugin; - } - onOpen() { - const { contentEl } = this; - contentEl.empty(); - contentEl.addClass("ms-layout-settings-modal"); - const form = contentEl.createDiv({ cls: "modal-form" }); - let handlesOnHoverValue = this.plugin.showHandlesOnHover; - let hideDetachValue = this.plugin.hideDetachButton; - let modalAnimationValue = this.plugin.enableModalAnimation; - let cloneNearFileValue = this.plugin.cloneNearFile; - let cloneRestoreDelayValue = this.plugin.cloneRestoreDelay; - let noteRestoreDelayValue = this.plugin.noteRestoreDelay; - new import_obsidian.Setting(contentEl).setName(t(this.app, "showHandlesOnHoverLabel")).addToggle((toggle) => toggle.setValue(handlesOnHoverValue).onChange((value) => { - handlesOnHoverValue = value; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "hideDetachButtonLabel")).addToggle((toggle) => toggle.setValue(hideDetachValue).onChange((value) => { - hideDetachValue = value; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "modalAnimationLabel")).addToggle((toggle) => toggle.setValue(modalAnimationValue).onChange((value) => { - modalAnimationValue = value; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "cloneNearFile")).addToggle((toggle) => toggle.setValue(cloneNearFileValue).onChange((value) => { - cloneNearFileValue = value; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "cloneRestoreDelayLabel")).addSlider((slider) => slider.setLimits(0, 1e3, 10).setValue(cloneRestoreDelayValue).setDynamicTooltip().onChange((value) => { - cloneRestoreDelayValue = value; - })); - new import_obsidian.Setting(contentEl).setName(t(this.app, "noteRestoreDelayLabel")).addSlider((slider) => slider.setLimits(0, 1e3, 10).setValue(noteRestoreDelayValue).setDynamicTooltip().onChange((value) => { - noteRestoreDelayValue = value; - })); - const buttons = contentEl.createDiv({ cls: "modal-button-container" }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "ok")).setCta().onClick(async () => { - this.plugin.showHandlesOnHover = handlesOnHoverValue; - if (!handlesOnHoverValue) { - this.plugin.cloneShowAudioHandles = false; - } - this.plugin.hideDetachButton = hideDetachValue; - this.plugin.enableModalAnimation = modalAnimationValue; - this.plugin.cloneRestoreDelay = cloneRestoreDelayValue; - this.plugin.cloneNearFile = cloneNearFileValue; - this.plugin.noteRestoreDelay = noteRestoreDelayValue; - await this.plugin.persistAll(); - this.plugin.applyCssVariables(); - this.close(); - }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "reset")).onClick(async () => { - this.plugin.showHandlesOnHover = true; - this.plugin.cloneNearFile = true; - this.plugin.cloneShowAudioHandles = true; - this.plugin.hideDetachButton = true; - this.plugin.enableModalAnimation = true; - this.plugin.cloneRestoreDelay = 50; - this.plugin.noteRestoreDelay = 50; - await this.plugin.persistAll(); - this.plugin.applyCssVariables(); - new import_obsidian.Notice("\u2714\u27F2"); - this.close(); - new _MediaLayoutSettingsModal(this.app, this.plugin).open(); - }); - new import_obsidian.ButtonComponent(buttons).setButtonText(t(this.app, "cancel")).onClick(() => { - this.close(); - }); - } - onClose() { - this.contentEl.empty(); - } -}; -var MediaSize = class extends import_obsidian.Plugin { - notes = {}; - presets = {}; - store = {}; - userPresets = []; - observers = []; - meBgColor = "#242424"; - cloneAnimation = "popup"; - cloneBgOpacity = 1; - cloneControlsOpacity = 0.35; - cloneHideDelay = 2; - noteRestoreDelay = 50; - cloneRestoreDelay = 50; - enableModalAnimation = true; - hideDetachButton = true; - showHandlesOnHover = true; - cloneNearFile = true; - cloneShowAudioHandles = true; - currentNotePath = null; - pendingTimers = /* @__PURE__ */ new Set(); - presetTimerId = null; - persistInProgress = false; - persistQueue = null; - persistDebounceTimer = null; - persistDebounceDelay = 150; - maxPersistDelay = 3e3; - lastPersistTime = 0; - hasPendingChanges = false; - selectedKeys = /* @__PURE__ */ new Set(); - isMarqueeSelecting = false; - marqueeStart = null; - marqueeWithCtrl = false; - scrollRatioX = 0; - scrollRatioY = 0; - hScrollEl = null; - vScrollEl = null; - wasLockedBeforeFs = false; - debugLog = []; - maxDebugLines = 2e3; - enableDebugMode = false; - logFilePath = ""; - autoSaveLogTimer = null; - backupTimer = null; - lastBackupHash = ""; - computeOriginalAnchor(host) { - const media = host.querySelector("video, img, audio"); - const embed = host.closest(".cm-embed-block, .media-embed, .internal-embed"); - const src = media?.getAttribute("src") || media?.dataset?.src || "" || (embed?.getAttribute?.("src") || embed?.dataset?.src || "") || ""; - const root = host.closest(".markdown-reading-view, .markdown-preview-view, .cm-editor"); - let nth = 0; - if (src && root) { - const all = Array.from(root.querySelectorAll("video, img, audio")).filter((el) => { - const s = el.getAttribute("src") || el?.dataset?.src || ""; - return s === src; - }); - nth = Math.max(0, all.indexOf(media)); - } - const sig = media?.getAttribute("alt") || media?.getAttribute("title") || (media?.textContent || "").slice(0, 64) || ""; - return `${src || "nosrc"}#${nth}:${sig}`; - } - ensureOriginalAnchor(host) { - let a = host.dataset.msAnchor; - if (!a) { - a = this.computeOriginalAnchor(host); - host.dataset.msAnchor = a; - } - return a; - } - buildAnchorIndex(root) { - const map = /* @__PURE__ */ new Map(); - root.querySelectorAll(".ms-host[data-ms-key]").forEach((h) => { - const a = this.ensureOriginalAnchor(h); - const k = h.dataset.msKey; - if (a && k) map.set(a, k); - }); - return map; - } - assetUrl(relPath) { - const full = (0, import_obsidian.normalizePath)(`${this.app.vault.configDir}/plugins/${this.manifest.id}/${relPath}`); - return this.app.vault.adapter.getResourcePath(full); - } - ensureFloatLayer(root) { - const isReading = !!root.closest(".markdown-reading-view, .markdown-preview-view"); - this.debug("ensureFloatLayer called", { - isReading, - rootClass: root.className, - rootTag: root.tagName - }); - let sizer = findContentSizer(root.closest(".markdown-source-view, .markdown-reading-view")); - this.debug("Found sizer", { - found: !!sizer, - sizerClass: sizer?.className, - sizerTag: sizer?.tagName - }); - if (!sizer) { - this.warn("No sizer found, using root as fallback", {}); - sizer = root; - } - let layer = sizer.querySelector(":scope > .ms-float-layer"); - if (!layer) { - this.debug("Creating new float-layer in sizer", {}); - layer = document.createElement("div"); - layer.className = "ms-float-layer"; - layer.style.display = "block"; - layer.style.visibility = "visible"; - sizer.appendChild(layer); - this.debug("Float-layer created and appended", { - parent: sizer.className, - layerInDOM: document.contains(layer) - }); - } else { - this.debug("Float-layer already exists", {}); - } - const path = this.getActiveNote()?.path ?? ""; - if (path) { - layer.dataset.msNote = path; - layer.querySelectorAll(".ms-host[data-ms-note]").forEach((el) => { - const n = el.dataset.msNote || ""; - if (n && n !== path) el.remove(); - }); - } - this.debug("Float-layer ready", { - childCount: layer.children.length, - path - }); - return layer; - } - lastCtxEvt = null; - controlsProxy = /* @__PURE__ */ new WeakMap(); - layoutPresets = {}; - layoutNames = {}; - async onload() { - const persisted = await this.loadData(); - let dataCorrupted = false; - if (persisted && typeof persisted === "object") { - if (persisted.notes) { - this.notes = persisted.notes ?? {}; - this.presets = persisted.presets ?? {}; - const hasNotes = Object.keys(this.notes).length > 0; - if (!hasNotes && persisted.userPresets && persisted.userPresets.length > 0) { - console.error("[Media Layout] WARNING: Loaded data has empty notes but has other data - possible corruption!"); - dataCorrupted = true; - } - } else if (persisted.items) { - console.log("Media-Size: Migrating old data structure."); - this.notes = {}; - for (const notePath in persisted.items) { - this.notes[notePath] = { - originals: persisted.items[notePath], - clones: {} - }; - } - this.presets = {}; - this.store = persisted.items; - } else { - this.notes = {}; - this.presets = {}; - } - if (dataCorrupted) { - console.warn("[Media Layout] Attempting auto-recovery from latest backup..."); - const backups = await this.listBackups(); - if (backups.length > 0) { - try { - const latestBackup = backups[0]; - const content = await this.app.vault.adapter.read(latestBackup.path); - const backupData = JSON.parse(content); - if (backupData.notes && Object.keys(backupData.notes).length > 0) { - this.notes = backupData.notes; - this.presets = backupData.presets || {}; - console.log(`[Media Layout] \u2705 Auto-recovered from backup: ${latestBackup.name}`); - new import_obsidian.Notice(`\u{1F504} Data recovered from backup: ${latestBackup.name}`); - } - } catch (e) { - console.error("[Media Layout] Auto-recovery failed:", e); - } - } - } - this.meBgColor = persisted.meBgColor ?? "#242424"; - this.userPresets = Array.isArray(persisted.userPresets) ? persisted.userPresets.slice(0, MAX_WIDTH_PRESETS) : []; - this.layoutPresets = persisted.layoutPresets ?? {}; - this.layoutNames = persisted.layoutNames ?? {}; - this.cloneAnimation = persisted.cloneAnimation ?? "popup"; - this.cloneBgOpacity = persisted.cloneBgOpacity ?? 1; - this.cloneControlsOpacity = persisted.cloneControlsOpacity ?? 0.35; - this.cloneHideDelay = persisted.cloneHideDelay ?? 2; - this.noteRestoreDelay = persisted.noteRestoreDelay ?? 200; - this.cloneRestoreDelay = persisted.cloneRestoreDelay ?? 300; - this.showHandlesOnHover = persisted.showHandlesOnHover ?? true; - this.enableModalAnimation = persisted.enableModalAnimation ?? true; - this.hideDetachButton = persisted.hideDetachButton ?? true; - this.cloneShowAudioHandles = persisted.cloneShowAudioHandles ?? true; - this.cloneNearFile = persisted.cloneNearFile ?? true; - } else { - this.notes = {}; - this.presets = {}; - } - this.applyCssVariables(); - const ctxHandler = (e) => this.lastCtxEvt = e; - document.addEventListener("contextmenu", ctxHandler, true); - this.register(() => document.removeEventListener("contextmenu", ctxHandler, true)); - this.registerDomEvent(document, "mousedown", (e) => { - if (e.ctrlKey || e.metaKey) this.handleMarqueeStart(e); - }, { capture: true }); - document.addEventListener("mousedown", this.handleDeselectClick, true); - this.register(() => document.removeEventListener("mousedown", this.handleDeselectClick, true)); - this.registerMarkdownPostProcessor((el, ctx) => { - const notePath = ctx?.sourcePath || this.getActiveNote()?.path || ""; - this.processRoot(el, notePath); - }); - this.app.workspace.onLayoutReady(() => { - this.schedule(() => { - this.scanActive(); - const notePath = this.getActiveNote()?.path; - if (notePath) this.restoreClonesFromStore(notePath); - this.applyCloneSettingsToAll(); - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (view) this.app.workspace.trigger("markdown-render", view.contentEl); - }, 500); - }); - let switchDebounceTimer = null; - let switchInProgress = false; - let lastSwitchPath = null; - const scheduleSwitch = (file) => { - if (switchDebounceTimer) clearTimeout(switchDebounceTimer); - switchDebounceTimer = window.setTimeout(() => handleFileSwitch(file), 0); - }; - const handleFileSwitch = async (file) => { - const newPath = file?.path ?? this.getActiveNote()?.path ?? null; - if (switchInProgress) { - console.warn("[Media Layout] Switch already in progress, ignoring duplicate call"); - return; - } - if (newPath === lastSwitchPath && Date.now() - this.lastSwitchTime < 100) { - console.warn("[Media Layout] Duplicate switch to same file, ignoring"); - return; - } - switchInProgress = true; - this.lastSwitchTime = Date.now(); - lastSwitchPath = newPath; - try { - this.cancelScheduled?.(); - await this.flushPersist(); - await this.hardCleanupForSwitch(this.currentNotePath ?? this.getActiveNote()?.path ?? void 0); - this.currentNotePath = newPath; - this.scanActive(); - } finally { - switchInProgress = false; - } - }; - this.registerEvent(this.app.workspace.on("file-open", (file) => scheduleSwitch(file))); - this.registerEvent(this.app.workspace.on("active-leaf-change", () => { - scheduleSwitch(this.app.workspace.getActiveFile()); - this.schedule(() => { - const notePath = this.getActiveNote()?.path; - if (notePath) this.realignClonesIfNeeded(notePath); - }, 100); - })); - this.app.workspace.onLayoutReady(() => scheduleSwitch(this.app.workspace.getActiveFile())); - this.registerEvent(this.app.workspace.on("layout-change", () => { - this.scanActive(); - this.schedule(() => { - const leaves = this.app.workspace.getLeavesOfType("markdown"); - leaves.forEach((leaf) => { - const file = leaf.view?.file; - if (file?.path) { - this.restoreClonesFromStore(file.path); - this.realignClonesIfNeeded(file.path); - } - }); - }, 150); - })); - this.registerDomEvent(window, "resize", () => { - this.schedule(() => { - const notePath = this.getActiveNote()?.path; - if (!notePath) return; - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (!view) return; - const containerEl = view.containerEl; - const read = containerEl.querySelector(".markdown-reading-view"); - const isReadingMode = read && getComputedStyle(read).display !== "none"; - if (isReadingMode) { - this.restoreClonesFromStore(notePath); - this.ensureNotesFromStore(notePath); - } - }, 50); - }); - let scrollTimeout = null; - this.registerDomEvent(document, "scroll", (e) => { - const target = e.target; - if (!target || !target.closest || !target.closest(".markdown-reading-view")) return; - if (scrollTimeout !== null) { - clearTimeout(scrollTimeout); - } - scrollTimeout = window.setTimeout(() => { - const notePath = this.getActiveNote()?.path; - if (!notePath) return; - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (!view) return; - const containerEl = view.containerEl; - const read = containerEl.querySelector(".markdown-reading-view"); - const isReadingMode = read && getComputedStyle(read).display !== "none"; - if (isReadingMode) { - const root = this.getAttachRoot(); - if (!root) return; - const layer = root.querySelector(".ms-float-layer"); - const noteData = this.notes[notePath]; - const hasStoredClones = noteData?.clones && Object.keys(noteData.clones).length > 0; - const hasStoredNotes = noteData?.originals && Object.values(noteData.originals).some((o) => o.txt !== void 0); - const hasDomClones = layer?.querySelector("[data-ms-parent-key]"); - const hasDomNotes = layer?.querySelector(".ms-note-host"); - if (hasStoredClones && !hasDomClones) { - this.restoreClonesFromStore(notePath); - } - if (hasStoredNotes && !hasDomNotes) { - this.ensureNotesFromStore(notePath); - } - } - scrollTimeout = null; - }, 100); - }, true); - this.registerEvent( - this.app.workspace.on("editor-menu", (menu, _editor, view) => { - const ev = this.lastCtxEvt; - if (!ev) return; - const target = ev.target; - if (!target) return; - if (this.isMarqueeSelecting || ev.ctrlKey && target.closest(".ms-host")) { - return; - } - menu.addItem((i) => { - i.setTitle(t(this.app, "addNote")).setIcon("layout-template"); - i.onClick(async () => { - const viewRoot = this.getAttachRoot(); - if (!viewRoot) return; - const notePath2 = view?.file?.path ?? this.getActiveNote()?.path ?? ""; - const key2 = this.makeNoteKey(); - const defaultWidth = 260; - const defaultHeight = 160; - const vr = viewRoot.getBoundingClientRect(); - const x = ev.clientX - vr.left + viewRoot.scrollLeft - defaultWidth * 2 | 0; - const y = ev.clientY - vr.top + viewRoot.scrollTop - defaultHeight * 1.5 | 0; - this.createNoteHost(viewRoot, key2, x, y, { w: defaultWidth, h: defaultHeight, txt: "", fontSize: 16, focus: true }); - await this.saveState(notePath2, key2, { w: defaultWidth, h: defaultHeight, x, y, txt: "", fontSize: 16 }); - }); - }); - const host = target.closest(".ms-host") || target.closest(".internal-embed, .media-embed, .mx-media-embed, img, video, audio"); - if (!host) return; - const isClone = !!host.dataset.msParentKey; - const isNote = host.classList.contains("ms-note-host"); - const media = isNote ? host.querySelector(".ms-note") : host.querySelector("video, img, audio") || (["IMG", "VIDEO", "AUDIO"].includes(host.tagName) ? host : null); - if (!media && !isNote) return; - const kind = isNote ? "note" : media.tagName === "IMG" ? "image" : media.tagName === "AUDIO" ? "audio" : "video"; - const notePath = view?.file?.path ?? this.getActiveNote()?.path ?? ""; - const key = isNote ? host.dataset.msKey ?? this.makeNoteKey() : host.dataset.msKey ?? this.makeEmbedKey(this.getSrcAndBase(host, media).src); - host.dataset.msKey = key; - const isUrlMedia = !isNote && !isClone && media && media.src && media.src.startsWith("http"); - const isTargetSelected = this.selectedKeys.has(key); - if (isTargetSelected && this.selectedKeys.size > 1) { - this.buildGroupContextMenu(menu, host, key, kind, notePath); - return; - } - if (isUrlMedia) { - menu.addItem( - (i) => i.setTitle(t(this.app, "urlMediaDownloadAndReplace")).setIcon("download").onClick(async () => { - const url = media.src; - if (url && url.startsWith("http")) { - await this.convertUrlMediaToLocal(url, notePath); - } - }) - ); - return; - } - if (isClone) { - if (kind !== "note") { - this.addSizePresetsMenu(menu, notePath, key, host, media, kind); - } - this.addPositionPresetsMenu(menu, notePath); - if (kind === "image") { - menu.addItem( - (i) => i.setTitle(t(this.app, "setSize")).setIcon("scaling").onClick(async () => { - this.ensureParentAnchor(host); - const cur = this.readAbsRect(host); - new ImageSizeModal( - this.app, - cur.w || media.clientWidth || 0, - cur.h || media.clientHeight || 0, - async (w, h, keep) => { - if (w && h) await this.applyAndSaveSize(notePath, key, host, media, kind, { w, h }, keep ? false : true); - } - ).open(); - }) - ); - } - if (kind === "image") { - menu.addItem( - (i) => i.setTitle(t(this.app, "addLink")).setIcon("link").onClick(async () => { - const currentState = this.notes[notePath]?.clones?.[key]; - const initialLink = currentState?.link || ""; - new LinkModal(this.app, initialLink, async (link) => { - if (link !== null) { - await this.saveState(notePath, key, { link }); - this.applyLinkOverlay(host, link); - } - }).open(); - }) - ); - } - menu.addItem( - (i) => i.setTitle(t(this.app, "openLocation")).setIcon("folder-open").onClick(async () => { - const notePath2 = view?.file?.path ?? this.getActiveNote()?.path ?? ""; - const { src } = this.getSrcAndBase(host, media); - const tf = this.resolveTFile(notePath2, src); - if (!tf) return new import_obsidian.Notice(t(this.app, "fileNotFound")); - const adapter = this.app.vault.adapter; - if (!(adapter instanceof import_obsidian.FileSystemAdapter)) { - new import_obsidian.Notice(t(this.app, "localOnly")); - return; - } - const full = adapter.getFullPath(tf.path); - try { - const shell = window.require?.("electron")?.shell; - if (shell?.showItemInFolder) shell.showItemInFolder(full); - else this.revealInOS(full); - } catch { - this.revealInOS(full); - } - }) - ); - if (kind === "video" || kind === "audio") { - menu.addItem((i) => { - i.setTitle(t(this.app, "bgColor")).setIcon("palette"); - i.onClick(() => { - const tr = (k) => t(this.app, k); - const initialColor = this.notes[notePath]?.clones?.[key]?.bgColor || this.meBgColor; - new BgColorModal(this.app, initialColor, async ({ hex, isLocal, removeBg }) => { - if (removeBg) { - host.querySelector(".ms-media-bg")?.remove(); - host.querySelector(".ms-local-bg")?.remove(); - await this.saveState(notePath, key, { bgColor: "transparent" }); - } else if (isLocal) { - const localBg = this.ensureLocalBg(host); - localBg.style.backgroundColor = hex; - await this.saveState(notePath, key, { bgColor: hex }); - } else { - await this.setMediaBgColor(hex, false, host, notePath, key); - host.querySelector(".ms-local-bg")?.remove(); - await this.saveState(notePath, key, { bgColor: void 0 }); - } - }, tr, { isClone: true }).open(); - }); - }); - } - menu.addItem((i) => { - i.setTitle(t(this.app, "cloneSettingsTitle")).setIcon("sliders-horizontal").onClick(() => { - new CloneSettingsModal(this.app, { - animation: this.cloneAnimation, - bgOpacity: this.cloneBgOpacity, - controlsOpacity: this.cloneControlsOpacity, - hideDelay: this.cloneHideDelay, - showAudioHandles: this.cloneShowAudioHandles - }, async (settings) => { - if (settings) { - this.cloneAnimation = settings.animation; - this.cloneBgOpacity = settings.bgOpacity; - this.cloneControlsOpacity = settings.controlsOpacity; - this.cloneHideDelay = settings.hideDelay; - this.cloneShowAudioHandles = settings.showAudioHandles; - await this.persistAll(); - this.applyCloneSettingsToAll(); - } - }).open(); - }); - }); - menu.addItem((i) => { - i.setTitle(t(this.app, "bringLayerUp")).setIcon("arrow-up-from-line"); - i.onClick(async () => { - this.adjustZIndex(host, 1); - }); - }); - menu.addItem((i) => { - i.setTitle(t(this.app, "sendLayerDown")).setIcon("arrow-down-from-line"); - i.onClick(async () => { - this.adjustZIndex(host, -1); - }); - }); - menu.addSeparator(); - const isLocked = host.dataset.msLocked === "1"; - menu.addItem((item) => { - item.setTitle(isLocked ? t(this.app, "unlock") : t(this.app, "lock")).setIcon(isLocked ? "unlock" : "lock"); - item.onClick(async () => { - const newState = !isLocked; - host.dataset.msLocked = newState ? "1" : "0"; - await this.saveState(notePath, key, { locked: newState }); - }); - }); - menu.addItem((item) => { - item.setTitle(t(this.app, "deleteClone")).setIcon("trash"); - item.setTitle(t(this.app, "deleteClone")).setIcon("trash"); - item.onClick(async () => { - host.remove(); - if (notePath && key) { - delete this.notes[notePath]?.clones?.[key]; - await this.persistAll(); - } - }); - }); - return; - } - if (kind !== "note") { - this.addSizePresetsMenu(menu, notePath, key, host, media, kind); - } - if (kind !== "note") { - this.addPositionPresetsMenu(menu, notePath); - } - if (kind === "image") { - menu.addItem( - (i) => i.setTitle(t(this.app, "setSize")).setIcon("scaling").onClick(async () => { - this.ensureParentAnchor(host); - const cur = this.readAbsRect(host); - new ImageSizeModal( - this.app, - cur.w || media.clientWidth || 0, - cur.h || media.clientHeight || 0, - async (w, h, keep) => { - if (w && h) await this.applyAndSaveSize(notePath, key, host, media, kind, { w, h }, keep ? false : true); - } - ).open(); - }) - ); - } - menu.addItem( - (i) => i.setTitle(t(this.app, "openLocation")).setIcon("folder-open").onClick(async () => { - const notePath2 = view?.file?.path ?? this.getActiveNote()?.path ?? ""; - const { src } = this.getSrcAndBase(host, media); - const tf = this.resolveTFile(notePath2, src); - if (!tf) return new import_obsidian.Notice(t(this.app, "fileNotFound")); - const adapter = this.app.vault.adapter; - if (!(adapter instanceof import_obsidian.FileSystemAdapter)) { - new import_obsidian.Notice(t(this.app, "localOnly")); - return; - } - const full = adapter.getFullPath(tf.path); - try { - const shell = window.require?.("electron")?.shell; - if (shell?.showItemInFolder) { - shell.showItemInFolder(full); - } else { - this.revealInOS(full); - } - } catch { - this.revealInOS(full); - } - }) - ); - const isDetached = host.dataset.msDetached === "1"; - if (!this.hideDetachButton && !isDetached && kind !== "note") { - menu.addItem((item) => { - item.setTitle(t(this.app, "detach")).setIcon("pin-off"); - item.onClick(async () => { - if (host.dataset.msDetached === "1") return; - await this.maybeDetach(notePath, key, host); - const el = item.dom; - el?.remove(); - }); - }); - } - if (isDetached) { - menu.addItem( - (i) => i.setTitle(t(this.app, "restorePos")).setIcon("home").onClick(async () => { - await this.applyAndSaveSize(notePath, key, host, media, kind, { x: 0, y: 0, w: void 0, h: void 0, reset: true }); - }) - ); - } - if (kind !== "note") { - menu.addItem((item) => { - item.setTitle(t(this.app, "duplicate")).setIcon("copy"); - item.onClick(async () => { - const viewRoot = this.getHostRoot(host); - if (!viewRoot) return; - const cloneHost = this.createCleanCloneElement(host); - const cloneKey = `${key}::clone::${this.makeNoteKey()}`; - const defaultWidth = 480; - const defaultHeight = 270; - const vr = viewRoot.getBoundingClientRect(); - let x, y; - if (this.cloneNearFile) { - x = ev.clientX - vr.left - defaultWidth + viewRoot.scrollLeft | 0; - y = ev.clientY - vr.top - defaultHeight + viewRoot.scrollTop | 0; - } else { - x = ev.clientX - vr.left - defaultWidth | 0; - y = ev.clientY - vr.top - defaultHeight | 0; - } - this.createCloneHost(viewRoot, cloneHost, cloneKey, key, x, y, true); - this.schedule(() => { - this.applyCloneSettingsToAll(); - }, 150); - }); - }); - } - if (kind === "video" || kind === "audio") { - menu.addItem((i) => { - i.setTitle(t(this.app, "bgColor")).setIcon("palette"); - i.onClick(() => { - const tr = (k) => t(this.app, k); - new BgColorModal(this.app, this.meBgColor, ({ hex, isLocal }) => this.setMediaBgColor(hex, isLocal, host, notePath, key), tr, { isClone }).open(); - }); - }); - } - if (!isClone && kind !== "note") { - const firstChild = host.children[0]; - const isVisible = !firstChild || !firstChild.style.opacity || parseFloat(firstChild.style.opacity) > 0; - menu.addItem((i) => { - i.setTitle(isVisible ? t(this.app, "makeInvisible") : t(this.app, "makeVisible")).setIcon(isVisible ? "eye-off" : "eye"); - i.onClick(async () => { - const newOpacity = isVisible ? "0" : "1"; - Array.from(host.children).forEach((child) => { - if (child instanceof HTMLElement && !child.classList.contains("ms-overlay")) { - child.style.opacity = newOpacity; - } - }); - await this.saveState(notePath, key, { opacity: parseFloat(newOpacity) }); - }); - }); - } - if (kind !== "note" && host.dataset.msDetached === "1") { - const isLocked = host.dataset.msLocked === "1"; - if (!isLocked) { - menu.addItem((item) => { - item.setTitle(t(this.app, "lock")).setIcon("lock"); - item.onClick(async () => { - host.dataset.msLocked = "1"; - await this.saveState(notePath, key, { locked: true }); - item.dom?.remove(); - }); - }); - } else { - menu.addItem((item) => { - item.setTitle(t(this.app, "unlock")).setIcon("unlock"); - item.onClick(async () => { - host.dataset.msLocked = "0"; - await this.saveState(notePath, key, { locked: false }); - item.dom?.remove(); - }); - }); - } - } - { - const noteHost = target.closest(".ms-note-host"); - if (noteHost) { - const key2 = noteHost.dataset.msKey; - const notePath2 = view?.file?.path ?? this.getActiveNote()?.path ?? ""; - menu.addSeparator(); - menu.addItem( - (mi) => mi.setTitle(t(this.app, "deleteNote")).onClick(async () => { - noteHost.remove(); - if (notePath2 && key2) { - delete this.notes[notePath2]?.originals?.[key2]; - await this.persistAll(); - } - }) - ); - } - } - }) - ); - this.addCommand({ - id: "ping", - name: t(this.app, "cmdPing"), - callback: () => new import_obsidian.Notice(t(this.app, "pingOk")) - }); - this.addCommand({ - id: "open-settings", - name: "Settings", - callback: () => { - new MediaLayoutSettingsModal(this.app, this).open(); - } - }); - this.addCommand({ - id: "export-logs", - name: "\u{1F4CB} Export debug logs to note", - callback: async () => { - await this.exportLogsToNote(); - } - }); - this.addCommand({ - id: "save-logs", - name: "\u{1F4BE} Save debug logs to file", - callback: async () => { - const path = await this.saveDebugLog(); - new import_obsidian.Notice(`\u{1F4BE} Logs saved to: ${path}`); - } - }); - this.addCommand({ - id: "clear-logs", - name: "\u{1F5D1}\uFE0F Clear debug logs", - callback: () => { - this.clearDebugLog(); - } - }); - this.addCommand({ - id: "toggle-debug", - name: "\u{1F50D} Toggle debug mode", - callback: () => { - this.enableDebugMode = !this.enableDebugMode; - new import_obsidian.Notice(`\u{1F50D} Debug mode: ${this.enableDebugMode ? "ON" : "OFF"}`); - this.info("Debug mode toggled", { enabled: this.enableDebugMode }); - if (this.enableDebugMode) { - this.autoSaveLogTimer = this.registerInterval(window.setInterval(() => { - this.saveDebugLog(); - }, 3e4)); - } else if (this.autoSaveLogTimer !== null) { - window.clearInterval(this.autoSaveLogTimer); - this.autoSaveLogTimer = null; - } - } - }); - this.addCommand({ - id: "create-backup", - name: "\u{1F4BE} Create backup now", - callback: async () => { - await this.createBackup(); - new import_obsidian.Notice("\u2705 Backup created successfully"); - } - }); - this.addCommand({ - id: "restore-backup", - name: "\u267B\uFE0F Restore from backup", - callback: async () => { - const backups = await this.listBackups(); - if (backups.length === 0) { - new import_obsidian.Notice("\u26A0\uFE0F No backups found"); - return; - } - const menu = new import_obsidian.Menu(); - backups.forEach((backup) => { - const dateStr = backup.date.toLocaleString(); - menu.addItem((item) => { - item.setTitle(`${dateStr}`).setIcon("clock").onClick(async () => { - const confirmed = await new Promise((resolve) => { - const modal = new import_obsidian.Modal(this.app); - modal.titleEl.setText("Restore from backup?"); - modal.contentEl.createEl("p", { - text: `This will restore data from: ${dateStr} -Current data will be overwritten.` - }); - const btnContainer = modal.contentEl.createDiv({ cls: "modal-button-container" }); - const btnRestore = btnContainer.createEl("button", { text: "Restore" }); - const btnCancel = btnContainer.createEl("button", { text: "Cancel" }); - btnRestore.addEventListener("click", () => { - modal.close(); - resolve(true); - }); - btnCancel.addEventListener("click", () => { - modal.close(); - resolve(false); - }); - modal.open(); - }); - if (confirmed) { - await this.restoreFromBackup(backup.path); - } - }); - }); - }); - menu.showAtMouseEvent(window.event); - } - }); - this.addCommand({ - id: "list-backups", - name: "\u{1F4CB} List all backups", - callback: async () => { - const backups = await this.listBackups(); - if (backups.length === 0) { - new import_obsidian.Notice("\u26A0\uFE0F No backups found"); - return; - } - let message = `Found ${backups.length} backup(s): - -`; - backups.forEach((backup, index) => { - message += `${index + 1}. ${backup.date.toLocaleString()} -`; - }); - new import_obsidian.Notice(message, 1e4); - console.log("[Media Layout] Available backups:", backups); - } - }); - const sb = this.addStatusBarItem(); - sb.setText(t(this.app, "sbReady")); - sb.setAttr("title", t(this.app, "sbTitle")); - sb.addEventListener("click", () => this.scanActive()); - this.backupTimer = this.registerInterval(window.setInterval(() => { - this.createBackup(); - }, 10 * 60 * 1e3)); - this.schedule(() => this.createBackup(), 1e4); - this.register(() => { - window.removeEventListener("beforeunload", this.emergencyFlush); - }); - window.addEventListener("beforeunload", this.emergencyFlush); - } - emergencyFlush = () => { - if (this.persistDebounceTimer !== null) { - clearTimeout(this.persistDebounceTimer); - this.persistDebounceTimer = null; - } - this.persistAll().catch((err) => { - console.error("[Media Layout] Emergency flush failed:", err); - }); - }; - async onunload() { - if (this.enableDebugMode && this.debugLog.length > 0) { - await this.saveDebugLog(); - } - if (this.autoSaveLogTimer) { - clearInterval(this.autoSaveLogTimer); - } - if (this.backupTimer) { - clearInterval(this.backupTimer); - this.backupTimer = null; - } - await this.flushPersist(); - await this.persistAll(); - this.observers.forEach((o) => o.disconnect()); - this.observers.length = 0; - } - log(level, message, data) { - const timestamp = (/* @__PURE__ */ new Date()).toISOString(); - const entry = { - timestamp, - level, - message, - data - }; - this.debugLog.push(entry); - if (this.debugLog.length > this.maxDebugLines) { - this.debugLog.shift(); - } - if (this.enableDebugMode || level === "WARN" || level === "ERROR") { - const dataStr = data ? ` ${JSON.stringify(data)}` : ""; - const fullMessage = `[Media Layout] [${level}] ${message}${dataStr}`; - switch (level) { - case "ERROR": - console.error(fullMessage); - break; - case "WARN": - console.warn(fullMessage); - break; - default: - console.log(fullMessage); - } - } - } - debug(message, data) { - this.log("DEBUG", message, data); - } - info(message, data) { - this.log("INFO", message, data); - } - warn(message, data) { - this.log("WARN", message, data); - } - error(message, data) { - this.log("ERROR", message, data); - } - async saveDebugLog() { - try { - const logPath = (0, import_obsidian.normalizePath)(`${this.manifest.dir}/debug.log`); - const logContent = this.debugLog.map((entry) => { - const dataStr = entry.data ? ` | Data: ${JSON.stringify(entry.data)}` : ""; - return `${entry.timestamp} [${entry.level}] ${entry.message}${dataStr}`; - }).join("\n"); - await this.app.vault.adapter.write(logPath, logContent); - this.logFilePath = logPath; - console.log("[Media Layout] Debug log saved:", logPath, `(${this.debugLog.length} entries)`); - return logPath; - } catch (err) { - console.error("[Media Layout] Failed to save debug log:", err); - throw err; - } - } - async exportLogsToNote() { - try { - const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5); - const noteName = `Media Layout Debug ${timestamp}.md`; - let content = `# Media Layout Debug Log - -`; - content += `Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()} -`; - content += `Total entries: ${this.debugLog.length} -`; - content += `Debug mode: ${this.enableDebugMode ? "ON" : "OFF"} - -`; - content += `--- - -`; - const byLevel = { - ERROR: this.debugLog.filter((e) => e.level === "ERROR"), - WARN: this.debugLog.filter((e) => e.level === "WARN"), - INFO: this.debugLog.filter((e) => e.level === "INFO"), - DEBUG: this.debugLog.filter((e) => e.level === "DEBUG") - }; - content += `## Summary - -`; - content += `- Errors: ${byLevel.ERROR.length} -`; - content += `- Warnings: ${byLevel.WARN.length} -`; - content += `- Info: ${byLevel.INFO.length} -`; - content += `- Debug: ${byLevel.DEBUG.length} - -`; - if (byLevel.ERROR.length > 0) { - content += `## Recent Errors - -`; - byLevel.ERROR.slice(-10).forEach((entry) => { - content += `### ${entry.timestamp} -`; - content += `**${entry.message}** -`; - if (entry.data) { - content += `\`\`\`json -${JSON.stringify(entry.data, null, 2)} -\`\`\` -`; - } - content += ` -`; - }); - } - content += `## Full Log - -`; - content += `\`\`\` -`; - this.debugLog.forEach((entry) => { - const dataStr = entry.data ? ` | ${JSON.stringify(entry.data)}` : ""; - content += `${entry.timestamp} [${entry.level}] ${entry.message}${dataStr} -`; - }); - content += `\`\`\` -`; - await this.app.vault.create(noteName, content); - new import_obsidian.Notice(`\u2705 Logs exported to: ${noteName}`); - const file = this.app.vault.getAbstractFileByPath(noteName); - if (file instanceof import_obsidian.TFile) { - await this.app.workspace.getLeaf(false).openFile(file); - } - } catch (err) { - this.error("Failed to export logs to note", { error: String(err) }); - new import_obsidian.Notice("\u274C Failed to export logs"); - } - } - clearDebugLog() { - this.debugLog = []; - this.info("Debug log cleared"); - new import_obsidian.Notice("\u{1F5D1}\uFE0F Debug log cleared"); - } - simpleHash(str) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = (hash << 5) - hash + str.charCodeAt(i); - hash = hash & hash; - } - return hash.toString(); - } - async createBackup() { - try { - const pluginDir = this.manifest.dir; - if (!pluginDir) { - console.warn("[Media Layout] Plugin dir is undefined, cannot create backup"); - return; - } - const data = { - notes: this.notes ?? {}, - presets: this.presets ?? {}, - meBgColor: this.meBgColor ?? "#242424", - userPresets: this.userPresets ?? [], - layoutPresets: this.layoutPresets ?? {}, - layoutNames: this.layoutNames ?? {}, - cloneAnimation: this.cloneAnimation, - cloneBgOpacity: this.cloneBgOpacity, - cloneControlsOpacity: this.cloneControlsOpacity, - cloneHideDelay: this.cloneHideDelay, - noteRestoreDelay: this.noteRestoreDelay, - cloneRestoreDelay: this.cloneRestoreDelay, - cloneShowAudioHandles: this.cloneShowAudioHandles, - enableModalAnimation: this.enableModalAnimation, - hideDetachButton: this.hideDetachButton, - cloneNearFile: this.cloneNearFile, - showHandlesOnHover: this.showHandlesOnHover, - backupTimestamp: (/* @__PURE__ */ new Date()).toISOString() - }; - const dataStr = JSON.stringify(data); - const currentHash = this.simpleHash(dataStr); - if (currentHash === this.lastBackupHash) { - console.log("[Media Layout] Data unchanged, skipping backup"); - return; - } - this.lastBackupHash = currentHash; - const hasNotes = this.notes && Object.keys(this.notes).length > 0; - if (!hasNotes) { - console.warn("[Media Layout] Skipping backup: this.notes is empty"); - return; - } - const now = /* @__PURE__ */ new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, "0"); - const day = String(now.getDate()).padStart(2, "0"); - const hour = String(now.getHours()).padStart(2, "0"); - const minute = String(now.getMinutes()).padStart(2, "0"); - const backupFileName = `${day}_${month}_${year}_${hour}\uA789${minute}_data_backup.json`; - const backupPath = (0, import_obsidian.normalizePath)(`${pluginDir}/${backupFileName}`); - await this.app.vault.adapter.write(backupPath, JSON.stringify(data, null, 2)); - await this.cleanupOldBackups(); - this.log("INFO", "Backup created successfully", { fileName: backupFileName }); - console.log(`[Media Layout] Backup created: ${backupFileName}`); - } catch (e) { - console.error("[Media Layout] Failed to create backup:", e); - this.error("Failed to create backup", { error: String(e) }); - } - } - async cleanupOldBackups() { - try { - const pluginDir = this.manifest.dir; - if (!pluginDir) return; - const files = await this.app.vault.adapter.list(pluginDir); - const backupFiles = files.files.filter((f) => f.endsWith("_data_backup.json")).map((f) => ({ - path: f, - name: f.split("/").pop() || f - })).sort((a, b) => b.name.localeCompare(a.name)); - if (backupFiles.length > 4) { - const toDelete = backupFiles.slice(4); - for (const file of toDelete) { - await this.app.vault.adapter.remove(file.path); - this.log("INFO", "Old backup removed", { fileName: file.name }); - console.log(`[Media Layout] Old backup removed: ${file.name}`); - } - } - } catch (e) { - console.error("[Media Layout] Failed to cleanup old backups:", e); - this.error("Failed to cleanup old backups", { error: String(e) }); - } - } - async listBackups() { - try { - const pluginDir = this.manifest.dir; - if (!pluginDir) return []; - const files = await this.app.vault.adapter.list(pluginDir); - const backupFiles = files.files.filter((f) => f.endsWith("_data_backup.json")).map((f) => { - const name = f.split("/").pop() || f; - const match = name.match(/^(\d{2})_(\d{2})_(\d{4})_(\d{2})꞉(\d{2})_data_backup\.json$/); - let date = /* @__PURE__ */ new Date(); - if (match) { - const [, day, month, year, hour, minute] = match; - date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute)); - } - return { path: f, name, date }; - }).sort((a, b) => b.date.getTime() - a.date.getTime()); - return backupFiles; - } catch (e) { - console.error("[Media Layout] Failed to list backups:", e); - return []; - } - } - async restoreFromBackup(backupPath) { - try { - const content = await this.app.vault.adapter.read(backupPath); - const data = JSON.parse(content); - this.notes = data.notes || {}; - this.presets = data.presets || {}; - this.userPresets = data.userPresets || []; - this.layoutPresets = data.layoutPresets || {}; - this.layoutNames = data.layoutNames || {}; - this.meBgColor = data.meBgColor || "#242424"; - this.showHandlesOnHover = data.showHandlesOnHover ?? true; - this.hideDetachButton = data.hideDetachButton ?? true; - this.cloneNearFile = data.cloneNearFile ?? true; - this.cloneAnimation = data.cloneAnimation || "popup"; - this.cloneBgOpacity = data.cloneBgOpacity ?? 1; - this.cloneControlsOpacity = data.cloneControlsOpacity ?? 0.35; - this.cloneHideDelay = data.cloneHideDelay ?? 2; - this.cloneShowAudioHandles = data.cloneShowAudioHandles ?? true; - this.cloneRestoreDelay = data.cloneRestoreDelay ?? 300; - this.noteRestoreDelay = data.noteRestoreDelay ?? 200; - this.enableModalAnimation = data.enableModalAnimation ?? true; - await this.persistAll(); - const fileName = backupPath.split("/").pop() || backupPath; - new import_obsidian.Notice(`\u2714\uFE0F Data restored from: ${fileName}`); - this.log("INFO", "Backup restored successfully", { fileName }); - this.scanActive(); - } catch (e) { - console.error("[Media Layout] Failed to restore from backup:", e); - this.error("Failed to restore from backup", { error: String(e) }); - new import_obsidian.Notice("\u274C Failed to restore from backup"); - } - } - scanActive() { - this.disconnectObservers(); - const root = this.getActiveContainerEl(); - if (!root) return; - const notePath = this.getActiveNote()?.path ?? ""; - this.currentNotePath = notePath; - this.processRoot(root, notePath); - const mo = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "childList") { - mutation.addedNodes.forEach((node) => { - if (node instanceof HTMLElement) { - const currentPath = this.getActiveNote()?.path ?? ""; - if (currentPath) { - this.processRoot(node, currentPath); - } - } - }); - } - } - }); - mo.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "src"] }); - this.observers.push(mo); - this.restoreClonesFromStore(notePath); - this.ensureNotesFromStore(notePath); - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (view) { - setTimeout(() => { - this.app.workspace.trigger("markdown-render", view.contentEl); - }, 100); - } - } - disconnectObservers() { - this.observers.forEach((o) => o.disconnect()); - this.observers.length = 0; - } - processRoot(rootEl, notePath) { - const elementsToProcess = rootEl.matches(".internal-embed, .media-embed, .mx-media-embed, img, video, audio, .ms-note-host") ? [rootEl] : Array.from(rootEl.querySelectorAll( - ".internal-embed, .media-embed, .mx-media-embed, img, video, audio, .markdown-reading-view video, .markdown-preview-view video, .ms-note-host" - )); - const nodes = elementsToProcess; - nodes.forEach((hostLike, idx) => { - const isNoteHost = hostLike.classList?.contains?.("ms-note-host"); - const host = isNoteHost ? hostLike : ["IMG", "VIDEO", "AUDIO"].includes(hostLike.tagName) ? hostLike.closest(".internal-embed, .media-embed, .mx-media-embed") ?? hostLike : hostLike; - if (!(host instanceof HTMLElement)) return; - if (host.dataset.msInit === "1") return; - const media = isNoteHost ? host.querySelector(".ms-note") : host.querySelector("video, img, audio") ?? (["IMG", "VIDEO", "AUDIO"].includes(host.tagName) ? host : null); - if (!media) return; - const tag = isNoteHost ? "NOTE" : (media.tagName || "").toUpperCase(); - const kind = tag === "NOTE" ? "note" : tag === "IMG" ? "image" : tag === "AUDIO" ? "audio" : "video"; - host.dataset.msInit = "1"; - host.classList.add("ms-host"); - const { src, base } = isNoteHost ? { src: "", base: "" } : this.getSrcAndBase(host, media); - const key = isNoteHost ? host.dataset.msKey ?? this.makeNoteKey() : this.makeEmbedKey(src); - host.dataset.msKey = key; - const overlay = this.ensureOverlay(host); - const handles = this.ensureHandles(overlay, false, kind === "note"); - host.append(overlay); - if (kind === "image") { - overlay.classList.add("--img"); - const onDown = (e) => { - const m = e; - if (m.button !== 0) return; - window.getSelection()?.removeAllRanges(); - }; - overlay.addEventListener("pointerdown", onDown, { capture: true }); - overlay.addEventListener("mousedown", onDown, { capture: true }); - const killAfterUp = (e) => { - const m = e; - if (m.button !== 0) return; - e.preventDefault(); - e.stopImmediatePropagation?.(); - requestAnimationFrame(() => window.getSelection()?.removeAllRanges()); - }; - overlay.addEventListener("mouseup", killAfterUp, { capture: true }); - overlay.addEventListener("click", killAfterUp, { capture: true }); - overlay.addEventListener("dblclick", killAfterUp, { capture: true }); - } - this.applyKindBaseStyles(host, media, kind); - const saved = this.loadStateWithFallback(notePath, key, base); - if (kind === "image" && saved) { - console.log(`\u{1F7E2} [RESTORE] Applying saved state for image:`, { - key, - savedW: saved.w, - savedH: saved.h, - domW: host.getBoundingClientRect().width, - domH: host.getBoundingClientRect().height - }); - } - if (saved) this.applyState(host, media, kind, saved); - else { - if (kind !== "image" && kind !== "note") { - const rect = host.getBoundingClientRect(); - if (rect.width) this.applyState(host, media, kind, { w: Math.round(rect.width) }); - } - } - this.mountControlsForHost(host, media); - this._attachDragMoveHandler(host, overlay, notePath, key, kind); - this._attachResizeHandlers(host, overlay, handles, notePath, key, kind, media); - }); - } - async applyAndSaveSize(notePath, key, host, media, kind, patch, forceFreeForImage) { - const cur = this.readAbsRect(host); - const next = { - w: patch.w ?? cur.w, - h: patch.h ?? cur.h, - x: typeof patch.x === "number" ? patch.x : cur.x || 0, - y: typeof patch.y === "number" ? patch.y : cur.y || 0, - free: forceFreeForImage ? true : host.dataset.msFree === "1" - }; - const wasDetached = host.dataset.msDetached === "1"; - next.detached = patch?.detached ?? wasDetached; - if (kind === "image" && !forceFreeForImage) { - const ar = this.getAR("image", media); - if (typeof patch.w === "number" && typeof patch.h !== "number") next.h = Math.round(patch.w / ar); - if (typeof patch.h === "number" && typeof patch.w !== "number") next.w = Math.round(patch.h * ar); - } - if (patch.reset) { - next.x = 0; - next.y = 0; - next.w = void 0; - next.h = void 0; - next.free = false; - next.detached = false; - next.locked = false; - this.applyKindBaseStyles(host, media, kind); - this.reattach(host); - } else { - this.applyState(host, media, kind, next); - } - await this.saveState(notePath, key, next); - return next; - } - async applyLayout(notePath, layout) { - const root = this.getAttachRoot(); - if (root) { - for (const [key, st] of Object.entries(layout)) { - if (!key.includes("::clone::")) continue; - const existingClone = root.querySelector(`[data-ms-key="${CSS.escape(key)}"]`); - if (existingClone) { - const media = existingClone.querySelector("video, img, audio"); - const tag = media?.tagName?.toUpperCase(); - const kind = tag === "IMG" ? "image" : tag === "AUDIO" ? "audio" : "video"; - if (media) { - this.applyState(existingClone, media, kind, st); - await this.saveState(notePath, key, st, false); - } - continue; - } - let parentKey = st.parentKey; - const parentAnchor = st.parentAnchor; - let originalHost = parentKey ? this.findHostByKey(parentKey) : null; - if (!originalHost && parentAnchor) { - const anchorIndex = this.buildAnchorIndex(root); - const remappedKey = anchorIndex.get(parentAnchor); - if (remappedKey) { - parentKey = remappedKey; - originalHost = this.findHostByKey(parentKey); - } - } - if (!originalHost && parentKey) { - const virtualHost = await this.createHostFromTFile(notePath, parentKey); - if (virtualHost) { - originalHost = virtualHost; - } - } - if (!originalHost || !parentKey) { - console.warn(`[MS] applyLayout: Could not find or create original for clone ${key}. Skipping.`); - continue; - } - const cloneHost = this.createCleanCloneElement(originalHost); - const cloneState = this.notes[notePath]?.clones?.[key]; - const fileNameFromStore = cloneState?.fileName; - const fileNameFromPreset = st?.fileName; - let fileName = fileNameFromStore ?? fileNameFromPreset; - if (!fileName && parentKey) { - const originalHost2 = this.findHostByKey(parentKey); - const mediaEl = originalHost2?.querySelector("video, audio, img"); - if (originalHost2 && mediaEl) { - const { base } = this.getSrcAndBase(originalHost2, mediaEl); - fileName = base || void 0; - } - } - await this.saveState(notePath, key, st, false); - this.createCloneHost(root, cloneHost, key, parentKey, st.x ?? 0, st.y ?? 0, true, fileName); - } - } - const hosts = this.enumerateHostsInNote(notePath); - if (!hosts.length) return; - const toMove = []; - const noteData = this.notes[notePath] ??= { originals: {}, clones: {} }; - const originals = noteData.originals ??= {}; - const clones = noteData.clones ??= {}; - for (const { host, media, kind, key } of hosts) { - const st0 = layout[key]; - if (!st0) continue; - const prev = originals[key] ?? {}; - const st = { - ...st0, - detached: st0.detached ?? host.dataset.msDetached === "1", - locked: st0.locked ?? host.dataset.msLocked === "1", - x: st0.x ?? 0, - y: st0.y ?? 0, - bgColor: st0.bgColor, - headerColor: st0.headerColor, - transparent: st0.transparent, - txt: st0.txt, - fontSize: st0.fontSize, - textStrokeColor: st0.textStrokeColor, - textStroke: st0.textStroke, - textColor: st0.textColor, - free: st0.free ?? prev.free ?? false, - volume: st0.volume - }; - if (!key.includes("::clone::") && kind !== "note") continue; - if (kind === "note") { - host.classList.remove("ms-note-transparent"); - if (st.bgColor || st.headerColor) st.transparent = false; - } - const hasRotate = st.rotate !== void 0 && st.rotate !== 0; - let w, h; - if (st.w !== void 0 && st.h !== void 0) { - w = st.w; - h = st.h; - } else if (hasRotate) { - w = parseFloat(host.style.width) || host.offsetWidth || media?.clientWidth || 0; - h = parseFloat(host.style.height) || host.offsetHeight || media?.clientHeight || 0; - } else { - w = host.getBoundingClientRect().width || media?.clientWidth || 0; - h = host.getBoundingClientRect().height || media?.clientHeight || 0; - } - if (st.detached) { - this.ensureParentAnchor(host); - host.style.position = "absolute"; - host.style.left = "0px"; - host.style.top = "0px"; - host.dataset.msDetached = "1"; - host.dataset.msLocked = st.locked ? "1" : "0"; - } else { - host.dataset.msDetached = "0"; - host.dataset.msLocked = "0"; - this.reattach(host); - } - this.applyBox(host, media, kind, w, h, st.free === true); - const rotateAngle = typeof st.rotate === "number" ? st.rotate : void 0; - this.applyTransform(host, st.x || 0, st.y || 0, rotateAngle); - if (host.dataset.msParentKey) { - clones[key] = { - ...clones[key] || {}, - parentKey: host.dataset.msParentKey, - w, - h, - x: st.x, - y: st.y, - locked: !!st.locked, - volume: st.volume, - zIndex: st.zIndex, - bgColor: st.bgColor, - opacity: st.opacity, - rotate: st.rotate - }; - } else if (key.includes("::clone::")) { - clones[key] = { - ...clones[key] || {}, - parentKey: st.parentKey, - w, - h, - x: st.x, - y: st.y, - locked: !!st.locked, - rotate: st.rotate - }; - } else { - originals[key] = { - ...prev, - w, - h, - x: st.x, - y: st.y, - free: !!st.free, - detached: !!st.detached, - zIndex: st.zIndex, - opacity: st.opacity, - transparent: st.transparent, - bgColor: st.bgColor, - textStrokeColor: st.textStrokeColor, - textStroke: st.textStroke, - textColor: st.textColor, - headerColor: st.headerColor, - locked: !!st.locked, - rotate: st.rotate - }; - } - toMove.push({ host, x: st.x || 0, y: st.y || 0 }); - this.applyState(host, media, kind, st); - this.debug("applyLayout: z-index applied", { - key, - zIndexInState: st.zIndex, - zIndexInDOM: host.style.zIndex, - isClone: !!host.dataset.msParentKey - }); - await this.saveState(notePath, key, st, false); - } - await new Promise((r) => requestAnimationFrame(() => r())); - for (const { host, x, y } of toMove) this.applyTransform(host, x, y); - const currentNotePath = this.getActiveNote()?.path; - if (currentNotePath) { - const attachRoot = this.getAttachRoot(); - if (attachRoot) { - const layer = this.ensureFloatLayer(attachRoot); - for (const [key, st] of Object.entries(layout)) { - if (key.startsWith("note::")) { - const exists = layer.querySelector(`.ms-note-host[data-ms-key="${CSS.escape(key)}"]`); - if (!exists) { - const isTransparent = st.bgColor || st.headerColor ? false : st.transparent; - const host = this.createNoteHost(attachRoot, key, st.x ?? 0, st.y ?? 0, { - w: st.w, - h: st.h, - txt: st.txt ?? "", - fontSize: st.fontSize ?? 16, - bgColor: st.bgColor, - headerColor: st.headerColor - }); - if (st.transparent) { - host.classList.add("ms-note-transparent"); - } - if (isTransparent) { - host.querySelector(".ms-note-body").style.color = st.textColor; - host.classList.add("ms-note-transparent"); - } - if (currentNotePath) { - await this.saveState(currentNotePath, key, st); - } - } - } - } - } - } - for (const { host } of toMove) { - if (host.dataset.msInit !== "1") { - host.dataset.msInit = "1"; - const overlay = this.ensureOverlay(host); - const isNote = host.dataset.msKind === "note"; - const handles = this.ensureHandles(overlay, false, isNote); - host.append(overlay); - } - } - for (const { host, x, y } of toMove) { - const r = this.readAbsRect(host); - const dx = x - (r.x || 0); - const dy = y - (r.y || 0); - if (dx !== 0 || dy !== 0) { - const st = getComputedStyle(host); - const m = /matrix\(([^)]+)\)/.exec(st.transform || ""); - const tx = m ? parseFloat(m[1].split(",")[4]) || 0 : 0; - const ty = m ? parseFloat(m[1].split(",")[5]) || 0 : 0; - this.applyTransform(host, tx + dx, ty + dy); - } - } - const stopAt = Date.now() + 500; - const realign = (host, x, y) => { - const r = this.readAbsRect(host); - const dx = x - (r.x || 0); - const dy = y - (r.y || 0); - if (dx || dy) { - const st = getComputedStyle(host); - const m = /matrix\(([^)]+)\)/.exec(st.transform || ""); - const tx = m ? parseFloat(m[1].split(",")[4]) || 0 : 0; - const ty = m ? parseFloat(m[1].split(",")[5]) || 0 : 0; - this.applyTransform(host, tx + dx, ty + dy); - } - }; - const observers = []; - for (const { host, x, y } of toMove) { - const ro = new ResizeObserver(() => { - if (Date.now() > stopAt) return; - realign(host, x, y); - }); - ro.observe(host); - observers.push(ro); - setTimeout(() => { - if (Date.now() <= stopAt) realign(host, x, y); - }, 10); - setTimeout(() => { - if (Date.now() <= stopAt) realign(host, x, y); - }, 100); - setTimeout(() => { - if (Date.now() <= stopAt) realign(host, x, y); - }, 300); - requestAnimationFrame(() => realign(host, x, y)); - } - setTimeout(() => observers.forEach((o) => o.disconnect()), 600); - } - async saveLayoutToState(notePath, layout) { - for (const [key, st] of Object.entries(layout)) { - await this.saveState(notePath, key, st); - } - } - applyState(host, media, kind, st) { - const isClone = !!host.dataset.msParentKey; - host.dataset.msKind = kind; - const hasRotate = (isClone || kind === "note") && typeof st.rotate === "number" && st.rotate !== 0; - let w, h; - if (st.w !== void 0 && st.h !== void 0) { - w = st.w; - h = st.h; - } else if (hasRotate) { - w = parseFloat(host.style.width) || host.offsetWidth || media?.clientWidth || 0; - h = parseFloat(host.style.height) || host.offsetHeight || media?.clientHeight || 0; - } else { - w = host.getBoundingClientRect().width || media?.clientWidth || 0; - h = host.getBoundingClientRect().height || media?.clientHeight || 0; - } - if (isClone && (typeof st.w !== "number" || typeof st.h !== "number")) { - this.debug("applyState: Clone has missing size in state, using fallback", { - key: host.dataset.msKey, - hasW: typeof st.w === "number", - hasH: typeof st.h === "number", - fallbackW: w, - fallbackH: h - }); - } - this.applyBox(host, media, kind, w, h, st.free === true); - if (st.detached || isClone || kind === "note") { - this.ensureParentAnchor(host); - host.style.position = "absolute"; - host.style.left = "0px"; - host.style.top = "0px"; - host.dataset.msDetached = "1"; - const rotateAngle = (isClone || kind === "note") && typeof st.rotate === "number" ? st.rotate : void 0; - this.applyTransform(host, st.x || 0, st.y || 0, rotateAngle); - host.dataset.msLocked = st.locked ? "1" : "0"; - } else { - host.dataset.msDetached = "0"; - host.dataset.msLocked = "0"; - if (!isClone) this.reattach(host); - } - if (isClone && typeof st.volume === "number" && media instanceof HTMLMediaElement) { - media.volume = clamp(st.volume, 0, 1); - media.muted = st.volume === 0; - } - if (isClone && st.bgColor) { - const localBg = this.ensureLocalBg(host); - if (st.bgColor === "transparent") { - host.querySelector(".ms-media-bg")?.remove(); - localBg.remove(); - return; - } - localBg.style.backgroundColor = st.bgColor; - } - if ((isClone || st.detached) && typeof st.zIndex === "number") { - host.style.zIndex = String(st.zIndex); - } - if (isClone && kind === "image" && st.link) { - this.applyLinkOverlay(host, st.link); - } - if (!isClone && typeof st.opacity === "number") { - const opacityValue = String(st.opacity); - Array.from(host.children).forEach((child) => { - if (child instanceof HTMLElement && !child.classList.contains("ms-overlay")) { - child.style.opacity = opacityValue; - } - }); - } - if (kind === "note") { - const noteBody = host.querySelector(".ms-note"); - const textBody = host.querySelector(".ms-note-body"); - if (textBody) { - textBody.textContent = st.txt ?? ""; - textBody.style.fontSize = `${Math.max(10, st.fontSize ?? 16)}px`; - } - const noteHeader = host.querySelector(".ms-note-top"); - if (noteBody) { - noteBody.style.backgroundColor = st.bgColor || ""; - } - const isTransparent = st.bgColor || st.headerColor ? false : st.transparent; - if (noteHeader) { - const headerColor = st.headerColor; - noteHeader.style.backgroundColor = headerColor || ""; - } - if (isTransparent) { - host.classList.add("ms-note-transparent"); - } - if (kind === "note") { - const noteBody2 = host.querySelector(".ms-note-body"); - if (noteBody2) { - const hasStroke = !!st.textStroke; - noteBody2.style.color = st.textColor || ""; - noteBody2.classList.toggle("ms-note-text-stroked", hasStroke); - if (hasStroke) { - noteBody2.style.setProperty("--note-stroke-color", st.textStrokeColor || st.textColor || "#FFFFFF"); - } - } - } - } - } - applyBox(host, media, kind, w, h, freeImage = false) { - if (kind === "image") { - console.log(`\u{1F7E1} [APPLY BOX] Setting image size:`, { - key: host.dataset.msKey, - applyW: w, - applyH: h, - currentDomW: host.getBoundingClientRect().width, - free: freeImage - }); - } - if (kind === "video") { - host.style.width = `${Math.round(w)}px`; - host.style.maxWidth = `${Math.round(w)}px`; - const isClone = !!host.dataset.msParentKey; - const hasME = this.isMediaExtendedActive(host); - const videoAR = isClone || hasME ? 16 / 9 : media ? this.getAR(kind, media) : 16 / 9; - host.style.aspectRatio = `${videoAR}`; - if (isClone) { - host.style.height = `${Math.round(w / videoAR)}px`; - } - if (media) media.style.width = "100%"; - if (media) media.style.height = "100%"; - if (media) media.style.objectFit = "contain"; - if (media) media.style.objectPosition = "center center"; - } else if (kind === "audio") { - host.style.width = `${Math.round(w)}px`; - host.style.maxWidth = `${Math.round(w)}px`; - if (host.dataset.msParentKey) { - host.style.height = "70px"; - } else { - host.style.aspectRatio = "12 / 1"; - } - if (media) media.style.width = "100%"; - if (media) media.style.height = "100%"; - } else if (kind === "note") { - const iw = Math.round(w); - const ih = Math.max(60, Math.round(typeof h === "number" ? h : 160)); - host.style.width = `${iw}px`; - host.style.maxWidth = `${iw}px`; - host.style.height = `${ih}px`; - host.style.aspectRatio = "auto"; - host.style.display = "block"; - } else { - if (!media) return; - const ar = this.getAR("image", media); - const iw = Math.round(w); - const ih = Math.max(1, Math.round(typeof h === "number" ? h : iw / ar)); - const isWrapped = host !== media; - if (isWrapped) { - host.style.width = `${iw}px`; - host.style.maxWidth = `${iw}px`; - host.style.height = `${ih}px`; - host.style.aspectRatio = "auto"; - if (media) media.style.width = "100%"; - if (media) media.style.height = "100%"; - if (media) media.style.objectFit = "fill"; - } - host.dataset.msFree = freeImage ? "1" : "0"; - } - host.style.display = "block"; - } - readAbsRect(host) { - const sizer = host.closest(".cm-sizer, .markdown-preview-sizer"); - if (!sizer) { - console.warn("[Media Layout] readAbsRect: No sizer found for", host.dataset.msKey, "- returning zeros"); - return { x: 0, y: 0, w: 0, h: 0 }; - } - const isClone = !!host.dataset.msParentKey; - const isNote = host.dataset.msKind === "note"; - const hasRotate = (isClone || isNote) && host.style.transform && host.style.transform.includes("rotate("); - if (hasRotate) { - const transformMatch = host.style.transform.match(/translate\(([^,)]+)(?:px)?(?:,\s*|\s+)([^)]+)(?:px)?\)/); - let x2 = 0, y2 = 0; - if (transformMatch) { - x2 = parseFloat(transformMatch[1]) || 0; - y2 = parseFloat(transformMatch[2]) || 0; - } - const isInFloatLayer = !!host.closest(".ms-float-layer"); - const isReadingMode = !!host.closest(".markdown-reading-view, .markdown-preview-view"); - if (isInFloatLayer && isReadingMode) { - x2 = x2 - 29; - y2 = y2 + 53; - } - const w2 = parseFloat(host.style.width) || host.offsetWidth; - const h2 = parseFloat(host.style.height) || host.offsetHeight; - return { x: x2, y: y2, w: w2, h: h2 }; - } - const hostRect = host.getBoundingClientRect(); - const sizerRect = sizer.getBoundingClientRect(); - const x = hostRect.left - sizerRect.left; - const y = hostRect.top - sizerRect.top; - const w = hostRect.width; - const h = hostRect.height; - return { x, y, w, h }; - } - readAbsOrig(host) { - const st = getComputedStyle(host); - const w = parseFloat(st.width) || void 0; - const h = parseFloat(st.height) || void 0; - const tr = st.transform; - let x = 0, y = 0; - if (tr && tr !== "none") { - const m = tr.match(/matrix\(([^)]+)\)/); - if (m) { - const parts = m[1].split(",").map((s) => parseFloat(s.trim())); - x = parts[4] || 0; - y = parts[5] || 0; - } - } - const free = host.dataset.msFree === "1"; - return { w, h, x, y, free }; - } - readVisualRectForSave(host) { - const root = this.getHostRoot(host); - const r = host.getBoundingClientRect(); - if (!root) return { x: r.left, y: r.top, w: r.width, h: r.height }; - const rp = root.getBoundingClientRect(); - const cs = getComputedStyle(root); - const originLeft = rp.left + (parseFloat(cs.borderLeftWidth) || 0) - root.scrollLeft; - const originTop = rp.top + (parseFloat(cs.borderTopWidth) || 0) - root.scrollTop; - return { x: r.left - originLeft, y: r.top - originTop, w: r.width, h: r.height }; - } - applyTransform(host, x, y, rotate) { - const isInFloatLayer = !!host.closest(".ms-float-layer"); - const isReadingMode = !!host.closest(".markdown-reading-view, .markdown-preview-view"); - let finalX = x; - let finalY = y; - if (isInFloatLayer && isReadingMode) { - finalX = x + 29; - finalY = y - 53; - } - let rotateValue = ""; - if (typeof rotate === "number") { - rotateValue = ` rotate(${rotate}deg)`; - } else { - const currentTransform = host.style.transform || ""; - const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/); - rotateValue = rotateMatch ? ` ${rotateMatch[0]}` : ""; - } - host.style.transform = `translate(${finalX}px, ${finalY}px)${rotateValue}`; - if (this.enableDebugMode && host.dataset.msParentKey) { - const leafCount = this.app.workspace.getLeavesOfType("markdown").length; - this.debug("applyTransform to clone", { - cloneId: host.dataset.msKey, - x: finalX, - y: finalY, - isReadingMode, - isInFloatLayer, - originalX: x, - originalY: y, - leafCount - }); - } - } - readAppliedRect(host) { - return this.readAbsRect(host); - } - async saveState(notePath, key, partial, autoPersist = true) { - if (!notePath) return; - this.notes ||= {}; - this.notes[notePath] ||= { originals: {}, clones: {} }; - const isCloneKey = key.includes("::clone::"); - const isCloneDom = !!this.getActiveContainerEl()?.querySelector(`[data-ms-key="${CSS.escape(key)}"][data-ms-parent-key]`); - const isClone = isCloneKey || isCloneDom; - const bucket = isClone ? this.notes[notePath].clones : this.notes[notePath].originals; - if (!bucket[key]) { - bucket[key] = {}; - } - const sanitized = {}; - for (const [k, v] of Object.entries(partial)) { - if (k === "x" || k === "y" || k === "w" || k === "h" || k === "rotate" || k === "zIndex" || k === "opacity" || k === "volume" || k === "fontSize") { - const num = Number(v); - if (!Number.isFinite(num)) { - console.warn(`[Media Layout] Invalid ${k} value: ${v}, skipping`); - continue; - } - if ((k === "w" || k === "h") && num <= 0) { - console.warn(`[Media Layout] Invalid ${k} value (must be > 0): ${v}, skipping`); - continue; - } - if ((k === "opacity" || k === "volume") && (num < 0 || num > 1)) { - sanitized[k] = Math.max(0, Math.min(1, num)); - continue; - } - sanitized[k] = num; - } else if (k === "free" || k === "detached" || k === "locked" || k === "transparent" || k === "textStroke" || k === "showAudioHandles") { - sanitized[k] = Boolean(v); - } else if (k === "txt" || k === "bgColor" || k === "headerColor" || k === "textColor" || k === "textStrokeColor" || k === "link" || k === "fileName" || k === "animation" || k === "dock" || k === "parentKey" || k === "parentAnchor") { - if (typeof v === "string" || v === void 0) { - sanitized[k] = v; - } else { - console.warn(`[Media Layout] Invalid ${k} value (expected string): ${v}, skipping`); - } - } else if (k === "bgOpacity" || k === "controlsOpacity" || k === "hideDelay" || k === "order") { - const num = Number(v); - if (Number.isFinite(num) && num >= 0) { - sanitized[k] = num; - } else { - console.warn(`[Media Layout] Invalid ${k} value: ${v}, skipping`); - } - } else { - sanitized[k] = v; - } - } - bucket[key] = { ...bucket[key] || {}, ...sanitized }; - if (partial.w !== void 0 || partial.h !== void 0) { - const hostEl = this.getActiveContainerEl()?.querySelector(`[data-ms-key="${CSS.escape(key)}"]`); - const kind = hostEl?.dataset.msKind; - if (kind === "image") { - console.log(`\u{1F535} [SAVE] Image size change:`, { - key, - newW: partial.w, - newH: partial.h, - currentInMemory: { w: bucket[key].w, h: bucket[key].h }, - stackTrace: new Error().stack?.split("\n").slice(2, 4).join("\n") - }); - } - } - this.hasPendingChanges = true; - if (autoPersist === "immediate") { - await this.persistAll(); - } else if (autoPersist === true) { - this.persistAllDebounced(); - } - } - async persistAll() { - if (this.persistInProgress) { - if (this.persistQueue) { - await this.persistQueue; - } - if (this.hasPendingChanges) { - return this.persistAll(); - } - return; - } - this.persistInProgress = true; - const currentPersist = (async () => { - try { - if (!this.notes || typeof this.notes !== "object") { - console.error("[Media Layout] CRITICAL: this.notes is invalid, aborting save to prevent data loss!", { - notesType: typeof this.notes, - notesValue: this.notes - }); - return; - } - const hasNotes = Object.keys(this.notes).length > 0; - if (!hasNotes && this.lastPersistTime > 0) { - console.error("[Media Layout] CRITICAL: this.notes is empty but we had data before, aborting save to prevent data loss!"); - return; - } - const hadChangesAtStart = this.hasPendingChanges; - this.hasPendingChanges = false; - const data = { - notes: this.notes, - presets: this.presets ?? {}, - meBgColor: this.meBgColor ?? "#242424", - userPresets: this.userPresets ?? [], - layoutPresets: this.layoutPresets ?? {}, - layoutNames: this.layoutNames ?? {}, - cloneAnimation: this.cloneAnimation, - cloneBgOpacity: this.cloneBgOpacity, - cloneControlsOpacity: this.cloneControlsOpacity, - cloneHideDelay: this.cloneHideDelay, - noteRestoreDelay: this.noteRestoreDelay, - cloneRestoreDelay: this.cloneRestoreDelay, - cloneShowAudioHandles: this.cloneShowAudioHandles, - enableModalAnimation: this.enableModalAnimation, - hideDetachButton: this.hideDetachButton, - cloneNearFile: this.cloneNearFile, - showHandlesOnHover: this.showHandlesOnHover - }; - await this.saveData(data); - this.lastPersistTime = Date.now(); - } catch (error) { - console.error("[Media Layout] Failed to persist data:", error); - this.hasPendingChanges = true; - } finally { - this.persistInProgress = false; - this.persistQueue = null; - } - })(); - this.persistQueue = currentPersist; - await currentPersist; - if (this.hasPendingChanges) { - return this.persistAll(); - } - } - persistAllDebounced() { - if (!this.hasPendingChanges) { - return; - } - const now = Date.now(); - const timeSinceLastPersist = now - this.lastPersistTime; - if (timeSinceLastPersist >= this.maxPersistDelay) { - if (this.persistDebounceTimer !== null) { - clearTimeout(this.persistDebounceTimer); - this.persistDebounceTimer = null; - } - this.persistAll().catch((err) => { - console.error("[Media Layout] Force persist failed:", err); - }); - return; - } - if (this.persistDebounceTimer !== null) { - clearTimeout(this.persistDebounceTimer); - } - this.persistDebounceTimer = window.setTimeout(() => { - this.persistDebounceTimer = null; - if (this.hasPendingChanges) { - this.persistAll().catch((err) => { - console.error("[Media Layout] Debounced persist failed:", err); - }); - } - }, this.persistDebounceDelay); - } - async flushPersist() { - if (this.persistDebounceTimer !== null) { - clearTimeout(this.persistDebounceTimer); - this.persistDebounceTimer = null; - } - await this.persistAll(); - } - addUserPreset(n) { - this.userPresets = [n, ...this.userPresets.filter((v) => v !== n)].slice(0, MAX_WIDTH_PRESETS); - return this.persistAll(); - } - removeUserPreset(n) { - this.userPresets = this.userPresets.filter((v) => v !== n); - return this.persistAll(); - } - loadStateWithFallback(notePath, key, basename) { - const noteData = this.notes[notePath]; - if (!noteData) return void 0; - const originals = noteData.originals ?? {}; - const byKey = originals[key]; - if (byKey) return byKey; - if (basename) { - for (const k of Object.keys(originals)) { - if (k.startsWith("note::") || k.includes("::clone::")) continue; - const b = this.extractBaseFromKey(k); - if (b && b === basename) { - const stateToMigrate = originals[k]; - originals[key] = stateToMigrate; - this.persistAll(); - console.log(`Media-Size: Migrated state from old key "${k}" to new key "${key}"`); - return stateToMigrate; - } - } - } - return void 0; - } - isMediaExtendedActive(host) { - const hasPlugin = this.app.plugins?.enabledPlugins?.has?.("media-extended") || this.app.plugins?.plugins?.["media-extended"]; - if (host) { - const hasMEContainer = !!host.closest(".mx-media-embed, .media-embed"); - return hasPlugin && hasMEContainer; - } - return !!hasPlugin; - } - getAR(kind, media) { - if (kind === "image") { - const img = media; - const iw = img.naturalWidth || img.clientWidth || 1; - const ih = img.naturalHeight || img.clientHeight || 1; - return iw / Math.max(1, ih); - } - if (kind === "video") { - const vid = media; - const vw = vid.videoWidth || 1920; - const vh = vid.videoHeight || 1080; - return vw / Math.max(1, vh); - } - return 9 / 1; - } - getAttachRoot() { - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (!view) return null; - const src = view.containerEl.querySelector(".markdown-source-view .cm-scroller"); - const read = view.containerEl.querySelector(".markdown-reading-view"); - const readVisible = read ? getComputedStyle(read).display !== "none" : false; - return readVisible ? read || src : src || read; - } - getHostRoot(host) { - const containerEl = host.closest(".workspace-leaf-content"); - if (!containerEl) return null; - const src = containerEl.querySelector(".markdown-source-view .cm-scroller"); - if (src) return src; - const read = containerEl.querySelector(".markdown-reading-view"); - if (read) return read; - return null; - } - getHostContainerEl(host) { - return host.closest(".workspace-leaf-content"); - } - isControlsTarget(el) { - if (!el) return false; - return !!el.closest( - ".ob-video-controls, .media-controls, .mejs__controls, .plyr__controls, .vjs-control-bar, .me-controls, [class*='controls']" - ); - } - isControlsPoint(host, x, y) { - const sels = [ - ".ob-video-controls", - ".media-controls", - ".mejs__controls", - ".plyr__controls", - ".vjs-control-bar", - ".me-controls", - "[class*='controls']" - ]; - let panel = null; - for (const s of sels) { - panel = host.querySelector(s); - if (panel) break; - } - if (!panel) return false; - const btns = panel.querySelectorAll("button, [role='button'], .plyr__control, .vjs-control"); - for (const b of Array.from(btns)) { - const r = b.getBoundingClientRect(); - if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return true; - } - return false; - } - extractBaseFromKey(key) { - const parts = key.split("::"); - const fromSrc = parts.find((p) => p.includes("/")) ?? parts[0]; - const lastSlash = Math.max(fromSrc.lastIndexOf("/"), fromSrc.lastIndexOf("\\")); - return fromSrc.substring(lastSlash + 1) || null; - } - getSrcAndBase(host, el) { - const s = host.getAttribute("src") || host.getAttribute("alt") || el.getAttribute("src") || ""; - const lastSlash = Math.max(s.lastIndexOf("/"), s.lastIndexOf("\\")); - const base = s.substring(lastSlash + 1); - return { src: s, base }; - } - realignClonesIfNeeded(notePath) { - const leaves = this.app.workspace.getLeavesOfType("markdown").filter((leaf) => { - const view = leaf.view; - return view instanceof import_obsidian.MarkdownView && view.file?.path === notePath; - }); - if (!leaves.length) return; - const noteData = this.notes[notePath]; - if (!noteData?.clones) return; - leaves.forEach((leaf) => { - const view = leaf.view; - if (!(view instanceof import_obsidian.MarkdownView)) return; - const containerEl = view.containerEl; - if (!containerEl) return; - for (const [key, st] of Object.entries(noteData.clones)) { - const host = containerEl.querySelector(`[data-ms-key="${CSS.escape(key)}"]`); - if (!host || !st.x || !st.y) continue; - const r = this.readAbsRect(host); - const dx = (st.x || 0) - (r.x || 0); - const dy = (st.y || 0) - (r.y || 0); - if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { - const style = getComputedStyle(host); - const m = /matrix\(([^)]+)\)/.exec(style.transform || ""); - const tx = m ? parseFloat(m[1].split(",")[4]) || 0 : 0; - const ty = m ? parseFloat(m[1].split(",")[5]) || 0 : 0; - this.applyTransform(host, tx + dx, ty + dy); - } - } - }); - } - findHostByKey(key) { - const root = this.getActiveContainerEl(); - if (!root) return null; - const hosts = root.querySelectorAll(`.ms-host[data-ms-key="${CSS.escape(key)}"]`); - return hosts.length > 0 ? hosts[0] : null; - } - enumerateHostsInNote(notePath) { - const root = this.getActiveContainerEl(); - if (!root) return []; - const nodes = root.querySelectorAll( - ".internal-embed, .media-embed, .mx-media-embed, img, video, audio, .ms-note-host" - ); - const list = []; - nodes.forEach((hostLike) => { - const isNote = hostLike.classList.contains("ms-note-host"); - const host = isNote ? hostLike : ["IMG", "VIDEO", "AUDIO"].includes(hostLike.tagName) ? hostLike.closest(".internal-embed, .media-embed, .mx-media-embed") ?? hostLike : hostLike; - const media = isNote ? host.querySelector(".ms-note") : host.querySelector("video, img, audio") ?? host; - if (!media) return; - const tag = isNote ? "NOTE" : (media.tagName || "").toUpperCase(); - const kind = tag === "NOTE" ? "note" : tag === "IMG" ? "image" : tag === "AUDIO" ? "audio" : "video"; - const { src } = isNote ? { src: "" } : this.getSrcAndBase(host, media); - let key = host.dataset.msKey; - if (!key) key = isNote ? this.makeNoteKey() : this.makeEmbedKey(src); - host.dataset.msKey = key; - list.push({ host, media, kind, key }); - }); - return list; - } - addSizePresetsMenu(menu, notePath, key, host, media, kind) { - menu.addItem((i) => { - i.setTitle(t(this.app, "presets")).setIcon("expand"); - const sub = i.setSubmenu(); - this.userPresets.forEach((val) => { - sub.addItem((si) => { - si.setTitle(String(val)).onClick(async () => { - await this.applyAndSaveSize(notePath, key, host, media, kind, { w: val }); - }); - const root = si.dom; - root.classList.add("ms-preset-item"); - const btn = root.createDiv({ cls: "ms-preset-del" }); - btn.onclick = async (ev) => { - ev.stopPropagation(); - this.removeUserPreset(val); - root.remove(); - }; - }); - }); - if (this.userPresets.length) sub.addSeparator(); - sub.addItem( - (si) => si.setTitle(t(this.app, "fit")).onClick(async () => { - const parent = host.parentElement ?? host; - const pw = Math.round(parent.clientWidth || parent.getBoundingClientRect().width); - await this.applyAndSaveSize(notePath, key, host, media, kind, { w: pw }); - }) - ); - if (kind !== "audio") { - sub.addItem( - (si) => si.setTitle(t(this.app, "natural")).onClick(async () => { - if (kind === "image") { - const iw = media.naturalWidth || media.clientWidth || 0; - const ih = media.naturalHeight || media.clientHeight || 0; - await this.applyAndSaveSize(notePath, key, host, media, kind, { w: iw, h: ih }); - } else { - const v = media; - const w = v.videoWidth > 0 ? v.videoWidth : media.clientWidth || 0; - await this.applyAndSaveSize(notePath, key, host, media, kind, { w }); - } - }) - ); - } - sub.addSeparator(); - [144, 480, 640, 720, 1280, 1920].forEach( - (sz) => sub.addItem((si) => si.setTitle(String(sz)).onClick(async () => { - await this.applyAndSaveSize(notePath, key, host, media, kind, { w: sz }); - })) - ); - sub.addItem( - (si) => si.setTitle(t(this.app, "add")).setIcon("plus").onClick(async () => { - if (this.userPresets.length >= MAX_WIDTH_PRESETS) { - new import_obsidian.Notice("\u274C " + t(this.app, "maxReached")); - return; - } - new WidthPresetModal(this.app, async (n) => { - if (n == null) return; - await this.addUserPreset(n); - await this.applyAndSaveSize(notePath, key, host, media, kind, { w: n }); - new import_obsidian.Notice(t(this.app, "added", { n })); - }).open(); - }) - ); - }); - } - addPositionPresetsMenu(menu, notePath) { - menu.addItem((i) => { - i.setTitle(t(this.app, "posPresets")).setIcon("save"); - const sub = i.setSubmenu(); - sub.addItem((si) => { - si.setTitle(t(this.app, "resetAll")).setIcon("rotate-ccw").onClick(async () => { - await this.resetAll(notePath); - }); - }); - sub.addItem((si) => { - si.setTitle(t(this.app, "saveLayout")).setIcon("save").onClick(async (si2) => { - const layout = this.collectLayout(notePath); - const arr = this.layoutPresets[notePath] ??= []; - if (arr.length >= MAX_LAYOUT_PRESETS) { - new import_obsidian.Notice("\u2716 " + t(this.app, "layoutMax")); - return; - } - arr.push(JSON.parse(JSON.stringify(layout))); - const names = this.layoutNames[notePath] ??= []; - const baseName = t(this.app, "layoutN", { n: "" }).replace("{n}", "").trim(); - let maxNum = 0; - names.forEach((name) => { - if (name.startsWith(baseName)) { - const num = parseInt(name.substring(baseName.length).trim(), 10); - if (!isNaN(num) && num > maxNum) maxNum = num; - } - }); - names.push(t(this.app, "layoutN", { n: maxNum + 1 })); - await this.persistAll(); - }); - }); - (this.layoutPresets[notePath] ?? []).forEach((layout, idx) => { - sub.addItem((si) => { - const names = this.layoutNames[notePath] ??= []; - const title = names[idx] ?? t(this.app, "layoutN", { n: idx + 1 }); - si.setTitle(title).onClick(async () => { - if (this.presetTimerId !== null) { - clearTimeout(this.presetTimerId); - this.pendingTimers.delete(this.presetTimerId); - this.presetTimerId = null; - } - await this.applyLayout(notePath, layout); - await this.persistAll(); - this.presetTimerId = this.schedule(async () => { - const currentPath = this.getActiveNote()?.path; - if (currentPath !== notePath) { - console.log("[Media Layout] Preset application cancelled: file changed"); - this.presetTimerId = null; - return; - } - await this.applyLayout(notePath, layout); - this.applyCloneSettingsToAll(); - await this.persistAll(); - this.presetTimerId = null; - }, 500); - }); - const root = si.dom; - root.classList.add("ms-preset-item"); - const edit = root.createDiv({ cls: "ms-preset-edit" }); - edit.onclick = async (ev) => { - ev.stopPropagation(); - new RenamePresetModal(this.app, names[idx] ?? "", async (val) => { - if (val == null) return; - names[idx] = val; - si.dom.querySelector(".menu-item-title")?.setText(val); - await this.persistAll(); - }).open(); - }; - const del = root.createDiv({ cls: "ms-preset-del" }); - del.onclick = async (ev) => { - ev.stopPropagation(); - const arr = this.layoutPresets[notePath] ?? []; - const pos = arr.indexOf(layout); - if (pos >= 0) { - arr.splice(pos, 1); - if (names && pos < names.length) names.splice(pos, 1); - } - root.remove(); - await this.persistAll(); - }; - }); - }); - sub.addSeparator(); - sub.addItem((si) => { - si.setTitle(t(this.app, "copyLayout")).setIcon("copy").onClick(async () => { - const noteData = this.notes[notePath]; - const presets = this.layoutPresets[notePath]; - const names = this.layoutNames[notePath]; - const hasNoteData = noteData && (Object.keys(noteData.originals).length || Object.keys(noteData.clones).length); - const hasPresets = presets && presets.length > 0; - if (!hasNoteData && !hasPresets) { - new import_obsidian.Notice("No layout data to copy."); - return; - } - const dataToCopy = { - noteData: noteData ?? { originals: {}, clones: {} }, - presets: presets ?? [], - names: names ?? [] - }; - await navigator.clipboard.writeText(JSON.stringify(dataToCopy, null, 2)); - new import_obsidian.Notice("\u2714\uFE0F " + t(this.app, "layoutCopied")); - }); - }); - sub.addItem((si) => { - si.setTitle(t(this.app, "pasteLayout")).setIcon("paste").onClick(async () => { - try { - const text = await navigator.clipboard.readText(); - const data = JSON.parse(text); - if (data && data.noteData && Array.isArray(data.presets) && Array.isArray(data.names)) { - this.notes[notePath] = data.noteData; - this.layoutPresets[notePath] = data.presets; - this.layoutNames[notePath] = data.names; - await this.persistAll(); - new import_obsidian.Notice("\u2714\uFE0F " + t(this.app, "layoutPasted")); - this.scanActive(); - } else { - new import_obsidian.Notice("\u274C " + t(this.app, "invalidLayoutData")); - } - } catch (e) { - new import_obsidian.Notice("\u274C " + t(this.app, "pasteLayoutFailed")); - console.error("Media-Size paste error:", e); - } - }); - }); - }); - } - restoreClonesFromStore(notePath) { - try { - if (!notePath) return; - const clones = this.notes[notePath]?.clones; - if (!clones || !Object.keys(clones).length) return; - this.debug("restoreClonesFromStore started", { - notePath, - cloneCount: Object.keys(clones).length, - cloneKeys: Object.keys(clones) - }); - const leaves = this.app.workspace.getLeavesOfType("markdown").filter((leaf) => { - const view = leaf.view; - return view instanceof import_obsidian.MarkdownView && view.file?.path === notePath; - }); - this.debug("Found leaves for note", { - notePath, - leafCount: leaves.length - }); - if (!leaves.length) return; - leaves.forEach((leaf) => { - const view = leaf.view; - if (!(view instanceof import_obsidian.MarkdownView)) return; - const containerEl = view.containerEl; - if (!containerEl) return; - const ATTEMPTS = 20; - let left = ATTEMPTS; - const tick = () => { - const src = containerEl.querySelector(".markdown-source-view .cm-scroller"); - const read = containerEl.querySelector(".markdown-reading-view"); - const srcVisible = src ? getComputedStyle(src.parentElement).display !== "none" : false; - const readVisible = read ? getComputedStyle(read).display !== "none" : false; - const root = readVisible ? read || src : src || read; - const isReadingModeActive = readVisible; - this.debug("tick - searching for root", { - notePath, - hasSrc: !!src, - hasRead: !!read, - hasRoot: !!root, - srcVisible, - readVisible, - isReadingModeActive, - selectedRootClass: root?.className, - attemptsLeft: left - }); - if (!root) { - if (--left > 0) { - this.schedule(tick, 50); - } - return; - } - this.debug("Root found, calling ensureFloatLayer", { - notePath, - rootClass: root.className, - isReadingModeActive - }); - const mount = this.ensureFloatLayer(root); - if ((mount.dataset.msNote || "") !== notePath) return; - this.debug("Layer ready, scheduling clone restore", { notePath }); - this.schedule(async () => { - const hosts = root.querySelectorAll(".ms-host[data-ms-key]"); - if (!hosts.length) { - this.debug("No hosts found yet, retrying", { notePath, attemptsLeft: left }); - if (--left > 0) { - this.schedule(tick, 50); - } - return; - } - this.debug("Found hosts, building anchor index", { notePath, hostCount: hosts.length }); - const anchorIndex = this.buildAnchorIndex(root); - this.debug("Starting PARALLEL clone restoration", { notePath, totalClones: Object.keys(clones).length }); - const clonePromises = []; - for (const [cloneId, state] of Object.entries(clones)) { - if (mount.querySelector(`[data-ms-key="${CSS.escape(cloneId)}"]`)) { - this.debug("Clone already exists, skipping", { cloneId }); - continue; - } - const st = state; - let parentKey = st.parentKey; - const parentAnchor = st.parentAnchor; - if (!parentKey || !root.querySelector(`.ms-host[data-ms-key="${CSS.escape(parentKey)}"]`)) { - if (parentAnchor) { - const remap = anchorIndex.get(parentAnchor); - if (remap) { - parentKey = st.parentKey = remap; - } - } - } - clonePromises.push(this.tryRestoreSingleClone(notePath, cloneId, st, root)); - } - await Promise.all(clonePromises); - this.debug("All clones restored in parallel", { notePath, count: clonePromises.length }); - this.schedule(() => this.applyCloneSettingsToAll(), 150); - }, this.cloneRestoreDelay); - }; - this.schedule(tick, 50); - }); - } catch (error) { - console.error("[Media Layout] Failed to restore clones from store:", error); - this.error("Failed to restore clones", { notePath, error: String(error) }); - } - } - async tryRestoreSingleClone(notePath, cloneId, state, root) { - if (!root) return Promise.resolve(); - this.debug("tryRestoreSingleClone started", { cloneId, parentKey: state.parentKey }); - const mount = this.ensureFloatLayer(root); - if (mount.querySelector(`[data-ms-key="${CSS.escape(cloneId)}"]`)) return Promise.resolve(); - let parentKey = state.parentKey; - if (!parentKey || !root.querySelector(`.ms-host[data-ms-key="${CSS.escape(parentKey)}"]`)) { - const a = state.parentAnchor; - if (a) { - const idx = this.buildAnchorIndex(root); - const remap = idx.get(a); - if (remap) { - parentKey = state.parentKey = remap; - } - } - } - if (!parentKey) { - this.warn("Parent not found for clone", { cloneId, originalParentKey: state.parentKey, parentAnchor: state.parentAnchor }); - return Promise.resolve(); - } - this.debug("Parent found, creating clone from TFile", { cloneId, parentKey }); - let cloneHost = null; - let originalHost = null; - const newHost = await this.createHostFromTFile(notePath, state.parentKey); - if (newHost) { - cloneHost = newHost; - originalHost = newHost; - } - if (!cloneHost) { - console.warn(`[MS] Failed to create clone ${cloneId}: TFile not found for ${state.parentKey}`); - return Promise.resolve(); - } - this.createCloneHost(root, cloneHost, cloneId, state.parentKey, state.x, state.y, true); - const newClonedMedia = cloneHost.querySelector("video, img, audio"); - if (newClonedMedia) { - const tag = (newClonedMedia.tagName || "").toUpperCase(); - const kind = tag === "IMG" ? "image" : tag === "AUDIO" ? "audio" : "video"; - let w = state.w; - let h = state.h; - if (typeof w !== "number" || typeof h !== "number") { - const savedCloneState = this.notes[notePath]?.clones?.[cloneId]; - if (savedCloneState) { - w = savedCloneState.w; - h = savedCloneState.h; - } - } - const fullState = { ...state, w, h, link: state.link, zIndex: state.zIndex }; - this.debug("tryRestoreSingleClone: applying state", { - cloneId, - hasW: typeof fullState.w === "number", - hasH: typeof fullState.h === "number", - hasZIndex: typeof fullState.zIndex === "number", - w: fullState.w, - h: fullState.h, - zIndex: fullState.zIndex, - zIndexFromCloneHost: cloneHost.style.zIndex - }); - this.applyState(cloneHost, newClonedMedia, kind, fullState); - const actualZIndex = parseInt(cloneHost.style.zIndex || "", 10); - if (typeof state.zIndex === "number" && actualZIndex !== state.zIndex) { - this.warn("tryRestoreSingleClone: z-index mismatch, forcing correct value", { - cloneId, - expectedZIndex: state.zIndex, - actualZIndex, - fixing: true - }); - cloneHost.style.zIndex = String(state.zIndex); - } - } - return Promise.resolve(); - } - async createHostFromTFile(notePath, parentKey) { - const basename = this.extractBaseFromKey(parentKey); - if (!basename) return null; - const files = this.app.vault.getFiles().filter((f) => f.basename === basename || f.name === basename); - const file = files[0]; - if (!file) return null; - const fileExt = file.extension.toLowerCase(); - const mediaTypes = { - "jpg": "image", - "jpeg": "image", - "png": "image", - "gif": "image", - "webp": "image", - "svg": "image", - "mp4": "video", - "webm": "video", - "ogv": "video", - "mov": "video", - "mkv": "video", - "mp3": "audio", - "wav": "audio", - "m4a": "audio", - "ogg": "audio", - "3gp": "audio", - "flac": "audio" - }; - const kind = mediaTypes[fileExt]; - if (!kind) return null; - const host = document.createElement("div"); - host.className = "internal-embed media-embed is-loaded"; - let mediaEl; - const resourcePath = this.app.vault.adapter.getResourcePath(file.path); - if (kind === "image") { - mediaEl = document.createElement("img"); - mediaEl.loading = "eager"; - } else if (kind === "video") { - mediaEl = document.createElement("video"); - mediaEl.preload = "metadata"; - } else { - mediaEl = document.createElement("audio"); - mediaEl.preload = "metadata"; - } - mediaEl.src = resourcePath; - host.appendChild(mediaEl); - return host; - } - layoutClonesInDock(notePath, side = "right") { - const root = this.getAttachRoot(); - if (!root) return; - const layer = this.ensureFloatLayer(root); - const dock = this.ensureDock(root, side); - const clones = Array.from(layer.querySelectorAll("[data-ms-parent-key]")); - const gridY = 24; - const pad = 12; - let y = pad, x = pad; - clones.forEach((el, i) => { - el.style.position = "absolute"; - el.style.left = "0"; - el.style.top = "0"; - this.applyTransform(el, x, y); - y += (el.getBoundingClientRect().height || 120) + gridY; - }); - } - ensureDock(root, side = "right") { - const layer = this.ensureFloatLayer(root); - let dock = layer.querySelector(`.ms-dock.ms-dock--${side}`); - if (!dock) { - dock = document.createElement("div"); - dock.className = `ms-dock ms-dock--${side}`; - layer.appendChild(dock); - } - return dock; - } - async resetLayoutForActive(side = "right") { - const notePath = this.getActiveNote()?.path ?? ""; - const root = this.getAttachRoot(); - if (!notePath || !root) return; - for (const it of this.enumerateHostsInNote(notePath)) { - const isClone = !!it.host.dataset.msParentKey; - if (isClone) continue; - it.host.dataset.msDetached = "0"; - it.host.dataset.msLocked = "0"; - this.reattach(it.host); - this.applyTransform(it.host, 0, 0); - } - const layer = this.ensureFloatLayer(root); - layer.querySelectorAll("[data-ms-parent-key]").forEach((el) => el.remove()); - this.restoreClonesFromStore(notePath); - this.layoutClonesInDock(notePath, side); - await this.persistAll(); - } - collectLayout(notePath) { - const layout = {}; - const noteData = this.notes[notePath] ?? { originals: {}, clones: {} }; - const originals = noteData.originals ?? {}; - const clones = noteData.clones ?? {}; - for (const it of this.enumerateHostsInNote(notePath)) { - const isClone = !!it.host.dataset.msParentKey; - const isNote = it.kind === "note"; - if (!isClone && !isNote) continue; - const r = this.readAbsRect(it.host); - const prev = isClone ? clones[it.key] ?? {} : originals[it.key] ?? {}; - if (isClone) { - let fileName = clones[it.key]?.fileName ?? it.host.dataset.msFileName; - if (!fileName) { - const mediaEl = it.host.querySelector("img,video,audio"); - if (mediaEl) { - if (this.getSrcAndBase) { - const { base } = this.getSrcAndBase(it.host, mediaEl); - fileName = base || void 0; - } else { - const src = mediaEl.currentSrc || mediaEl.src || ""; - fileName = src ? src.split("/").pop() : void 0; - } - } - } - const hasRotate = typeof clones[it.key]?.rotate === "number" && clones[it.key].rotate !== 0; - const stateW = clones[it.key]?.w; - const stateH = clones[it.key]?.h; - const finalW = hasRotate && typeof stateW === "number" ? stateW : r.w; - const finalH = hasRotate && typeof stateH === "number" ? stateH : r.h; - layout[it.key] = { - ...prev, - parentKey: it.host.dataset.msParentKey, - w: finalW, - h: finalH, - x: Math.round(r.x), - y: Math.round(r.y), - locked: it.host.dataset.msLocked === "1", - bgColor: clones[it.key]?.bgColor, - fileName, - link: clones[it.key]?.link, - rotate: clones[it.key]?.rotate - }; - } else { - const originals2 = noteData.originals || {}; - const hasRotateNote = typeof originals2[it.key]?.rotate === "number" && originals2[it.key].rotate !== 0; - const stateWNote = originals2[it.key]?.w; - const stateHNote = originals2[it.key]?.h; - const finalWNote = hasRotateNote && typeof stateWNote === "number" ? stateWNote : r.w; - const finalHNote = hasRotateNote && typeof stateHNote === "number" ? stateHNote : r.h; - layout[it.key] = { - ...prev, - w: finalWNote, - h: finalHNote, - x: Math.round(r.x), - y: Math.round(r.y), - detached: it.host.dataset.msDetached === "1", - locked: it.host.dataset.msLocked === "1", - rotate: originals2[it.key]?.rotate - }; - } - const zi = parseInt(it.host.style.zIndex || "", 10); - if (Number.isFinite(zi)) layout[it.key].zIndex = zi; - if (isNote && (layout[it.key].bgColor || layout[it.key].headerColor)) { - delete layout[it.key].transparent; - } - } - for (const [key, st] of Object.entries(originals)) { - if (key.startsWith("note::") && !layout[key]) { - layout[key] = { ...st }; - } - } - const currentClones = this.notes[notePath]?.clones ?? {}; - for (const [key, st] of Object.entries(currentClones)) { - if (!layout[key]) layout[key] = st; - } - return layout; - } - ensureNotesFromStore(notePath) { - this.schedule(() => { - if (!notePath) return; - const attachRoot = this.getAttachRoot(); - if (!attachRoot) { - this.warn("ensureNotesFromStore: no attachRoot found", {}); - return; - } - this.debug("ensureNotesFromStore starting", { - notePath, - attachRootClass: attachRoot.className - }); - const layer = this.ensureFloatLayer(attachRoot); - const saved = this.notes[notePath]?.originals ?? {}; - const noteKeys = Object.keys(saved).filter((k) => k.startsWith("note::")); - this.debug("Found notes to restore", { - notePath, - noteCount: noteKeys.length, - noteKeys - }); - for (const [key, st] of Object.entries(saved)) { - if (!key.startsWith("note::")) continue; - const exists = layer.querySelector(`.ms-note-host[data-ms-key="${CSS.escape(key)}"]`); - if (exists) { - this.debug("Note already exists, skipping", { key }); - continue; - } - this.debug("Creating note host", { - key, - x: st.x, - y: st.y - }); - const x = st.x ?? 0, y = st.y ?? 0; - const host = this.createNoteHost(attachRoot, key, x, y, { - w: st.w, - h: st.h, - txt: st.txt ?? "", - fontSize: st.fontSize ?? 16 - }); - const media = host.querySelector(".ms-note"); - if (media) this.applyState(host, null, "note", st); - } - }, this.noteRestoreDelay); - } - async resetAll(notePath) { - if (!notePath) return; - const noteData = this.notes[notePath]; - if (noteData) { - noteData.clones = {}; - noteData.originals = {}; - const root2 = this.getActiveContainerEl(); - if (root2) { - root2.querySelectorAll("[data-ms-parent-key]").forEach((el) => el.remove()); - } - } - const root = this.getActiveContainerEl(); - if (root) { - root.querySelectorAll(".ms-note-host").forEach((noteHost) => { - noteHost.remove(); - }); - } - if (noteData?.originals) { - for (const key in noteData.originals) if (key.startsWith("note::")) delete noteData.originals[key]; - } - for (const it of this.enumerateHostsInNote(notePath)) { - if (it.kind === "note") continue; - this.reattach(it.host); - this.applyTransform(it.host, 0, 0); - it.host.classList.remove("ms-host"); - it.host.removeAttribute("data-ms-detached"); - it.host.removeAttribute("data-ms-locked"); - it.host.removeAttribute("data-ms-free"); - it.host.removeAttribute("data-ms-kind"); - it.host.removeAttribute("data-ms-init"); - it.host.removeAttribute("data-ms-key"); - const overlay = it.host.querySelector(".ms-overlay"); - if (overlay) overlay.remove(); - it.host.style.position = ""; - it.host.style.left = ""; - it.host.style.top = ""; - it.host.style.transform = ""; - it.host.style.width = ""; - it.host.style.height = ""; - it.host.style.maxWidth = ""; - it.host.style.aspectRatio = ""; - it.host.style.display = ""; - if (it.media) { - it.media.style.width = ""; - it.media.style.height = ""; - it.media.style.objectFit = ""; - it.media.style.objectPosition = ""; - it.media.style.display = ""; - } - } - await this.persistAll(); - await new Promise((r) => setTimeout(r, 50)); - const activeRoot = this.getActiveContainerEl(); - if (activeRoot) { - this.processRoot(activeRoot, notePath); - this.applyCloneSettingsToAll(); - } - } - makeEmbedKey(src) { - const last = Math.max(src.lastIndexOf("/"), src.lastIndexOf("\\")); - const base = last >= 0 ? src.substring(last + 1) : src; - return base || "media"; - } - resolveTFile(notePath, linkPath) { - if (!linkPath) return null; - return this.app.metadataCache.getFirstLinkpathDest(linkPath, notePath) ?? null; - } - getActiveNote() { - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - return view?.file ?? null; - } - getActiveContainerEl() { - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - return view?.containerEl ?? null; - } - isInReadingMode(el) { - if (el) { - return !!el.closest(".markdown-reading-view, .markdown-preview-view"); - } - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (!view) return false; - return !!view.containerEl.querySelector(".markdown-reading-view"); - } - ensureOverlay(host) { - const overlay = host.querySelector(".ms-overlay") ?? (() => { - const d = document.createElement("div"); - d.className = "ms-overlay"; - host.appendChild(d); - return d; - })(); - return overlay; - } - ensureHandles(overlay, isClone = false, isNote = false) { - const need = (cls) => overlay.querySelector(`.${cls.split(" ").join(".")}`) ?? (() => { - const h = document.createElement("div"); - h.className = cls; - overlay.appendChild(h); - return h; - })(); - const hRB = need("ms-handle ms-handle--rb"); - const hLB = need("ms-handle ms-handle--lb"); - const hRT = need("ms-handle ms-handle--rt"); - const hLT = need("ms-handle ms-handle--lt"); - const needRotation = isClone || isNote; - const hT = needRotation ? need("ms-handle ms-handle--t ms-handle--rotate") : null; - const hB = needRotation ? need("ms-handle ms-handle--b ms-handle--rotate") : null; - const hL = needRotation ? need("ms-handle ms-handle--l ms-handle--rotate") : null; - const hR = needRotation ? need("ms-handle ms-handle--r ms-handle--rotate") : null; - return { rb: hRB, lb: hLB, rt: hRT, lt: hLT, t: hT, b: hB, l: hL, r: hR }; - } - applyKindBaseStyles(host, media, kind) { - host.classList.add("ms-host"); - host.dataset.msKind = kind; - const isFloating = host.dataset.msDetached === "1" || kind === "note"; - if (!isFloating) { - host.style.position = "relative"; - host.style.display = "block"; - host.style.transform = "translate(0px, 0px)"; - host.dataset.msFree = "0"; - } else { - host.style.display = "block"; - } - if (kind === "video") { - const isClone = !!host.dataset.msParentKey; - const hasME = this.isMediaExtendedActive(host); - const videoAR = isClone || hasME ? 16 / 9 : this.getAR(kind, media); - host.style.aspectRatio = `${videoAR}`; - media.style.width = "100%"; - media.style.height = "100%"; - media.style.objectFit = "contain"; - media.style.objectPosition = "center center"; - media.style.display = "block"; - } else if (kind === "audio") { - host.style.aspectRatio = "9 / 1"; - media.style.width = "100%"; - media.style.height = "100%"; - media.style.display = "block"; - } else if (kind === "image") { - host.style.aspectRatio = "auto"; - media.style.display = "block"; - const isWrapped = host !== media; - if (isWrapped) { - media.style.width = "100%"; - media.style.height = "100%"; - media.style.objectFit = "fill"; - } - host.classList.add("cm-ignore"); - host.setAttribute("contenteditable", "false"); - host.tabIndex = -1; - try { - media.draggable = false; - } catch { - } - } else if (kind === "note") { - } - } - revealInOS(full) { - try { - const w = window; - if (!w.require) return; - const cp = w.require("child_process"); - if (!cp?.exec) return; - const platform = (w.process?.platform || navigator.platform || "").toLowerCase(); - if (platform.includes("win")) { - cp.exec(`explorer.exe /select,"${full.replace(/\//g, "\\")}"`); - return; - } - if (platform.includes("mac") || platform.includes("darwin")) { - cp?.exec?.(`open -R "${full}"`); - return; - } - const dir = full.replace(/\/[^\/]+$/, ""); - cp?.exec?.(`xdg-open "${dir}"`); - } catch { - } - } - async convertUrlMediaToLocal(url, notePath) { - try { - this.info("Starting URL conversion", { url, notePath }); - new import_obsidian.Notice(t(this.app, "urlMediaDownloading")); - this.debug("Fetching URL via requestUrl", { url }); - const response = await (0, import_obsidian.requestUrl)({ url }); - this.debug("Response received", { status: response.status }); - if (response.status !== 200) { - new import_obsidian.Notice(t(this.app, "urlMediaDownloadError", { status: response.status })); - this.error("Download failed", { url, status: response.status }); - return; - } - const arrayBuffer = response.arrayBuffer; - const contentType = response.headers["content-type"] || "image/png"; - this.debug("Data received", { contentType, size: arrayBuffer.byteLength }); - const urlObj = new URL(url); - let fileName = urlObj.pathname.split("/").pop() || "image"; - this.debug("Original fileName", { fileName }); - try { - fileName = decodeURIComponent(fileName); - this.debug("Decoded fileName", { fileName }); - } catch (e) { - this.warn("Failed to decode fileName, using as is", { fileName, error: e }); - } - if (!fileName.includes(".")) { - const ext = contentType.split("/")[1]?.split(";")[0] || "png"; - fileName = `${fileName}.${ext}`; - } - fileName = fileName.replace(/[\\/:*?"<>|]/g, "_"); - this.debug("Cleaned fileName", { fileName }); - const attachmentFolder = this.app.vault.getConfig("attachmentFolderPath") || ""; - this.debug("Attachment folder setting", { attachmentFolder }); - let savePath; - if (attachmentFolder) { - const cleanFolder = attachmentFolder.startsWith("/") ? attachmentFolder.slice(1) : attachmentFolder; - savePath = (0, import_obsidian.normalizePath)(cleanFolder + "/" + fileName); - } else { - const noteDir = notePath.substring(0, notePath.lastIndexOf("/")) || ""; - savePath = (0, import_obsidian.normalizePath)((noteDir ? noteDir + "/" : "") + fileName); - } - this.debug("Save path calculated", { savePath }); - let finalPath = savePath; - let counter = 1; - while (await this.app.vault.adapter.exists(finalPath)) { - const ext = fileName.substring(fileName.lastIndexOf(".")); - const name = fileName.substring(0, fileName.lastIndexOf(".")); - finalPath = savePath.replace(fileName, `${name}_${counter}${ext}`); - counter++; - } - this.debug("Final path determined", { finalPath, counter }); - const folderPath = finalPath.substring(0, finalPath.lastIndexOf("/")); - if (folderPath && !await this.app.vault.adapter.exists(folderPath)) { - this.debug("Creating folder", { folderPath }); - await this.app.vault.createFolder(folderPath); - } - this.debug("Writing file", { finalPath, size: arrayBuffer.byteLength }); - await this.app.vault.adapter.writeBinary(finalPath, arrayBuffer); - this.info("File saved successfully", { finalPath }); - const view = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); - if (!view) { - this.warn("No active MarkdownView found", { notePath }); - new import_obsidian.Notice(t(this.app, "urlMediaFileSavedLinkFailed")); - return; - } - const editor = view.editor; - const content = editor.getValue(); - this.debug("Editor content retrieved", { contentLength: content.length }); - const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const pattern = new RegExp(`!\\[([^\\]]*)\\]\\(${escapedUrl}\\)`, "g"); - this.debug("Regex pattern created", { pattern: pattern.source }); - const baseName = finalPath.split("/").pop() || fileName; - this.debug("Base name for link", { baseName }); - const newContent = content.replace(pattern, `[$1](${url}) ![[${baseName}]]`); - if (newContent !== content) { - this.info("Content changed, updating editor", { baseName }); - editor.setValue(newContent); - new import_obsidian.Notice(t(this.app, "urlMediaSuccess")); - } else { - this.warn("Content did not change - pattern not found", { url, pattern: pattern.source }); - new import_obsidian.Notice(t(this.app, "urlMediaFileSavedPatternNotFound")); - } - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : void 0; - this.error("Error converting URL media", { - url, - notePath, - error: errorMsg, - stack: errorStack - }); - new import_obsidian.Notice(t(this.app, "urlMediaError", { error: errorMsg || t(this.app, "urlMediaUnknownError") })); - } - } - isControlsProxyHost(host, x, y) { - const wrap = this.controlsProxy.get(host)?.root; - if (!wrap) return false; - for (const el of Array.from(wrap.children)) { - const r = el.getBoundingClientRect(); - if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return true; - } - return false; - } - forwardFrom(ev, target, type, cx, cy) { - const host = ev.currentTarget?.closest(".ms-host"); - const isClone = !!host?.dataset.msParentKey; - const media = host?.querySelector("video, audio"); - const btn = target; - if (isClone && type !== "click") return; - if (!isClone && btn?.isConnected && type === "click") { - try { - btn.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window })); - return; - } catch { - } - } - if (type === "click" && media) { - const hint = `${btn?.getAttribute("aria-label") || ""} ${btn?.getAttribute("title") || ""} ${btn?.className || ""}`.toLowerCase(); - if (/play|pause|toggle/.test(hint)) { - media.paused ? media.play().catch(() => { - }) : media.pause(); - media.focus?.(); - return; - } - if (/mute/.test(hint)) { - media.muted = !media.muted; - media.focus?.(); - return; - } - if (/full.?screen|fs/.test(hint)) { - const box = host?.closest(".mejs__container") || host; - if (box && !document.fullscreenElement) box?.requestFullscreen?.(); - else document.exitFullscreen?.(); - return; - } - const sb = btn.closest(".plyr__progress, .vjs-progress-holder, .mejs__time-slider"); - if (sb && !isNaN(media.duration)) { - const r = sb.getBoundingClientRect(); - const frac = r.width ? Math.min(Math.max(ev.clientX - r.left, 0), r.width) / r.width : 0; - media.currentTime = media.duration * frac; - return; - } - const vb = btn.closest('.plyr__volume, .vjs-volume-bar, .mejs__volume, input[type=range][aria-label*="volume" i]'); - if (vb) { - const r = vb.getBoundingClientRect(); - const frac = r.width ? Math.min(Math.max(ev.clientX - r.left, 0), r.width) / r.width : 0; - media.volume = Math.min(1, Math.max(0, frac)); - return; - } - } - } - makeNoteKey() { - return `note::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`; - } - ensureParentAnchor(host) { - const p = host.parentElement; - if (!p) return null; - const cs = getComputedStyle(p); - if (cs.position === "static") { - p.style.position = "relative"; - } - return p; - } - applyAudioHandlesToAll() { - const root = this.getActiveContainerEl(); - if (!root) return; - root.querySelectorAll('.ms-host[data-ms-kind="audio"]').forEach((h) => { - h.dataset.msShowHandles = this.cloneShowAudioHandles ? "true" : "false"; - }); - } - ensureLocalBg(host) { - let localBg = host.querySelector(".ms-local-bg"); - if (!localBg) { - localBg = document.createElement("div"); - localBg.className = "ms-local-bg"; - Object.assign(localBg.style, { - position: "absolute", - inset: "0", - zIndex: "1", - borderRadius: "inherit" - }); - host.insertBefore(localBg, host.firstChild); - } - return localBg; - } - ensureMediaBg(host) { - let mediaBg = host.querySelector(".ms-media-bg"); - if (!mediaBg) { - mediaBg = document.createElement("div"); - mediaBg.className = "ms-media-bg"; - Object.assign(mediaBg.style, { - position: "absolute", - left: "0", - top: "0", - width: "100%", - height: "100%", - zIndex: "-1", - borderRadius: "inherit", - backgroundColor: this.meBgColor - }); - host.insertBefore(mediaBg, host.firstChild); - } - return mediaBg; - } - applyCssVariables() { - const root = document.documentElement; - const color = /^#([0-9a-f]{6})$/i.test(this.meBgColor) ? this.meBgColor : "#242424"; - const delay = Math.max(0, this.cloneHideDelay).toFixed(2); - root.style.setProperty("--ms-media-bg-color", color); - root.style.setProperty("--ms-clone-hide-delay", `${delay}s`); - root.style.setProperty("--ms-handles-opacity", this.showHandlesOnHover ? "1" : "0"); - root.style.setProperty("--ms-modal-animation", this.enableModalAnimation ? "enabled" : "disabled"); - } - async setMediaBgColor(hex, isLocal, host, notePath, key) { - if (!isLocal) { - this.meBgColor = /^#([0-9a-f]{6})$/i.test(hex) ? hex.toUpperCase() : "#242424"; - this.applyCssVariables(); - await this.persistAll(); - if (host) { - this.ensureMediaBg(host); - } - } else if (host && notePath && key) { - const localBg = this.ensureLocalBg(host); - localBg.style.backgroundColor = hex; - await this.saveState(notePath, key, { bgColor: hex }); - } - } - async maybeDetach(notePath, key, host) { - if (!notePath || !key) return; - if (host.dataset.msDetached === "1") return; - const parent = host.parentElement; - if (!parent) return; - const rNow = this.readAbsRect(host); - const x = Math.round(rNow.x || 0); - const y = Math.round(rNow.y || 0); - this.ensureParentAnchor(host); - host.style.position = "absolute"; - host.style.left = "0px"; - host.style.top = "0px"; - host.dataset.msDetached = "1"; - this.applyTransform(host, x, y); - const r = this.readAbsRect(host); - const currentState = this.loadStateWithFallback(notePath, key, "") ?? {}; - await this.saveState(notePath, key, { - ...currentState, - detached: true, - locked: host.dataset.msLocked === "1" - }); - } - reattach(host) { - host.dataset.msDetached = "0"; - host.style.position = "relative"; - host.style.left = ""; - host.style.top = ""; - this.applyTransform(host, 0, 0); - const p = host.parentElement; - if (p && p.dataset.msAnchor === "1") { - p.style.position = ""; - delete p.dataset.msAnchor; - } - } - createCleanCloneElement(originalHost) { - const mediaToClone = originalHost.querySelector("video, audio, img"); - if (mediaToClone && (mediaToClone.tagName === "VIDEO" || mediaToClone.tagName === "AUDIO")) { - const cloneHost2 = document.createElement("div"); - cloneHost2.className = originalHost.className; - cloneHost2.classList.remove("is-loaded"); - const mediaClone = mediaToClone.cloneNode(true); - mediaClone.removeAttribute("id"); - mediaClone.removeAttribute("data-mx-id"); - cloneHost2.appendChild(mediaClone); - return cloneHost2; - } - const cloneHost = originalHost.cloneNode(true); - cloneHost.querySelectorAll(".ms-overlay, .ms-proxy-ctrl, .ms-handle, .ms-dock, .ms-clone-controls").forEach((n) => n.remove()); - ["msInit", "msKind", "msFree", "msLocked", "msDetached", "msKey", "data-mx-id"].forEach((a) => delete cloneHost.dataset[a]); - cloneHost.removeAttribute("id"); - cloneHost.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id")); - cloneHost.style.zIndex = ""; - return cloneHost; - } - createCloneHost(container, cloneHost, key, parentKey, x, y, initialize = true, fileName) { - cloneHost.dataset.msKey = key; - cloneHost.dataset.msParentKey = parentKey; - const notePath = this.getActiveNote()?.path ?? ""; - if (!notePath) return; - cloneHost.dataset.msNote = notePath; - if (fileName) { - cloneHost.dataset.msFileName = fileName; - } - const parentHost = container.querySelector(`.ms-host[data-ms-key="${CSS.escape(parentKey)}"]`); - if (parentHost) { - const anchor = this.ensureOriginalAnchor(parentHost); - cloneHost.dataset.msParentAnchor = anchor; - try { - const note = this.notes[notePath] ||= { originals: {}, clones: {} }; - const st = note.clones[key] ||= { - parentKey, - x: 0, - y: 0, - w: 0, - h: 0 - }; - st.parentAnchor = anchor; - this.persistAll?.(); - } catch (e) { - console.warn("Failed to persist clone parentAnchor", e); - } - } - cloneHost.style.position = "absolute"; - cloneHost.style.left = "0px"; - cloneHost.style.top = "0px"; - const existingZIndex = this.notes[notePath]?.clones?.[key]?.zIndex; - cloneHost.style.zIndex = typeof existingZIndex === "number" ? String(existingZIndex) : "2900"; - this.debug("createCloneHost: z-index set", { - key, - hasExistingZIndex: typeof existingZIndex === "number", - zIndex: cloneHost.style.zIndex - }); - cloneHost.style.transform = ""; - cloneHost.style.willChange = "transform"; - cloneHost.style.pointerEvents = "auto"; - this.debug("Clone host element created", { key, x, y }); - const mount = this.ensureFloatLayer(container); - mount.appendChild(cloneHost); - if (this.enableDebugMode) { - const sizer = mount.parentElement; - const leafCount = this.app.workspace.getLeavesOfType("markdown").length; - if (!sizer) { - this.warn("createCloneHost: NO SIZER FOUND!", { - key, - x, - y, - leafCount, - containerClass: container.className - }); - } else { - const sizerRect = sizer.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - const mountRect = mount.getBoundingClientRect(); - this.debug(`createCloneHost: sizer found`, { - key, - requestedCoords: { x, y }, - leafCount, - sizerPos: { left: sizerRect.left, top: sizerRect.top, width: sizerRect.width, height: sizerRect.height }, - containerPos: { left: containerRect.left, top: containerRect.top }, - mountPos: { left: mountRect.left, top: mountRect.top } - }); - } - } - this.applyTransform(cloneHost, Math.round(x), Math.round(y)); - setTimeout(async () => { - const existingState = this.notes[notePath]?.clones?.[key]; - const hasValidSize = existingState && typeof existingState.w === "number" && typeof existingState.h === "number"; - const hasRotate = typeof existingState?.rotate === "number" && existingState.rotate !== 0; - let fallbackW, fallbackH; - if (hasRotate) { - fallbackW = parseFloat(cloneHost.style.width) || cloneHost.offsetWidth || 480; - fallbackH = parseFloat(cloneHost.style.height) || cloneHost.offsetHeight || 270; - } else { - const rect = cloneHost.getBoundingClientRect(); - fallbackW = rect.width || 480; - fallbackH = rect.height || 270; - } - const currentZIndex = parseInt(cloneHost.style.zIndex || "", 10); - const zIndexToSave = Number.isFinite(currentZIndex) ? currentZIndex : existingState?.zIndex; - this.debug("createCloneHost setTimeout: saving z-index", { - key, - currentZIndexFromDOM: currentZIndex, - existingZIndexFromState: existingState?.zIndex, - zIndexToSave, - hasRotate, - sizeSaved: hasValidSize - }); - await this.saveState(notePath, key, { - ...existingState || {}, - parentKey, - w: hasValidSize ? existingState.w : fallbackW, - h: hasValidSize ? existingState.h : fallbackH, - fileName: cloneHost.dataset.msFileName || existingState?.fileName, - x, - y, - link: existingState?.link, - zIndex: zIndexToSave, - detached: true - }); - }, 100); - if (!initialize) return; - const media = cloneHost.querySelector("video, img, audio") ?? (["IMG", "VIDEO", "AUDIO"].includes(cloneHost.tagName) ? cloneHost : null); - if (!media) return; - if (media instanceof HTMLMediaElement) { - media.controls = false; - } - if (media.tagName === "VIDEO") { - if (!media.parentElement?.classList.contains("ms-media-wrapper")) { - const wrapper = document.createElement("div"); - wrapper.className = "ms-media-wrapper"; - wrapper.style.position = "relative"; - wrapper.style.zIndex = "2"; - wrapper.style.width = "100%"; - wrapper.style.height = "100%"; - cloneHost.insertBefore(wrapper, media).appendChild(media); - } - media.style.objectFit = "contain"; - media.style.aspectRatio = "16 / 9"; - media.style.willChange = "transform"; - cloneHost.addEventListener("fullscreenchange", () => { - if (document.fullscreenElement === cloneHost) { - this.wasLockedBeforeFs = cloneHost.dataset.msLocked === "1"; - cloneHost.dataset.msLocked = "1"; - } else { - const restore = () => { - if (this.hScrollEl) this.hScrollEl.scrollLeft = this.scrollRatioX * (this.hScrollEl.scrollWidth - this.hScrollEl.clientWidth); - if (this.vScrollEl) this.vScrollEl.scrollTop = this.scrollRatioY * (this.vScrollEl.scrollHeight - this.vScrollEl.clientHeight); - }; - let attempts = 0; - const maxAttempts = 10; - const interval = 20; - const tryRestore = () => { - restore(); - attempts++; - if (attempts < maxAttempts) { - setTimeout(tryRestore, interval); - } - }; - if (this.hScrollEl || this.vScrollEl) { - requestAnimationFrame(tryRestore); - } - cloneHost.dataset.msLocked = this.wasLockedBeforeFs ? "1" : "0"; - } - }); - } - const tag = (media.tagName || "").toUpperCase(); - const kind = tag === "IMG" ? "image" : tag === "AUDIO" ? "audio" : "video"; - const defaultWidth = 480; - let defaultHeight = void 0; - if (kind === "image") { - const ar = this.getAR(kind, media); - defaultHeight = defaultWidth / ar; - } - this.applyBox(cloneHost, media, kind, defaultWidth, defaultHeight, false); - cloneHost.dataset.msDetached = "1"; - cloneHost.dataset.msInit = "1"; - cloneHost.classList.add("ms-host"); - this.applyKindBaseStyles(cloneHost, media, kind); - const overlay = this.ensureOverlay(cloneHost); - const handles = this.ensureHandles(overlay, true); - const savedState = this.notes[notePath]?.clones?.[key]; - if (savedState && savedState.bgColor === "transparent") { - } else if (kind === "audio" || kind === "video" && cloneHost.dataset.msParentKey) { - const bg = document.createElement("div"); - bg.className = "ms-media-bg"; - Object.assign(bg.style, { - position: "absolute", - left: "0", - top: "0", - width: "100%", - height: "100%", - zIndex: "-1", - borderRadius: "inherit", - backgroundColor: this.meBgColor - }); - if (!media.parentElement?.classList.contains("ms-media-wrapper")) { - const wrapper = document.createElement("div"); - wrapper.className = "ms-media-wrapper"; - } - if (kind === "audio") { - bg.style.top = "auto"; - bg.style.bottom = "0"; - bg.style.height = "70px"; - } - cloneHost.insertBefore(bg, cloneHost.firstChild); - } - cloneHost.append(overlay); - this.mountControlsForHost(cloneHost, media); - this._attachDragMoveHandler(cloneHost, overlay, notePath, key, kind); - this._attachResizeHandlers(cloneHost, overlay, handles, notePath, key, kind, media); - cloneHost.addEventListener("contextmenu", (e) => { - e.preventDefault(); - e.stopPropagation(); - const existingMenus = document.querySelectorAll(".menu.media-size-clone-menu"); - existingMenus.forEach((menu2) => { - const menuInstance = menu2; - if (menuInstance.app && typeof menuInstance.app.workspace.trigger === "function") { - menuInstance.hide(); - } - }); - this.lastCtxEvt = e; - const menu = new import_obsidian.Menu(); - this.app.workspace.trigger("editor-menu", menu, null, this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView)); - menu.showAtMouseEvent(e); - menu.dom.addClass("media-size-clone-menu"); - }); - } - schedule(fn, ms) { - const id = window.setTimeout(() => { - this.pendingTimers.delete(id); - try { - fn(); - } catch (e) { - console.error(e); - } - }, ms); - this.pendingTimers.add(id); - return id; - } - cancelScheduled() { - this.pendingTimers.forEach((id) => clearTimeout(id)); - this.pendingTimers.clear(); - } - async hardCleanupForSwitch(prevNotePath) { - this.debug("hardCleanupForSwitch started", { prevNotePath }); - this.cancelScheduled?.(); - try { - this.disconnectObservers?.(); - } catch { - } - if (prevNotePath) { - const leaves = this.app.workspace.getLeavesOfType("markdown").filter((leaf) => { - const view = leaf.view; - return view instanceof import_obsidian.MarkdownView && view.file?.path === prevNotePath; - }); - if (leaves.length) { - const noteData = this.notes[prevNotePath] ??= { originals: {}, clones: {} }; - leaves.forEach((leaf) => { - const view = leaf.view; - if (!(view instanceof import_obsidian.MarkdownView)) return; - const containerEl = view.containerEl; - if (!containerEl) return; - const src = containerEl.querySelector(".markdown-source-view .cm-scroller"); - const read = containerEl.querySelector(".markdown-reading-view"); - const root = src || read; - if (!root) return; - const floatLayer = root.querySelector(".ms-float-layer"); - if (floatLayer) { - floatLayer.querySelectorAll("[data-ms-key]").forEach((host) => { - const key = host.dataset.msKey; - if (!key) return; - const isClone = !!host.dataset.msParentKey; - const isNote = host.classList.contains("ms-note-host"); - if (!isClone && !isNote) return; - try { - const r = this.readAbsRect(host); - if (!r.x && !r.y && !r.w && !r.h) { - console.warn(`[MS] Skipping save for ${key}: invalid rect (no wrapper?)`, r); - return; - } - const bucket = isClone ? noteData.clones : noteData.originals; - if (bucket[key]) { - bucket[key].x = r.x ?? 0; - bucket[key].y = r.y ?? 0; - bucket[key].w = r.w ?? 0; - bucket[key].h = r.h ?? 0; - const leafCount = this.app.workspace.getLeavesOfType("markdown").length; - if (leafCount > 1 && isClone) { - console.log(`[Media Layout] SAVING coords (${leafCount} tabs):`, { - file: prevNotePath, - key, - savedCoords: { x: r.x, y: r.y, w: r.w, h: r.h } - }); - } - } - } catch (e) { - console.warn(`[MS] Failed to save coords for ${key}:`, e); - } - }); - } - root.querySelectorAll('.ms-host[data-ms-detached="1"]').forEach((host) => { - const key = host.dataset.msKey; - if (!key) return; - const isClone = !!host.dataset.msParentKey; - const isNote = host.classList.contains("ms-note-host"); - if (isClone || isNote) return; - try { - const r = this.readAbsRect(host); - const originals = noteData.originals; - if (originals[key]) { - originals[key].x = r.x ?? 0; - originals[key].y = r.y ?? 0; - originals[key].w = r.w ?? 0; - originals[key].h = r.h ?? 0; - } - } catch (e) { - console.warn(`[MS] Failed to save coords for detached original ${key}:`, e); - } - }); - root.querySelectorAll('.ms-host:not([data-ms-detached="1"])').forEach((host) => { - const key = host.dataset.msKey; - if (!key) return; - const isClone = !!host.dataset.msParentKey; - const isNote = host.classList.contains("ms-note-host"); - if (isClone || isNote) return; - try { - const originals = noteData.originals; - if (originals[key]) { - originals[key].free = host.dataset.msFree === "1"; - if (!originals[key].w || !originals[key].h || originals[key].w < 1 || originals[key].h < 1) { - const domRect = host.getBoundingClientRect(); - if (domRect.width > 0 && domRect.height > 0) { - originals[key].w = domRect.width; - originals[key].h = domRect.height; - console.warn(`[Media Layout] Restored invalid size for ${key} from DOM`); - } - } - } - } catch (e) { - console.warn(`[MS] Failed to process in-flow original ${key}:`, e); - } - }); - }); - await this.persistAll(); - } - } - const allHosts = document.querySelectorAll(".ms-host[data-ms-key]"); - allHosts.forEach((host) => { - const proxy = this.controlsProxy.get(host); - if (proxy) { - try { - proxy.ro.disconnect(); - proxy.root.remove(); - this.controlsProxy.delete(host); - } catch (e) { - console.warn("[Media Layout] Failed to cleanup controlsProxy:", e); - } - } - }); - const rootToClean = this.getAttachRoot?.(); - rootToClean?.querySelector(".ms-float-layer")?.replaceChildren(); - } - createNoteHost(container, key, x, y, st) { - const host = document.createElement("div"); - host.className = "ms-note-host ms-host"; - host.dataset.msDetached = "1"; - host.dataset.msKey = key; - host.style.position = "absolute"; - host.style.left = "0px"; - host.style.top = "0px"; - host.style.zIndex = "3000"; - this.applyTransform(host, Math.round(x), Math.round(y)); - host.style.width = `${Math.round(st?.w ?? 260)}px`; - host.style.height = `${Math.round(st?.h ?? 160)}px`; - host.dataset.msNote = this.getActiveNote()?.path ?? ""; - const note = document.createElement("div"); - note.className = "ms-note"; - const top = document.createElement("div"); - top.className = "ms-note-top"; - const close = document.createElement("div"); - close.className = "ms-note-close"; - top.appendChild(close); - const body = document.createElement("div"); - body.className = "ms-note-body"; - const isReadingMode = this.isInReadingMode(container); - body.setAttribute("contenteditable", isReadingMode ? "false" : "true"); - if (isReadingMode) { - body.style.cursor = "default"; - } - const stateForColor = st ?? this.notes[this.getActiveNote()?.path ?? ""]?.originals?.[key]; - if (stateForColor?.bgColor) { - note.style.backgroundColor = stateForColor.bgColor; - } - if (stateForColor?.headerColor) { - top.style.backgroundColor = stateForColor.headerColor; - } - body.textContent = st?.txt ?? ""; - body.style.fontSize = `${Math.max(10, st?.fontSize ?? 16)}px`; - let saveTimer = null; - const flushText = async () => { - const notePath = host.dataset.msNote || ""; - if (!notePath) return; - await this.saveState(notePath, key, { - txt: body.textContent ?? "", - fontSize: parseFloat(body.style.fontSize) || 16 - }); - }; - const scheduleTextSave = () => { - clearTimeout(saveTimer); - saveTimer = setTimeout(flushText, 300); - }; - if (!isReadingMode) { - body.addEventListener("input", scheduleTextSave); - body.addEventListener("blur", () => { - clearTimeout(saveTimer); - flushText(); - }); - } - note.append(top, body); - host.appendChild(note); - const mount = this.ensureFloatLayer(container); - mount.appendChild(host); - const overlay = this.ensureOverlay(host); - this.ensureHandles(overlay, false, true); - host.append(overlay); - let sx = 0, sy = 0, ox = 0, oy = 0, dragging = false; - const dragController = new AbortController(); - if (!isReadingMode) { - top.addEventListener("pointerdown", (e) => { - if (e.button !== 0) return; - e.preventDefault(); - e.stopPropagation(); - dragging = true; - const currentRect = this.readAppliedRect(host); - ox = currentRect.x || 0; - oy = currentRect.y || 0; - sx = e.clientX; - sy = e.clientY; - }); - const onMove = (e) => { - if (!dragging) return; - const nx = ox + (e.clientX - sx); - const ny = oy + (e.clientY - sy); - this.applyTransform(host, nx, ny); - }; - const onUp = async () => { - if (!dragging) return; - dragging = false; - const notePath = host.dataset.msNote || ""; - if (!notePath) return; - const r = this.readAbsRect(host); - await this.saveState(notePath, key, { w: r.w, h: r.h, x: r.x, y: r.y, txt: body.textContent ?? "", fontSize: parseFloat(body.style.fontSize) || 16 }); - }; - window.addEventListener("pointermove", onMove, { capture: true, signal: dragController.signal }); - window.addEventListener("pointerup", onUp, { capture: true, signal: dragController.signal }); - } - if (!isReadingMode) { - host.addEventListener("wheel", async (e) => { - if (!e.ctrlKey && !e.shiftKey) return; - e.preventDefault(); - const cur = parseFloat(body.style.fontSize) || 16; - const delta = e.shiftKey ? 1 : 10; - const next = Math.max(8, Math.min(300, cur + (e.deltaY < 0 ? delta : -delta))); - body.style.fontSize = `${next}px`; - const notePath = host.dataset.msNote || ""; - if (notePath) { - const r = this.readAbsRect(host); - await this.saveState(notePath, key, { w: r.w, h: r.h, x: r.x, y: r.y, txt: body.textContent ?? "", fontSize: next }); - } - }, { passive: false }); - } - if (isReadingMode) { - close.style.display = "none"; - } else { - close.onclick = async (ev) => { - ev.stopPropagation(); - const notePath = host.dataset.msNote || ""; - dragController.abort(); - host.remove(); - if (notePath) { - delete this.notes[notePath]?.originals?.[key]; - await this.persistAll(); - } - }; - } - const cleanupObserver = new MutationObserver(() => { - if (!document.contains(host)) { - dragController.abort(); - cleanupObserver.disconnect(); - } - }); - if (host.parentElement) { - cleanupObserver.observe(host.parentElement, { childList: true }); - } - if (!isReadingMode) { - host.addEventListener("contextmenu", (e) => { - e.preventDefault(); - e.stopPropagation(); - const key2 = host.dataset.msKey; - if (key2 && this.selectedKeys.has(key2) && this.selectedKeys.size > 1) { - const menu2 = new import_obsidian.Menu(); - this.app.workspace.trigger("editor-menu", menu2, null, this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView)); - menu2.showAtMouseEvent(e); - return; - } - const menu = new import_obsidian.Menu(); - menu.addItem((mi) => { - mi.setTitle(t(this.app, "bgColor")).setIcon("palette"); - mi.onClick(() => { - const tr = (k) => t(this.app, k); - const notePath = host.dataset.msNote; - if (!notePath || !key2) return; - const currentState = this.notes[notePath]?.originals?.[key2] ?? {}; - const initialColor = currentState.bgColor || "#242424"; - new BgColorModal(this.app, initialColor, async (result) => { - const { hex, removeBg, applyToHeader } = result; - const noteBody = host.querySelector(".ms-note"); - const noteHeader = host.querySelector(".ms-note-top"); - if (hex === "#262626" && !applyToHeader) { - if (noteHeader) noteHeader.style.backgroundColor = ""; - await this.saveState(notePath, key2, { headerColor: void 0 }); - } - if (removeBg) { - host.classList.add("ms-note-transparent"); - await this.saveState(notePath, key2, { transparent: true, bgColor: void 0, headerColor: void 0 }); - } else { - host.classList.remove("ms-note-transparent"); - const stateUpdate = { transparent: false }; - if (applyToHeader) { - if (noteHeader) noteHeader.style.backgroundColor = hex; - stateUpdate.headerColor = hex; - } else { - if (noteBody) noteBody.style.backgroundColor = hex; - stateUpdate.bgColor = hex; - } - await this.saveState(notePath, key2, stateUpdate); - } - }, tr, { isNoteBox: true }).open(); - }); - }); - menu.addItem((mi) => { - mi.setTitle(t(this.app, "changeTextColor")).setIcon("highlighter"); - mi.onClick(() => { - const tr = (k) => t(this.app, k); - const notePath = host.dataset.msNote; - if (!notePath || !key2) return; - const currentState = this.notes[notePath]?.originals?.[key2] ?? {}; - const initialColor = currentState.textColor || "#FFFFFF"; - const hasStroke = currentState.textStroke || false; - const colorToEdit = hasStroke ? currentState.textStrokeColor || initialColor : initialColor; - new BgColorModal(this.app, initialColor, async (result) => { - const { hex, addStroke } = result; - const currentFillColor = body.style.color || currentState.textColor || "#FFFFFF"; - body.classList.toggle("ms-note-text-stroked", addStroke); - if (addStroke) { - body.style.setProperty("--note-stroke-color", hex); - await this.saveState(notePath, key2, { textStroke: true, textStrokeColor: hex, textColor: currentFillColor }); - } else { - body.style.color = hex; - await this.saveState(notePath, key2, { textStroke: false, textColor: hex }); - } - }, tr, { isTextColor: true, initialStrokeState: hasStroke }).open(); - }); - }); - menu.addSeparator(); - menu.addItem((item) => { - item.setTitle(t(this.app, "deleteNote")).setIcon("trash").onClick(async () => { - const notePath = host.dataset.msNote || ""; - host.remove(); - if (notePath && key2) { - delete this.notes[notePath]?.originals?.[key2]; - await this.persistAll(); - } - }); - }); - menu.showAtMouseEvent(e); - }); - } - if (st?.focus && !isReadingMode) { - setTimeout(() => body.focus(), 0); - } - this.processRoot(this.getActiveContainerEl(), this.getActiveNote()?.path ?? ""); - return host; - } - findScrollableParents(startEl) { - let hScroll = null; - let vScroll = null; - let el = startEl; - while (el && el !== document.body && (!hScroll || !vScroll)) { - const style = getComputedStyle(el); - if (!hScroll && (style.overflowX === "auto" || style.overflowX === "scroll") && el.scrollWidth > el.clientWidth) { - hScroll = el; - } - if (!vScroll && (style.overflowY === "auto" || style.overflowY === "scroll") && el.scrollHeight > el.clientHeight) { - vScroll = el; - } - el = el.parentElement; - } - return { hScroll: hScroll ?? this.getAttachRoot(), vScroll: vScroll ?? this.getAttachRoot() }; - } - _attachDragMoveHandler(host, overlay, notePath, key, kind) { - const startMove = (e) => { - if (this.isInReadingMode(host)) { - return; - } - const isClone = !!host.dataset.msParentKey; - const isNote = kind === "note"; - if ((e.ctrlKey || e.metaKey) && (isClone || isNote)) { - this.handleSelectionClick(e, key); - return; - } - const t2 = e.target; - if (t2?.closest(".ms-clone-controls, .ms-cc-btn, .ms-cc-timeline, .ms-proxy-btn, .ms-proxy-ctrl, .ms-handle, .ms-note-body")) { - return; - } - if (e.button !== 0) return; - if (kind !== "note" && !isClone && host.dataset.msDetached !== "1") return; - if (host.dataset.msLocked === "1") return; - const sx = e.clientX ?? e.clientX ?? 0; - const sy = e.clientY ?? e.clientY ?? 0; - if (this.isControlsTarget(t2) || this.isControlsPoint(host, sx, sy) || this.isControlsProxyHost(host, sx, sy)) return; - const isMovingGroup = this.selectedKeys.has(key) && this.selectedKeys.size > 1; - const groupElements = []; - if (isMovingGroup) { - this.selectedKeys.forEach((selectedKey) => { - const el = this.findHostByKey(selectedKey); - if (el) { - const elCur = this.readAbsRect(el); - groupElements.push({ host: el, x0: elCur.x || 0, y0: elCur.y || 0 }); - } - }); - } - let moved = false; - const isDetachedOriginal = !isClone && kind !== "note" && host.dataset.msDetached === "1"; - const cur = isDetachedOriginal ? this.readAbsOrig(host) : this.readAbsRect(host); - let x0 = cur.x || 0; - let y0 = cur.y || 0; - const controller = new AbortController(); - const onMove = (ev) => { - const dx = (ev.clientX ?? ev.clientX ?? 0) - sx; - const dy = (ev.clientY ?? ev.clientY ?? 0) - sy; - if (!moved && Math.hypot(dx, dy) < 4) return; - if (!moved) { - moved = true; - overlay.classList.add("--dragmove"); - document.body.classList.add("ms-noselect"); - } - ev.preventDefault(); - if (isMovingGroup) { - groupElements.forEach((item) => { - let nx = item.x0 + dx, ny = item.y0 + dy; - if (ev.shiftKey) { - nx = snap(nx, 20); - ny = snap(ny, 20); - } - this.applyTransform(item.host, nx, ny); - }); - } else { - let nx = x0 + dx, ny = y0 + dy; - if (ev.shiftKey) { - nx = snap(nx, 20); - ny = snap(ny, 20); - } - this.applyTransform(host, nx, ny); - } - }; - const onUp = async () => { - controller.abort(); - overlay.classList.remove("--drag", "--dragmove"); - document.body.classList.remove("ms-noselect"); - if (!moved) return; - if (isMovingGroup) { - for (const item of groupElements) { - const rect = this.readAbsRect(item.host); - const itemKey = item.host.dataset.msKey; - await this.saveState(notePath, itemKey, { x: rect.x || 0, y: rect.y || 0 }); - } - } else { - const rect = this.readAbsRect(host); - const bucket = isClone ? this.notes[notePath]?.clones : this.notes[notePath]?.originals; - const existingRotate = bucket?.[key]?.rotate; - const hasRotate = isClone && typeof existingRotate === "number" || isNote && typeof existingRotate === "number"; - const stateToSave = { - x: rect.x || 0, - y: rect.y || 0, - free: host.dataset.msFree === "1", - detached: host.dataset.msDetached === "1" || isClone, - locked: host.dataset.msLocked === "1" - }; - if (!hasRotate) { - stateToSave.w = rect.w; - stateToSave.h = rect.h; - } else { - stateToSave.rotate = existingRotate; - } - if (kind === "note") { - const body = host.querySelector(".ms-note-body"); - stateToSave.txt = body?.textContent ?? ""; - stateToSave.fontSize = body ? parseFloat(body.style.fontSize) || 16 : 16; - } - if (isClone && (kind === "video" || kind === "audio")) { - stateToSave.fileName = host.dataset.msFileName; - } - await this.saveState(notePath, key, stateToSave); - } - }; - window.addEventListener("pointermove", onMove, { capture: true, signal: controller.signal }); - window.addEventListener("pointerup", onUp, { capture: true, signal: controller.signal }); - }; - host.addEventListener("pointerdown", startMove, { capture: true }); - } - _attachResizeHandlers(host, overlay, handles, notePath, key, kind, media) { - if (this.isInReadingMode(host)) { - return; - } - const map = { lt: { h: "left", v: "top" }, rt: { h: "right", v: "top" }, lb: { h: "left", v: "bottom" }, rb: { h: "right", v: "bottom" } }; - if (this.selectedKeys.size > 1) { - return; - } - ["lt", "rt", "lb", "rb"].forEach((keyH) => { - const handle = handles[keyH]; - const startDrag = (e) => { - e.preventDefault(); - e.stopPropagation(); - overlay.classList.add("--drag"); - document.body.classList.add("ms-noselect"); - const startX = e.clientX; - const startY = e.clientY; - const r0 = host.getBoundingClientRect(); - const isClone2 = !!host.dataset.msParentKey; - const isDetachedOriginal = !isClone2 && kind !== "note" && host.dataset.msDetached === "1"; - const cur = isDetachedOriginal ? this.readAbsOrig(host) : this.readAbsRect(host); - const startW = cur.w ?? r0.width; - const startH = cur.h ?? r0.height; - const startXoff = cur.x || 0; - const startYoff = cur.y || 0; - const from = map[keyH]; - const hasME = this.isMediaExtendedActive(host); - const baseAR = kind === "image" ? startW / Math.max(1, startH) : kind === "video" ? isClone2 || hasME ? 16 / 9 : media ? this.getAR(kind, media) : 16 / 9 : kind === "audio" ? 9 / 1 : 0; - const controller = new AbortController(); - const rotateAngle = isClone2 ? this.notes[notePath]?.clones?.[key]?.rotate || 0 : kind === "note" ? this.notes[notePath]?.originals?.[key]?.rotate || 0 : 0; - const hasRotate = rotateAngle !== 0; - const onMove = (ev) => { - ev.preventDefault(); - let dx = ev.clientX - startX; - let dy = ev.clientY - startY; - if (hasRotate) { - const angleRad = rotateAngle * Math.PI / 180; - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); - const rotatedDx = dx * cos + dy * sin; - const rotatedDy = -dx * sin + dy * cos; - dx = rotatedDx; - dy = rotatedDy; - } - const alt = ev.altKey; - const shift = ev.shiftKey; - const ctrl = ev.ctrlKey || ev.metaKey; - const isDetached = host.dataset.msDetached === "1" || isClone2; - let newW = startW, newH = startH; - if (isDetached) { - if (from.h === "right") newW = startW + dx; - else newW = startW - dx; - if (from.v === "bottom") newH = startH + dy; - else newH = startH - dy; - } else { - newW = startW + dx; - newH = startH + dy; - } - if (alt) { - if (from.h === "right" || from.h === "left") newW = startW + 2 * (from.h === "right" ? dx : -dx); - if (from.v === "bottom" || from.v === "top") newH = startH + 2 * (from.v === "bottom" ? dy : -dy); - } - let aw = clamp(newW, 64, 8192); - let ah = clamp(newH, 32, 8192); - if (shift) { - aw = snap(aw, 20); - ah = snap(ah, 20); - } - const free = ctrl; - if (kind === "image") { - if (!free) { - const useW = Math.abs(dx) >= Math.abs(dy); - if (useW) ah = Math.round(aw / baseAR); - else aw = Math.round(ah * baseAR); - } - } else if (kind === "note") { - } else { - ah = Math.round(aw / baseAR); - } - if (isDetached) { - let newX = startXoff, newY = startYoff; - if (hasRotate || alt) { - newX = startXoff - Math.round((aw - startW) / 2); - newY = startYoff - Math.round((ah - startH) / 2); - } else { - if (from.h === "left") newX = startXoff + startW - aw; - if (from.v === "top" && kind !== "audio") newY = startYoff + startH - ah; - } - if (shift) { - newX = snap(newX, 20); - newY = snap(newY, 20); - } - this.applyTransform(host, newX, newY); - } - this.applyBox(host, media, kind, aw, ah, free); - }; - const onUp = async () => { - controller.abort(); - overlay.classList.remove("--drag"); - document.body.classList.remove("ms-noselect"); - const bucket = isClone2 ? this.notes[notePath]?.clones : this.notes[notePath]?.originals; - const existingRotate = bucket?.[key]?.rotate; - const hasRotate2 = isClone2 && typeof existingRotate === "number" || isNote && typeof existingRotate === "number"; - const rect = this.readAbsRect(host); - let finalW = rect.w; - let finalH = rect.h; - if (hasRotate2) { - finalW = parseFloat(host.style.width) || rect.w; - finalH = parseFloat(host.style.height) || rect.h; - } - const stateToSave = { - w: finalW, - h: finalH, - x: rect.x || 0, - y: rect.y || 0, - free: host.dataset.msFree === "1", - detached: host.dataset.msDetached === "1" || isClone2, - locked: host.dataset.msLocked === "1" - }; - if (hasRotate2) { - stateToSave.rotate = existingRotate; - } - if (kind === "note") { - const body = host.querySelector(".ms-note-body"); - stateToSave.txt = body?.textContent ?? ""; - stateToSave.fontSize = body ? parseFloat(body.style.fontSize) || 16 : 16; - } - if (isClone2 && (kind === "video" || kind === "audio")) { - stateToSave.fileName = host.dataset.msFileName; - } - await this.saveState(notePath, key, stateToSave); - }; - if ("pointerId" in e && typeof handle.setPointerCapture === "function") { - handle.setPointerCapture(e.pointerId); - } - window.addEventListener("pointermove", onMove, { capture: true, signal: controller.signal }); - window.addEventListener("pointerup", onUp, { capture: true, signal: controller.signal }); - }; - handle.addEventListener("pointerdown", startDrag); - }); - const isClone = !!host.dataset.msParentKey; - const isNote = kind === "note"; - if ((isClone || isNote) && handles.t && handles.b && handles.l && handles.r) { - const cursorCache = {}; - const getRotationCursor = (effectiveAngle) => { - const roundedAngle = Math.round(effectiveAngle / 5) * 5; - if (cursorCache[roundedAngle]) { - return cursorCache[roundedAngle]; - } - const baseCursor = "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAAAsTAAALEwEAmpwYAAABNmlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjarY6xSsNQFEDPi6LiUCsEcXB4kygotupgxqQtRRCs1SHJ1qShSmkSXl7VfoSjWwcXd7/AyVFwUPwC/0Bx6uAQIYODCJ7p3MPlcsGo2HWnYZRhEGvVbjrS9Xw5+8QMUwDQCbPUbrUOAOIkjvjB5ysC4HnTrjsN/sZ8mCoNTIDtbpSFICpA/0KnGsQYMIN+qkHcAaY6addAPAClXu4vQCnI/Q0oKdfzQXwAZs/1fDDmADPIfQUwdXSpAWpJOlJnvVMtq5ZlSbubBJE8HmU6GmRyPw4TlSaqo6MukP8HwGK+2G46cq1qWXvr/DOu58vc3o8QgFh6LFpBOFTn3yqMnd/n4sZ4GQ5vYXpStN0ruNmAheuirVahvAX34y/Axk/96FpPYgAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAUggAARVYAAA6lwAAF2/XWh+QAAAJeUlEQVR42uybe0xU2R3HPxdYmMOAlJAaFIQuNBlZwfVN6gu7prUWqq21krpGZYtYjcE0uD6jTXzEWOkawVWsGmuqqLFb2sVXs4qOoCtxUxVdtW7Xqosa8IUKHpRhbv/gXvZCh2FAzM5c9pfczJ0753W/8zvf3+Oco9A18gbQACQAAvgXPiKqqr6Wdv21zyTgT4CqgaJq16fAW74CkLtL6WB7bwNzgNlAKTAKICAgAIfDgc1mw2KxcPHiRYBnQH8p5U1vBshisbj9XWln2sQCNmCkBsYIAD8/P5xOJ/369cNms5Genk5MTAyxsbGEhYWRmZnJ3r17AT6UUs4zE0ARQBwwS7taSFJSEkIIpk+fTlRUFMOHDyc4OJiAgIAW5SoqKkhOTgaokFK+7csA6W8WAmwH0pt/CAggJiaGsLAwJk2ahNVqJT09HavVSlBQkNtGDx48qN+W4uOiA3QQSAkPDyc0NJSJEycyZMgQUlNTAbBarR436HQ6KS8vx9CuT4uiWZvPExISKCoqokePHoSHhzcxuKJ0uMHHjx8TFxdHfX09wGFgjpTytq9zkAogpXzlDmtraxk6dCg3b7YwXieAXVLKXb4GkJ/2WQaQn5//yh2GhIRw5coVqqqqmDx5sj6AHwJ/FkLYhRA/97UpBjAJ+EgHKTMzs8s6aGhoYPfu3SxcuJDa2lojeRdLKdf7kpmfBvwFYP369cyb17Xui9PpJD8/n8WLFxsfnwIWSCnP+Yof1AxSTk4Oq1evfi2u/aFDh5g7dy7379/XH3+kkfl9X/Ckfwn8FWDmzJls2bLltQzM6XSyePFiI++VA6VSyvd9IdQYAZQAgVOnTmXHjh2vbYDV1dUMGjSIhw8f6o/swCop5XFvsmKt5TQwGKgtLCzsUtJuLT179qSyshK73U5wcDBACnBMCJHrDVbMz81vl4EfAHV79uxxC1JjYyO5ublcv3690/mVYcOGUV1dzbJly/RHOUKIo0KIN73BzLuTROAsYJ02bRrbtm1zWWjMmDGcP3+eiIgIxo8fT0ZGBgMHDsTf37/Dgzp37hzvvPMODodD99EWSCnLvS3d0RqkciDYFUgVFRWMGjWKly9fYgx2FUUhOTmZgoIC4uPjOzTwuro64uLiePr0qf5ou2bpHN7AQa6mWzLwfPfu3WRlZbX48auvvjLGbW8B2xwOBw0NDZSVlZGYmMiAAQM4ffq0xwO3Wq1UVVUxf/58/VEm8IkQoq83e96JwHNAnTFjhiqlVB88eKBmZWXpqdYdUko9pnsDsAA7gS8A1c/PTx09erR67949VUrp8XXhwgW9fVXzwhP1fl716uqUqw7SZ0DQuHHjGDx4MMXFxVy6dAlggJTyoqtKQogCmlK1AMydO5e1a9cSGBjoUacOh4PQ0FAMseMcKeVlb+EgVyAVA9+Dr1OwQICUsrGtSkKIKGAL8DOdpwoLC0lLS/M4tdK7d28eP34MUAlkSCmPeQMHueKk/kAqsFUDZ6s7cLR0yh0p5QRgEFDicDiYMmUKGRkZvHjxwqOO7969qyfyojVO+uk3beZp56URQvi3B04bGpUDLAfCYmNjKSkpoXfv3h7VXbNmjTFWTJVSHvYmDWoNUmMn6/0RGAocv3XrFvHx8ezfv9+jusuWLWPlypXNLoAQItFrNehVRQgRrnFTOkBKSgpHjx71qO6UKVMoLi7uNHG/LpLuUoAMQM0BsoG+QUFB1NTUeFQvJiZGT510GCSfAkgDKVHTppHQlOP2JFwRQhhdgPeklF94DQd1pWj//hztRQkJCfFIkwx/1EgN4NcezX/TIGVpuSF69eplzBe1KQYgxwohfmtagDSQrgLvag4p0dHRNDQ0uK0TFBTEzp079a/vdoVl8zoOcsEtkUAhTUtHHvVns9m4ffu2zkfpUsq7piHpNkCyaemOkZ72abVa9fDnYynlRNOQdBt/wr+NxB0ZGdluncrKSv12ghAiw3Qc5M66PXnyhNmzZ7stHx4eTkFBgf71PSFEjCk5qA0/6RxgKS8vp3///p76R4ellKmmnGIuNGkHQHJyMnV1dW7LV1VV6bejOmPVfA4gDaR5wD+haTXE3UpKjx49GDhwIEBoZxxInwRIkzlA6Y0bN1iyZInbgmfOnGn2soUQv+sWAEkp/wu8D7Bx40bjzhGXsm/fPv12kqlJ2gUJHwF+IoTg0aNHbsuGhYXpS1N5Usr5piTpNqbap1JKzp4967ag3W43Evb3u4UGaVq0DljoyXgiIiJ4/vw5wDEp5Y+6gwYhpVyke9mGtX2XcufOHaMWxXcLDdK0aDhNu1I6okWfqKr6Y9NrkAbKGV2Lli9f7mmcNlJRlO90Cw3StGgk2u7+9sZlsGj7VFX9tek1SAOlDKiAph0n7uTKlSvNXNSsLYoSpihKpGkB0qQUYPLkyW4LRUVFNd8qirJVUZSzQA1wT1GUE4qi9DXdFNOmmT/g8PPzo7a21u2a/9SpUykqKmoRt1ksFqqrqwGuAf1Np0HaKu9/nE4nN27ccFs2NzeXyMhI8vPzsdvtnD9/ni+//JKUlBSAvsCHZtSg7wJ/AGampaVx4MABt+WfPXtm3FYDwLVr1/QMwLUAs2iOECINmA78Cpq25Jw8ebLdeq3BAThy5EgznwWYBJztwG90J9Bms5GUlMTYsWP13Scet9XY2MiuXc2Hkg6aIZoPAuoDAwNZsWIFI0aMICEhAYvF0u7JyLamXHR0tO4jWcygQVaA4OBgcnJyXrmxq1ev6tuPARw+b8WklI+AspqaGnJzX31zvt1u19fTtqqq2miWdEcq2vnYdevWkZ2d3al2Xr58SZ8+ffS92amqqh42S7rjEFoqddGiReTl5XWqnRcvXhg3w58wWzRfZARpw4YNHY9RSkv1w8hHVFWVZgxWm0FaunQpH3zwQYfqG1K2ZaZMdxg4aQLwD4BVq1axYMGCduvU19cTERGhE/RoVVVLzRrNI6X8GJgATcmz9evbPztcV1dn3PX/mZnTHTpIxUAawIoVK9oFqaSkROefv+v8Y2qADNYtVQepLT9JVVVj/NXiaIMpOcidn5SXl8esWbP+7x0iIyN1E/+mqqo3u4UGtdKkaQDZ2dkcP97yvHB1dbUOzi0jON0GIA2kPcDvAdLS0jh16lQL/0e/bV2v2wCkgbQSWA0wbtw4Nm3axOXLl9m8eXNzKMa3AsAqvj7BqF/lrk4c+ndTgE4AnwMPaTo2+jdVVX/hquD/BgANh4vTAjsODAAAAABJRU5ErkJggg=="; - const svgRotation = roundedAngle - 45 + 90; - const svgCursor = ``; - const encoded = encodeURIComponent(svgCursor).replace(/'/g, "%27"); - const cursor = `url('data:image/svg+xml;charset=utf-8,${encoded}') 36 36, grabbing`; - cursorCache[roundedAngle] = cursor; - return cursor; - }; - const rotationHandles = [ - { handle: handles.t, name: "t" }, - { handle: handles.b, name: "b" }, - { handle: handles.l, name: "l" }, - { handle: handles.r, name: "r" } - ]; - rotationHandles.forEach(({ handle, name }) => { - if (!handle) return; - let isRotating = false; - const updateCursorOnHover = (e) => { - if (isRotating) return; - const currentTransform = host.style.transform || ""; - const rotateMatch = currentTransform.match(/rotate\(([^)]+)deg\)/); - const currentRotate = rotateMatch ? parseFloat(rotateMatch[1]) : 0; - let baseAngle = 0; - if (name === "t") baseAngle = 270; - else if (name === "r") baseAngle = 0; - else if (name === "b") baseAngle = 90; - else if (name === "l") baseAngle = 180; - const effectiveAngle = baseAngle + currentRotate; - const cursor = getRotationCursor(effectiveAngle); - handle.style.cursor = cursor; - handle.style.setProperty("cursor", cursor, "important"); - }; - handle.addEventListener("mouseenter", updateCursorOnHover); - handle.addEventListener("mousemove", updateCursorOnHover); - updateCursorOnHover(new MouseEvent("mousemove")); - const startRotate = (e) => { - e.preventDefault(); - e.stopPropagation(); - isRotating = true; - overlay.classList.add("--drag"); - document.body.classList.add("ms-noselect"); - const savedState = this.notes[notePath]?.clones?.[key]; - const realWidth = savedState?.w || parseFloat(host.style.width) || host.offsetWidth; - const realHeight = savedState?.h || parseFloat(host.style.height) || host.offsetHeight; - const rect = host.getBoundingClientRect(); - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; - const currentTransform = host.style.transform || ""; - const rotateMatch = currentTransform.match(/rotate\(([^)]+)deg\)/); - const startRotate2 = rotateMatch ? parseFloat(rotateMatch[1]) : 0; - const startAngle = Math.atan2( - e.clientY - centerY, - e.clientX - centerX - ) * (180 / Math.PI); - let baseAngle = 0; - if (name === "t") baseAngle = 270; - else if (name === "r") baseAngle = 0; - else if (name === "b") baseAngle = 90; - else if (name === "l") baseAngle = 180; - let cursorUpdateTimer = null; - let currentRotation = startRotate2; - let currentCursor = getRotationCursor(baseAngle + startRotate2); - const controller = new AbortController(); - const onMove = (ev) => { - ev.preventDefault(); - document.body.style.cursor = currentCursor; - handle.style.cursor = currentCursor; - handle.style.setProperty("cursor", currentCursor, "important"); - if (cursorUpdateTimer) { - clearTimeout(cursorUpdateTimer); - cursorUpdateTimer = null; - } - const currentAngle = Math.atan2( - ev.clientY - centerY, - ev.clientX - centerX - ) * (180 / Math.PI); - let deltaAngle = currentAngle - startAngle; - let newRotate = startRotate2 + deltaAngle; - if (ev.shiftKey) { - newRotate = Math.round(newRotate / 15) * 15; - } - currentRotation = newRotate; - const translateMatch = currentTransform.match(/translate\(([^)]+)\)/); - const translateValue = translateMatch ? translateMatch[1] : "0px, 0px"; - host.style.transform = `translate(${translateValue}) rotate(${newRotate}deg)`; - cursorUpdateTimer = window.setTimeout(() => { - const effectiveAngle = baseAngle + currentRotation; - currentCursor = getRotationCursor(effectiveAngle); - document.body.style.cursor = currentCursor; - handle.style.cursor = currentCursor; - handle.style.setProperty("cursor", currentCursor, "important"); - }, 50); - }; - const onUp = async () => { - if (cursorUpdateTimer) { - clearTimeout(cursorUpdateTimer); - cursorUpdateTimer = null; - } - isRotating = false; - controller.abort(); - overlay.classList.remove("--drag"); - document.body.classList.remove("ms-noselect"); - document.body.style.cursor = ""; - const finalTransform = host.style.transform || ""; - const finalRotateMatch = finalTransform.match(/rotate\(([^)]+)deg\)/); - const finalRotate = finalRotateMatch ? parseFloat(finalRotateMatch[1]) : 0; - await this.saveState(notePath, key, { rotate: finalRotate }); - }; - if ("pointerId" in e && typeof handle.setPointerCapture === "function") { - handle.setPointerCapture(e.pointerId); - } - window.addEventListener("pointermove", onMove, { capture: true, signal: controller.signal }); - window.addEventListener("pointerup", onUp, { capture: true, signal: controller.signal }); - }; - handle.addEventListener("pointerdown", startRotate); - }); - } - } - adjustZIndex(host, direction) { - const currentZ = parseInt(host.style.zIndex || "2900", 10); - const newZ = currentZ + direction; - host.style.zIndex = String(newZ); - const notePath = this.getActiveNote()?.path; - const key = host.dataset.msKey; - if (notePath && key && host.dataset.msParentKey) { - this.saveState(notePath, key, { zIndex: newZ }); - } - } - handleMarqueeStart(e) { - const attachRoot = this.getAttachRoot(); - if (!attachRoot?.contains(e.target) || e.target.closest(".ms-handle")) { - return; - } - const host = e.target.closest(".ms-host"); - if (host && host.dataset.msKey) { - return; - } - e.preventDefault(); - e.stopPropagation(); - this.marqueeWithCtrl = e.ctrlKey || e.metaKey; - const startX = e.clientX; - const startY = e.clientY; - const onMove = (moveEvent) => { - this.isMarqueeSelecting = true; - if (!this.marqueeStart) { - this.marqueeStart = { x: startX, y: startY }; - } - this.ensureMarquee(); - if (this.isMarqueeSelecting) { - this.updateMarquee(moveEvent.clientX, moveEvent.clientY); - } - }; - const onUp = (upEvent) => { - window.removeEventListener("pointermove", onMove, true); - window.removeEventListener("pointerup", onUp, true); - if (this.isMarqueeSelecting) { - this.applyMarqueeSelection(); - this.removeMarquee(); - this.isMarqueeSelecting = false; - this.marqueeStart = null; - this.marqueeWithCtrl = false; - } - this.updateSelectionVisuals(); - }; - window.addEventListener("pointermove", onMove, true); - window.addEventListener("pointerup", onUp, true); - } - handleSelectionClick(e, key) { - e.preventDefault(); - e.stopPropagation(); - this.toggleSelection(key); - this.updateSelectionVisuals(); - } - toggleSelection(key) { - if (this.selectedKeys.has(key)) { - this.selectedKeys.delete(key); - } else { - this.selectedKeys.add(key); - } - } - updateSelectionVisuals() { - this.getActiveContainerEl()?.querySelectorAll(".ms-host").forEach((el) => { - const host = el; - const key = host.dataset.msKey; - if (key && this.selectedKeys.has(key)) { - host.classList.add("--selected"); - } else { - host.classList.remove("--selected"); - } - }); - this.getActiveContainerEl()?.querySelectorAll(".ms-overlay").forEach((overlay) => { - const handles = overlay.querySelectorAll(".ms-handle"); - const display = this.selectedKeys.size > 1 ? "none" : ""; - handles.forEach((h) => h.style.display = display); - }); - } - handleDeselectClick = (e) => { - if (e.button !== 0) return; - if (e.ctrlKey || e.metaKey) return; - if (e.target.closest(".ms-host, .menu")) return; - if (this.selectedKeys.size > 0) { - this.selectedKeys.clear(); - this.updateSelectionVisuals(); - } - }; - ensureMarquee() { - let marquee = document.getElementById("ms-marquee"); - if (!marquee) { - marquee = document.createElement("div"); - marquee.id = "ms-marquee"; - document.body.appendChild(marquee); - } - return marquee; - } - updateMarquee(endX, endY) { - const marquee = this.ensureMarquee(); - if (!this.marqueeStart) return; - const x = Math.min(this.marqueeStart.x, endX); - const y = Math.min(this.marqueeStart.y, endY); - const width = Math.abs(this.marqueeStart.x - endX); - const height = Math.abs(this.marqueeStart.y - endY); - Object.assign(marquee.style, { - left: `${x}px`, - top: `${y}px`, - width: `${width}px`, - height: `${height}px`, - display: "block" - }); - } - applyMarqueeSelection() { - const marquee = document.getElementById("ms-marquee"); - if (!marquee) return; - const marqueeRect = marquee.getBoundingClientRect(); - if (!this.marqueeWithCtrl) { - this.selectedKeys.clear(); - } - const hosts = this.getActiveContainerEl()?.querySelectorAll(".ms-host"); - hosts?.forEach((host) => { - const isClone = !!host.dataset.msParentKey; - const isNote = host.classList.contains("ms-note-host"); - if (isClone || isNote) { - const hostRect = host.getBoundingClientRect(); - if (hostRect.left < marqueeRect.right && hostRect.right > marqueeRect.left && hostRect.top < marqueeRect.bottom && hostRect.bottom > marqueeRect.top) { - const key = host.dataset.msKey; - if (key) { - if (this.marqueeWithCtrl) { - this.toggleSelection(key); - } else { - this.selectedKeys.add(key); - } - } - } - } - }); - } - async buildGroupContextMenu(menu, host, key, kind, notePath) { - menu.addItem((item) => { - item.setTitle(t(this.app, "deleteSelected", { n: this.selectedKeys.size })).setIcon("trash").onClick(async () => { - const noteData = this.notes[notePath]; - if (!noteData) return; - const keysToDelete = new Set(this.selectedKeys); - for (const selectedKey of keysToDelete) { - const el = this.findHostByKey(selectedKey); - if (!el) continue; - const isClone = !!el.dataset.msParentKey; - const isNote = el.classList.contains("ms-note-host"); - el.remove(); - if (isClone) delete noteData.clones?.[selectedKey]; - if (isNote) delete noteData.originals?.[selectedKey]; - } - this.selectedKeys.clear(); - this.updateSelectionVisuals(); - await this.persistAll(); - }); - }); - } - removeMarquee() { - document.getElementById("ms-marquee")?.remove(); - } - mountControlsForHost(host, mediaEl) { - const isClone = !!host.dataset.msParentKey; - host.style.pointerEvents = "auto"; - if (isClone) { - this.hydrateCloneMedia(host); - host.querySelectorAll(".mejs__controls, .plyr__controls, .vjs-control-bar").forEach((el) => el.style.display = "none"); - this.mountCloneControls(host, mediaEl); - this.installClickToggleDebug(host, mediaEl); - } else { - this.mountControlsProxy(host, mediaEl); - } - } - hydrateCloneMedia(host) { - const parentKey = host.dataset.msParentKey; - if (!parentKey) return; - const origHost = this.findHostByKey(parentKey); - const cloned = host.querySelector("video, audio"); - const original = origHost?.querySelector("video, audio"); - if (!cloned || !original) return; - const src = (original.currentSrc || original.src || "").trim(); - if (!src) return; - if (cloned.src !== src) { - cloned.src = src; - try { - cloned.load(); - } catch { - } - } - } - mountControlsProxy(host, media) { - host.querySelectorAll(".ms-proxy-ctrl").forEach((n) => n.remove()); - const sels = [ - ".ob-video-controls", - ".media-controls", - ".mejs__controls", - ".plyr__controls", - ".vjs-control-bar", - ".me-controls", - "[class*='controls']" - ]; - let panel = null; - for (const s of sels) { - panel = host.querySelector(s); - if (panel) break; - } - if (!panel) return; - const prev = this.controlsProxy.get(host); - if (prev) { - prev.ro.disconnect(); - prev.root.remove(); - this.controlsProxy.delete(host); - } - const wrap = document.createElement("div"); - wrap.className = "ms-proxy-ctrl"; - wrap.style.position = "absolute"; - wrap.style.inset = "0"; - wrap.style.pointerEvents = "none"; - wrap.style.zIndex = "2147483647"; - host.appendChild(wrap); - const rebuild = () => { - wrap.innerHTML = ""; - const hostR = host.getBoundingClientRect(); - const btns = panel.querySelectorAll( - "button, [role='button'], .plyr__control, .vjs-control" - ); - btns.forEach((btn) => { - const rb = btn.getBoundingClientRect(); - const proxy = document.createElement("div"); - proxy.className = "ms-proxy-btn"; - proxy.style.position = "absolute"; - proxy.style.pointerEvents = "auto"; - proxy.style.left = `${rb.left - hostR.left}px`; - proxy.style.top = `${rb.top - hostR.top}px`; - proxy.style.width = `${rb.width}px`; - proxy.style.height = `${rb.height}px`; - const down = (ev) => { - ev.stopPropagation(); - this.forwardFrom(ev, btn, "pointerdown"); - }; - const up = (ev) => { - ev.stopPropagation(); - this.forwardFrom(ev, btn, "pointerup"); - }; - const isClone = !!host.dataset.msParentKey; - const clk = (ev) => { - ev.stopPropagation(); - ev.preventDefault(); - try { - btn.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window })); - btn.click?.(); - } catch { - } - if (isClone && media instanceof HTMLMediaElement) { - const hint = `${btn.getAttribute("aria-label") || btn.getAttribute("title") || btn.className || ""}`.toLowerCase(); - if (/play|pause|toggle/.test(hint)) { - media.paused ? media.play().catch(() => { - }) : media.pause(); - media.focus?.(); - return; - } - if (/mute/.test(hint)) { - media.muted = !media.muted; - media.focus?.(); - return; - } - const tl = btn.closest(".plyr__progress, .vjs-progress-holder, .mejs__total-time, input[type=range]"); - if (tl && Number.isFinite(media.duration)) { - const r = tl.getBoundingClientRect(); - const frac = r.width ? Math.min(Math.max(ev.clientX - r.left, 0), r.width) / r.width : 0; - media.currentTime = media.duration * frac; - return; - } - const vb = btn.closest('.plyr__volume, .vjs-volume-bar, .mejs__volume, input[type=range][aria-label*="volume" i]'); - if (vb) { - const r = vb.getBoundingClientRect(); - const frac = r.width ? Math.min(Math.max(ev.clientX - r.left, 0), r.width) / r.width : 0; - media.volume = Math.min(1, Math.max(0, frac)); - return; - } - } - }; - proxy.addEventListener("pointerdown", down, { capture: true }); - proxy.addEventListener("pointerup", up, { capture: true }); - proxy.addEventListener("mousedown", down, { capture: true }); - proxy.addEventListener("mouseup", up, { capture: true }); - proxy.addEventListener("click", clk, { capture: true }); - wrap.appendChild(proxy); - }); - }; - const ro = new ResizeObserver(() => rebuild()); - ro.observe(host); - ro.observe(panel); - this.controlsProxy.set(host, { root: wrap, ro }); - rebuild(); - } - mountCloneControls(host, mediaEl) { - if (!host.dataset.msParentKey) return; - const media = mediaEl; - if (!(media instanceof HTMLMediaElement)) return; - if (host.querySelector(".ms-clone-controls")) return; - const isVideo = media.tagName === "VIDEO"; - const root = document.createElement("div"); - root.className = "ms-clone-controls"; - Object.assign(root.style, { - position: "absolute", - left: "8px", - right: "8px", - bottom: "8px", - display: "flex", - flexDirection: "column", - gap: "6px", - pointerEvents: "none", - background: "rgba(0,0,0,.35)", - borderRadius: "8px", - padding: "6px 8px", - backdropFilter: "blur(2px)", - zIndex: "2147483647", - userSelect: "none" - }); - const stop = (e) => e.stopPropagation(); - root.addEventListener("mousedown", stop, false); - root.addEventListener("click", stop, false); - const smartClickHandler = (e, action) => { - const target = e.currentTarget; - e.preventDefault(); - e.stopPropagation(); - const onUp2 = (upEvent) => { - window.removeEventListener("mouseup", onUp2, true); - if (upEvent.button === 2) { - return; - } - const dx = upEvent.clientX - e.clientX; - const dy = upEvent.clientY - e.clientY; - if (Math.hypot(dx, dy) < 4) { - action(e); - } - }; - window.addEventListener("mouseup", onUp2, true); - }; - let fullscreenHideTimer = null; - const onFsMouseMove = () => { - if (document.fullscreenElement === host) { - host.classList.remove("ms-fs-inactive"); - clearTimeout(fullscreenHideTimer); - fullscreenHideTimer = setTimeout(() => { - host.classList.add("ms-fs-inactive"); - }, 5e3); - } - }; - const onFsChange = () => { - if (document.fullscreenElement === host) { - window.addEventListener("mousemove", onFsMouseMove, true); - onFsMouseMove(); - } else { - window.removeEventListener("mousemove", onFsMouseMove, true); - clearTimeout(fullscreenHideTimer); - host.classList.remove("ms-fs-inactive"); - } - }; - host.addEventListener("fullscreenchange", onFsChange); - const ro = new ResizeObserver((entries) => { - const { width } = entries[0].contentRect; - root.classList.toggle("ms-cc-compact", width < 240); - }); - ro.observe(host); - const timeline = document.createElement("div"); - timeline.className = "ms-cc-timeline"; - Object.assign(timeline.style, { - position: "relative", - width: "100%", - height: "6px", - borderRadius: "3px", - background: "rgba(255,255,255,.25)", - cursor: "pointer", - pointerEvents: "auto" - }); - const progress = document.createElement("div"); - progress.className = "ms-cc-progress"; - Object.assign(progress.style, { - position: "absolute", - left: "0", - top: "0", - bottom: "0", - width: "0%", - background: "rgba(255,255,255,.9)", - borderRadius: "3px", - pointerEvents: "none" - }); - timeline.appendChild(progress); - const thumb = document.createElement("div"); - thumb.className = "ms-cc-thumb"; - Object.assign(thumb.style, { - position: "absolute", - top: "50%", - width: "12px", - height: "12px", - background: "#fff", - borderRadius: "50%", - transform: "translate(-50%, -50%)", - pointerEvents: "none", - opacity: "0", - transition: "opacity 0.1s ease-out" - }); - timeline.appendChild(thumb); - const btnBar = document.createElement("div"); - btnBar.className = "ms-cc-btn-bar"; - Object.assign(btnBar.style, { display: "flex", alignItems: "center", gap: "8px", width: "100%" }); - const makeBtn = (svgIcon, title) => { - const b = document.createElement("div"); - b.className = "ms-cc-btn"; - Object.assign(b.style, { - width: "26px", - height: "26px", - borderRadius: "6px", - display: "inline-flex", - alignItems: "center", - justifyContent: "center", - pointerEvents: "auto", - cursor: "pointer", - color: "#fff", - background: "rgba(255,255,255,.15)" - }); - b.onmouseenter = () => b.style.background = "rgba(255,255,255,.25)"; - b.onmouseleave = () => b.style.background = "rgba(255,255,255,.15)"; - b.innerHTML = svgIcon; - b.title = title; - return b; - }; - const ICONS = { - play: ``, - pause: ``, - mute: ``, - volumeMute: ``, - volumeLow: ``, - volumeHigh: ``, - volumeOff: ``, - rewind: ``, - forward: ``, - fullscreen: ``, - edit: ``, - more: `` - }; - const leftGroup = document.createElement("div"); - leftGroup.style.display = "flex"; - leftGroup.style.alignItems = "center"; - if (!isVideo) { - const btnRewind = makeBtn(ICONS.rewind, "Rewind 30s"); - btnRewind.addEventListener("mousedown", (e) => smartClickHandler(e, () => media.currentTime = Math.max(0, media.currentTime - 10))); - btnRewind.style.marginRight = "15px"; - leftGroup.appendChild(btnRewind); - } - const btnPlay = makeBtn(ICONS.play, "Play/Pause"); - if (!isVideo) { - const btnForward = makeBtn(ICONS.forward, "Forward 30s"); - btnForward.classList.add("ms-cc-hide-compact"); - btnForward.addEventListener("mousedown", (e) => smartClickHandler(e, () => media.currentTime = Math.min(media.duration, media.currentTime + 30))); - btnPlay.style.marginRight = "15px"; - leftGroup.appendChild(btnPlay); - btnForward.style.marginRight = "15px"; - leftGroup.appendChild(btnForward); - } else { - leftGroup.appendChild(btnPlay); - btnPlay.style.marginRight = "8px"; - } - const btnMute = makeBtn(ICONS.mute, "Mute/Unmute"); - const volumeSlider = document.createElement("input"); - volumeSlider.type = "range"; - volumeSlider.min = "0"; - volumeSlider.max = "1"; - volumeSlider.step = "0.05"; - volumeSlider.classList.add("ms-cc-hide-compact"); - Object.assign(volumeSlider.style, { width: "70px", cursor: "pointer", accentColor: "#fff", pointerEvents: "auto" }); - if (!isVideo) { - volumeSlider.style.marginLeft = "0px"; - } - leftGroup.append(btnMute, volumeSlider); - const nameLabel = document.createElement("div"); - nameLabel.className = "ms-cc-name-label"; - Object.assign(nameLabel.style, { - flex: "1 1 auto", - minWidth: "0", - textAlign: "center", - color: "rgba(255,255,255,.7)", - fontSize: "12px", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - pointerEvents: "none", - padding: "0 8px" - }); - const rightGroup = document.createElement("div"); - rightGroup.style.display = "flex"; - rightGroup.style.alignItems = "center"; - rightGroup.style.marginLeft = "auto"; - if (isVideo) { - let wasLockedBeforeFs = false; - let scrollRatioX = 0, scrollRatioY = 0, hScrollEl = null, vScrollEl = null; - const btnFs = makeBtn(ICONS.fullscreen, "Fullscreen"); - btnFs.addEventListener("mousedown", (e) => smartClickHandler(e, () => { - if (!document.fullscreenElement) { - const { hScroll, vScroll } = this.findScrollableParents(host.parentElement); - this.hScrollEl = hScroll; - this.vScrollEl = vScroll; - if (this.hScrollEl && this.hScrollEl.scrollWidth > this.hScrollEl.clientWidth) { - this.scrollRatioX = this.hScrollEl.scrollLeft / (this.hScrollEl.scrollWidth - this.hScrollEl.clientWidth); - } - if (this.vScrollEl && this.vScrollEl.scrollHeight > this.vScrollEl.clientHeight) { - this.scrollRatioY = this.vScrollEl.scrollTop / (this.vScrollEl.scrollHeight - this.vScrollEl.clientHeight); - } - } - if (!document.fullscreenElement) host.requestFullscreen().catch(() => { - }); - else document.exitFullscreen().catch(() => { - }); - })); - btnFs.style.marginRight = "10px"; - rightGroup.appendChild(btnFs); - } - const btnEdit = makeBtn(ICONS.edit, t(this.app, "edit")); - const btnMore = makeBtn(ICONS.more, "More options"); - btnEdit.classList.add("ms-cc-hide-compact"); - btnMore.classList.add("ms-cc-hide-compact"); - btnEdit.style.marginRight = "10px"; - btnEdit.addEventListener("mousedown", (e) => smartClickHandler(e, (evt) => { - new CloneSettingsModal(this.app, { - animation: this.cloneAnimation, - bgOpacity: this.cloneBgOpacity, - controlsOpacity: this.cloneControlsOpacity, - hideDelay: this.cloneHideDelay, - showAudioHandles: this.cloneShowAudioHandles - }, async (settings) => { - if (settings) { - this.cloneAnimation = settings.animation; - this.cloneBgOpacity = settings.bgOpacity; - this.cloneControlsOpacity = settings.controlsOpacity; - this.cloneHideDelay = settings.hideDelay; - this.cloneShowAudioHandles = settings.showAudioHandles; - await this.persistAll(); - this.applyCloneSettingsToAll(); - } - }).open(); - })); - btnMore.addEventListener("mousedown", (e) => smartClickHandler(e, (evt) => { - const origHost = this.findHostByKey(host.dataset.msParentKey); - const meEmbed = origHost?.closest(".mx-media-embed"); - const target = meEmbed || origHost; - if (target) { - const rect = btnMore.getBoundingClientRect(); - target.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom })); - } - })); - rightGroup.append(btnEdit, btnMore); - const centerGroup = document.createElement("div"); - Object.assign(centerGroup.style, { - flex: "1 1 auto", - minWidth: "0", - display: "flex", - alignItems: "center", - justifyContent: "center", - pointerEvents: "none" - }); - const timeLabel = document.createElement("div"); - timeLabel.className = "ms-cc-time-label"; - Object.assign(timeLabel.style, { - color: "rgba(255,255,255,.7)", - fontSize: "12px", - whiteSpace: "nowrap", - paddingRight: "8px" - }); - timeLabel.textContent = "0:00 / 0:00 |"; - centerGroup.append(timeLabel, nameLabel); - btnBar.append(leftGroup, centerGroup, rightGroup); - root.append(timeline, btnBar); - host.appendChild(root); - const parentKey = host.dataset.msParentKey; - if (parentKey) { - const notePath = this.getActiveNote()?.path ?? ""; - const savedState = this.notes[notePath]?.clones?.[host.dataset.msKey]; - const savedFileName = savedState?.fileName; - const formatTime2 = (seconds) => { - if (!isFinite(seconds)) return "0:00"; - const m = Math.floor(seconds / 60); - const s = Math.floor(seconds % 60).toString().padStart(2, "0"); - return `${m}:${s}`; - }; - if (savedFileName) { - nameLabel.textContent = savedFileName; - nameLabel.title = savedFileName; - host.dataset.msFileName = savedFileName; - } else { - const originalHost = this.findHostByKey(parentKey); - if (originalHost) { - const mediaEl2 = originalHost.querySelector("video, audio, img"); - if (mediaEl2) { - const { base } = this.getSrcAndBase(originalHost, mediaEl2); - nameLabel.textContent = base || "Untitled"; - nameLabel.title = base || "Untitled"; - host.dataset.msFileName = base || "Untitled"; - } - } - } - } - const refresh = (isSeeking = false, formatTime2) => { - btnPlay.innerHTML = media.paused ? ICONS.play : ICONS.pause; - if (media.muted || media.volume === 0) { - btnMute.innerHTML = ICONS.volumeOff; - } else if (media.volume <= 0.5) { - btnMute.innerHTML = ICONS.volumeLow; - } else { - btnMute.innerHTML = ICONS.volumeHigh; - } - volumeSlider.value = String(media.muted ? 0 : media.volume); - const dur = Number.isFinite(media.duration) ? media.duration : 0; - const frac = dur ? media.currentTime / dur : 0; - progress.style.width = Math.round(Math.max(0, Math.min(1, frac)) * 100) + "%"; - thumb.style.left = progress.style.width; - if (!isSeeking) { - timeLabel.textContent = `${formatTime2(media.currentTime)} / ${formatTime2(dur)} | `; - } - }; - const formatTime = (seconds) => { - if (!isFinite(seconds)) return "0:00"; - const m = Math.floor(seconds / 60); - const s = Math.floor(seconds % 60).toString().padStart(2, "0"); - return `${m}:${s}`; - }; - refresh(false, formatTime); - btnPlay.addEventListener("mousedown", (e) => smartClickHandler(e, (evt) => { - if (media.paused) media.play().catch(() => void 0); - else media.pause(); - })); - btnMute.addEventListener("mousedown", (e) => smartClickHandler(e, (evt) => { - media.muted = !media.muted; - })); - volumeSlider.addEventListener("input", () => { - media.volume = Number(volumeSlider.value); - if (media.volume > 0) media.muted = false; - const notePath = this.getActiveNote()?.path ?? ""; - const key = host.dataset.msKey; - if (notePath && key) { - this.saveState(notePath, key, { volume: media.volume }); - } - }); - let seeking = false; - const seekTo = (x) => { - const r = timeline.getBoundingClientRect(); - const frac = r.width ? Math.min(1, Math.max(0, (x - r.left) / r.width)) : 0; - if (isFinite(media.duration) && media.duration > 0) { - media.currentTime = media.duration * frac; - refresh(true, formatTime); - } - }; - timeline.addEventListener("pointerdown", (e) => { - e.stopPropagation(); - seeking = true; - e.currentTarget.setPointerCapture?.(e.pointerId); - seekTo(e.clientX); - }, { capture: true }); - timeline.addEventListener("pointerenter", () => { - thumb.style.opacity = "1"; - }); - timeline.addEventListener("pointerleave", () => { - if (!seeking) thumb.style.opacity = "0"; - }); - const onMove = (e) => { - if (seeking) seekTo(e.clientX); - }; - const onUp = (e) => { - if (seeking) { - seeking = false; - e.currentTarget.releasePointerCapture?.(e.pointerId); - } - if (!timeline.matches(":hover")) thumb.style.opacity = "0"; - }; - window.addEventListener("pointermove", onMove, true); - window.addEventListener("pointerup", onUp, true); - media.addEventListener("timeupdate", () => refresh(false, formatTime)); - media.addEventListener("play", () => refresh(false, formatTime)); - media.addEventListener("pause", () => refresh(false, formatTime)); - media.addEventListener("volumechange", () => refresh(false, formatTime)); - const mo = new MutationObserver(() => { - if (!document.contains(host)) { - window.removeEventListener("pointermove", onMove, true); - window.removeEventListener("mousemove", onFsMouseMove, true); - window.removeEventListener("pointerup", onUp, true); - ro.disconnect(); - mo.disconnect(); - } - }); - mo.observe(host.parentElement, { childList: true }); - } - installClickToggleDebug(host, mediaEl) { - const media = mediaEl; - if (!(media instanceof HTMLMediaElement)) return; - const onMouseDown = (e) => { - if (e.target.closest(".ms-clone-controls")) return; - if (e.button === 2) { - return; - } - const onMouseUp = (upEvent) => { - window.removeEventListener("mouseup", onMouseUp, true); - const dx = upEvent.clientX - e.clientX; - const dy = upEvent.clientY - e.clientY; - if (Math.hypot(dx, dy) < 4) { - if (media.paused) { - media.play().catch((err) => console.warn("[MS] clone play() rejected:", err)); - } else { - media.pause(); - } - } - }; - window.addEventListener("mouseup", onMouseUp, true); - }; - host.addEventListener("mousedown", onMouseDown, false); - } - applyLinkOverlay(host, link) { - let overlay = host.querySelector(".ms-link-overlay"); - if (!link) { - overlay?.remove(); - return; - } - if (!overlay) { - overlay = document.createElement("div"); - overlay.className = "ms-link-overlay"; - Object.assign(overlay.style, { - position: "absolute", - inset: "3px", - zIndex: "2500", - cursor: "pointer", - pointerEvents: "auto" - }); - host.appendChild(overlay); - } - overlay.__ms_clickHandler?.(); - const onMouseDown = (e) => { - if (e.button !== 0) return; - const onMouseUp = (upEvent) => { - window.removeEventListener("mouseup", onMouseUp, true); - const dx = upEvent.clientX - e.clientX; - const dy = upEvent.clientY - e.clientY; - if (Math.hypot(dx, dy) < 4) { - window.open(link, "_blank"); - } - }; - window.addEventListener("mouseup", onMouseUp, true); - }; - overlay.addEventListener("mousedown", onMouseDown); - overlay.__ms_clickHandler = () => overlay.removeEventListener("mousedown", onMouseDown); - } - applyCloneSettingsToAll() { - const root = this.getActiveContainerEl(); - if (!root) return; - root.querySelectorAll(".ms-host[data-ms-parent-key]").forEach((cloneHost) => { - cloneHost.dataset.msAnimation = this.cloneAnimation; - const bgs = cloneHost.querySelectorAll(".ms-media-bg, .ms-local-bg"); - if (bgs.length > 0) { - bgs.forEach((bg) => { - bg.style.opacity = String(this.cloneBgOpacity); - }); - } else if (cloneHost.dataset.msKind === "video" || cloneHost.dataset.msKind === "audio") { - } - const controls = cloneHost.querySelector(".ms-clone-controls"); - if (controls) { - controls.style.backgroundColor = `rgba(0,0,0,${this.cloneControlsOpacity})`; - } - if (cloneHost.dataset.msKind === "audio") { - cloneHost.dataset.msShowHandles = this.cloneShowAudioHandles ? "true" : "false"; - } - }); - } -};