mirror of
https://github.com/maradotwebp/obsidian-avatar.git
synced 2026-07-22 07:30:24 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53a4f03995 | ||
|
|
253668b983 | ||
|
|
0777249a18 | ||
|
|
6f1d8be50b | ||
|
|
06e96c659e | ||
|
|
123a7a2907 | ||
|
|
2daa4ef222 | ||
|
|
9c74ec76d9 | ||
|
|
ce254f6ab0 | ||
|
|
20f7b30d75 |
8 changed files with 141 additions and 46 deletions
19
README.md
19
README.md
|
|
@ -22,12 +22,27 @@ 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
|
||||
|
|
@ -36,7 +51,7 @@ The description editor supports all markdown features of Obsidian.
|
|||
2. Open `Settings` -> `Community Plugins`
|
||||
3. Click on `Turn on Community plugins` if prompted
|
||||
4. Click on `Browse`, and search for `Avatar`
|
||||
6. Install & enable the plugin
|
||||
5. Install & enable the plugin
|
||||
|
||||
### Manual installation
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "avatar",
|
||||
"name": "Avatar",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Display an avatar image in your notes.",
|
||||
"author": "froehlichA",
|
||||
"authorUrl": "https://github.com/froehlichA",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-avatar",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.0",
|
||||
"description": "An obsidian plugin for displaying an avatar image in front of your notes. ",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,45 @@
|
|||
import {
|
||||
App,
|
||||
MarkdownPostProcessorContext,
|
||||
MarkdownRenderer, MarkdownView,
|
||||
TFile
|
||||
MarkdownRenderer,
|
||||
MarkdownView,
|
||||
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";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
interface AvatarViewState {
|
||||
image: string;
|
||||
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;
|
||||
description: string;
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +58,16 @@
|
|||
|
||||
let inSourceMode = 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();
|
||||
|
|
@ -46,7 +83,7 @@
|
|||
}
|
||||
|
||||
function enterEditMode() {
|
||||
if(inSourceMode && !editMode) {
|
||||
if (inSourceMode && !editMode) {
|
||||
editMode = true;
|
||||
queueMicrotask(() => {
|
||||
descriptionEditEl.focus();
|
||||
|
|
@ -64,7 +101,7 @@
|
|||
}
|
||||
|
||||
function updateImage() {
|
||||
if(inSourceMode) {
|
||||
if (inSourceMode) {
|
||||
new SelectImageModal(app, (path) => {
|
||||
setState((fm) => {
|
||||
fm.image = path;
|
||||
|
|
@ -75,33 +112,49 @@
|
|||
|
||||
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="flex">
|
||||
<div class="avatar-view flex" class:reverse={state?.side === "right"}>
|
||||
<div
|
||||
class="avatar relative"
|
||||
class="avatar-container 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" src={normalizeImgPath(state?.image) ?? fallbackImage} />
|
||||
<img
|
||||
class="avatar"
|
||||
alt="Avatar"
|
||||
style:width={`${avatarSize}px`}
|
||||
style:height={`${avatarSize}px`}
|
||||
style:object-position={objectPosition}
|
||||
src={state.image ? normalizeImgPath(state?.image) : fallbackImage}
|
||||
/>
|
||||
{#if inSourceMode && hoverOnImage}
|
||||
<Fab>
|
||||
<ObsidianIcon id="edit"></ObsidianIcon>
|
||||
</Fab>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="description" on:click={enterEditMode}>
|
||||
<div
|
||||
class="description relative"
|
||||
on:click={enterEditMode}
|
||||
class:editmode={!!editMode}
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:this={descriptionEditEl}
|
||||
|
|
@ -110,7 +163,7 @@
|
|||
bind:value={state.description}
|
||||
></textarea>
|
||||
<span
|
||||
class="avatar-plugin--md-preview with-placeholder"
|
||||
class="avatar-plugin--md with-placeholder"
|
||||
hidden={editMode}
|
||||
bind:this={descriptionPreviewEl}
|
||||
data-placeholder="Write your story..."
|
||||
|
|
@ -131,20 +184,25 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.flex {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
.avatar-container {
|
||||
flex: 0 0 auto;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
|
@ -152,10 +210,13 @@
|
|||
.description {
|
||||
flex: 1 1 auto;
|
||||
word-break: break-word;
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.description.editmode {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
import type {MarkdownPostProcessorContext} from "obsidian";
|
||||
import type {CodeBlockProcessorProps} from "./renderCodeBlockProcessor";
|
||||
import {parseYaml, stringifyYaml} from "obsidian";
|
||||
import type { App, 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;
|
||||
|
||||
export type StateProvider<T> = (props: CodeBlockProcessorProps, source: string, node: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
state: State<T>,
|
||||
setState: SetState<T>
|
||||
declare let app: App;
|
||||
|
||||
export type StateProvider<T> = (
|
||||
props: CodeBlockProcessorProps,
|
||||
source: string,
|
||||
node: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext
|
||||
) => {
|
||||
state: State<T>;
|
||||
setState: SetState<T>;
|
||||
};
|
||||
|
||||
export function withCodeblockState<T>(): StateProvider<T> {
|
||||
|
|
@ -18,12 +25,12 @@ export function withCodeblockState<T>(): StateProvider<T> {
|
|||
} catch (_) {}
|
||||
|
||||
const setState: SetState<T> = (stateSetter) => {
|
||||
let newState = { ...state };
|
||||
const 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 },
|
||||
|
|
@ -34,7 +41,7 @@ export function withCodeblockState<T>(): StateProvider<T> {
|
|||
|
||||
return {
|
||||
state,
|
||||
setState
|
||||
}
|
||||
setState,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
13
styles.css
13
styles.css
|
|
@ -2,6 +2,15 @@
|
|||
float: right;
|
||||
}
|
||||
|
||||
.avatar-plugin--md-preview > * {
|
||||
margin-top: 0;
|
||||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,8 @@
|
|||
"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.5": "0.15.0",
|
||||
"1.0.6": "0.15.0",
|
||||
"1.0.7": "0.15.0",
|
||||
"1.1.0": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue