mirror of
https://github.com/maradotwebp/obsidian-avatar.git
synced 2026-07-22 07:30:24 +00:00
Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
12 changed files with 80 additions and 207 deletions
21
LICENSE.md
21
LICENSE.md
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright © 2023 Alexander Fröhlich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
33
README.md
33
README.md
|
|
@ -22,39 +22,8 @@ description: This will be displayed as the description!
|
|||
```
|
||||
````
|
||||
|
||||
**In live preview mode**, click on the image to change it _(then either select one from your vault, or paste in a URL to a picture online)_, or edit the description by clicking on it _(Don't forget to click the button to save your changes!)_
|
||||
**In live preview mode**, click on the image to change it *(then either select one from your vault, or paste in a URL to a picture online)*, or edit the description by clicking on it *(Don't forget to click the button to save your changes!)*
|
||||
|
||||

|
||||
|
||||
The description editor supports all markdown features of Obsidian.
|
||||
|
||||
## More options
|
||||
|
||||
The following options are available through the YAML codeblock:
|
||||
|
||||
- `image?: string`
|
||||
- A path or URL to an image. Optional, defaults to a picture of the file path initials.
|
||||
- `description: string`
|
||||
- The description displayed next to the image.
|
||||
- `side?: "left" | "right"`
|
||||
- The side on which the avatar will be displayed. Optional, defaults to `"left"`.
|
||||
- `size?: "small" | "medium" | "large" | number`
|
||||
- The size of the avatar. May be either `small` (180x180px), `medium` (240x240px), `large` (320x320px), or any custom width as a number. Optional, defaults to `medium`.
|
||||
- `objectPosition?: string`
|
||||
- How to position the image within the square container surrounding it. For example, `"left"` would show the left side of a landscape picture, `"right"` the right side. <small>Refer to https://developer.mozilla.org/en-US/docs/Web/CSS/object-position for more information.</small>
|
||||
|
||||
## Installation
|
||||
|
||||
### Using Obsidian
|
||||
|
||||
1. Open Obsidian
|
||||
2. Open `Settings` -> `Community Plugins`
|
||||
3. Click on `Turn on Community plugins` if prompted
|
||||
4. Click on `Browse`, and search for `Avatar`
|
||||
5. Install & enable the plugin
|
||||
|
||||
### Manual installation
|
||||
|
||||
1. Download the latest release .zip from the `Releases` section of this Github repository
|
||||
2. Extract the .zip to your vault's plugin folder: `<vault>/.obsidian/plugins`
|
||||
3. Reload Obsidian
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"id": "avatar",
|
||||
"id": "obsidian-avatar",
|
||||
"name": "Avatar",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Display an avatar image in your notes.",
|
||||
"description": "Display an avatar image in front of your notes.",
|
||||
"author": "froehlichA",
|
||||
"authorUrl": "https://github.com/froehlichA",
|
||||
"authorUrl": "https://github.com/froehlichA/obsidian-avatar",
|
||||
"fundingUrl": "https://github.com/froehlichA/obsidian-avatar",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.0",
|
||||
"description": "An obsidian plugin for displaying an avatar image in front of your notes. ",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -4,43 +4,16 @@
|
|||
MarkdownPostProcessorContext,
|
||||
MarkdownRenderer,
|
||||
MarkdownView,
|
||||
TFile,
|
||||
TFile
|
||||
} from "obsidian";
|
||||
import Fab from "./Fab.svelte";
|
||||
import ObsidianIcon from "./ObsidianIcon.svelte";
|
||||
import type { SetState, State } from "../core/stateProviders";
|
||||
import { SelectImageModal } from "./SelectImageModal";
|
||||
import { AvatarPlugin } from "../plugin";
|
||||
import { onMount } from "svelte";
|
||||
import type {SetState, State} from "../core/stateProviders";
|
||||
import {SelectImageModal} from "./SelectImageModal";
|
||||
import {AvatarPlugin} from "../plugin";
|
||||
|
||||
interface AvatarViewState {
|
||||
image?: string;
|
||||
/**
|
||||
* The side on which the image will be displayed.
|
||||
*
|
||||
* @default "left"
|
||||
*/
|
||||
side?: "left" | "right";
|
||||
/**
|
||||
* The size of the avatar image.
|
||||
*
|
||||
* small: 180x180
|
||||
* medium: 240x240
|
||||
* large: 300x300
|
||||
*
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "small" | "medium" | "large" | number;
|
||||
/**
|
||||
* Alignment of the avatar image within the container.
|
||||
*
|
||||
* Corresponds to the `object-position` CSS property.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
|
||||
*
|
||||
* @default "50% 50%"
|
||||
*/
|
||||
objectPosition?: string;
|
||||
image: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
|
|
@ -56,34 +29,16 @@
|
|||
let descriptionEditEl: HTMLElement;
|
||||
let descriptionPreviewEl: HTMLElement;
|
||||
|
||||
let inSourceMode = false;
|
||||
$: inSourceMode = app ? isSourceMode(app) : false;
|
||||
$: fallbackImage = `https://ui-avatars.com/api/?name=${ctx?.sourcePath.split("/").at(-1) ?? "::"}&size=240`;
|
||||
$: avatarSize = state.size
|
||||
? typeof state.size === "string"
|
||||
? {
|
||||
small: 180,
|
||||
medium: 240,
|
||||
large: 300,
|
||||
}[state.size]
|
||||
: state.size
|
||||
: 240;
|
||||
$: objectPosition = state.objectPosition;
|
||||
|
||||
onMount(() => {
|
||||
inSourceMode = isSourceMode();
|
||||
let x = app.workspace.on("layout-change", () => {
|
||||
inSourceMode = isSourceMode();
|
||||
});
|
||||
return () => app.workspace.offref(x);
|
||||
});
|
||||
|
||||
function isSourceMode() {
|
||||
const view = app.workspace.getActiveViewOfType(MarkdownView);
|
||||
return view?.getMode?.() === "source";
|
||||
function isSourceMode(app: App) {
|
||||
const view = app.workspace.getMostRecentLeaf()?.view as MarkdownView;
|
||||
return view?.getMode() === "source";
|
||||
}
|
||||
|
||||
function enterEditMode() {
|
||||
if (inSourceMode && !editMode) {
|
||||
if(inSourceMode && !editMode) {
|
||||
editMode = true;
|
||||
queueMicrotask(() => {
|
||||
descriptionEditEl.focus();
|
||||
|
|
@ -101,7 +56,7 @@
|
|||
}
|
||||
|
||||
function updateImage() {
|
||||
if (inSourceMode) {
|
||||
if(inSourceMode) {
|
||||
new SelectImageModal(app, (path) => {
|
||||
setState((fm) => {
|
||||
fm.image = path;
|
||||
|
|
@ -112,49 +67,33 @@
|
|||
|
||||
function normalizeImgPath(src: string): string {
|
||||
const file = app.vault.getAbstractFileByPath(src);
|
||||
if (file && file instanceof TFile) {
|
||||
if(file && file instanceof TFile) {
|
||||
return app.vault.getResourcePath(file);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
$: if (descriptionPreviewEl && plugin && state && state?.description) {
|
||||
descriptionPreviewEl.innerHTML = "";
|
||||
MarkdownRenderer.renderMarkdown(
|
||||
state.description,
|
||||
descriptionPreviewEl,
|
||||
ctx?.sourcePath ?? "",
|
||||
plugin,
|
||||
);
|
||||
$: if(descriptionPreviewEl && plugin && state && state?.description) {
|
||||
descriptionPreviewEl.innerHTML = '';
|
||||
MarkdownRenderer.renderMarkdown(state.description, descriptionPreviewEl, ctx?.sourcePath ?? "", plugin);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="avatar-view flex" class:reverse={state?.side === "right"}>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="avatar-container relative"
|
||||
class="avatar relative"
|
||||
on:click={updateImage}
|
||||
on:mouseenter={() => (hoverOnImage = true)}
|
||||
on:mouseleave={() => (hoverOnImage = false)}
|
||||
on:mouseenter={() => hoverOnImage = true}
|
||||
on:mouseleave={() => hoverOnImage = false}
|
||||
>
|
||||
<img
|
||||
class="avatar"
|
||||
alt="Avatar"
|
||||
style:width={`${avatarSize}px`}
|
||||
style:height={`${avatarSize}px`}
|
||||
style:object-position={objectPosition}
|
||||
src={state.image ? normalizeImgPath(state?.image) : fallbackImage}
|
||||
/>
|
||||
<img class="avatar" alt="Avatar" src={normalizeImgPath(state?.image) ?? fallbackImage} />
|
||||
{#if inSourceMode && hoverOnImage}
|
||||
<Fab>
|
||||
<ObsidianIcon id="edit"></ObsidianIcon>
|
||||
</Fab>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="description relative"
|
||||
on:click={enterEditMode}
|
||||
class:editmode={!!editMode}
|
||||
>
|
||||
<div class="description" on:click={enterEditMode}>
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:this={descriptionEditEl}
|
||||
|
|
@ -163,7 +102,7 @@
|
|||
bind:value={state.description}
|
||||
></textarea>
|
||||
<span
|
||||
class="avatar-plugin--md with-placeholder"
|
||||
class="avatar-plugin--md-preview"
|
||||
hidden={editMode}
|
||||
bind:this={descriptionPreviewEl}
|
||||
data-placeholder="Write your story..."
|
||||
|
|
@ -184,25 +123,20 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.flex {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
|
@ -210,20 +144,16 @@
|
|||
.description {
|
||||
flex: 1 1 auto;
|
||||
word-break: break-word;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.description.editmode {
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.with-placeholder:empty:before {
|
||||
[contenteditable=true]:empty:not(:focus):before{
|
||||
content: attr(data-placeholder);
|
||||
color: var(--text-faint);
|
||||
font-style: italic;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type {App, MarkdownPostProcessorContext, Plugin} from "obsidian";
|
||||
import type {SvelteComponent} from "svelte";
|
||||
import type {StateProvider} from "./stateProviders";
|
||||
import {MarkdownRenderChild} from "obsidian";
|
||||
|
||||
export interface CodeBlockProcessorProps extends Record<string, any> {
|
||||
app: App;
|
||||
|
|
@ -21,7 +20,7 @@ export function renderCodeBlockProcessor<S>(
|
|||
) {
|
||||
return (source: string, containerEl: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
const node = containerEl.createEl("div");
|
||||
const svelteComponent = new component({
|
||||
new component({
|
||||
target: containerEl,
|
||||
props: {
|
||||
...props,
|
||||
|
|
@ -29,13 +28,5 @@ export function renderCodeBlockProcessor<S>(
|
|||
ctx
|
||||
}
|
||||
});
|
||||
|
||||
class UnloadSvelteComponent extends MarkdownRenderChild {
|
||||
onunload() {
|
||||
svelteComponent.$destroy();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.addChild(new UnloadSvelteComponent(node));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
import type { App, MarkdownPostProcessorContext } from "obsidian";
|
||||
import type { CodeBlockProcessorProps } from "./renderCodeBlockProcessor";
|
||||
import { parseYaml, stringifyYaml } from "obsidian";
|
||||
import type {MarkdownPostProcessorContext} from "obsidian";
|
||||
import type {CodeBlockProcessorProps} from "./renderCodeBlockProcessor";
|
||||
import {parseYaml, stringifyYaml} from "obsidian";
|
||||
|
||||
export type State<T> = Partial<T>;
|
||||
export type SetState<T> = (setter: (state: Partial<T>) => void) => void;
|
||||
|
||||
declare let app: App;
|
||||
|
||||
export type StateProvider<T> = (
|
||||
props: CodeBlockProcessorProps,
|
||||
source: string,
|
||||
node: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext
|
||||
) => {
|
||||
state: State<T>;
|
||||
setState: SetState<T>;
|
||||
export type StateProvider<T> = (props: CodeBlockProcessorProps, source: string, node: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
state: State<T>,
|
||||
setState: SetState<T>
|
||||
};
|
||||
|
||||
export function withCodeblockState<T>(): StateProvider<T> {
|
||||
|
|
@ -25,12 +18,12 @@ export function withCodeblockState<T>(): StateProvider<T> {
|
|||
} catch (_) {}
|
||||
|
||||
const setState: SetState<T> = (stateSetter) => {
|
||||
const newState = { ...state };
|
||||
let newState = { ...state };
|
||||
stateSetter(newState);
|
||||
const newStateStr: string = stringifyYaml(newState);
|
||||
|
||||
const info = ctx.getSectionInfo(node);
|
||||
if (info) {
|
||||
if(info) {
|
||||
app.workspace.activeEditor?.editor?.replaceRange(
|
||||
newStateStr + "```",
|
||||
{ line: info.lineStart + 1, ch: 0 },
|
||||
|
|
@ -41,7 +34,7 @@ export function withCodeblockState<T>(): StateProvider<T> {
|
|||
|
||||
return {
|
||||
state,
|
||||
setState,
|
||||
};
|
||||
setState
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,35 @@
|
|||
import {Plugin} from 'obsidian';
|
||||
import type {AvatarPluginSettings} from "./settings";
|
||||
import {DEFAULT_SETTINGS} from "./settings";
|
||||
import {renderCodeBlockProcessor} from "./core/renderCodeBlockProcessor";
|
||||
import AvatarView from "./components/AvatarView.svelte";
|
||||
import {withCodeblockState} from "./core/stateProviders";
|
||||
|
||||
export class AvatarPlugin extends Plugin {
|
||||
settings: AvatarPluginSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("avatar", renderCodeBlockProcessor(
|
||||
AvatarView,
|
||||
{ app: this.app, plugin: this },
|
||||
withCodeblockState()
|
||||
));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
...(await this.loadData())
|
||||
};
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
src/settings.ts
Normal file
7
src/settings.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface AvatarPluginSettings {
|
||||
mySetting: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: AvatarPluginSettings = {
|
||||
mySetting: 'default'
|
||||
}
|
||||
13
styles.css
13
styles.css
|
|
@ -2,15 +2,6 @@
|
|||
float: right;
|
||||
}
|
||||
|
||||
.markdown-preview-view .avatar-view {
|
||||
margin-block-start: var(--p-spacing);
|
||||
margin-block-end: var(--p-spacing);
|
||||
}
|
||||
|
||||
.avatar-plugin--md > *:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.avatar-plugin--md > *:last-child {
|
||||
margin-block-end: 0;
|
||||
.avatar-plugin--md-preview > * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
{
|
||||
"1.0.0": "0.15.0",
|
||||
"1.0.1": "0.15.0",
|
||||
"1.0.2": "0.15.0",
|
||||
"1.0.3": "0.15.0",
|
||||
"1.0.4": "0.15.0",
|
||||
"1.0.5": "0.15.0",
|
||||
"1.0.6": "0.15.0",
|
||||
"1.0.7": "0.15.0",
|
||||
"1.1.0": "0.15.0"
|
||||
}
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue