diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index e2dc767..62c00a3 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -10,7 +10,7 @@ export interface Admonition { command: boolean; injectColor: boolean; noTitle: boolean; - copy: boolean; + copy?: boolean; } export interface NestedAdmonition { diff --git a/src/assets/main.css b/src/assets/main.css index 37893c7..363f441 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -10,15 +10,6 @@ .admonition { margin-top: var(--admonition-margin-top); margin-bottom: var(--admonition-margin-bottom); - margin-left: 0; - margin-right: 0; - padding: 0; - overflow: hidden; - color: var(--text-normal); - page-break-inside: avoid; - background-color: var(--background-secondary); - border-left: 0.2rem solid rgb(var(--admonition-color)); - border-radius: 0.1rem; box-shadow: 0 0.2rem 0.5rem var(--background-modifier-box-shadow); } @@ -29,52 +20,18 @@ opacity: 0.1; } -.admonition-title { - position: relative; - padding: 0.6rem 0.25em; - font-weight: 700; - background-color: rgba(var(--admonition-color), 0.1); -} - -.admonition-title-content { - display: flex; - justify-content: flex-start; - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -.admonition-title-icon { - color: rgb(var(--admonition-color)); - display: flex; - align-items: center; - justify-content: center; - margin: 0 0.5em 0 0.25em; - min-width: 1em; - width: min-content; -} - -.admonition-title-markdown { - display: block; -} - .admonition-title.no-title { display: none; } -.admonition-plugin.no-title .admonition-content-holder, -.admonition-plugin.no-title .admonition-content-holder > .admonition-content { - margin-top: 0; - margin-bottom: 0; -} - -.admonition-content-holder { +.admonition-content, +.callout-content { position: relative; } -.admonition-content { - margin: 10px 15px; - position: relative; - overflow-x: auto; +.admonition.no-title .admonition-content { + margin-top: 0; + margin-bottom: 0; } .admonition-content-copy { @@ -82,8 +39,9 @@ cursor: pointer; opacity: 0; position: absolute; - right: 0.375rem; - top: -5px; + margin: 0.375rem; + right: 0; + top: 0; transition: 0.3s opacity ease-in; } @@ -92,6 +50,7 @@ } .admonition:hover .admonition-content-copy, +.callout:hover .admonition-content-copy, .admonition-content-copy:hover { opacity: 1; } @@ -99,52 +58,6 @@ opacity: 0; } -details.admonition:not([open]) { - padding-bottom: 0; -} - -details.admonition > summary { - outline: none; - display: block !important; - list-style: none !important; - list-style-type: none !important; - min-height: 1rem; - border-top-left-radius: 0.1rem; - border-top-right-radius: 0.1rem; - cursor: pointer; -} - -details.admonition > summary::-webkit-details-marker, -details.admonition > summary::marker { - display: none !important; -} - -details.admonition > summary > .collapser { - position: absolute; - top: 50%; - right: 8px; - transform: translateY(-50%); - content: ""; -} - -details.admonition > summary > .collapser > .handle { - transform: rotate(0deg); - transition: transform 0.25s; - background-color: currentColor; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: contain; - mask-size: contain; - -webkit-mask-image: var(--admonition-details-icon); - mask-image: var(--admonition-details-icon); - width: 20px; - height: 20px; -} - -details.admonition[open] > summary > .collapser > .handle { - transform: rotate(90deg); -} - /** Settings */ .admonition-settings .additional { margin: 6px 12px; @@ -360,19 +273,19 @@ input.is-invalid { .admonition.no-drop { box-shadow: none; } -.admonition.no-drop > .admonition-title.no-title + .admonition-content-holder { +.admonition.no-drop > .admonition-title.no-title + .admonition-content { margin-top: 0; margin-bottom: 0; } -.admonition.no-drop .admonition .admonition-content-holder { +.admonition.no-drop .admonition .admonition-content { border-right: 1px solid rgba(var(--admonition-color), 0.2); border-bottom: 1px solid rgba(var(--admonition-color), 0.2); } .admonition.no-drop .admonition .admonition-title.no-title - + .admonition-content-holder { + + .admonition-content { border-top: 1px solid rgba(var(--admonition-color), 0.2); margin-top: 0; margin-bottom: 0; diff --git a/src/callout/manager.ts b/src/callout/manager.ts index f1e30b5..44dfa0b 100644 --- a/src/callout/manager.ts +++ b/src/callout/manager.ts @@ -1,7 +1,7 @@ import { - addIcon, Component, MarkdownPostProcessorContext, + Notice, setIcon } from "obsidian"; import { Admonition } from "src/@types"; @@ -52,24 +52,46 @@ export default class CalloutManager extends Component { const callout = el?.querySelector(".callout"); if (!callout) return; //apply metadata + + const type = callout.dataset.callout; + const admonition = this.plugin.admonitions[type]; + if (!admonition) return; + + const titleEl = callout.querySelector(".callout-title"); + const content = + callout.querySelector(".callout-content"); + const section = ctx.getSectionInfo(el); if (section) { - const { text, lineStart } = section; + const { text, lineStart, lineEnd } = section; const definition = text.split("\n")[lineStart]; const [, metadata] = definition.match(/> \[!.+\|(.*)]/) ?? []; if (metadata) { callout.dataset.calloutMetadata = metadata; } - } - - const type = callout.dataset.callout; - const admonition = this.plugin.data.userAdmonitions[type]; - if (!admonition) return; - const titleEl = callout.querySelector(".callout-title"); - const content = - callout.querySelector(".callout-content"); + if ( + this.plugin.admonitions[type].copy ?? + this.plugin.data.copyButton + ) { + let copy = content.createDiv("admonition-content-copy"); + setIcon(copy, "copy"); + copy.addEventListener("click", () => { + navigator.clipboard + .writeText( + text + .split("\n") + .slice(lineStart + 1, lineEnd + 1) + .join("\n") + .replace(/^> /gm, "") + ) + .then(async () => { + new Notice("Callout content copied to clipboard."); + }); + }); + } + } if (admonition.noTitle && !callout.dataset.calloutFold) { titleEl.addClass("no-title"); @@ -79,53 +101,7 @@ export default class CalloutManager extends Component { this.plugin.data.autoCollapse && !callout.dataset.calloutFold ) { - if (!content) return; - callout.addClass("is-collapsible"); - if (this.plugin.data.defaultCollapseType == "closed") { - callout.dataset.calloutFold = "-"; - callout.addClass("is-collapsed"); - } else { - callout.dataset.calloutFold = "+"; - } - - const iconEl = titleEl.createDiv("callout-fold"); - - setIcon(iconEl, "chevron-down"); - - let collapsed = callout.hasClass("is-collapsed"); - - this.getComputedHeights(content); - - if (collapsed) { - for (const prop of this.heights) { - content.style.setProperty(prop, "0px"); - } - } - titleEl.onclick = (event: MouseEvent) => { - event.preventDefault(); - - function transitionEnd(evt: TransitionEvent) { - content.removeEventListener("transitionend", transitionEnd); - content.style.removeProperty("transition"); - } - content.addEventListener("transitionend", transitionEnd); - content.style.setProperty( - "transition", - "all 100ms cubic-bezier(.02, .01, .47, 1)" - ); - collapsed = callout.hasClass("is-collapsed"); - if (event.button == 0) { - for (const prop of this.heights) { - const heights = this.getComputedHeights(content); - content.style.setProperty( - prop, - collapsed ? heights[prop] : "0px" - ); - } - - callout.toggleClass("is-collapsed", !collapsed); - } - }; + this.setCollapsible(callout); } if ( @@ -144,6 +120,61 @@ export default class CalloutManager extends Component { callout.addClass("drop-shadow"); } } + + setCollapsible(callout: HTMLElement) { + const titleEl = callout.querySelector(".callout-title"); + const content = + callout.querySelector(".callout-content"); + + if (!content) return; + callout.addClass("is-collapsible"); + if (this.plugin.data.defaultCollapseType == "closed") { + callout.dataset.calloutFold = "-"; + callout.addClass("is-collapsed"); + } else { + callout.dataset.calloutFold = "+"; + } + + const iconEl = titleEl.createDiv("callout-fold"); + + setIcon(iconEl, "chevron-down"); + + let collapsed = callout.hasClass("is-collapsed"); + + this.getComputedHeights(content); + + if (collapsed) { + for (const prop of this.heights) { + content.style.setProperty(prop, "0px"); + } + } + titleEl.onclick = (event: MouseEvent) => { + event.preventDefault(); + + function transitionEnd(evt: TransitionEvent) { + content.removeEventListener("transitionend", transitionEnd); + content.style.removeProperty("transition"); + } + content.addEventListener("transitionend", transitionEnd); + content.style.setProperty( + "transition", + "all 100ms cubic-bezier(.02, .01, .47, 1)" + ); + collapsed = callout.hasClass("is-collapsed"); + if (event.button == 0) { + for (const prop of this.heights) { + const heights = this.getComputedHeights(content); + content.style.setProperty( + prop, + collapsed ? heights[prop] : "0px" + ); + } + + callout.toggleClass("is-collapsed", !collapsed); + } + }; + } + getComputedHeights(el: HTMLDivElement): Heights { if (this.heightMap.has(el)) { return this.heightMap.get(el); @@ -176,9 +207,10 @@ export default class CalloutManager extends Component { } else { rule = `.callout[data-callout="${admonition.type}"] { --callout-color: ${admonition.color}; - --callout-icon: '${(this.plugin.iconManager - .getIconNode(admonition.icon) - ?.outerHTML ?? "").replace(/(width|height)=(\\?"|')\d+(\\?"|')/g, "")}'; + --callout-icon: '${( + this.plugin.iconManager.getIconNode(admonition.icon)?.outerHTML ?? + "" + ).replace(/(width|height)=(\\?"|')\d+(\\?"|')/g, "")}'; }`; } if (this.indexing.contains(admonition.type)) { diff --git a/src/main.ts b/src/main.ts index 2d3f190..a611ccc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,8 +17,6 @@ import { AdmonitionIconDefinition } from "./@types"; import { - COPY_ICON, - COPY_ICON_NAME, getParametersFromSource, SPIN_ICON, SPIN_ICON_NAME, @@ -149,7 +147,6 @@ export default class ObsidianAdmonition extends Plugin { addIcon(ADD_COMMAND_NAME, ADD_ADMONITION_COMMAND_ICON); addIcon(REMOVE_COMMAND_NAME, REMOVE_ADMONITION_COMMAND_ICON); addIcon(WARNING_ICON_NAME, WARNING_ICON); - addIcon(COPY_ICON_NAME, COPY_ICON); addIcon(SPIN_ICON_NAME, SPIN_ICON); /** Add generic commands. */ @@ -340,6 +337,9 @@ ${editor.getDoc().getSelection()} sourcePath, src ); + if (collapse && collapse != "none") { + this.calloutManager.setCollapsible(admonitionElement); + } /** * Replace the
 tag with the new admonition.
              */
@@ -371,6 +371,14 @@ ${editor.getDoc().getSelection()}
         }
     }
 
+    /**
+     *  .callout.admonition.is-collapsible.is-collapsed
+     *      .callout-title
+     *          .callout-icon
+     *          .callout-title-inner
+     *          .callout-fold
+     *      .callout-content
+     */
     getAdmonitionElement(
         type: string,
         title: string,
@@ -378,73 +386,26 @@ ${editor.getDoc().getSelection()}
         color?: string,
         collapse?: string
     ): HTMLElement {
-        let admonition, titleEl;
-        let attrs: { style?: string; open?: string } = color
-            ? {
-                  style: `--admonition-color: ${color};`
-              }
-            : {};
-        if (collapse && collapse != "none") {
-            if (collapse === "open") {
-                attrs.open = "open";
+        const admonition = createDiv({
+            cls: `callout admonition admonition-${type} admonition-plugin ${
+                !title?.trim().length ? "no-title" : ""
+            }`,
+            attr: {
+                style: `--admonition-color: ${color};`,
+                "data-callout": type,
+                "data-callout-fold": ""
             }
-            admonition = createEl("details", {
-                cls: `admonition admonition-${type} admonition-plugin ${
-                    !title?.trim().length ? "no-title" : ""
-                }`,
-                attr: attrs
-            });
-            titleEl = admonition.createEl("summary", {
-                cls: `admonition-title ${
-                    !title?.trim().length ? "no-title" : ""
-                }`
-            });
-        } else {
-            admonition = createDiv({
-                cls: `admonition admonition-${type} admonition-plugin ${
-                    !title?.trim().length ? "no-title" : ""
-                }`,
-                attr: attrs
-            });
-            titleEl = admonition.createDiv({
-                cls: `admonition-title ${
-                    !title?.trim().length ? "no-title" : ""
-                }`
-            });
-        }
+        });
+        const titleEl = admonition.createDiv({
+            cls: `callout-title admonition-title ${
+                !title?.trim().length ? "no-title" : ""
+            }`
+        });
 
         if (title && title.trim().length) {
-            /**
-             * Title structure
-             * .admonition-title
-             *      .admonition-title-content - Rendered Markdown top-level element (e.g. H1/2/3 etc, p)
-             *          div.admonition-title-icon
-             *              svg
-             *          div.admonition-title-markdown - Container of rendered markdown
-             *              ...rendered markdown children...
-             */
-
-            //get markdown
-            const markdownHolder = createDiv();
-            MarkdownRenderer.renderMarkdown(title, markdownHolder, "", null);
-
-            //admonition-title-content is first child of rendered markdown
-
-            const admonitionTitleContent =
-                markdownHolder.children[0]?.tagName === "P"
-                    ? createDiv()
-                    : markdownHolder.children[0];
-
-            //get children of markdown element, then remove them
-            const markdownElements = Array.from(
-                markdownHolder.children[0]?.childNodes || []
-            );
-            admonitionTitleContent.innerHTML = "";
-            admonitionTitleContent.addClass("admonition-title-content");
-
             //build icon element
-            const iconEl = admonitionTitleContent.createDiv(
-                "admonition-title-icon"
+            const iconEl = titleEl.createDiv(
+                "callout-icon admonition-title-icon"
             );
             if (icon && icon.name && icon.type) {
                 iconEl.appendChild(
@@ -452,20 +413,28 @@ ${editor.getDoc().getSelection()}
                 );
             }
 
-            //add markdown children back
-            const admonitionTitleMarkdown = admonitionTitleContent.createDiv(
-                "admonition-title-markdown"
+            //get markdown
+            const titleInnerEl = titleEl.createDiv(
+                "callout-title-inner admonition-title-content"
             );
-            for (let i = 0; i < markdownElements.length; i++) {
-                admonitionTitleMarkdown.appendChild(markdownElements[i]);
+            MarkdownRenderer.renderMarkdown(title, titleInnerEl, "", null);
+            if (
+                titleInnerEl.firstElementChild &&
+                titleInnerEl.firstElementChild instanceof HTMLParagraphElement
+            ) {
+                titleInnerEl.setChildrenInPlace([
+                    titleInnerEl.firstElementChild.firstChild
+                ]);
             }
-            titleEl.appendChild(admonitionTitleContent || createDiv());
         }
 
         //add them to title element
 
         if (collapse) {
-            titleEl.createDiv("collapser").createDiv("handle");
+            admonition.addClass("is-collapsible");
+            if (collapse == "closed") {
+                admonition.addClass("is-collapsed");
+            }
         }
         if (!this.data.dropShadow) {
             admonition.addClass("no-drop");
@@ -554,13 +523,12 @@ ${editor.getDoc().getSelection()}
         admonitionElement: HTMLElement,
         content: string
     ) {
-        const contentHolder = admonitionElement.createDiv(
-            "admonition-content-holder"
+        const contentEl = admonitionElement.createDiv(
+            "callout-content admonition-content"
         );
-        const contentEl = contentHolder.createDiv("admonition-content");
         if (this.admonitions[type].copy ?? this.data.copyButton) {
-            let copy = contentHolder.createDiv("admonition-content-copy");
-            setIcon(copy, COPY_ICON_NAME);
+            let copy = contentEl.createDiv("admonition-content-copy");
+            setIcon(copy, "copy");
             copy.addEventListener("click", () => {
                 navigator.clipboard.writeText(content.trim()).then(async () => {
                     new Notice("Admonition content copied to clipboard.");
diff --git a/src/settings.ts b/src/settings.ts
index 0e3d9f9..cd48955 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -896,9 +896,11 @@ class SettingsModal extends Modal {
             this.icon,
             this.injectColor ?? this.plugin.data.injectColor ? this.color : null
         );
-        this.admonitionPreview.createDiv("admonition-content").createEl("p", {
-            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla."
-        });
+        this.admonitionPreview
+            .createDiv("callout-content admonition-content")
+            .createEl("p", {
+                text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla."
+            });
 
         contentEl.appendChild(this.admonitionPreview);
         let typeText: TextComponent;
@@ -1228,7 +1230,7 @@ class SettingsModal extends Modal {
                     this.color = `${color.r}, ${color.g}, ${color.b}`;
                     this.admonitionPreview.setAttribute(
                         "style",
-                        `--admonition-color: ${this.color};`
+                        `--callout-color: ${this.color};`
                     );
                 });
             })
@@ -1248,7 +1250,7 @@ class SettingsModal extends Modal {
                         } else {
                             this.admonitionPreview.setAttribute(
                                 "style",
-                                `--admonition-color: ${this.color};`
+                                `--callout-color: ${this.color};`
                             );
                         }