Completed asking user if they want to add embeds after pasting url (Code cleanup needed)

- User pastes url
- Plugin suggests to embed (has an option to cancel or tap anywhere else to cancel)
- After accepting to embed, mark the link for embedding [ae:embed](link)
This commit is contained in:
GnoxNahte 2024-03-09 10:18:47 +08:00
parent dc82436d49
commit db45c00a7e
3 changed files with 195 additions and 5 deletions

121
main.ts
View file

@ -1,15 +1,36 @@
import { CodepenEmbed } from 'embeds/codepen';
import { TwitterEmbed } from 'embeds/twitter';
import { EmbedBase } from 'embeds/embedBase';
import { Plugin } from 'obsidian';
import { Editor, Plugin } from 'obsidian';
import { YouTubeEmbed } from 'embeds/youtube';
import { SteamEmbed } from 'embeds/steam';
import { RedditEmbed } from 'embeds/reddit';
import { AutoEmbedSettingTab, DEFAULT_SETTINGS, PluginSettings } from 'settings-tab';
import { ImgurEmbed } from 'embeds/imgur';
import { SpotifyEmbed } from 'embeds/spotify';
import SuggestEmbed from 'suggestEmbed';
import { isURL, regexUrl } from 'utility';
// Remember to rename these classes and interfaces!
class PasteInfo {
trigger: boolean;
text: string;
constructor(trigger: boolean, text: string) {
this.trigger = trigger;
this.text = text;
}
}
interface EditPosition {
line: number;
ch: number;
}
interface Selection {
start: EditPosition
end: EditPosition
text: string;
}
export default class AutoEmbedPlugin extends Plugin {
settings: PluginSettings;
@ -21,13 +42,29 @@ export default class AutoEmbedPlugin extends Plugin {
new CodepenEmbed(this),
new SpotifyEmbed(this),
new ImgurEmbed(this),
]
];
isShiftDown = false;
pasteInfo: PasteInfo = new PasteInfo(false, "");
async onload() {
console.log('loading plugin');
await this.loadSettings();
this.addSettingTab(new AutoEmbedSettingTab(this.app, this));
this.registerDomEvent(document, "keydown", (e) => {
if (e.shiftKey)
this.isShiftDown = true;
})
this.registerDomEvent(document, "keydown", (e) => {
if (!e.shiftKey)
this.isShiftDown = false;
})
this.registerEditorSuggest(new SuggestEmbed(this));
this.app.workspace.on("editor-paste", this.onPaste.bind(this));
this.registerMarkdownPostProcessor((el, ctx) => {
console.log("Registering markdown")
@ -63,12 +100,37 @@ export default class AutoEmbedPlugin extends Plugin {
await this.saveData(this.settings);
}
private onPaste(e: ClipboardEvent, editor: Editor) {
if (e.defaultPrevented)
return;
// Usually "Ctrl-Shift-V" means paste text without formatting. So don't embed
if (this.isShiftDown)
return;
// Check if valid url
const clipboardData = e.clipboardData?.getData("text/plain");
if (!clipboardData || clipboardData === "" || !isURL(clipboardData))
return;
this.pasteInfo.trigger = true;
this.pasteInfo.text = clipboardData;
// TODO: Delete?
// Do this to be able to select the url.
// Obsidian pastes after this function, so
// e.preventDefault();
// editor.replaceRange(clipboardData, editor.getCursor());
// this.selectLink(editor);
}
private handleAnchor(a: HTMLAnchorElement) {
const innerText = a.innerText;
// Removes all spaces
const innerTextTrim = innerText.replace(/\s/g, "");
if (innerTextTrim.includes("|noembed")) {
if (!innerTextTrim.includes("ae:embed")) {
return;
}
@ -87,9 +149,57 @@ export default class AutoEmbedPlugin extends Plugin {
this.insertEmbed(a, embed);
}
private getSelection(editor: Editor) : Selection | null {
if (!editor.somethingSelected())
return null;
return {
start: editor.getCursor("from"),
end: editor.getCursor("to"),
text: editor.getSelection()
}
}
// Used if there's nothing selected. Mostly when the user pastes a link
selectLink(editor: Editor) {
const cursor = editor.getCursor();
const lineText = editor.getLine(cursor.line);
console.log("line text: "+ lineText)
const matchedLinks = lineText.matchAll(regexUrl);
for (const match of matchedLinks) {
console.log("match: " + match[0])
// Check if the cursor is within the match
console.log("start: " + match.index + "|end: " + (match.index??0 + match[0].length) + "|cursor: "+ cursor.ch);
if (match.index &&
match.index <= cursor.ch && // Is start of match before cursor
match.index + match[0].length >= cursor.ch // Is end of match after cursor
) {
console.log("Selected: " + match[0]);
editor.setSelection(
{
line: cursor.line,
ch: match.index
},
{
line: cursor.line,
ch: match.index + match[0].length
}
);
break;
}
}
}
markToEmbed(selection: Selection, editor: Editor) {
// const selection = this.getSelection(editor);
editor.replaceRange(`[ae:embed](${selection.text})`, selection.start, selection.end);
console.log(`Replacing: [ae:embed](${selection}), Start:${selection.start.ch}, End: ${selection.end.ch}`)
}
private createEmbed(embedSource: EmbedBase, link: string) {
const embed = embedSource.createEmbed(link);
return embed;
return embed;
}
private insertEmbed(a: HTMLAnchorElement, container: HTMLElement) {
@ -97,3 +207,4 @@ export default class AutoEmbedPlugin extends Plugin {
parent?.replaceChild(container, a);
}
}

69
suggestEmbed.ts Normal file
View file

@ -0,0 +1,69 @@
import AutoEmbedPlugin from "main";
import { Editor, EditorPosition, EditorSuggest, EditorSuggestContext, EditorSuggestTriggerInfo, TFile } from "obsidian";
class Suggestion {
choice: string;
constructor(choice: string) {
this.choice = choice;
}
}
export default class SuggestEmbed extends EditorSuggest<Suggestion> {
private plugin: AutoEmbedPlugin;
private editor: Editor;
constructor(plugin: AutoEmbedPlugin) {
super(plugin.app);
this.plugin = plugin;
}
onTrigger(cursor: EditorPosition, editor: Editor, file: TFile | null): EditorSuggestTriggerInfo | null {
console.log("on trigger");
this.editor = editor;
if (!this.plugin.pasteInfo.trigger)
return null;
this.plugin.pasteInfo.trigger = false;
console.log("Cursor-ch: " + cursor.ch);
return {
start: cursor,
end: cursor,
query: this.plugin.pasteInfo.text,
}
}
getSuggestions(context: EditorSuggestContext): Suggestion[] | Promise<Suggestion[]> {
console.log("get suggestion");
return [new Suggestion("Embed Link"), new Suggestion("Cancel")];
}
renderSuggestion(suggestion: Suggestion, el: HTMLElement): void {
console.log("render suggestion");
el.setText(suggestion.choice);
}
selectSuggestion(suggestion: Suggestion, e: KeyboardEvent | MouseEvent): void {
console.log("select suggestion");
const cursor = this.editor.getCursor();
if (suggestion.choice === "Embed Link") {
console.log("Selected create embed");
const text = this.plugin.pasteInfo.text;
this.plugin.markToEmbed(
{
text: text,
start: {
line: cursor.line,
ch: cursor.ch - text.length
},
end: cursor
}
,this.editor);
}
else
console.log("select suggestion: " + suggestion.choice);
this.close();
}
}

10
utility.ts Normal file
View file

@ -0,0 +1,10 @@
export const regexUrl = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi
export function isURL(str: string) : boolean {
try {
new URL(str);
return true;
} catch {
return false;
}
}