Skip to content

Commit

Permalink
4.0.0
Browse files Browse the repository at this point in the history
- Nested admonitions now possible (closes #16 )
  • Loading branch information
valentine195 committed May 3, 2021
1 parent 27c4a60 commit cc5acdf
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 65 deletions.
6 changes: 6 additions & 0 deletions @types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export interface Admonition {
color: string;
}

export interface INestedAdmonition {
type: string;
start: number;
end: number;
src: string;
}
export declare class ObsidianAdmonitionPlugin extends Plugin_2 {
removeAdmonition: (admonition: Admonition) => Promise<void>;
admonitions: { [admonitionType: string]: Admonition };
Expand Down
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla.
```
````

### Content

Content is the actual text of the admonition.

**Note: As of 0.2.0, this is no longer required. Anything other than `title:` and `collapse:` will be included as the content.**

### Titles

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

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

## Nesting Admonitions

Admonitions may be nested inside each other indefinitely using the [Python Markdown](https://python-markdown.github.io/extensions/admonition/) syntax.

> :warning: **Please note that this syntax _cannot_ be used for the original admonition. It must be a codeblock (```).**
Example:

````
```ad-note
title: Nested Admonitions
collapse: open
Hello!
!!! ad-note
title: This admonition is nested.
This is a nested admonition!
!!! ad-warning
title: This admonition is closed.
collapse: close
This is in the original admonition.
```
````

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

## Admonition Types

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

# Version History

## 4.0.0

- Nested admonitions are now possible

## 3.3.0

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

## 3.2.0

Expand Down
Binary file added images/nested.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-admonition",
"name": "Admonition",
"version": "3.3.4",
"version": "4.0.0",
"minAppVersion": "0.11.0",
"description": "Admonition block-styled content for Obsidian.md",
"author": "Jeremy Valentine",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-admonition",
"version": "3.3.4",
"version": "4.0.0",
"description": "Admonition block-styled content for Obsidian.md",
"main": "main.js",
"scripts": {
Expand Down
96 changes: 42 additions & 54 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ import {
Notice,
Plugin
} from "obsidian";
import { Admonition, ObsidianAdmonitionPlugin } from "../@types/types";
import { getAdmonitionElement } from "./util";
import {
Admonition,
INestedAdmonition,
ObsidianAdmonitionPlugin
} from "../@types/types";
import {
getAdmonitionElement,
getMatches,
getParametersFromSource
} from "./util";

import * as CodeMirror from "./codemirror";

Expand Down Expand Up @@ -100,6 +108,9 @@ export default class ObsidianAdmonition
admonitions: { [admonitionType: string]: Admonition } = {};
userAdmonitions: { [admonitionType: string]: Admonition } = {};
syntaxHighlight: boolean;
get types() {
return Object.keys(this.admonitions);
}
async saveSettings() {
await this.saveData({
syntaxHighlight: this.syntaxHighlight || false,
Expand Down Expand Up @@ -260,63 +271,40 @@ export default class ObsidianAdmonition
return;
}
try {
/**
* Find title and collapse parameters.
*/
let matchedParameters =
src.match(/^\b(title|collapse)\b:([\s\S]*?)$/gm) || [];

let params = Object.fromEntries(
matchedParameters.map((p) => {
let [, param, rest] = p.match(
/^\b(title|collapse)\b:([\s\S]*?)$/
);
return [param.trim(), rest.trim()];
})
);

let {
title = type[0].toUpperCase() + type.slice(1).toLowerCase(),
collapse
} = params;
collapse,
content
} = getParametersFromSource(type, src);

let match = new RegExp(`^!!! ad-(${this.types.join("|")})$`, "gm");

let nestedAdmonitions = content.match(match) || [];

if (nestedAdmonitions.length) {
let matches = [getMatches(content, 0, nestedAdmonitions[0])];
for (let i = 1; i < nestedAdmonitions.length; i++) {
matches.push(
getMatches(
content,
matches[i - 1].end,
nestedAdmonitions[i]
)
);
}

/**
* Get the content. Content should be everything that is not the title or collapse parameters.
* Remove any "content: " fields (legacy from < v0.2.0)
*/
let content = src
.replace(/^\b(title|collapse)\b:([\s\S]*?)$/gm, "")
.replace(/^\bcontent\b:\s?/gm, "");
/**
* If the admonition should collapse, but something other than open or closed was provided, set to closed.
*/
if (
Object.prototype.hasOwnProperty.call(params, "collapse") &&
(params.collapse.length == 0 ||
params.collapse === undefined ||
collapse !== "open")
) {
collapse = "closed";
}
/**
* If the admonition should collapse, but title was blanked, set the default title.
*/
if (
Object.prototype.hasOwnProperty.call(params, "title") &&
(params.title === undefined || params.title.length === 0) &&
collapse
) {
title = type[0].toUpperCase() + type.slice(1).toLowerCase();
new Notice(
"An admonition must have a title if it is collapsible."
);
let split = content.split("\n");

for (let m of matches.reverse()) {
split.splice(
m.start,
m.end - m.start + 1,
`\`\`\`ad-${m.type}\n${m.src}\n\`\`\``
);
}
content = split.join("\n");
}

/**
* Build the correct admonition element.
* Collapsible -> <details> <summary> Title </summary> <div> Content </div> </details>
* Regular -> <div> <div> Title </div> <div> Content </div> </div>
*/
let admonitionElement = getAdmonitionElement(
type,
title,
Expand Down
81 changes: 80 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,86 @@ import {
icon,
IconName
} from "@fortawesome/fontawesome-svg-core";
import { MarkdownRenderer } from "obsidian";
import { INestedAdmonition } from "../@types/types";
import { MarkdownRenderer, Notice } from "obsidian";

export function getMatches(
src: string,
from: number,
toMatch: string
): INestedAdmonition {
const split = src.split("\n").slice(from);
const first = split.indexOf(split.find((l) => l == toMatch));

let next = first + 1;
for (; next < split.length; next++) {
if (!/^(?: {2,4}|\t)+[\s\S]*?/.test(split[next])) break;
}

let innerSrc = split.slice(first + 1, next).join("\n");

const toRemove = innerSrc.split("\n")[0].match(/^(\s+)/);
innerSrc = innerSrc.replace(new RegExp(`^${toRemove[0] || ""}`, "gm"), "");

return {
start: first + from,
end: next + from - 1,
src: innerSrc,
type: toMatch.split("-").pop()
};
}

export function getParametersFromSource(type: string, src: string) {
/**
* Find title and collapse parameters.
*/
let matchedParameters =
src.match(/^\b(title|collapse)\b:([\s\S]*?)$/gm) || [];

let params = Object.fromEntries(
matchedParameters.map((p) => {
let [, param, rest] = p.match(/^\b(title|collapse)\b:([\s\S]*?)$/);
return [param.trim(), rest.trim()];
})
);

let {
title = type[0].toUpperCase() + type.slice(1).toLowerCase(),
collapse
} = params;

/**
* Get the content. Content should be everything that is not the title or collapse parameters.
* Remove any "content: " fields (legacy from < v0.2.0)
*/
let content = src
.replace(/^\b(title|collapse)\b:([\s\S]*?)$/gm, "")
.replace(/^\bcontent\b:\s?/gm, "");
/**
* If the admonition should collapse, but something other than open or closed was provided, set to closed.
*/
if (
Object.prototype.hasOwnProperty.call(params, "collapse") &&
(params.collapse.length == 0 ||
params.collapse === undefined ||
collapse !== "open")
) {
collapse = "closed";
}
/**
* If the admonition should collapse, but title was blanked, set the default title.
*/
if (
Object.prototype.hasOwnProperty.call(params, "title") &&
(params.title === undefined || params.title.length === 0) &&
collapse
) {
title = type[0].toUpperCase() + type.slice(1).toLowerCase();
new Notice("An admonition must have a title if it is collapsible.");
}

return { title, collapse, content };
}

export /* async */ function getAdmonitionElement(
type: string,
Expand Down
3 changes: 2 additions & 1 deletion versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"2.0.1": "0.11.0",
"3.1.2": "0.11.0",
"3.2.2": "0.11.0",
"3.3.4": "0.11.0"
"3.3.4": "0.11.0",
"4.0.0": "0.11.0"
}

0 comments on commit cc5acdf

Please sign in to comment.