mirror of
https://github.com/pcoletsos/obsidian-dynamic-wide-content.git
synced 2026-07-22 07:45:47 +00:00
feat: scaffold dynamic wide content plugin
This commit is contained in:
commit
3c2c0f7ba6
11 changed files with 854 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
.DS_Store
|
||||
*.log
|
||||
.obsidian/
|
||||
data.json
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Panagiotis Koletsos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
91
README.md
Normal file
91
README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Dynamic Wide Content
|
||||
|
||||
**Wide tables, diagrams, images, and code blocks without widening your prose.**
|
||||
|
||||
Dynamic Wide Content fixes a common Obsidian annoyance: wide material gets
|
||||
squeezed, clipped, or forced into awkward wrapping by the readable line length.
|
||||
|
||||
The plugin keeps your prose exactly where it belongs: in Obsidian's comfortable
|
||||
reading column. Only genuinely wide blocks break out into a wider scrollable
|
||||
frame.
|
||||
|
||||
## What It Does
|
||||
|
||||
- Preserves Obsidian's normal readable prose width.
|
||||
- Expands only wide Markdown tables, code blocks, Mermaid diagrams, PlantUML
|
||||
diagrams, and image embeds.
|
||||
- Adds horizontal scrolling inside the widened block frame when content is too
|
||||
wide for the pane.
|
||||
- Works in Reading view and Live Preview.
|
||||
- Does not require disabling readable line length globally.
|
||||
|
||||
## Why This Exists
|
||||
|
||||
Readable line length is excellent for paragraphs, headings, lists, and normal
|
||||
notes. It is painful for wide content:
|
||||
|
||||
- pipeline or comparison tables
|
||||
- architecture diagrams
|
||||
- generated Mermaid and PlantUML output
|
||||
- screenshots and reference images
|
||||
- long code or log blocks
|
||||
|
||||
Dynamic Wide Content makes those blocks usable without turning every note into
|
||||
a full-width wall of text.
|
||||
|
||||
## Settings
|
||||
|
||||
- **Maximum wide content width**: Controls how far wide blocks may expand.
|
||||
- **Viewport side margin**: Keeps widened blocks away from the edge of the
|
||||
Obsidian pane.
|
||||
- **Widen tables**: Allows Markdown tables to break out of the prose column.
|
||||
- **Widen diagrams**: Allows Mermaid and PlantUML diagrams to use a wider frame.
|
||||
- **Widen images**: Allows embedded images to display wider than prose.
|
||||
- **Widen code blocks**: Allows preformatted code blocks to use a wider frame.
|
||||
- **Keep table cells on one line**: Prevents wide tables from turning into tall,
|
||||
wrapped rows.
|
||||
- **Reading view support**: Applies widening in Reading view.
|
||||
- **Live Preview support**: Applies widening in Live Preview rendered embeds.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
1. Download `manifest.json`, `main.js`, and `styles.css` from a release.
|
||||
2. Create this folder in your vault:
|
||||
|
||||
```text
|
||||
VaultFolder/.obsidian/plugins/dynamic-wide-content/
|
||||
```
|
||||
|
||||
3. Copy the three files into that folder.
|
||||
4. Reload Obsidian.
|
||||
5. Enable **Dynamic Wide Content** in Community Plugins.
|
||||
|
||||
## Development
|
||||
|
||||
This repository follows the standard Obsidian plugin shape.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
For release builds:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Community plugin releases should include:
|
||||
|
||||
- `manifest.json`
|
||||
- `main.js`
|
||||
- `styles.css`
|
||||
|
||||
## Privacy
|
||||
|
||||
Dynamic Wide Content is a local UI plugin. It does not read note content,
|
||||
contact any network service, collect analytics, or write files.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
43
esbuild.config.mjs
Normal file
43
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const production = process.argv[2] === "production";
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: "/* Dynamic Wide Content */"
|
||||
},
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: production ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: production
|
||||
});
|
||||
|
||||
if (production) {
|
||||
await context.rebuild();
|
||||
await context.dispose();
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
296
main.js
Normal file
296
main.js
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
const { Plugin, PluginSettingTab, Setting } = require("obsidian");
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
maxWidth: 1600,
|
||||
viewportMargin: 64,
|
||||
widenTables: true,
|
||||
widenCodeBlocks: true,
|
||||
widenDiagrams: true,
|
||||
widenImages: true,
|
||||
noWrapTableCells: true,
|
||||
readingView: true,
|
||||
livePreview: true
|
||||
};
|
||||
|
||||
module.exports = class DynamicWideContentPlugin extends Plugin {
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.styleEl = document.createElement("style");
|
||||
this.styleEl.id = "dynamic-wide-content-styles";
|
||||
document.head.appendChild(this.styleEl);
|
||||
this.register(() => this.styleEl?.remove());
|
||||
this.updateStyles();
|
||||
this.addSettingTab(new DynamicWideContentSettingTab(this.app, this));
|
||||
this.addCommand({
|
||||
id: "refresh-dynamic-wide-content",
|
||||
name: "Refresh wide content styles",
|
||||
callback: () => this.updateStyles()
|
||||
});
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
updateStyles() {
|
||||
if (!this.styleEl) return;
|
||||
this.styleEl.textContent = buildCss(this.settings);
|
||||
}
|
||||
};
|
||||
|
||||
class DynamicWideContentSettingTab extends PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl("h2", { text: "Dynamic Wide Content" });
|
||||
containerEl.createEl("p", {
|
||||
text: "Use wide tables, diagrams, images, and code blocks without widening normal prose."
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Maximum wide content width")
|
||||
.setDesc("Controls how far wide blocks may expand while prose keeps Obsidian's readable width.")
|
||||
.addSlider((slider) => slider
|
||||
.setLimits(900, 2400, 50)
|
||||
.setValue(this.plugin.settings.maxWidth)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.maxWidth = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Viewport side margin")
|
||||
.setDesc("Keeps widened blocks away from the edge of the Obsidian pane.")
|
||||
.addSlider((slider) => slider
|
||||
.setLimits(0, 160, 8)
|
||||
.setValue(this.plugin.settings.viewportMargin)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.viewportMargin = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
addToggle(containerEl, "Widen tables", "Allow Markdown tables to break out of the prose column.", this.plugin, "widenTables");
|
||||
addToggle(containerEl, "Widen diagrams", "Allow Mermaid and PlantUML diagrams to use a wider scrollable frame.", this.plugin, "widenDiagrams");
|
||||
addToggle(containerEl, "Widen images", "Allow embedded images to display wider than prose.", this.plugin, "widenImages");
|
||||
addToggle(containerEl, "Widen code blocks", "Allow preformatted code blocks to use a wider scrollable frame.", this.plugin, "widenCodeBlocks");
|
||||
addToggle(containerEl, "Keep table cells on one line", "Prevents wide tables from wrapping every cell into tall unreadable rows.", this.plugin, "noWrapTableCells");
|
||||
addToggle(containerEl, "Reading view support", "Apply selective widening in Reading view.", this.plugin, "readingView");
|
||||
addToggle(containerEl, "Live Preview support", "Apply selective widening to rendered embeds in Live Preview.", this.plugin, "livePreview");
|
||||
}
|
||||
}
|
||||
|
||||
function addToggle(containerEl, name, desc, plugin, key) {
|
||||
new Setting(containerEl)
|
||||
.setName(name)
|
||||
.setDesc(desc)
|
||||
.addToggle((toggle) => toggle
|
||||
.setValue(plugin.settings[key])
|
||||
.onChange(async (value) => {
|
||||
plugin.settings[key] = value;
|
||||
await plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
|
||||
function buildCss(settings) {
|
||||
const readingBlocks = [];
|
||||
const innerBlocks = [];
|
||||
const livePreviewBlocks = [];
|
||||
const mediaContent = [];
|
||||
|
||||
if (settings.widenTables) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-table",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> table)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .table-wrapper)"
|
||||
);
|
||||
innerBlocks.push(".markdown-rendered .table-wrapper");
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(table)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.table-wrapper)"
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.widenCodeBlocks) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-pre",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> pre)"
|
||||
);
|
||||
innerBlocks.push(".markdown-rendered pre");
|
||||
livePreviewBlocks.push(".markdown-source-view.mod-cm6 .cm-embed-block:has(pre)");
|
||||
}
|
||||
|
||||
if (settings.widenDiagrams) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-lang-mermaid",
|
||||
".markdown-rendered .el-lang-plantuml",
|
||||
".markdown-rendered .el-lang-plantuml-png",
|
||||
".markdown-rendered .el-lang-plantuml-svg",
|
||||
".markdown-rendered .el-lang-puml",
|
||||
".markdown-rendered .el-lang-puml-png",
|
||||
".markdown-rendered .el-lang-puml-svg",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .mermaid)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-mermaid)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml-png)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml-svg)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml-png)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml-svg)"
|
||||
);
|
||||
innerBlocks.push(
|
||||
".markdown-rendered .mermaid",
|
||||
".markdown-rendered .block-language-mermaid",
|
||||
".markdown-rendered .block-language-plantuml",
|
||||
".markdown-rendered .block-language-plantuml-png",
|
||||
".markdown-rendered .block-language-plantuml-svg",
|
||||
".markdown-rendered .block-language-puml",
|
||||
".markdown-rendered .block-language-puml-png",
|
||||
".markdown-rendered .block-language-puml-svg",
|
||||
".markdown-rendered .plantuml-preview-view"
|
||||
);
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.mermaid)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-mermaid)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-plantuml)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-puml)"
|
||||
);
|
||||
mediaContent.push(
|
||||
".markdown-rendered .mermaid svg",
|
||||
".markdown-rendered .block-language-mermaid svg",
|
||||
".markdown-rendered .block-language-plantuml img",
|
||||
".markdown-rendered .block-language-plantuml svg",
|
||||
".markdown-rendered .block-language-plantuml-png img",
|
||||
".markdown-rendered .block-language-plantuml-svg svg",
|
||||
".markdown-rendered .block-language-puml img",
|
||||
".markdown-rendered .block-language-puml svg",
|
||||
".markdown-rendered .block-language-puml-png img",
|
||||
".markdown-rendered .block-language-puml-svg svg",
|
||||
".markdown-rendered .plantuml-preview-view img",
|
||||
".markdown-rendered .plantuml-preview-view svg"
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.widenImages) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-embed-image",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .internal-embed.image-embed)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .image-embed)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(img)"
|
||||
);
|
||||
innerBlocks.push(
|
||||
".markdown-rendered .internal-embed.image-embed",
|
||||
".markdown-rendered .image-embed"
|
||||
);
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.internal-embed.image-embed)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.image-embed)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(img)"
|
||||
);
|
||||
mediaContent.push(
|
||||
".markdown-rendered .internal-embed.image-embed img",
|
||||
".markdown-rendered .image-embed img"
|
||||
);
|
||||
}
|
||||
|
||||
const css = [
|
||||
`body {
|
||||
--dynamic-wide-content-max: min(${settings.maxWidth}px, calc(100vw - ${settings.viewportMargin}px));
|
||||
}`
|
||||
];
|
||||
|
||||
if (settings.readingView) {
|
||||
css.push(`
|
||||
.markdown-preview-view.is-readable-line-width .markdown-preview-sizer,
|
||||
.markdown-reading-view.is-readable-line-width .markdown-preview-sizer,
|
||||
.markdown-preview-view .markdown-preview-section,
|
||||
.markdown-reading-view .markdown-preview-section {
|
||||
overflow: visible !important;
|
||||
}`);
|
||||
|
||||
if (readingBlocks.length > 0) {
|
||||
css.push(`
|
||||
${readingBlocks.join(",\n")} {
|
||||
position: relative !important;
|
||||
left: 50% !important;
|
||||
width: max-content !important;
|
||||
max-width: var(--dynamic-wide-content-max) !important;
|
||||
margin-top: 0.75em !important;
|
||||
margin-bottom: 0.75em !important;
|
||||
overflow-x: auto !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (innerBlocks.length > 0) {
|
||||
css.push(`
|
||||
${innerBlocks.join(",\n")} {
|
||||
max-width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.livePreview && livePreviewBlocks.length > 0) {
|
||||
css.push(`
|
||||
.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
${livePreviewBlocks.join(",\n")} {
|
||||
position: relative !important;
|
||||
left: 50% !important;
|
||||
width: max-content !important;
|
||||
max-width: var(--dynamic-wide-content-max) !important;
|
||||
overflow-x: auto !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenTables) {
|
||||
css.push(`
|
||||
.markdown-rendered table {
|
||||
display: table !important;
|
||||
width: max-content !important;
|
||||
min-width: 100% !important;
|
||||
max-width: none !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenTables && settings.noWrapTableCells) {
|
||||
css.push(`
|
||||
.markdown-rendered th,
|
||||
.markdown-rendered td {
|
||||
white-space: nowrap !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenCodeBlocks) {
|
||||
css.push(`
|
||||
.markdown-rendered pre > code {
|
||||
white-space: pre !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (mediaContent.length > 0) {
|
||||
css.push(`
|
||||
${mediaContent.join(",\n")} {
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
height: auto !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
return css.join("\n");
|
||||
}
|
||||
10
manifest.json
Normal file
10
manifest.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "dynamic-wide-content",
|
||||
"name": "Dynamic Wide Content",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Display wide tables, diagrams, images, and code blocks in scrollable wide frames while preserving Obsidian's normal readable prose width.",
|
||||
"author": "Panagiotis Koletsos",
|
||||
"authorUrl": "https://github.com/pcoletsos",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "obsidian-dynamic-wide-content",
|
||||
"version": "1.0.0",
|
||||
"description": "Wide tables, diagrams, images, and code blocks without widening your Obsidian prose.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
|
||||
},
|
||||
"keywords": [
|
||||
"obsidian",
|
||||
"obsidian-plugin",
|
||||
"markdown",
|
||||
"tables",
|
||||
"mermaid",
|
||||
"plantuml",
|
||||
"wide-content",
|
||||
"readable-line-length"
|
||||
],
|
||||
"author": "Panagiotis Koletsos",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.30",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "^0.20.2",
|
||||
"obsidian": "latest",
|
||||
"typescript": "^5.4.3"
|
||||
}
|
||||
}
|
||||
329
src/main.ts
Normal file
329
src/main.ts
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
import { App, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
|
||||
interface DynamicWideContentSettings {
|
||||
maxWidth: number;
|
||||
viewportMargin: number;
|
||||
widenTables: boolean;
|
||||
widenCodeBlocks: boolean;
|
||||
widenDiagrams: boolean;
|
||||
widenImages: boolean;
|
||||
noWrapTableCells: boolean;
|
||||
readingView: boolean;
|
||||
livePreview: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: DynamicWideContentSettings = {
|
||||
maxWidth: 1600,
|
||||
viewportMargin: 64,
|
||||
widenTables: true,
|
||||
widenCodeBlocks: true,
|
||||
widenDiagrams: true,
|
||||
widenImages: true,
|
||||
noWrapTableCells: true,
|
||||
readingView: true,
|
||||
livePreview: true
|
||||
};
|
||||
|
||||
export default class DynamicWideContentPlugin extends Plugin {
|
||||
settings: DynamicWideContentSettings;
|
||||
private styleEl: HTMLStyleElement | null = null;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.styleEl = document.createElement("style");
|
||||
this.styleEl.id = "dynamic-wide-content-styles";
|
||||
document.head.appendChild(this.styleEl);
|
||||
this.register(() => this.styleEl?.remove());
|
||||
|
||||
this.updateStyles();
|
||||
|
||||
this.addSettingTab(new DynamicWideContentSettingTab(this.app, this));
|
||||
this.addCommand({
|
||||
id: "refresh-dynamic-wide-content",
|
||||
name: "Refresh wide content styles",
|
||||
callback: () => this.updateStyles()
|
||||
});
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
updateStyles() {
|
||||
if (!this.styleEl) return;
|
||||
this.styleEl.textContent = buildCss(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicWideContentSettingTab extends PluginSettingTab {
|
||||
plugin: DynamicWideContentPlugin;
|
||||
|
||||
constructor(app: App, plugin: DynamicWideContentPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl("h2", { text: "Dynamic Wide Content" });
|
||||
containerEl.createEl("p", {
|
||||
text: "Use wide tables, diagrams, images, and code blocks without widening normal prose."
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Maximum wide content width")
|
||||
.setDesc("Controls how far wide blocks may expand while prose keeps Obsidian's readable width.")
|
||||
.addSlider((slider) => slider
|
||||
.setLimits(900, 2400, 50)
|
||||
.setValue(this.plugin.settings.maxWidth)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.maxWidth = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Viewport side margin")
|
||||
.setDesc("Keeps widened blocks away from the edge of the Obsidian pane.")
|
||||
.addSlider((slider) => slider
|
||||
.setLimits(0, 160, 8)
|
||||
.setValue(this.plugin.settings.viewportMargin)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.viewportMargin = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
addToggle(containerEl, "Widen tables", "Allow Markdown tables to break out of the prose column.", this.plugin, "widenTables");
|
||||
addToggle(containerEl, "Widen diagrams", "Allow Mermaid and PlantUML diagrams to use a wider scrollable frame.", this.plugin, "widenDiagrams");
|
||||
addToggle(containerEl, "Widen images", "Allow embedded images to display wider than prose.", this.plugin, "widenImages");
|
||||
addToggle(containerEl, "Widen code blocks", "Allow preformatted code blocks to use a wider scrollable frame.", this.plugin, "widenCodeBlocks");
|
||||
addToggle(containerEl, "Keep table cells on one line", "Prevents wide tables from wrapping every cell into tall unreadable rows.", this.plugin, "noWrapTableCells");
|
||||
addToggle(containerEl, "Reading view support", "Apply selective widening in Reading view.", this.plugin, "readingView");
|
||||
addToggle(containerEl, "Live Preview support", "Apply selective widening to rendered embeds in Live Preview.", this.plugin, "livePreview");
|
||||
}
|
||||
}
|
||||
|
||||
function addToggle(
|
||||
containerEl: HTMLElement,
|
||||
name: string,
|
||||
desc: string,
|
||||
plugin: DynamicWideContentPlugin,
|
||||
key: keyof Pick<DynamicWideContentSettings,
|
||||
"widenTables" |
|
||||
"widenCodeBlocks" |
|
||||
"widenDiagrams" |
|
||||
"widenImages" |
|
||||
"noWrapTableCells" |
|
||||
"readingView" |
|
||||
"livePreview">
|
||||
) {
|
||||
new Setting(containerEl)
|
||||
.setName(name)
|
||||
.setDesc(desc)
|
||||
.addToggle((toggle) => toggle
|
||||
.setValue(plugin.settings[key])
|
||||
.onChange(async (value) => {
|
||||
plugin.settings[key] = value;
|
||||
await plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
|
||||
function buildCss(settings: DynamicWideContentSettings): string {
|
||||
const readingBlocks: string[] = [];
|
||||
const innerBlocks: string[] = [];
|
||||
const livePreviewBlocks: string[] = [];
|
||||
const mediaContent: string[] = [];
|
||||
|
||||
if (settings.widenTables) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-table",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> table)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .table-wrapper)"
|
||||
);
|
||||
innerBlocks.push(".markdown-rendered .table-wrapper");
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(table)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.table-wrapper)"
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.widenCodeBlocks) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-pre",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> pre)"
|
||||
);
|
||||
innerBlocks.push(".markdown-rendered pre");
|
||||
livePreviewBlocks.push(".markdown-source-view.mod-cm6 .cm-embed-block:has(pre)");
|
||||
}
|
||||
|
||||
if (settings.widenDiagrams) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-lang-mermaid",
|
||||
".markdown-rendered .el-lang-plantuml",
|
||||
".markdown-rendered .el-lang-plantuml-png",
|
||||
".markdown-rendered .el-lang-plantuml-svg",
|
||||
".markdown-rendered .el-lang-puml",
|
||||
".markdown-rendered .el-lang-puml-png",
|
||||
".markdown-rendered .el-lang-puml-svg",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .mermaid)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-mermaid)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml-png)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-plantuml-svg)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml-png)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .block-language-puml-svg)"
|
||||
);
|
||||
innerBlocks.push(
|
||||
".markdown-rendered .mermaid",
|
||||
".markdown-rendered .block-language-mermaid",
|
||||
".markdown-rendered .block-language-plantuml",
|
||||
".markdown-rendered .block-language-plantuml-png",
|
||||
".markdown-rendered .block-language-plantuml-svg",
|
||||
".markdown-rendered .block-language-puml",
|
||||
".markdown-rendered .block-language-puml-png",
|
||||
".markdown-rendered .block-language-puml-svg",
|
||||
".markdown-rendered .plantuml-preview-view"
|
||||
);
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.mermaid)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-mermaid)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-plantuml)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.block-language-puml)"
|
||||
);
|
||||
mediaContent.push(
|
||||
".markdown-rendered .mermaid svg",
|
||||
".markdown-rendered .block-language-mermaid svg",
|
||||
".markdown-rendered .block-language-plantuml img",
|
||||
".markdown-rendered .block-language-plantuml svg",
|
||||
".markdown-rendered .block-language-plantuml-png img",
|
||||
".markdown-rendered .block-language-plantuml-svg svg",
|
||||
".markdown-rendered .block-language-puml img",
|
||||
".markdown-rendered .block-language-puml svg",
|
||||
".markdown-rendered .block-language-puml-png img",
|
||||
".markdown-rendered .block-language-puml-svg svg",
|
||||
".markdown-rendered .plantuml-preview-view img",
|
||||
".markdown-rendered .plantuml-preview-view svg"
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.widenImages) {
|
||||
readingBlocks.push(
|
||||
".markdown-rendered .el-embed-image",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .internal-embed.image-embed)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(> .image-embed)",
|
||||
".markdown-rendered .markdown-preview-sizer > div:has(img)"
|
||||
);
|
||||
innerBlocks.push(
|
||||
".markdown-rendered .internal-embed.image-embed",
|
||||
".markdown-rendered .image-embed"
|
||||
);
|
||||
livePreviewBlocks.push(
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.internal-embed.image-embed)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(.image-embed)",
|
||||
".markdown-source-view.mod-cm6 .cm-embed-block:has(img)"
|
||||
);
|
||||
mediaContent.push(
|
||||
".markdown-rendered .internal-embed.image-embed img",
|
||||
".markdown-rendered .image-embed img"
|
||||
);
|
||||
}
|
||||
|
||||
const css: string[] = [
|
||||
`body {
|
||||
--dynamic-wide-content-max: min(${settings.maxWidth}px, calc(100vw - ${settings.viewportMargin}px));
|
||||
}`
|
||||
];
|
||||
|
||||
if (settings.readingView) {
|
||||
css.push(`
|
||||
.markdown-preview-view.is-readable-line-width .markdown-preview-sizer,
|
||||
.markdown-reading-view.is-readable-line-width .markdown-preview-sizer,
|
||||
.markdown-preview-view .markdown-preview-section,
|
||||
.markdown-reading-view .markdown-preview-section {
|
||||
overflow: visible !important;
|
||||
}`);
|
||||
|
||||
if (readingBlocks.length > 0) {
|
||||
css.push(`
|
||||
${readingBlocks.join(",\n")} {
|
||||
position: relative !important;
|
||||
left: 50% !important;
|
||||
width: max-content !important;
|
||||
max-width: var(--dynamic-wide-content-max) !important;
|
||||
margin-top: 0.75em !important;
|
||||
margin-bottom: 0.75em !important;
|
||||
overflow-x: auto !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (innerBlocks.length > 0) {
|
||||
css.push(`
|
||||
${innerBlocks.join(",\n")} {
|
||||
max-width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.livePreview && livePreviewBlocks.length > 0) {
|
||||
css.push(`
|
||||
.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
${livePreviewBlocks.join(",\n")} {
|
||||
position: relative !important;
|
||||
left: 50% !important;
|
||||
width: max-content !important;
|
||||
max-width: var(--dynamic-wide-content-max) !important;
|
||||
overflow-x: auto !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenTables) {
|
||||
css.push(`
|
||||
.markdown-rendered table {
|
||||
display: table !important;
|
||||
width: max-content !important;
|
||||
min-width: 100% !important;
|
||||
max-width: none !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenTables && settings.noWrapTableCells) {
|
||||
css.push(`
|
||||
.markdown-rendered th,
|
||||
.markdown-rendered td {
|
||||
white-space: nowrap !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (settings.widenCodeBlocks) {
|
||||
css.push(`
|
||||
.markdown-rendered pre > code {
|
||||
white-space: pre !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
if (mediaContent.length > 0) {
|
||||
css.push(`
|
||||
${mediaContent.join(",\n")} {
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
height: auto !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
return css.join("\n");
|
||||
}
|
||||
5
styles.css
Normal file
5
styles.css
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
Dynamic Wide Content injects its active CSS from main.js so user settings can
|
||||
change the selector set and maximum width. This file is intentionally minimal
|
||||
but kept for Obsidian community-plugin release packaging.
|
||||
*/
|
||||
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES2018",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES2018"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
3
versions.json
Normal file
3
versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"1.0.0": "1.5.0"
|
||||
}
|
||||
Loading…
Reference in a new issue