fix: find the width cap by measuring, not by class name
Measured chain from live preview showed the cap on .cm-content at 700px, while .cm-sizer - the element closest() matched first - is already uncapped at 1680px. The class-name shortcut therefore locked onto the wrong element and returned before the fallback walk could find the real one, so the cap survived and the container query correctly collapsed the grid to one column. widenHost now always measures computed max-width outward from the dashboard's parent. The decision is extracted as firstCappedIndex and tested against the real measured chains for both live preview and reading view, so a class-name assumption cannot silently break it again. Adds .cm-content to the CSS fallback.
This commit is contained in:
56
.obsidian/plugins/webinar-dash/main.js
vendored
56
.obsidian/plugins/webinar-dash/main.js
vendored
@@ -191,6 +191,26 @@ function parseConfig(source) {
|
||||
// folder is not a source and stays out of the queue.
|
||||
const RAW_EXTENSIONS = new Set(["md", "txt", "pdf"]);
|
||||
|
||||
// How far up from the dashboard to look for the element carrying Obsidian's
|
||||
// readable-line-width cap. Measured chains reach it in 3 hops; 8 is slack.
|
||||
const MAX_WIDEN_HOPS = 8;
|
||||
|
||||
// Given each ancestor's computed max-width, walking outward from the
|
||||
// dashboard's parent, return the index of the first that actually carries a
|
||||
// cap — or -1 if none does before the workspace chrome begins.
|
||||
//
|
||||
// Split out from the DOM walk so the decision can be tested against real
|
||||
// measured chains. Class-name matching got this wrong twice: in live preview
|
||||
// the cap sits on `.cm-content`, while `.cm-sizer` is already uncapped.
|
||||
function firstCappedIndex(ancestors) {
|
||||
for (let i = 0; i < ancestors.length && i < MAX_WIDEN_HOPS; i += 1) {
|
||||
if (ancestors[i].isWorkspaceLeaf) return -1;
|
||||
const max = ancestors[i].maxWidth;
|
||||
if (max && max !== "none") return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const ICONS = {
|
||||
terminal: "M4 17l6-6-6-6M12 19h8",
|
||||
check: "M20 6 9 17l-5-5",
|
||||
@@ -392,21 +412,29 @@ class WebinarDashPlugin extends PluginBase {
|
||||
// walk stops at the first capped ancestor and is bounded, so it cannot climb
|
||||
// out into the workspace chrome and widen something it shouldn't.
|
||||
widenHost(root) {
|
||||
const known = root.closest(".markdown-preview-sizer, .cm-sizer");
|
||||
if (known) {
|
||||
known.style.maxWidth = "none";
|
||||
return known;
|
||||
}
|
||||
// Deliberately measured, not matched by class name. In live preview the cap
|
||||
// sits on `.cm-content` (700px) while `.cm-sizer` — the obvious candidate,
|
||||
// and the one a class-based lookup finds first — is already uncapped at full
|
||||
// width. Shortcutting via closest() therefore locks onto the wrong element
|
||||
// and stops before reaching the real one. Reading computed max-width finds
|
||||
// whichever element actually carries the cap, in either view.
|
||||
//
|
||||
// Starts at root.parentElement so the dashboard's own 1600px cap survives,
|
||||
// stops at the first capped ancestor so nothing above the note widens, and
|
||||
// is bounded so it cannot climb into the workspace chrome.
|
||||
const chain = [];
|
||||
let el = root.parentElement;
|
||||
for (let hops = 0; el && hops < 6; hops += 1, el = el.parentElement) {
|
||||
if (el.classList.contains("workspace-leaf")) break;
|
||||
const max = window.getComputedStyle(el).maxWidth;
|
||||
if (max && max !== "none") {
|
||||
el.style.maxWidth = "none";
|
||||
return el;
|
||||
}
|
||||
for (let hops = 0; el && hops < MAX_WIDEN_HOPS; hops += 1, el = el.parentElement) {
|
||||
chain.push({
|
||||
el,
|
||||
maxWidth: window.getComputedStyle(el).maxWidth,
|
||||
isWorkspaceLeaf: el.classList.contains("workspace-leaf"),
|
||||
});
|
||||
}
|
||||
return null;
|
||||
const idx = firstCappedIndex(chain);
|
||||
if (idx < 0) return null;
|
||||
chain[idx].el.style.maxWidth = "none";
|
||||
return chain[idx].el;
|
||||
}
|
||||
|
||||
async renderAll(root, cfg) {
|
||||
@@ -625,5 +653,5 @@ module.exports.default = WebinarDashPlugin;
|
||||
module.exports.__test__ = {
|
||||
parseConfig, extractRawPath, derivePipeline,
|
||||
parseCoverageTable, groupByStation, reconcileConcepts,
|
||||
STATIONS, DEFAULTS, isSafeFilename,
|
||||
STATIONS, DEFAULTS, firstCappedIndex, isSafeFilename,
|
||||
};
|
||||
|
||||
4
.obsidian/plugins/webinar-dash/styles.css
vendored
4
.obsidian/plugins/webinar-dash/styles.css
vendored
@@ -87,8 +87,8 @@
|
||||
------------------------------------------------------------------ */
|
||||
.markdown-preview-sizer:has(.webinar-dash),
|
||||
.markdown-preview-view.is-readable-line-width .markdown-preview-sizer:has(.webinar-dash),
|
||||
.markdown-source-view.mod-cm6 .cm-sizer:has(.webinar-dash),
|
||||
.markdown-source-view.mod-cm6.is-readable-line-width .cm-sizer:has(.webinar-dash) {
|
||||
.markdown-source-view.mod-cm6 .cm-content:has(.webinar-dash),
|
||||
.markdown-source-view.mod-cm6 .cm-sizer:has(.webinar-dash) {
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
|
||||
56
.obsidian/plugins/webinar-dash/test/layout.test.js
vendored
Normal file
56
.obsidian/plugins/webinar-dash/test/layout.test.js
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { firstCappedIndex } = require("../main.js").__test__;
|
||||
|
||||
test("firstCappedIndex finds the cap on the real measured live-preview chain", () => {
|
||||
// Measured in Obsidian live preview via getComputedStyle, walking outward
|
||||
// from .webinar-dash. The cap is on .cm-content at 700px; .cm-sizer - the
|
||||
// element a class-name lookup finds first - is already uncapped at 1680px.
|
||||
// Matching by class name selected .cm-sizer and stopped, leaving the real
|
||||
// cap in place. This test pins the measurement so that cannot recur.
|
||||
const chain = [
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // block-language-webinar-dash
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // cm-preview-code-block
|
||||
{ maxWidth: "700px", isWorkspaceLeaf: false }, // cm-content <- the cap
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // cm-contentContainer
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // cm-sizer
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // cm-scroller
|
||||
];
|
||||
assert.equal(firstCappedIndex(chain), 2);
|
||||
});
|
||||
|
||||
test("firstCappedIndex finds the cap on a reading-view chain", () => {
|
||||
const chain = [
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // block-language-webinar-dash
|
||||
{ maxWidth: "700px", isWorkspaceLeaf: false }, // markdown-preview-sizer
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false }, // markdown-preview-view
|
||||
];
|
||||
assert.equal(firstCappedIndex(chain), 1);
|
||||
});
|
||||
|
||||
test("firstCappedIndex returns -1 when nothing above the dashboard is capped", () => {
|
||||
const chain = [
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false },
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false },
|
||||
];
|
||||
assert.equal(firstCappedIndex(chain), -1);
|
||||
});
|
||||
|
||||
test("firstCappedIndex stops at the workspace leaf rather than widening chrome", () => {
|
||||
const chain = [
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false },
|
||||
{ maxWidth: "none", isWorkspaceLeaf: true }, // workspace-leaf
|
||||
{ maxWidth: "900px", isWorkspaceLeaf: false }, // must never be reached
|
||||
];
|
||||
assert.equal(firstCappedIndex(chain), -1);
|
||||
});
|
||||
|
||||
test("firstCappedIndex takes the innermost cap when several ancestors are capped", () => {
|
||||
const chain = [
|
||||
{ maxWidth: "none", isWorkspaceLeaf: false },
|
||||
{ maxWidth: "700px", isWorkspaceLeaf: false },
|
||||
{ maxWidth: "1200px", isWorkspaceLeaf: false },
|
||||
];
|
||||
assert.equal(firstCappedIndex(chain), 1);
|
||||
});
|
||||
Reference in New Issue
Block a user