diff --git a/README.md b/README.md index 0d502d1..d1341b4 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,15 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. ``` ```` +### No Content + +An admonition with no content will render as just the title block. + +```` +```ad-note +``` +```` + ## Nesting Admonitions Nesting admonitions may be done by increasing the number of backticks. @@ -219,6 +228,14 @@ If a default admonition is overridden, it can be restored by deleting the user-d Please note that by default, the background color of the title is simply the color of the admonition at 10% opacity. CSS must be used to update this. +### Images as Icons + +Images can be uploaded to use as an admonition icon instead of an icon from Font Awesome or RPG Awesome. + +These images will be resized to 24px x 24px to be stored in the plugin's saved data. + +To remove an image icon, simply choose an icon in the icon chooser text box. + ## Customization This is all of the CSS applied to the admonitions. Override these classes to customize the look. @@ -671,7 +688,7 @@ If you're using Obsidian to run/plan a TTRPG, you may find my other plugin usefu - [Obsidian Leaflet](https://github.com/valentine195/obsidian-leaflet-plugin) - Add interactive maps to Obsidian.md notes - [Dice Roller](https://github.com/valentine195/obsidian-dice-roller) - Add some randomness to your notes -- [Initiative Tracker](https://github.com/valentine195/obsidian-initiative-tracker) - Track TTRPG Initiative in Obsidian +- [Initiative Tracker](https://github.com/valentine195/obsidian-initiative-tracker) - Track TTRPG Initiative in Obsidian - [5e Statblocks](https://github.com/valentine195/obsidian-5e-statblocks) - 5e-style statblocks inside notes! \ No newline at end of file diff --git a/manifest.json b/manifest.json index a3c96ce..3d2bdeb 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-admonition", "name": "Admonition", - "version": "6.2.10", + "version": "6.3.0", "minAppVersion": "0.11.0", "description": "Admonition block-styled content for Obsidian.md", "author": "Jeremy Valentine", diff --git a/package.json b/package.json index 63d82dd..3ae4487 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-admonition", - "version": "6.2.10", + "version": "6.3.0", "description": "Admonition block-styled content for Obsidian.md", "main": "main.js", "scripts": { diff --git a/src/@types/index.ts b/src/@types/index.ts index 5cdbb9a..162f970 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -29,8 +29,8 @@ export interface ISettingsData { } export type AdmonitionIconDefinition = { - type?: "font-awesome" | "rpg"; - name?: IconName | RPGIconName; + type?: "font-awesome" | "rpg" | "image"; + name?: IconName | RPGIconName | string; }; export type AdmonitionIconName = AdmonitionIconDefinition["name"]; diff --git a/src/assets/main.css b/src/assets/main.css index 1dabf79..b847f35 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -171,8 +171,12 @@ details.admonition[open] > summary > .collapser > .handle { } .has-invalid-message { - flex-grow: unset; - flex-flow: column nowrap; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "text image" + "inv inv"; } input.is-invalid { @@ -183,8 +187,13 @@ input.is-invalid { background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } +.admonition-type-setting input { + grid-column: span 2; +} + .invalid-feedback { display: block; + grid-area: inv; width: 100%; margin-top: 0.25rem; font-size: 0.875em; @@ -209,4 +218,12 @@ input.is-invalid { } .admonition-settings .coffee img { height: 30px; -} \ No newline at end of file +} + +.admonition-file-upload { + margin-right: 0; + margin-left: 12px; +} +.admonition-file-upload > input[type="file"] { + display: none; +} diff --git a/src/settings.ts b/src/settings.ts index d4891a1..01cdcbe 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -373,6 +373,7 @@ class SettingsModal extends Modal { titleSpan.prepend(iconEl); }); }); + typeSetting.controlEl.addClass("admonition-type-setting"); typeSetting.descEl.createSpan({ text: "This is used to create the admonition (e.g., " @@ -390,12 +391,19 @@ class SettingsModal extends Modal { text: ")" }); + const input = createEl("input", { + attr: { + type: "file", + name: "image", + accept: "image/*" + } + }); let iconText: TextComponent; const iconSetting = new Setting(settingDiv) .setName("Admonition Icon") .addText((text) => { iconText = text; - text.setValue(this.icon.name); + if (this.icon.type !== "image") text.setValue(this.icon.name); const validate = async () => { const v = text.inputEl.value; @@ -435,8 +443,64 @@ class SettingsModal extends Modal { modal.onClose = validate; text.inputEl.onblur = validate; + }) + .addButton((b) => { + b.setButtonText("Upload Image").setTooltip("Upload Image"); + b.buttonEl.addClass("admonition-file-upload"); + b.buttonEl.appendChild(input); + b.onClick(() => input.click()); }); + /** Image Uploader */ + input.onchange = async () => { + const { files } = input; + + if (!files.length) return; + + const image = files[0]; + const reader = new FileReader(); + reader.onloadend = (evt) => { + var image = new Image(); + image.onload = () => { + try { + // Resize the image + const canvas = document.createElement("canvas"), + max_size = 24; + let width = image.width, + height = image.height; + if (width > height) { + if (width > max_size) { + height *= max_size / width; + width = max_size; + } + } else { + if (height > max_size) { + width *= max_size / height; + height = max_size; + } + } + canvas.width = width; + canvas.height = height; + canvas + .getContext("2d") + .drawImage(image, 0, 0, width, height); + + this.icon = { + name: canvas.toDataURL("image/png"), + type: "image" + }; + this.display(); + } catch (e) { + new Notice("There was an error parsing the image."); + } + }; + image.src = evt.target.result.toString(); + }; + reader.readAsDataURL(image); + + input.value = null; + }; + const desc = iconSetting.descEl.createDiv(); desc.createEl("a", { text: "Font Awesome Icon", @@ -508,7 +572,10 @@ class SettingsModal extends Modal { error = true; } - if (!getIconType(iconText.inputEl.value)) { + if ( + !getIconType(iconText.inputEl.value) && + this.icon.type !== "image" + ) { SettingsModal.setValidationError( iconText, "Invalid icon name." @@ -516,7 +583,7 @@ class SettingsModal extends Modal { error = true; } - if (iconText.inputEl.value.length == 0) { + if (!this.icon.name.length) { SettingsModal.setValidationError( iconText, "Icon cannot be empty." @@ -578,9 +645,13 @@ class SettingsModal extends Modal { ".unset-align-items" ); - if (textInput.inputEl.parentElement.children[1]) { + if ( + textInput.inputEl.parentElement.querySelector(".invalid-feedback") + ) { textInput.inputEl.parentElement.removeChild( - textInput.inputEl.parentElement.children[1] + textInput.inputEl.parentElement.querySelector( + ".invalid-feedback" + ) ); } } diff --git a/src/util/icons.ts b/src/util/icons.ts index 3477583..d8dacbc 100644 --- a/src/util/icons.ts +++ b/src/util/icons.ts @@ -67,6 +67,11 @@ export function getIconModuleName(icon: AdmonitionIconDefinition) { } export function getIconNode(item: AdmonitionIconDefinition): Element { + if (item.type === "image") { + const img = new Image(); + img.src = item.name; + return img; + } if (item.type === "rpg") { if (!RPG[item.name as RPGIconName]) return null; const el = createDiv(); diff --git a/versions.json b/versions.json index 8415b84..dae90aa 100644 --- a/versions.json +++ b/versions.json @@ -11,5 +11,6 @@ "4.3.1": "0.12.0", "4.4.2": "0.12.2", "5.0.3": "0.12.2", - "6.2.10": "0.12.4" + "6.2.10": "0.12.4", + "6.3.0": "0.12.10" } \ No newline at end of file