chore: release 1.2.1

This commit is contained in:
amatya-aditya 2026-05-08 23:39:37 -05:00
parent 64a3e3bc47
commit 5c60aa8b15
9 changed files with 131 additions and 1654 deletions

View file

@ -6,5 +6,5 @@
"author": "Aditya Amatya",
"authorUrl": "https://github.com/amatya-aditya/",
"isDesktopOnly": false,
"version": "1.2.0"
"version": "1.2.1"
}

8
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.1-beta.1",
"name": "advanced-multi-column",
"version": "1.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-sample-plugin",
"version": "1.0.1-beta.1",
"name": "advanced-multi-column",
"version": "1.2.1",
"license": "0-BSD",
"dependencies": {
"obsidian": "1.10.3"

View file

@ -1,6 +1,6 @@
{
"name": "advanced-multi-column",
"version": "1.1.1",
"version": "1.2.1",
"description": "Allows you to create columns in Obsidian Markdown.",
"main": "main.js",
"type": "module",

View file

@ -93,39 +93,24 @@ function resolvePreviewElementForSizer(sizer: HTMLElement): HTMLElement | null {
}
function getWrapperHost(previewEl: HTMLElement): HTMLElement | null {
const host = previewEl.querySelector(
`:scope > .markdown-preview-sizer > .${RV_HOST_CLASS}, :scope > .${RV_HOST_CLASS}`,
);
const host = previewEl.querySelector(`:scope > .${RV_HOST_CLASS}`);
return host instanceof HTMLElement ? host : null;
}
function placeWrapperHost(sizer: HTMLElement, host: HTMLElement): void {
const footer = sizer.querySelector(":scope > .mod-footer");
if (footer instanceof HTMLElement) {
if (host.parentElement !== sizer || host.nextSibling !== footer) {
sizer.insertBefore(host, footer);
}
return;
}
if (host.parentElement !== sizer) {
sizer.appendChild(host);
}
}
function ensureWrapperHost(
previewEl: HTMLElement,
sizer: HTMLElement,
): HTMLElement {
const existing = getWrapperHost(previewEl);
if (existing) {
placeWrapperHost(sizer, existing);
return existing;
}
if (existing) return existing;
const host = document.createElement("div");
host.className = RV_HOST_CLASS;
placeWrapperHost(sizer, host);
if (sizer.nextSibling) {
previewEl.insertBefore(host, sizer.nextSibling);
} else {
previewEl.appendChild(host);
}
return host;
}
@ -185,7 +170,7 @@ function restoreScrollSnapshotStable(
});
}
/** Older versions moved .mod-footer out of the sizer; bring it back if found. */
/** Move any legacy relocated .mod-footer back into Obsidian's sizer. */
function restoreFooter(previewEl: HTMLElement, sizer: HTMLElement): void {
const footer = previewEl.querySelector(":scope > .mod-footer");
if (footer instanceof HTMLElement) {
@ -529,7 +514,7 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
hostChildren: host?.children.length ?? 0,
wrapperConnected: state?.wrapper.isConnected ?? false,
wrapperParentMatches: state ? state.wrapper.parentElement === state.host : false,
hostParentMatches: state ? state.host.parentElement === sizer : false,
hostParentMatches: state ? state.host.parentElement === state.previewEl : false,
};
};
@ -612,11 +597,6 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
}
}
}
if (!state.host.isConnected || state.host.parentElement !== sizer) {
handleWrapperDisappearance(sizer, state, "sizer-observer");
return;
}
placeWrapperHost(sizer, state.host);
});
sizerObserver.observe(sizer, {childList: true});
@ -642,6 +622,7 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
if (
!state.host.isConnected
|| state.host.parentElement !== state.previewEl
|| !state.wrapper.isConnected
|| state.wrapper.parentElement !== state.host
) {
@ -775,7 +756,6 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
return;
}
const view = resolveViewForSizer(sizer, plugin);
const scrollSnapshot = captureScrollSnapshot(previewEl);
const shouldRestoreScroll = (): boolean => {
if (!plugin.settings.enableReadingView) return false;
if (!sizer.isConnected) return false;
@ -842,7 +822,7 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
&& existing.fingerprint === fingerprint
&& existing.wrapper.isConnected
&& existing.host.isConnected
&& existing.host.parentElement === sizer
&& existing.host.parentElement === existing.previewEl
&& existing.wrapper.parentElement === existing.host
) {
if (!existing.previewEl.classList.contains(RV_ACTIVE_CLASS)) {
@ -852,7 +832,6 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
}
existing.previewEl.classList.add(RV_ACTIVE_CLASS);
hideSizerContent(sizer);
restoreScrollSnapshotStable(scrollSnapshot, shouldRestoreScroll);
rvWarn("render reused existing wrapper", sizerSnapshot(sizer, existing));
return;
}
@ -865,42 +844,18 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
}
const host = ensureWrapperHost(previewEl, sizer);
restoreFooter(previewEl, sizer);
const component = new Component();
component.load();
const renderId = ++renderIdSeq;
// Hide sizer content early and install a temporary observer to catch
// elements Obsidian adds during the async wrapper build (mode switch,
// scroll virtualization). This prevents flat content from flashing.
previewEl.classList.add(RV_ACTIVE_CLASS);
hideSizerContent(sizer);
const earlySizerObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of Array.from(mutation.addedNodes)) {
if (!(node instanceof HTMLElement)) continue;
const cls = node.className;
const isElDiv = cls.startsWith("el-") || cls.includes(" el-");
const isPusher = node.classList.contains("markdown-preview-pusher");
if (isElDiv || isPusher) {
node.classList.add(RV_HIDDEN_CLASS);
}
}
}
});
earlySizerObserver.observe(sizer, {childList: true});
let scrollSnapshot: ScrollSnapshot[] = [];
try {
const wrapper = await buildWrapper(plugin, sourcePath, text, regions, component);
// Disconnect the early observer; the full lifecycle observer replaces it
earlySizerObserver.disconnect();
if (!sizer.isConnected || renderTokens.get(sizer) !== token) {
component.unload();
previewEl.classList.remove(RV_ACTIVE_CLASS);
restoreSizerContent(sizer);
rvWarn("render result dropped: stale after async build", {
renderId,
token,
@ -913,10 +868,11 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
host.removeChild(host.firstChild);
}
wrapper.dataset.columnsRenderId = String(renderId);
scrollSnapshot = captureScrollSnapshot(previewEl);
previewEl.classList.add(RV_ACTIVE_CLASS);
host.appendChild(wrapper);
// Re-hide in case anything was added between observer batches
hideSizerContent(sizer);
placeWrapperHost(sizer, host);
restoreScrollSnapshotStable(scrollSnapshot, shouldRestoreScroll);
const state: RenderState = {
sourcePath,
@ -939,11 +895,12 @@ export function registerReadingView(plugin: ColumnsPlugin): () => void {
sizerChildren: sizer.children.length,
});
} catch (error) {
earlySizerObserver.disconnect();
component.unload();
previewEl.classList.remove(RV_ACTIVE_CLASS);
restoreSizerContent(sizer);
restoreScrollSnapshotStable(scrollSnapshot, shouldRestoreScroll);
if (scrollSnapshot.length > 0) {
restoreScrollSnapshotStable(scrollSnapshot, shouldRestoreScroll);
}
rvError("render failed", {
renderId,
sourcePath,

View file

@ -17,7 +17,7 @@
}
/* Collapse the sizer's own height/padding so it wraps tightly around
the remaining visible children (banner, metadata, title, columns, footer). */
the remaining visible children (banner, metadata, title, backlinks). */
.markdown-preview-view.amc-reading-columns-active > .markdown-preview-sizer {
min-height: 0 !important;
height: auto !important;
@ -26,35 +26,29 @@
/* Host container: hidden by default, shown when columns are active.
Inherit the sizer's readable-line-width constraints. */
.markdown-preview-view > .markdown-preview-sizer > .amc-reading-columns-host,
.markdown-preview-view > .amc-reading-columns-host {
display: none;
}
.markdown-preview-view.amc-reading-columns-active > .markdown-preview-sizer > .amc-reading-columns-host,
.markdown-preview-view.amc-reading-columns-active > .amc-reading-columns-host {
display: block;
width: 100%;
box-sizing: border-box;
}
/* Keep the backlinks footer in Obsidian's sizer so core settings can update it. */
.markdown-preview-view.amc-reading-columns-active > .markdown-preview-sizer > .mod-footer,
/* Clean up any footer left relocated by older versions. */
.markdown-preview-view.amc-reading-columns-active > .mod-footer {
width: 100%;
box-sizing: border-box;
}
/* When readable line width is on, cap host and footer to the file line width */
.markdown-preview-view.amc-reading-columns-active.is-readable-line-width > .markdown-preview-sizer > .amc-reading-columns-host,
.markdown-preview-view.amc-reading-columns-active.is-readable-line-width > .markdown-preview-sizer > .mod-footer,
.markdown-preview-view.amc-reading-columns-active.is-readable-line-width > .amc-reading-columns-host,
.markdown-preview-view.amc-reading-columns-active.is-readable-line-width > .mod-footer {
max-width: var(--file-line-width);
margin: 0 auto;
}
.markdown-preview-view.amc-reading-columns-active > .markdown-preview-sizer > .amc-reading-columns-host > .columns-rv-wrapper,
.markdown-preview-view.amc-reading-columns-active > .amc-reading-columns-host > .columns-rv-wrapper {
display: block;
width: 100%;
@ -584,9 +578,9 @@
.el-h5,
.el-h6
) {
margin-top: 0.3rem !important;
margin-bottom: 0.16rem !important;
line-height: 0.8rem !important;
margin-top: 0.35rem !important;
margin-bottom: 0.2rem !important;
line-height: normal !important;
}
.columns-container.columns-ui:not(.columns-reading) :is(.column-preview, .column-inline-edit-preview) .markdown-rendered :is(
@ -597,9 +591,10 @@
h5,
h6
) {
margin-block-start: 0.3rem !important;
margin-block-end: 0.16rem !important;
line-height: 0.8rem !important;
margin-block-start: 0.35rem !important;
margin-block-end: 0.2rem !important;
line-height: 1.2 !important;
overflow-wrap: anywhere;
}
.columns-container.columns-ui:not(.columns-reading) :is(.column-preview, .column-inline-edit-preview) .markdown-rendered :is(

1571
styles.css

File diff suppressed because one or more lines are too long

View file

@ -12,5 +12,6 @@
"1.1.0-beta.4": "0.15.0",
"1.1.0": "0.15.0",
"1.1.1": "0.15.0",
"1.2.0": "0.15.0"
"1.2.0": "0.15.0",
"1.2.1": "0.15.0"
}

View file

@ -51,6 +51,45 @@ You can attach width/stack/style tokens to each `col-break`.
%% col-break:35,stk:1,b:blue-soft,bc:blue,t:text,sb:1,sep:1,sc:blue,ss:dashed,sw:2 %%
```
## Column header syntax
Use a header line as the first non-empty line inside a column:
```md
%% col-start %%
%% col-break:b:blue-soft %%
!info: Project summary
Main content for this column.
%% col-break:b:green-soft %%
!tip: Next action
- Review the draft
- Send feedback
%% col-end %%
```
Format:
```md
!type: Header title
```
Rules:
1. The header line must be the first non-empty line in that column.
2. `type` must match a configured header type in **Settings -> Advanced multi column -> Headers**.
3. The header line is removed from the rendered body and shown as the styled column header.
4. If the type is not configured, the line remains normal column content.
Built-in header types:
| Type | Default icon | Default style |
|---|---|---|
| `note` | `pencil` | Blue tint |
| `info` | `info` | Cyan tint |
| `tip` | `lightbulb` | Green tint |
| `warning` | `triangle-alert` | Orange tint |
| `danger` | `zap` | Red tint |
## Token reference
| Token | Meaning | Values |
@ -79,4 +118,3 @@ Sidebar
Main content
%% col-end %%
```

View file

@ -55,6 +55,62 @@ Practical note:
1. Per-block styling from right-click popover is the most direct way to style specific layouts.
2. If a global appearance option does not visibly change an existing block, style that block from the context menu.
## Headers tab
### Enable column headers
1. Turn on to parse `!type: title` as a styled header when it appears as the first non-empty line in a column.
2. Turn off to render those lines as normal column content.
3. Default: **On**.
Example:
```md
%% col-start %%
%% col-break %%
!warning: Migration notes
Check compatibility before updating.
%% col-end %%
```
### Header types
Header types define which `!type:` IDs are recognized and how their rendered header looks.
Built-in types:
1. `note`
2. `info`
3. `tip`
4. `warning`
5. `danger`
Each header type can be configured with:
1. Name: the ID used after `!`, such as `info` in `!info: Details`.
2. Icon: any Obsidian/Lucide icon name, such as `info`, `hash`, or `triangle-alert`.
3. Background: the header background color.
4. Text color: the header text/icon color.
5. Font size: the rendered header text size.
6. Font weight: the rendered header text weight.
### Add a custom header type
1. Select **Add header type**.
2. Set a unique name, such as `quote`, `source`, or `todo`.
3. Pick an icon and colors.
4. Use it as the first non-empty line in a column:
```md
!todo: Follow up
```
Notes:
1. Header names are normalized for marker use.
2. Duplicate names are made unique automatically.
3. Built-in rows do not show the delete button while their original ID is unchanged.
## About tab
Contains:
@ -63,4 +119,3 @@ Contains:
2. GitHub and issue links.
3. Support links.
4. Other plugin links.