mirror of
https://github.com/amatya-aditya/advanced-multi-column.git
synced 2026-07-22 06:13:08 +00:00
chore: release 1.2.1
This commit is contained in:
parent
64a3e3bc47
commit
5c60aa8b15
9 changed files with 131 additions and 1654 deletions
|
|
@ -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
8
package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
1571
styles.css
File diff suppressed because one or more lines are too long
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 %%
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue