Compare commits

..

16 Commits

Author SHA1 Message Date
zsviczian
b7ba0f8909 2.7.0-beta-1 2024-12-10 22:22:59 +01:00
zsviczian
c28911c739 Merge pull request #2144 from dmscode/master
Update zh-cn.ts to 9e1d491
2024-12-10 20:15:37 +01:00
dmscode
28088754ad Update zh-cn.ts to 9e1d491 2024-12-09 08:06:00 +08:00
zsviczian
9e1d491981 2.6.8 2024-12-08 16:09:00 +01:00
zsviczian
ab5caa4877 Merge pull request #2142 from TrillStones/master
[Script Contribution] [ Update] Image Occlusion
2024-12-08 15:56:47 +01:00
trillstones
44b580ae78 add image for image-occlusion script 2024-12-08 19:26:50 +08:00
trillstones
3859eddc80 add new image for image-occlusion script 2024-12-08 19:01:13 +08:00
trillstones
6098e1b42e add setting - Generate Images No Matter What
change card's and folder's naming logic
2024-12-08 17:29:00 +08:00
zsviczian
6ad8d2f620 2.6.8 - before field suggester implementation 2024-12-08 07:00:11 +01:00
zsviczian
5b3f3a56ad 2.6.8-beta-3, 0.17.6-17 2024-12-07 22:32:10 +01:00
zsviczian
f746b4f4ac Merge pull request #2140 from TrillStones/master
[Script Contribution] Image Occlusion
2024-12-07 21:29:55 +01:00
trillstones
3e4a3ace56 Update index-new.md for image occlusion script 2024-12-07 11:54:14 +08:00
trillstones
c72f6add40 Update index-new.md for image occlusion script 2024-12-07 11:45:21 +08:00
trillstones
6cfb125a38 Update index-new.md for image occlusion script 2024-12-07 11:44:27 +08:00
trillstones
c91e57e341 add image for Image-occlusion 2024-12-07 11:36:26 +08:00
trillstones
0ddd75e5fe Add Image Occlusion Script 2024-12-07 11:10:04 +08:00
27 changed files with 2100 additions and 609 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<!-- Blue star background -->
<path
d="M50 5 L61 40 L98 40 L68 62 L79 95 L50 75 L21 95 L32 62 L2 40 L39 40 Z"
fill="#4a9eff"
stroke="#1e1e1e"
stroke-width="2"
/>
<!-- White "A" text -->
<text
x="50"
y="65"
font-family="Arial"
font-size="40"
fill="white"
text-anchor="middle"
dominant-baseline="middle"
>A</text>
</svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@@ -130,6 +130,7 @@ I would love to include your contribution in the script library. If you have a s
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.svg"/></div>|[[#Select Similar Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.svg"/></div>|[[#Slideshow]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20Ellipse.svg"/></div>|[[#Split Ellipse]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.svg"/></div>|[[#Image Occlusion]]|
## Collaboration and Export
**Keywords**: Sharing, Teamwork, Exporting, Distribution, Cooperative, Publish
@@ -154,6 +155,7 @@ I would love to include your contribution in the script library. If you have a s
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Crop%20Vintage%20Mask.svg"/></div>|[[#Crop Vintage Mask]]|
---
# Description and Installation
@@ -267,6 +269,8 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Crop%20Vintage%20Mask.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Adds a rounded mask to the image by adding a full cover black mask and a rounded rectangle white mask. The script is also useful for adding just a black mask. In this case, run the script, then delete the white mask and add your custom white mask.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-crop-vintage.jpg'></td></tr></table>
## Custom Zoom
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.md
@@ -395,6 +399,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## Image Occlusion
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/TrillStones'>@TrillStones</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw script for creating Anki image occlusion cards in Obsidian, similar to Anki's Image Occlusion Enhanced add-on but integrated into your Obsidian workflow.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-image-occlusion.png'></td></tr></table>
## Invert colors
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.6.8-beta-2",
"version": "2.7.0-beta-1",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.6.7",
"version": "2.6.8",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.6-16",
"@zsviczian/excalidraw": "0.17.6-19",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",

View File

@@ -6,13 +6,16 @@ import {
EditorSuggestTriggerInfo,
TFile,
} from "obsidian";
import { FRONTMATTER_KEYS_INFO } from "./SuggesterInfo";
import { FRONTMATTER_KEYS_INFO } from "../../dialogs/SuggesterInfo";
import {
EXCALIDRAW_AUTOMATE_INFO,
EXCALIDRAW_SCRIPTENGINE_INFO,
} from "./SuggesterInfo";
import type ExcalidrawPlugin from "../main";
} from "../../dialogs/SuggesterInfo";
import type ExcalidrawPlugin from "../../main";
/**
* The field suggester recommends document properties in source mode, ea and utils function and attribute names.
*/
export class FieldSuggester extends EditorSuggest<string> {
plugin: ExcalidrawPlugin;
suggestType: "ea" | "excalidraw" | "utils";

View File

@@ -0,0 +1,142 @@
import {
FuzzyMatch,
TFile,
CachedMetadata,
TextComponent,
App,
setIcon,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
import { t } from "src/lang/helpers";
import { LinkSuggestion } from "src/types/types";
import ExcalidrawPlugin from "src/main";
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, VIDEO_TYPES } from "src/constants/constants";
export class FileSuggestionModal extends SuggestionModal<LinkSuggestion> {
text: TextComponent;
cache: CachedMetadata;
filesAndAliases: LinkSuggestion[];
file: TFile;
constructor(app: App, input: TextComponent, items: TFile[], private plugin: ExcalidrawPlugin) {
const filesAndAliases = [];
for (const file of items) {
const path = file.path;
filesAndAliases.push({ file, path, alias: "" });
const metadata = app.metadataCache.getFileCache(file); // Get metadata for the file
const aliases = metadata?.frontmatter?.aliases || []; // Check for frontmatter aliases
for (const alias of aliases) {
if(!alias) continue; // Skip empty aliases
filesAndAliases.push({ file, path, alias });
}
}
super(app, input.inputEl, filesAndAliases);
this.limit = 20;
this.filesAndAliases = filesAndAliases;
this.text = input;
this.suggestEl.style.maxWidth = "100%";
this.suggestEl.style.width = `${input.inputEl.clientWidth}px`;
this.inputEl.addEventListener("input", () => this.getFile());
this.setPlaceholder(t("SELECT_FILE_TO_INSERT"));
this.emptyStateText = t("NO_MATCH");
}
getFile() {
const v = this.inputEl.value;
const file = this.app.vault.getAbstractFileByPath(v);
if (file === this.file) {
return;
}
if (!(file instanceof TFile)) {
return;
}
this.file = file;
this.onInputChanged();
}
getSelectedItem() {
return this.file;
}
getItemText(item: LinkSuggestion) {
return `${item.file.path}${item.alias ? `|${item.alias}` : ""}`;
}
onChooseItem(item: LinkSuggestion) {
this.file = item.file;
this.text.setValue(this.getItemText(item));
this.text.onChanged();
}
selectSuggestion({ item }: FuzzyMatch<LinkSuggestion>) {
this.file = item.file;
this.text.setValue(this.getItemText(item));
this.onClose();
this.text.onChanged();
this.close();
}
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
const { item, match: matches } = result || {};
itemEl.addClass("mod-complex");
const contentEl = itemEl.createDiv("suggestion-content");
const auxEl = itemEl.createDiv("suggestion-aux");
const titleEl = contentEl.createDiv("suggestion-title");
const noteEl = contentEl.createDiv("suggestion-note");
//el.style.flexDirection = "column";
//content.style.flexDirection = "initial";
if (!item) {
titleEl.setText(this.emptyStateText);
itemEl.addClass("is-selected");
return;
}
const path = item.file?.path ?? item.path;
const pathLength = path.length - item.file.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
const itemText = this.getItemText(item);
for (let i = pathLength; i < itemText.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
titleEl.appendChild(element);
element.appendText(itemText.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
titleEl.appendText(itemText[i]);
}
noteEl.setText(path);
if(this.plugin.isExcalidrawFile(item.file)) {
setIcon(auxEl, ICON_NAME);
} else if (item.file.extension === "md") {
setIcon(auxEl, "square-pen");
} else if (IMAGE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "image");
} else if (VIDEO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "monitor-play");
} else if (AUDIO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-audio");
} else if (CODE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-code");
} else if (item.file.extension === "canvas") {
setIcon(auxEl, "layout-dashboard");
} else if (item.file.extension === "pdf") {
setIcon(auxEl, "book-open-text");
} else {
auxEl.setText(item.file.extension);
}
}
getItems() {
return this.filesAndAliases;
}
}

View File

@@ -0,0 +1,87 @@
import {
FuzzyMatch,
CachedMetadata,
TextComponent,
App,
TFolder,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
export class FolderSuggestionModal extends SuggestionModal<TFolder> {
text: TextComponent;
cache: CachedMetadata;
folders: TFolder[];
folder: TFolder;
constructor(app: App, input: TextComponent, items: TFolder[]) {
super(app, input.inputEl, items);
this.folders = [...items];
this.text = input;
this.inputEl.addEventListener("input", () => this.getFolder());
}
getFolder() {
const v = this.inputEl.value;
const folder = this.app.vault.getAbstractFileByPath(v);
if (folder == this.folder) {
return;
}
if (!(folder instanceof TFolder)) {
return;
}
this.folder = folder;
this.onInputChanged();
}
getItemText(item: TFolder) {
return item.path;
}
onChooseItem(item: TFolder) {
this.text.setValue(item.path);
this.folder = item;
}
selectSuggestion({ item }: FuzzyMatch<TFolder>) {
const link = item.path;
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(result: FuzzyMatch<TFolder>, el: HTMLElement) {
const { item, match: matches } = result || {};
const content = el.createDiv({
cls: "suggestion-content",
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
const pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (let i = pathLength; i < item.path.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path,
});
}
getItems() {
return this.folders;
}
}

View File

@@ -0,0 +1,163 @@
import {
FuzzyMatch,
TFile,
BlockCache,
HeadingCache,
CachedMetadata,
TextComponent,
App,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
export class PathSuggestionModal extends SuggestionModal<
TFile | BlockCache | HeadingCache
> {
file: TFile;
files: TFile[];
text: TextComponent;
cache: CachedMetadata;
constructor(app: App, input: TextComponent, items: TFile[]) {
super(app, input.inputEl, items);
this.files = [...items];
this.text = input;
//this.getFile();
this.inputEl.addEventListener("input", this.getFile.bind(this));
}
getFile() {
const v = this.inputEl.value;
const file = this.app.metadataCache.getFirstLinkpathDest(
v.split(/[\^#]/).shift() || "",
"",
);
if (file == this.file) {
return;
}
this.file = file;
if (this.file) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
this.onInputChanged();
}
getItemText(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) {
return item.path;
}
if (Object.prototype.hasOwnProperty.call(item, "heading")) {
return (<HeadingCache>item).heading;
}
if (Object.prototype.hasOwnProperty.call(item, "id")) {
return (<BlockCache>item).id;
}
}
onChooseItem(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) {
this.text.setValue(item.basename);
this.file = item;
this.cache = this.app.metadataCache.getFileCache(this.file);
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
this.text.setValue(
`${this.file.basename}#${(<HeadingCache>item).heading}`,
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
this.text.setValue(`${this.file.basename}^${(<BlockCache>item).id}`);
}
}
selectSuggestion({ item }: FuzzyMatch<TFile | BlockCache | HeadingCache>) {
let link: string;
if (item instanceof TFile) {
link = item.basename;
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
link = `${this.file.basename}#${(<HeadingCache>item).heading}`;
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
link = `${this.file.basename}^${(<BlockCache>item).id}`;
}
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(
result: FuzzyMatch<TFile | BlockCache | HeadingCache>,
el: HTMLElement,
) {
const { item, match: matches } = result || {};
const content = el.createDiv({
cls: "suggestion-content",
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
if (item instanceof TFile) {
const pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (
let i = pathLength;
i < item.path.length - item.extension.length - 1;
i++
) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path,
});
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
content.setText((<HeadingCache>item).heading);
content.prepend(
createSpan({
cls: "suggestion-flair",
text: `H${(<HeadingCache>item).level}`,
}),
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
content.setText((<BlockCache>item).id);
}
}
get headings() {
if (!this.file) {
return [];
}
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return this.cache.headings || [];
}
get blocks() {
if (!this.file) {
return [];
}
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return Object.values(this.cache.blocks || {}) || [];
}
getItems() {
const v = this.inputEl.value;
if (/#/.test(v)) {
this.modifyInput = (i) => i.split(/#/).pop();
return this.headings;
} else if (/\^/.test(v)) {
this.modifyInput = (i) => i.split(/\^/).pop();
return this.blocks;
}
return this.files;
}
}

View File

@@ -0,0 +1,119 @@
import {
SuggestModal,
Scope,
} from "obsidian";
export class Suggester<T> {
owner: SuggestModal<T>;
items: T[];
suggestions: HTMLDivElement[];
selectedItem: number;
containerEl: HTMLElement;
constructor(owner: SuggestModal<T>, containerEl: HTMLElement, scope: Scope) {
this.containerEl = containerEl;
this.owner = owner;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this),
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this),
);
scope.register([], "ArrowUp", () => {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
});
scope.register([], "ArrowDown", () => {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
});
scope.register([], "Enter", (evt) => {
this.useSelectedItem(evt);
return false;
});
scope.register([], "Tab", (evt) => {
this.chooseSuggestion(evt);
return false;
});
}
chooseSuggestion(evt: KeyboardEvent) {
if (!this.items || !this.items.length) {
return;
}
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.onChooseSuggestion(currentValue, evt);
}
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
if (!this.suggestions || !this.suggestions.length) {
return;
}
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
if (!this.suggestions || !this.suggestions.length) {
return;
}
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
empty() {
this.containerEl.empty();
}
setSuggestions(items: T[]) {
this.containerEl.empty();
const els: HTMLDivElement[] = [];
items.forEach((item) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(item, suggestionEl);
els.push(suggestionEl);
});
this.items = items;
this.suggestions = els;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
if (!this.items || !this.items.length) {
return;
}
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
wrap(value: number, size: number): number {
return ((value % size) + size) % size;
}
setSelectedItem(index: number, scroll: boolean) {
const nIndex = this.wrap(index, this.suggestions.length);
const prev = this.suggestions[this.selectedItem];
const next = this.suggestions[nIndex];
if (prev) {
prev.removeClass("is-selected");
}
if (next) {
next.addClass("is-selected");
}
this.selectedItem = nIndex;
if (scroll) {
next.scrollIntoView(false);
}
}
}

View File

@@ -0,0 +1,128 @@
import {
FuzzyMatch,
App,
FuzzySuggestModal,
Scope,
} from "obsidian";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
import { Suggester } from "./Suggester";
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
items: T[] = [];
suggestions: HTMLDivElement[];
popper: WeakRef<PopperInstance>;
//@ts-ignore
scope: Scope = new Scope(this.app.scope);
suggester: Suggester<FuzzyMatch<T>>;
suggestEl: HTMLDivElement;
promptEl: HTMLDivElement;
emptyStateText: string = "No match found";
limit: number = 100;
shouldNotOpen: boolean;
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
super(app);
this.inputEl = inputEl;
this.items = items;
this.suggestEl = createDiv("suggestion-container");
this.contentEl = this.suggestEl.createDiv("suggestion");
this.suggester = new Suggester(this, this.contentEl, this.scope);
this.scope.register([], "Escape", this.onEscape.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onFocus.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this));
this.suggestEl.on(
"mousedown",
".suggestion-container",
(event: MouseEvent) => {
event.preventDefault();
},
);
}
empty() {
this.suggester.empty();
}
onInputChanged(): void {
if (this.shouldNotOpen) {
return;
}
const inputStr = this.modifyInput(this.inputEl.value);
const suggestions = this.getSuggestions(inputStr);
if (suggestions.length > 0) {
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
} else {
this.onNoSuggestion();
}
this.open();
}
onFocus(): void {
this.shouldNotOpen = false;
this.onInputChanged();
}
modifyInput(input: string): string {
return input;
}
onNoSuggestion() {
this.empty();
this.renderSuggestion(null, this.contentEl.createDiv("suggestion-item"));
}
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.pushScope(this.scope);
this.inputEl.ownerDocument.body.appendChild(this.suggestEl);
this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10],
},
},
{
name: "flip",
options: {
fallbackPlacements: ["top"],
},
},
],
}));
}
onEscape(): void {
this.close();
this.shouldNotOpen = true;
}
close(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.popScope(this.scope);
this.suggester.setSuggestions([]);
if (this.popper?.deref()) {
this.popper.deref().destroy();
}
this.inputEl.removeEventListener("input", this.onInputChanged.bind(this));
this.inputEl.removeEventListener("focus", this.onFocus.bind(this));
this.inputEl.removeEventListener("blur", this.close.bind(this));
this.suggestEl.detach();
}
createPrompt(prompts: HTMLSpanElement[]) {
if (!this.promptEl) {
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
}
const prompt = this.promptEl.createDiv("prompt-instruction");
for (const p of prompts) {
prompt.appendChild(p);
}
}
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
abstract getItemText(arg: T): string;
abstract getItems(): T[];
}

