Skip to content

Commit cc5acdf

Browse files
committed
4.0.0
- Nested admonitions now possible (closes #16 )
1 parent 27c4a60 commit cc5acdf

File tree

8 files changed

+165
-65
lines changed

8 files changed

+165
-65
lines changed

@types/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ export interface Admonition {
66
color: string;
77
}
88

9+
export interface INestedAdmonition {
10+
type: string;
11+
start: number;
12+
end: number;
13+
src: string;
14+
}
915
export declare class ObsidianAdmonitionPlugin extends Plugin_2 {
1016
removeAdmonition: (admonition: Admonition) => Promise<void>;
1117
admonitions: { [admonitionType: string]: Admonition };

README.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla.
3232
```
3333
````
3434

35-
### Content
36-
37-
Content is the actual text of the admonition.
38-
39-
**Note: As of 0.2.0, this is no longer required. Anything other than `title:` and `collapse:` will be included as the content.**
40-
4135
### Titles
4236

4337
The admonition will render with the type of admonition by default. If you wish to customize the title, you can do so this way:
@@ -77,6 +71,35 @@ If a blank title is provided, the collapse parameter will not do anything.
7771

7872
![](https://raw.githubusercontent.com/valentine195/obsidian-admonition/master/images/collapse.gif)
7973

74+
## Nesting Admonitions
75+
76+
Admonitions may be nested inside each other indefinitely using the [Python Markdown](https://python-markdown.github.io/extensions/admonition/) syntax.
77+
78+
> :warning: **Please note that this syntax _cannot_ be used for the original admonition. It must be a codeblock (```).**
79+
80+
Example:
81+
82+
````
83+
```ad-note
84+
title: Nested Admonitions
85+
collapse: open
86+
87+
Hello!
88+
89+
!!! ad-note
90+
title: This admonition is nested.
91+
This is a nested admonition!
92+
!!! ad-warning
93+
title: This admonition is closed.
94+
collapse: close
95+
96+
97+
This is in the original admonition.
98+
```
99+
````
100+
101+
![](https://raw.githubusercontent.com/valentine195/obsidian-admonition/master/images/nested.gif)
102+
80103
## Admonition Types
81104

82105
The following admonition types are currently supported:
@@ -260,13 +283,16 @@ An icon without a title will have this CSS:
260283

261284
# Version History
262285

286+
## 4.0.0
287+
288+
- Nested admonitions are now possible
289+
263290
## 3.3.0
264291

265292
- Added commands to open and collapse all admonitions in active note
266293
- Admonition icons now respect the font size of the admonition title
267294
- Collapse handle now centers inside the title element
268295
- CSS changes
269-
-
270296

271297
## 3.2.0
272298

images/nested.PNG

9.55 KB
Loading

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "obsidian-admonition",
33
"name": "Admonition",
4-
"version": "3.3.4",
4+
"version": "4.0.0",
55
"minAppVersion": "0.11.0",
66
"description": "Admonition block-styled content for Obsidian.md",
77
"author": "Jeremy Valentine",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-admonition",
3-
"version": "3.3.4",
3+
"version": "4.0.0",
44
"description": "Admonition block-styled content for Obsidian.md",
55
"main": "main.js",
66
"scripts": {

src/main.ts

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ import {
66
Notice,
77
Plugin
88
} from "obsidian";
9-
import { Admonition, ObsidianAdmonitionPlugin } from "../@types/types";
10-
import { getAdmonitionElement } from "./util";
9+
import {
10+
Admonition,
11+
INestedAdmonition,
12+
ObsidianAdmonitionPlugin
13+
} from "../@types/types";
14+
import {
15+
getAdmonitionElement,
16+
getMatches,
17+
getParametersFromSource
18+
} from "./util";
1119

1220
import * as CodeMirror from "./codemirror";
1321

@@ -100,6 +108,9 @@ export default class ObsidianAdmonition
100108
admonitions: { [admonitionType: string]: Admonition } = {};
101109
userAdmonitions: { [admonitionType: string]: Admonition } = {};
102110
syntaxHighlight: boolean;
111+
get types() {
112+
return Object.keys(this.admonitions);
113+
}
103114
async saveSettings() {
104115
await this.saveData({
105116
syntaxHighlight: this.syntaxHighlight || false,
@@ -260,63 +271,40 @@ export default class ObsidianAdmonition
260271
return;
261272
}
262273
try {
263-
/**
264-
* Find title and collapse parameters.
265-
*/
266-
let matchedParameters =
267-
src.match(/^\b(title|collapse)\b:([\s\S]*?)$/gm) || [];
268-
269-
let params = Object.fromEntries(
270-
matchedParameters.map((p) => {
271-
let [, param, rest] = p.match(
272-
/^\b(title|collapse)\b:([\s\S]*?)$/
273-
);
274-
return [param.trim(), rest.trim()];
275-
})
276-
);
277-
278274
let {
279275
title = type[0].toUpperCase() + type.slice(1).toLowerCase(),
280-
collapse
281-
} = params;
276+
collapse,
277+
content
278+
} = getParametersFromSource(type, src);
279+
280+
let match = new RegExp(`^!!! ad-(${this.types.join("|")})$`, "gm");
281+
282+
let nestedAdmonitions = content.match(match) || [];
283+
284+
if (nestedAdmonitions.length) {
285+
let matches = [getMatches(content, 0, nestedAdmonitions[0])];
286+
for (let i = 1; i < nestedAdmonitions.length; i++) {
287+
matches.push(
288+
getMatches(
289+
content,
290+
matches[i - 1].end,
291+
nestedAdmonitions[i]
292+
)
293+
);
294+
}
282295

283-
/**
284-
* Get the content. Content should be everything that is not the title or collapse parameters.
285-
* Remove any "content: " fields (legacy from < v0.2.0)
286-
*/
287-
let content = src
288-
.replace(/^\b(title|collapse)\b:([\s\S]*?)$/gm, "")
289-
.replace(/^\bcontent\b:\s?/gm, "");
290-
/**
291-
* If the admonition should collapse, but something other than open or closed was provided, set to closed.
292-
*/
293-
if (
294-
Object.prototype.hasOwnProperty.call(params, "collapse") &&
295-
(params.collapse.length == 0 ||
296-
params.collapse === undefined ||
297-
collapse !== "open")
298-
) {
299-
collapse = "closed";
300-
}
301-
/**
302-
* If the admonition should collapse, but title was blanked, set the default title.
303-
*/
304-
if (
305-
Object.prototype.hasOwnProperty.call(params, "title") &&
306-
(params.title === undefined || params.title.length === 0) &&
307-
collapse
308-
) {
309-
title = type[0].toUpperCase() + type.slice(1).toLowerCase();
310-
new Notice(
311-
"An admonition must have a title if it is collapsible."
312-
);
296+
let split = content.split("\n");
297+
298+
for (let m of matches.reverse()) {
299+
split.splice(
300+
m.start,
301+
m.end - m.start + 1,
302+
`\`\`\`ad-${m.type}\n${m.src}\n\`\`\``
303+
);
304+
}
305+
content = split.join("\n");
313306
}
314307

315-
/**
316-
* Build the correct admonition element.
317-
* Collapsible -> <details> <summary> Title </summary> <div> Content </div> </details>
318-
* Regular -> <div> <div> Title </div> <div> Content </div> </div>
319-
*/
320308
let admonitionElement = getAdmonitionElement(
321309
type,
322310
title,

src/util.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,86 @@ import {
33
icon,
44
IconName
55
} from "@fortawesome/fontawesome-svg-core";
6-
import { MarkdownRenderer } from "obsidian";
6+
import { INestedAdmonition } from "../@types/types";
7+
import { MarkdownRenderer, Notice } from "obsidian";
8+
9+
export function getMatches(
10+
src: string,
11+
from: number,
12+
toMatch: string
13+
): INestedAdmonition {
14+
const split = src.split("\n").slice(from);
15+
const first = split.indexOf(split.find((l) => l == toMatch));
16+
17+
let next = first + 1;
18+
for (; next < split.length; next++) {
19+
if (!/^(?: {2,4}|\t)+[\s\S]*?/.test(split[next])) break;
20+
}
21+
22+
let innerSrc = split.slice(first + 1, next).join("\n");
23+
24+
const toRemove = innerSrc.split("\n")[0].match(/^(\s+)/);
25+
innerSrc = innerSrc.replace(new RegExp(`^${toRemove[0] || ""}`, "gm"), "");
26+
27+
return {
28+
start: first + from,
29+
end: next + from - 1,
30+
src: innerSrc,
31+
type: toMatch.split("-").pop()
32+
};
33+
}
34+
35+
export function getParametersFromSource(type: string, src: string) {
36+
/**
37+
* Find title and collapse parameters.
38+
*/
39+
let matchedParameters =
40+
src.match(/^\b(title|collapse)\b:([\s\S]*?)$/gm) || [];
41+
42+
let params = Object.fromEntries(
43+
matchedParameters.map((p) => {
44+
let [, param, rest] = p.match(/^\b(title|collapse)\b:([\s\S]*?)$/);
45+
return [param.trim(), rest.trim()];
46+
})
47+
);
48+
49+
let {
50+
title = type[0].toUpperCase() + type.slice(1).toLowerCase(),
51+
collapse
52+
} = params;
53+
54+
/**
55+
* Get the content. Content should be everything that is not the title or collapse parameters.
56+
* Remove any "content: " fields (legacy from < v0.2.0)
57+
*/
58+
let content = src
59+
.replace(/^\b(title|collapse)\b:([\s\S]*?)$/gm, "")
60+
.replace(/^\bcontent\b:\s?/gm, "");
61+
/**
62+
* If the admonition should collapse, but something other than open or closed was provided, set to closed.
63+
*/
64+
if (
65+
Object.prototype.hasOwnProperty.call(params, "collapse") &&
66+
(params.collapse.length == 0 ||
67+
params.collapse === undefined ||
68+
collapse !== "open")
69+
) {
70+
collapse = "closed";
71+
}
72+
/**
73+
* If the admonition should collapse, but title was blanked, set the default title.
74+
*/
75+
if (
76+
Object.prototype.hasOwnProperty.call(params, "title") &&
77+
(params.title === undefined || params.title.length === 0) &&
78+
collapse
79+
) {
80+
title = type[0].toUpperCase() + type.slice(1).toLowerCase();
81+
new Notice("An admonition must have a title if it is collapsible.");
82+
}
83+
84+
return { title, collapse, content };
85+
}
786

887
export /* async */ function getAdmonitionElement(
988
type: string,

versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"2.0.1": "0.11.0",
55
"3.1.2": "0.11.0",
66
"3.2.2": "0.11.0",
7-
"3.3.4": "0.11.0"
7+
"3.3.4": "0.11.0",
8+
"4.0.0": "0.11.0"
89
}

0 commit comments

Comments
 (0)