View File

@@ -701,7 +701,11 @@ export class EmbeddedFilesLoader {
return;
}
const data = getMermaidText(element);
const result = await mermaidToExcalidraw(data, {fontSize: 20}, true);
const result = await mermaidToExcalidraw(
data,
{ themeVariables: { fontSize: "20" } },
true
);
if(!result) {
return;
}
@@ -767,7 +771,7 @@ export class EmbeddedFilesLoader {
}, 1200);
const iterator = loadIterator.bind(this)();
const concurency = 5;
const concurency = 3;
await new PromisePool(iterator, concurency).all();
clearInterval(addFilesTimer);

View File

@@ -17,7 +17,7 @@ import { MimeType } from "./EmbeddedFileLoader";
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings, TextMode, getTextMode } from "src/ExcalidrawView";
import { ExcalidrawData, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
import {
FRONTMATTER,
nanoid,
@@ -654,6 +654,13 @@ export class ExcalidrawAutomate {
0
)
: null;
if (template?.plaintext) {
if(params.plaintext) {
params.plaintext = params.plaintext + "\n\n" + template.plaintext;
} else {
params.plaintext = template.plaintext;
}
}
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
let frontmatter: string;
@@ -679,7 +686,13 @@ export class ExcalidrawAutomate {
: FRONTMATTER;
}
frontmatter += params.plaintext ? params.plaintext + "\n\n" : "";
frontmatter += params.plaintext
? (params.plaintext.endsWith("\n\n")
? params.plaintext
: (params.plaintext.endsWith("\n")
? params.plaintext + "\n"
: params.plaintext + "\n\n"))
: "";
if(template?.frontmatter && params?.frontmatterKeys) {
//the frontmatter tags supplyed to create take priority
frontmatter = mergeMarkdownFiles(template.frontmatter,frontmatter);
@@ -1476,7 +1489,12 @@ export class ExcalidrawAutomate {
diagram: string,
groupElements: boolean = true,
): Promise<string[]|string> {
const result = await mermaidToExcalidraw(diagram, {fontSize: this.style.fontSize});
const result = await mermaidToExcalidraw(
diagram, {
themeVariables: {fontSize: `${this.style.fontSize}`},
flowchart: {curve: this.style.roundness===null ? "linear" : "basis"},
}
);
const ids:string[] = [];
if(!result) return null;
if(result?.error) return result.error;
@@ -2930,6 +2948,7 @@ async function getTemplate(
frontmatter: string;
files: any;
hasSVGwithBitmap: boolean;
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
}> {
const app = plugin.app;
const vault = app.vault;
@@ -2955,6 +2974,7 @@ async function getTemplate(
frontmatter: "",
files: excalidrawData.scene.files,
hasSVGwithBitmap,
plaintext: "",
};
}
@@ -3027,7 +3047,7 @@ async function getTemplate(
}
excalidrawData.destroy();
const filehead = data.substring(0, trimLocation);
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
let files:any = {};
const sceneFilesSize = Object.values(scene.files).length;
if (sceneFilesSize > 0) {
@@ -3040,6 +3060,7 @@ async function getTemplate(
}
}
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
return {
elements: convertMarkdownLinksToObsidianURLs
? updateElementLinksToObsidianLinks({
@@ -3047,7 +3068,10 @@ async function getTemplate(
hostFile: file,
}) : groupElements,
appState: scene.appState,
frontmatter: filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead,
frontmatter,
plaintext: frontmatter !== filehead
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
: "",
files,
hasSVGwithBitmap,
};
@@ -3058,6 +3082,7 @@ async function getTemplate(
frontmatter: null,
files: [],
hasSVGwithBitmap,
plaintext: "",
};
}

View File

@@ -6,7 +6,38 @@ import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metad
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
import ExcalidrawPlugin from "./main";
interface MermaidConfig {
/**
* Whether to start the diagram automatically when the page loads.
* @default false
*/
startOnLoad?: boolean;
/**
* The flowchart curve style.
* @default "linear"
*/
flowchart?: {
curve?: "linear" | "basis";
};
/**
* Theme variables
* @default { fontSize: "25px" }
*/
themeVariables?: {
fontSize?: string;
};
/**
* Maximum number of edges to be rendered.
* @default 1000
*/
maxEdges?: number;
/**
* Maximum number of characters to be rendered.
* @default 1000
*/
maxTextSize?: number;
}
type EmbeddedLink =
| ({
@@ -159,7 +190,7 @@ declare namespace ExcalidrawLib {
function mermaidToExcalidraw(
mermaidDefinition: string,
opts: {fontSize: number},
opts: MermaidConfig,
forceSVG?: boolean,
): Promise<{
elements?: ExcalidrawElement[];

View File

@@ -3267,6 +3267,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
markdownlink: string,
path: string,
alias: string,
originalLink?: string,
) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addLink, "ExcalidrawView.addLink", markdownlink, path, alias);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
@@ -3280,16 +3281,28 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
const selectedElementId = Object.keys(api.getAppState().selectedElementIds)[0];
const selectedElement = api.getSceneElements().find(el=>el.id === selectedElementId);
if(!selectedElement || (selectedElement && selectedElement.link !== null)) {
if(!selectedElement || (!Boolean(originalLink) && (selectedElement && selectedElement.link !== null) )) {
if(selectedElement) new Notice("Selected element already has a link. Inserting link as text.");
this.addText(markdownlink);
return;
}
const ea = getEA(this) as ExcalidrawAutomate;
ea.copyViewElementsToEAforEditing([selectedElement]);
if(originalLink?.match(/\[\[(.*?)\]\]/)?.[1]) {
markdownlink = originalLink.replace(/(\[\[.*?\]\])/,markdownlink);
}
ea.getElement(selectedElementId).link = markdownlink;
await ea.addElementsToView(false, true);
ea.destroy();
if(Boolean(originalLink)) {
this.updateScene({
appState: {
showHyperlinkPopup: {
newValue : "info", oldValue : "editor"
}
}
});
}
}
public async addText (
@@ -3546,7 +3559,10 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
private clearHoverPreview() {
if (this.hoverPopover) {
//@ts-ignore
const hoverContainerEl = this.hoverPopover?.containerEl;
//don't auto hide hover-editor
if (this.hoverPopover && !hoverContainerEl?.parentElement?.hasClass("hover-editor")) {
this.hoverPreviewTarget = null;
//@ts-ignore
if(this.hoverPopover.embed?.editor) {
@@ -4659,7 +4675,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
//in compatibility mode, returns the original text, and for backward compatibility the text if originalText is not available
private onBeforeTextEdit (textElement: ExcalidrawTextElement, isExistingElement: boolean): string {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onBeforeTextEdit, "ExcalidrawView.onBeforeTextEdit", textElement);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
/*const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
const st = api.getAppState();
setDynamicStyle(
this.plugin.ea,
@@ -4667,7 +4683,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor,
this.plugin.settings.dynamicStyling,
api.getColorAtScenePoint({sceneX: this.currentPosition.x, sceneY: this.currentPosition.y})
);
);*/
if(!isExistingElement) {
return;
}
@@ -5091,6 +5107,15 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
new Notice("Image successfully converted to local file");
}
private insertLinkAction(linkVal: string) {
let link = linkVal.match(/\[\[(.*?)\]\]/)?.[1];
if(!link) {
link = linkVal.replaceAll("[","").replaceAll("]","");
link = link.split("|")[0].trim();
}
this.plugin.insertLinkDialog.start(this.file.path, (markdownlink: string, path:string, alias:string) => this.addLink(markdownlink, path, alias, linkVal), link);
}
private onContextMenu(elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) {
const React = this.packages.react;
const contextMenuActions = [];
@@ -5913,6 +5938,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
renderEmbeddable: this.renderEmbeddable.bind(this),
renderMermaid: shouldRenderMermaid,
showDeprecatedFonts: true,
insertLinkAction: this.insertLinkAction.bind(this),
},
this.renderCustomActionsMenu(),
this.renderWelcomeScreen(),

View File

@@ -195,6 +195,10 @@ export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"];
export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"];
export const MAX_IMAGE_SIZE = 500;
export const VIDEO_TYPES = ["mp4", "webm", "ogv", "mov", "mkv"];
export const AUDIO_TYPES = ["mp3", "wav", "m4a", "3gp", "flac", "ogg", "oga", "opus"];
export const CODE_TYPES = ["json", "css", "js"];
export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depricated?:boolean}} = {
"plugin": {name: "excalidraw-plugin", type: "text"},
"export-transparent": {name: "excalidraw-export-transparent", type: "checkbox"},

View File

@@ -1,569 +0,0 @@
import {
FuzzyMatch,
TFile,
BlockCache,
HeadingCache,
CachedMetadata,
TextComponent,
App,
TFolder,
FuzzySuggestModal,
SuggestModal,
Scope,
} from "obsidian";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
class Suggester<T> {
owner: SuggestModal<T>;
items: T[];
suggestions: HTMLDivElement[];
selectedItem: number;
containerEl: HTMLElement;
constructor(owner: SuggestModal<T>, containerEl: HTMLElement, scope: Scope) {
this.containerEl = containerEl;
this.owner = owner;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this),
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this),
);
scope.register([], "ArrowUp", () => {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
});
scope.register([], "ArrowDown", () => {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
});
scope.register([], "Enter", (evt) => {
this.useSelectedItem(evt);
return false;
});
scope.register([], "Tab", (evt) => {
this.chooseSuggestion(evt);
return false;
});
}
chooseSuggestion(evt: KeyboardEvent) {
if (!this.items || !this.items.length) {
return;
}
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.onChooseSuggestion(currentValue, evt);
}
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
if (!this.suggestions || !this.suggestions.length) {
return;
}
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
if (!this.suggestions || !this.suggestions.length) {
return;
}
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
empty() {
this.containerEl.empty();
}
setSuggestions(items: T[]) {
this.containerEl.empty();
const els: HTMLDivElement[] = [];
items.forEach((item) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(item, suggestionEl);
els.push(suggestionEl);
});
this.items = items;
this.suggestions = els;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
if (!this.items || !this.items.length) {
return;
}
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
wrap(value: number, size: number): number {
return ((value % size) + size) % size;
}
setSelectedItem(index: number, scroll: boolean) {
const nIndex = this.wrap(index, this.suggestions.length);
const prev = this.suggestions[this.selectedItem];
const next = this.suggestions[nIndex];
if (prev) {
prev.removeClass("is-selected");
}
if (next) {
next.addClass("is-selected");
}
this.selectedItem = nIndex;
if (scroll) {
next.scrollIntoView(false);
}
}
}
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
items: T[] = [];
suggestions: HTMLDivElement[];
popper: WeakRef<PopperInstance>;
//@ts-ignore
scope: Scope = new Scope(this.app.scope);
suggester: Suggester<FuzzyMatch<T>>;
suggestEl: HTMLDivElement;
promptEl: HTMLDivElement;
emptyStateText: string = "No match found";
limit: number = 100;
shouldNotOpen: boolean;
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
super(app);
this.inputEl = inputEl;
this.items = items;
this.suggestEl = createDiv("suggestion-container");
this.contentEl = this.suggestEl.createDiv("suggestion");
this.suggester = new Suggester(this, this.contentEl, this.scope);
this.scope.register([], "Escape", this.onEscape.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onFocus.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this));
this.suggestEl.on(
"mousedown",
".suggestion-container",
(event: MouseEvent) => {
event.preventDefault();
},
);
}
empty() {
this.suggester.empty();
}
onInputChanged(): void {
if (this.shouldNotOpen) {
return;
}
const inputStr = this.modifyInput(this.inputEl.value);
const suggestions = this.getSuggestions(inputStr);
if (suggestions.length > 0) {
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
} else {
this.onNoSuggestion();
}
this.open();
}
onFocus(): void {
this.shouldNotOpen = false;
this.onInputChanged();
}
modifyInput(input: string): string {
return input;
}
onNoSuggestion() {
this.empty();
this.renderSuggestion(null, this.contentEl.createDiv("suggestion-item"));
}
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.pushScope(this.scope);
this.inputEl.ownerDocument.body.appendChild(this.suggestEl);
this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10],
},
},
{
name: "flip",
options: {
fallbackPlacements: ["top"],
},
},
],
}));
}
onEscape(): void {
this.close();
this.shouldNotOpen = true;
}
close(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.popScope(this.scope);
this.suggester.setSuggestions([]);
if (this.popper?.deref()) {
this.popper.deref().destroy();
}
this.inputEl.removeEventListener("input", this.onInputChanged.bind(this));
this.inputEl.removeEventListener("focus", this.onFocus.bind(this));
this.inputEl.removeEventListener("blur", this.close.bind(this));
this.suggestEl.detach();
}
createPrompt(prompts: HTMLSpanElement[]) {
if (!this.promptEl) {
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
}
const prompt = this.promptEl.createDiv("prompt-instruction");
for (const p of prompts) {
prompt.appendChild(p);
}
}
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
abstract getItemText(arg: T): string;
abstract getItems(): T[];
}
export class PathSuggestionModal extends SuggestionModal<
TFile | BlockCache | HeadingCache
> {
file: TFile;
files: TFile[];
text: TextComponent;
cache: CachedMetadata;
constructor(app: App, input: TextComponent, items: TFile[]) {
super(app, input.inputEl, items);
this.files = [...items];
this.text = input;
//this.getFile();
this.inputEl.addEventListener("input", this.getFile.bind(this));
}
getFile() {
const v = this.inputEl.value;
const file = this.app.metadataCache.getFirstLinkpathDest(
v.split(/[\^#]/).shift() || "",
"",
);
if (file == this.file) {
return;
}
this.file = file;
if (this.file) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
this.onInputChanged();
}
getItemText(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) {
return item.path;
}
if (Object.prototype.hasOwnProperty.call(item, "heading")) {
return (<HeadingCache>item).heading;
}
if (Object.prototype.hasOwnProperty.call(item, "id")) {
return (<BlockCache>item).id;
}
}
onChooseItem(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) {
this.text.setValue(item.basename);
this.file = item;
this.cache = this.app.metadataCache.getFileCache(this.file);
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
this.text.setValue(
`${this.file.basename}#${(<HeadingCache>item).heading}`,
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
this.text.setValue(`${this.file.basename}^${(<BlockCache>item).id}`);
}
}
selectSuggestion({ item }: FuzzyMatch<TFile | BlockCache | HeadingCache>) {
let link: string;
if (item instanceof TFile) {
link = item.basename;
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
link = `${this.file.basename}#${(<HeadingCache>item).heading}`;
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
link = `${this.file.basename}^${(<BlockCache>item).id}`;
}
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(
result: FuzzyMatch<TFile | BlockCache | HeadingCache>,
el: HTMLElement,
) {
const { item, match: matches } = result || {};
const content = el.createDiv({
cls: "suggestion-content",
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
if (item instanceof TFile) {
const pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (
let i = pathLength;
i < item.path.length - item.extension.length - 1;
i++
) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path,
});
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
content.setText((<HeadingCache>item).heading);
content.prepend(
createSpan({
cls: "suggestion-flair",
text: `H${(<HeadingCache>item).level}`,
}),
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
content.setText((<BlockCache>item).id);
}
}
get headings() {
if (!this.file) {
return [];
}
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return this.cache.headings || [];
}
get blocks() {
if (!this.file) {
return [];
}
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return Object.values(this.cache.blocks || {}) || [];
}
getItems() {
const v = this.inputEl.value;
if (/#/.test(v)) {
this.modifyInput = (i) => i.split(/#/).pop();
return this.headings;
} else if (/\^/.test(v)) {
this.modifyInput = (i) => i.split(/\^/).pop();
return this.blocks;
}
return this.files;
}
}
export class FolderSuggestionModal extends SuggestionModal<TFolder> {
text: TextComponent;
cache: CachedMetadata;
folders: TFolder[];
folder: TFolder;
constructor(app: App, input: TextComponent, items: TFolder[]) {
super(app, input.inputEl, items);
this.folders = [...items];
this.text = input;
this.inputEl.addEventListener("input", () => this.getFolder());
}
getFolder() {
const v = this.inputEl.value;
const folder = this.app.vault.getAbstractFileByPath(v);
if (folder == this.folder) {
return;
}
if (!(folder instanceof TFolder)) {
return;
}
this.folder = folder;
this.onInputChanged();
}
getItemText(item: TFolder) {
return item.path;
}
onChooseItem(item: TFolder) {
this.text.setValue(item.path);
this.folder = item;
}
selectSuggestion({ item }: FuzzyMatch<TFolder>) {
const link = item.path;
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(result: FuzzyMatch<TFolder>, el: HTMLElement) {
const { item, match: matches } = result || {};
const content = el.createDiv({
cls: "suggestion-content",
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
const pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (let i = pathLength; i < item.path.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path,
});
}
getItems() {
return this.folders;
}
}
export class FileSuggestionModal extends SuggestionModal<TFile> {
text: TextComponent;
cache: CachedMetadata;
files: TFile[];
file: TFile;
constructor(app: App, input: TextComponent, items: TFile[]) {
super(app, input.inputEl, items);
this.limit = 20;
this.files = [...items];
this.text = input;
this.inputEl.addEventListener("input", () => this.getFile());
}
getFile() {
const v = this.inputEl.value;
const file = this.app.vault.getAbstractFileByPath(v);
if (file === this.file) {
return;
}
if (!(file instanceof TFile)) {
return;
}
this.file = file;
this.onInputChanged();
}
getSelectedItem() {
return this.file;
}
getItemText(item: TFile) {
return item.path;
}
onChooseItem(item: TFile) {
this.file = item;
this.text.setValue(item.path);
this.text.onChanged();
}
selectSuggestion({ item }: FuzzyMatch<TFile>) {
this.file = item;
this.text.setValue(item.path);
this.onClose();
this.text.onChanged();
this.close();
}
renderSuggestion(result: FuzzyMatch<TFile>, el: HTMLElement) {
const { item, match: matches } = result || {};
const content = el.createDiv({
cls: "suggestion-content",
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
const pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (let i = pathLength; i < item.path.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path,
});
}
getItems() {
return this.files;
}
}

View File

@@ -1,10 +1,12 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import { FuzzyMatch, FuzzySuggestModal, setIcon } from "obsidian";
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../constants/constants";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "src/main";
import { getLink } from "src/utils/FileUtils";
import { LinkSuggestion } from "src/types/types";
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
export class InsertLinkDialog extends FuzzySuggestModal<LinkSuggestion> {
private addText: Function;
private drawingPath: string;
@@ -28,7 +30,7 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
this.emptyStateText = t("NO_MATCH");
}
getItems(): any[] {
getItems(): LinkSuggestion[] {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
return (
this.app.metadataCache
@@ -39,11 +41,11 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
);
}
getItemText(item: any): string {
getItemText(item: LinkSuggestion): string {
return item.path + (item.alias ? `|${item.alias}` : "");
}
onChooseItem(item: any): void {
onChooseItem(item: LinkSuggestion): void {
let filepath = item.path;
if (item.file) {
filepath = this.app.metadataCache.fileToLinktext(
@@ -56,6 +58,65 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
}
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
const { item, match: matches } = result || {};
itemEl.addClass("mod-complex");
const contentEl = itemEl.createDiv("suggestion-content");
const auxEl = itemEl.createDiv("suggestion-aux");
const titleEl = contentEl.createDiv("suggestion-title");
const noteEl = contentEl.createDiv("suggestion-note");
if (!item) {
titleEl.setText(this.emptyStateText);
itemEl.addClass("is-selected");
return;
}
const path = item.file?.path ?? item.path;
const pathLength = path.length - (item.file?.name.length ?? 0);
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
const itemText = this.getItemText(item);
for (let i = pathLength; i < itemText.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
titleEl.appendChild(element);
element.appendText(itemText.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
titleEl.appendText(itemText[i]);
}
noteEl.setText(path);
if(!item.file) {
setIcon(auxEl, "ghost");
} else if(this.plugin.isExcalidrawFile(item.file)) {
setIcon(auxEl, ICON_NAME);
} else if (item.file.extension === "md") {
setIcon(auxEl, "square-pen");
} else if (IMAGE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "image");
} else if (VIDEO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "monitor-play");
} else if (AUDIO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-audio");
} else if (CODE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-code");
} else if (item.file.extension === "canvas") {
setIcon(auxEl, "layout-dashboard");
} else if (item.file.extension === "pdf") {
setIcon(auxEl, "book-open-text");
} else {
auxEl.setText(item.file.extension);
}
}
onClose(): void {
window.setTimeout(()=>{
this.addText = null
@@ -63,9 +124,19 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
super.onClose();
}
public start(drawingPath: string, addText: Function) {
private inLink: string;
onOpen(): void {
super.onOpen();
if(this.inLink) {
this.inputEl.value = this.inLink;
this.inputEl.dispatchEvent(new Event('input'));
}
}
public start(drawingPath: string, addText: Function, link?: string) {
this.addText = addText;
this.drawingPath = drawingPath;
this.inLink = link;
this.open();
}
}

View File

@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { getPDFDoc } from "src/utils/FileUtils";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
import { getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
@@ -206,7 +206,11 @@ export class InsertPDFModal extends Modal {
const search = new TextComponent(ce);
search.inputEl.style.width = "100%";
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"));
const suggester = new FileSuggestionModal(
this.app,
search,this.app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"),
this.plugin
);
search.onChange(async () => {
const file = suggester.getSelectedItem();
await setFile(file);

View File

@@ -16,6 +16,23 @@ export const RELEASE_NOTES: { [k: string]: string } = {
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
`,
"2.6.8":`
## New
- **QoL improvements**:
- Obsidian-link search button in Element Link Editor.
- Add Any File now searches file aliases as well.
- Cosmetic changes to file search modals (display path, show file type icon).
- Text Element cursor-color matches the text color.
- New script in script store: [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) by [@TrillStones](https://github.com/TrillStones) 🙏
## Fixed
- Excalidraw icon on the **ribbon menu kept reappearing** every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115)
- In pen mode, when **single-finger panning** is enabled, Excalidraw should still **allow actions with the mouse**.
- When **editing a drawing in split mode** (drawing is on one side, markdown view is on the other), editing the markdown note sometimes causes the drawing to re-zoom and jump away from the selected area.
- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041)
- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**.
`,
"2.6.7":`
Hoping to finally move on to 2.7.0... but still have one last bug to fix in 2.6.x!

View File

@@ -2,7 +2,7 @@ import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsi
import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
import { getEA } from "src";
@@ -146,7 +146,9 @@ export class UniversalInsertFileModal extends Modal {
const suggester = new FileSuggestionModal(
this.app,
search,
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file));
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file),
this.plugin
);
search.onChange(() => {
file = suggester.getSelectedItem();
updateForm();

View File

@@ -1,3 +1,4 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
@@ -10,6 +11,8 @@ declare const PLUGIN_VERSION:string;
// English
export default {
// Sugester
SELECT_FILE_TO_INSERT: "Select a file to insert",
// main.ts
CONVERT_URL_TO_FILE: "Save image from URL to local file",
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",

View File

@@ -1,3 +1,4 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
@@ -10,6 +11,8 @@ declare const PLUGIN_VERSION:string;
// 简体中文
export default {
// Sugester
SELECT_FILE_TO_INSERT: "选择一个要插入的文件",
// main.ts
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",

View File

@@ -19,7 +19,6 @@ import {
Workspace,
Editor,
MarkdownFileInfo,
loadMermaid,
} from "obsidian";
import {
BLANK_DRAWING,
@@ -113,7 +112,7 @@ import {
legacyExcalidrawPopoverObserver,
} from "./MarkdownPostProcessor";
import { FieldSuggester } from "./dialogs/FieldSuggester";
import { FieldSuggester } from "./Components/Suggesters/FieldSuggester";
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
import { Packages } from "./types/types";
import { PreviewImageType } from "./utils/UtilTypes";
@@ -460,14 +459,6 @@ export default class ExcalidrawPlugin extends Plugin {
}
this.logStartupEvent("Excalidraw config initialized");
try {
await loadMermaid();
} catch (e) {
new Notice("Error loading Mermaid", 6000);
console.error("Error loading Mermaid", e);
}
this.logStartupEvent("Mermaid loaded");
try {
this.addThemeObserver();
} catch (e) {

View File

@@ -1,3 +1,4 @@
import { TFile } from "obsidian";
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
import { ExcalidrawLib } from "../ExcalidrawLib";
@@ -33,6 +34,12 @@ export type DeviceType = {
export type Point = [number, number];
export type LinkSuggestion = {
file: TFile;
path: string;
alias?: string;
}
declare global {
interface Window {
ExcalidrawAutomate: ExcalidrawAutomate;