/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); var __export = (target, all) => { __markAsModule(target); for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __reExport = (target, module2, desc) => { if (module2 && typeof module2 === "object" || typeof module2 === "function") { for (let key of __getOwnPropNames(module2)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); } return target; }; var __toModule = (module2) => { return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // main.ts __export(exports, { default: () => ThumbyPlugin }); var import_obsidian2 = __toModule(require("obsidian")); // settings.ts var import_obsidian = __toModule(require("obsidian")); var ThumbySettingTab = class extends import_obsidian.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "Thumbnails Settings" }); console.log(this.plugin.settings); new import_obsidian.Setting(containerEl).setName("Save Thumbnail Info").setDesc("Save thumbnail information inside your note, so they work offline").addToggle((toggle) => toggle.setValue(this.plugin.settings.storeInfo).onChange((value) => __async(this, null, function* () { this.plugin.settings.storeInfo = value; yield this.plugin.saveSettings(); this.display(); }))); if (this.plugin.settings.storeInfo) { new import_obsidian.Setting(containerEl).setName("Save Images").setDesc("Save thumbnail images locally in vault").addToggle((toggle) => toggle.setValue(this.plugin.settings.saveImages).onChange((value) => __async(this, null, function* () { this.plugin.settings.saveImages = value; yield this.plugin.saveSettings(); this.display(); }))); if (this.plugin.settings.saveImages) { new import_obsidian.Setting(containerEl).setName("Image Location").setDesc("Where thumbnail images should be saved").addDropdown((dropdown) => dropdown.addOption("defaultAttachment", "Default attachment location").addOption("specifiedFolder", "In the folder specified below").setValue(this.plugin.settings.imageLocation).onChange((value) => __async(this, null, function* () { this.plugin.settings.imageLocation = value; this.display(); yield this.plugin.saveSettings(); }))); if (this.plugin.settings.imageLocation === "defaultAttachment") { const attachmentLocation = this.app.vault.getConfig("attachmentFolderPath"); new import_obsidian.Setting(containerEl).setName("Default attachment location").setDesc("Options > Files & Links > Default location for new attachments").addText((text) => text.setValue(attachmentLocation).setDisabled(true)).setClass("default-attachment-info"); } else if (this.plugin.settings.imageLocation === "specifiedFolder") { new import_obsidian.Setting(containerEl).setName("Image Folder").setDesc("The folder where thumbnail images should be saved").addText((text) => text.setPlaceholder("ex: Files/Thumbnails").setValue(this.plugin.settings.imageFolder).onChange((value) => __async(this, null, function* () { this.plugin.settings.imageFolder = value; yield this.plugin.saveSettings(); }))); } } } new import_obsidian.Setting(containerEl).setName("Responsive Card-Style Thumbnails").setDesc("Switch to card-style thumbnails for narrow screens").addToggle((toggle) => toggle.setValue(this.plugin.settings.responsiveCardStyle).onChange((value) => __async(this, null, function* () { this.plugin.settings.responsiveCardStyle = value; yield this.plugin.saveSettings(); this.display(); }))); new import_obsidian.Setting(containerEl).setName("YouTube API Key (optional)").setDesc("An API Key for the YouTube Data API").addExtraButton((btn) => btn.setIcon("info").setTooltip("A few videos have been discovered that can't be found the normal way. If you provide an API key for the YouTube Data API, this plugin will use the API as a backup.", { placement: "top" }).setDisabled(true)).addText((text) => text.setValue(this.plugin.settings.youtubeApiKey).onChange((value) => __async(this, null, function* () { this.plugin.settings.youtubeApiKey = value; yield this.plugin.saveSettings(); }))); } }; // main.ts var DEFAULT_SETTINGS = { storeInfo: false, saveImages: false, imageLocation: "defaultAttachment", imageFolder: "", responsiveCardStyle: true, youtubeApiKey: "" }; var URL_TYPES = { youtube: [ { match: "https://www.youtube.com/watch?v=", idPattern: /v=([-\w\d]+)/ }, { match: "https://youtu.be/", idPattern: /youtu.be\/([-\w\d]+)/ }, { match: "youtube.com/shorts/", idPattern: /shorts\/([-\w\d]+)/ }, { match: "youtube.com/live/", idPattern: /live\/(\w+)/ } ], vimeo: [ { match: "https://vimeo.com/", idPattern: /vimeo.com\/([\w\d]+)/ } ] }; var ThumbyPlugin = class extends import_obsidian2.Plugin { loadSettings() { return __async(this, null, function* () { this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); }); } saveSettings() { return __async(this, null, function* () { yield this.saveData(this.settings); const editors = document.querySelectorAll(".cm-editor"); for (const key in editors) { if (Object.prototype.hasOwnProperty.call(editors, key)) { const editor = editors[key]; this.responsiveCardCheck(editor); } } }); } responsiveCardCheck(editor) { const vidBlocks = editor.querySelectorAll(".block-language-vid"); for (const key in vidBlocks) { if (Object.prototype.hasOwnProperty.call(vidBlocks, key)) { const block = vidBlocks[key]; if (this.settings.responsiveCardStyle && block && block.offsetWidth < 370) { block.addClass("thumbnail-card-style"); } else { block.removeClass("thumbnail-card-style"); } } } } onload() { return __async(this, null, function* () { yield this.loadSettings(); this.addSettingTab(new ThumbySettingTab(this.app, this)); this.editorObserver = new ResizeObserver((entries) => { for (const editor of entries) { this.responsiveCardCheck(editor.target); } }); const editors = document.querySelectorAll(".cm-editor"); for (const key in editors) { if (Object.prototype.hasOwnProperty.call(editors, key)) { const editor = editors[key]; this.editorObserver.observe(editor); } } this.registerMarkdownCodeBlockProcessor("vid", (source, el, ctx) => __async(this, null, function* () { var _a, _b, _c; this.createDummyBlock(el); const sourceLines = source.trim().split("\n"); const url = sourceLines[0]; let info; if (this.settings.storeInfo) { info = this.parseStoredInfo(source); } if (!this.settings.storeInfo || !info.infoStored) { info = yield this.getVideoInfo(url); } if (info.networkError && !info.infoStored) { this.removeDummyBlock(el); const url2 = source.trim().split("\n")[0]; el.createEl("a", { text: url2, href: url2 }); return; } const sourcePath = typeof ctx == "string" ? ctx : (_c = (_b = ctx == null ? void 0 : ctx.sourcePath) != null ? _b : (_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.path) != null ? _c : ""; if (!info.vidFound) { const component = new import_obsidian2.MarkdownRenderChild(el); this.removeDummyBlock(el); import_obsidian2.MarkdownRenderer.renderMarkdown(`>[!WARNING] Cannot find video >${info.url}`, el, sourcePath, component); return; } if (this.hasManyUrls(sourceLines)) { const component = new import_obsidian2.MarkdownRenderChild(el); this.removeDummyBlock(el); import_obsidian2.MarkdownRenderer.renderMarkdown(`>[!WARNING] Cannot accept multiple URLs yet`, el, sourcePath, component); return; } if (this.settings.storeInfo && !info.infoStored) { this.storeVideoInfo(info, el, ctx); } if (!this.settings.storeInfo && sourceLines.length > 1) { this.removeStoredInfo(info, el, ctx); } this.removeDummyBlock(el); this.createThumbnail(el, info); })); this.addCommand({ id: "insert-thumbnail-from-clipboard", name: "Insert thumbnail from URL in clipboard", editorCallback: (editor, view) => __async(this, null, function* () { const clipText = yield navigator.clipboard.readText(); const id = yield this.getVideoId(clipText); if (id === "") { new import_obsidian2.Notice("No valid video in clipboard", 2e3); return; } editor.replaceSelection(`\`\`\`vid ${clipText} \`\`\``); }) }); this.addCommand({ id: "insert-video-title-link", name: "Insert link with video title from URL in clipboard", editorCallback: (editor, view) => __async(this, null, function* () { const clipText = yield navigator.clipboard.readText(); const id = yield this.getVideoId(clipText); if (id === "") { new import_obsidian2.Notice("No valid video in clipboard", 2e3); return; } const info = yield this.getVideoInfo(clipText); editor.replaceSelection(`[${info.title}](${info.url})`); }) }); }); } onunload() { this.editorObserver.disconnect(); } hasManyUrls(lines) { return lines.length > 1 && lines.every((e) => /^((https*:\/\/)|(www\.))+\S*$/.test(e.trim())); } createThumbnail(el, info) { let thumbnailUrl = info.thumbnail; if (this.pathIsLocal(thumbnailUrl)) { const file = this.app.vault.getAbstractFileByPath(thumbnailUrl); if (file) { thumbnailUrl = this.app.vault.getResourcePath(file); } } const container = el.createEl("a", { href: info.url }); container.addClass("thumbnail"); container.createEl("img", { attr: { "src": thumbnailUrl } }).addClass("thumbnail-img"); const textBox = container.createDiv(); textBox.addClass("thumbnail-text"); textBox.createDiv({ text: info.title, title: info.title }).addClass("thumbnail-title"); textBox.createEl("a", { text: info.author, href: info.authorUrl, title: info.author }).addClass("thumbnail-author"); const timestamp = this.getTimestamp(info.url); if (timestamp !== "") { container.createDiv({ text: timestamp }).addClass("timestamp"); } } createDummyBlock(el) { const container = el.createDiv(); container.addClass("dummy-container"); } removeDummyBlock(el) { const dummy = el.querySelector(".dummy-container"); if (dummy) { el.removeChild(dummy); } } getTimestamp(url) { let tIndex = url.indexOf("?t="); if (tIndex === -1) { tIndex = url.indexOf("&t="); } if (tIndex === -1) { tIndex = url.indexOf("#t="); } if (tIndex === -1) { return ""; } const search = /[?&#]t=(?:(\d+)h)*(?:(\d+)m)*(?:(\d+)s)*(\d+)*/.exec(url); search.shift(); const times = search.map((v) => parseInt(v) || 0); let seconds = times.pop(); if (times[2] > 59) { seconds = times[2]; } if (seconds) { times[2] = seconds % 60; times[1] = Math.floor(seconds / 60) % 60; times[0] = Math.floor(seconds / 3600); } const secStr = String(times[2]).padStart(2, "0"); let minStr = String(times[1]); const hrStr = String(times[0]); let timeStr = `${minStr}:${secStr}`; if (times[0]) { minStr = minStr.padStart(2, "0"); timeStr = `${hrStr}:${minStr}:${secStr}`; } return timeStr; } pathIsLocal(path) { return path.indexOf("https://") !== 0; } parseStoredInfo(source) { const info = { url: "", thumbnail: "", title: "", author: "", authorUrl: "", vidFound: false, networkError: false, infoStored: false, imageSaved: false }; const input = source.trim().split("\n"); if (input.length !== 5) { return info; } const parsedInput = { Url: "", Title: "", Author: "", Thumbnail: "", AuthorUrl: "" }; for (const [i, line] of input.entries()) { if (i !== 0) { const matches = line.match(/(\w+): (.+)/); if (matches === null) { return info; } const key = matches[1]; const val = matches[2]; parsedInput[key] = val; } else { parsedInput["Url"] = input[0]; } } for (const key in parsedInput) { if (Object.prototype.hasOwnProperty.call(parsedInput, key)) { const value = parsedInput[key]; if (!value || value === "") { return info; } } } info.url = parsedInput["Url"]; info.title = parsedInput["Title"]; info.author = parsedInput["Author"]; info.thumbnail = parsedInput["Thumbnail"]; info.authorUrl = parsedInput["AuthorUrl"]; info.vidFound = true; if (this.pathIsLocal(info.thumbnail)) { const existingFile = this.app.vault.getAbstractFileByPath(info.thumbnail); if (existingFile) { info.imageSaved = true; } else if (this.settings.saveImages) { return info; } if (!this.settings.saveImages) { return info; } } else if (this.settings.saveImages) { return info; } info.infoStored = true; return info; } storeVideoInfo(info, el, ctx) { return __async(this, null, function* () { const section = ctx.getSectionInfo(el); if (!section) { return; } if (this.settings.saveImages && !info.imageSaved) { info.thumbnail = yield this.saveImage(info); } const content = `\`\`\`vid ${info.url} Title: ${info.title} Author: ${info.author} Thumbnail: ${info.thumbnail} AuthorUrl: ${info.authorUrl} \`\`\``; const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView); if (view) { const startPos = { line: section.lineStart, ch: 0 }; const endPos = { line: section.lineEnd, ch: view.editor.getLine(section.lineEnd).length }; view.editor.replaceRange(content, startPos, endPos); } }); } saveImage(info) { return __async(this, null, function* () { const id = yield this.getVideoId(info.url); let filePath = ""; const currentNote = this.app.workspace.getActiveFile(); if (this.settings.imageLocation === "specifiedFolder") { filePath = `${this.settings.imageFolder}/${id}.jpg`; } else { filePath = yield this.app.vault.getAvailablePathForAttachments(id, "jpg", currentNote); const pathRegex = /(.*) \d+\.jpg/; filePath = filePath.replace(pathRegex, "$1.jpg"); } const existingFile = this.app.vault.getAbstractFileByPath(filePath); if (existingFile) { return existingFile.path; } const folderMatch = filePath.match(/(.+)\/.+\.jpg/); if (folderMatch) { const folderPath = folderMatch[1]; const existingFolder = this.app.vault.getAbstractFileByPath(folderPath); if (this.settings.imageLocation === "specifiedFolder" && !existingFolder) { new import_obsidian2.Notice(`Thumbnails: The folder you specified (${this.settings.imageFolder}) does not exist.`); return info.thumbnail; } } const reqParam = { url: info.thumbnail }; let file; try { const req = yield (0, import_obsidian2.requestUrl)(reqParam); if (req.status === 200) { file = yield this.app.vault.createBinary(filePath, req.arrayBuffer); } else { } } catch (error) { console.log(error); return info.thumbnail; } if (file) { const localUrl = file.path; return localUrl; } return info.thumbnail; }); } getTrimmedResourcePath(file) { const path = this.app.vault.getResourcePath(file); const endPos = path.indexOf(".jpg") + 4; return path.substring(0, endPos); } removeStoredInfo(info, el, ctx) { const section = ctx.getSectionInfo(el); if (!section) { return; } const content = `\`\`\`vid ${info.url} \`\`\``; const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView); if (view) { const startPos = { line: section.lineStart, ch: 0 }; const endPos = { line: section.lineEnd, ch: view.editor.getLine(section.lineEnd).length }; view.editor.replaceRange(content, startPos, endPos); } } getVideoInfo(url) { return __async(this, null, function* () { const info = { url, thumbnail: "", title: "", author: "", authorUrl: "", vidFound: false, networkError: false, infoStored: false, imageSaved: false }; let reqUrl = ""; let isYoutube = false; for (const type of URL_TYPES.youtube) { if (url.includes(type.match)) { isYoutube = true; } } let isVimeo = false; for (const type of URL_TYPES.vimeo) { if (url.includes(type.match)) { isVimeo = true; } } if (isYoutube) { reqUrl = `https://www.youtube.com/oembed?format=json&url=${url}`; } else if (isVimeo) { reqUrl = `https://vimeo.com/api/oembed.json?url=${url}`; } else { return info; } try { const reqParam = { url: reqUrl, throw: false }; const res = yield (0, import_obsidian2.requestUrl)(reqParam); if (res.status === 200) { info.title = res.json.title; info.author = res.json.author_name; info.authorUrl = res.json.author_url; info.vidFound = true; } else if (this.settings.youtubeApiKey && isYoutube) { console.log("Thumbnails: Oembed failed, using YouTube API"); const videoId = yield this.getVideoId(url); const youtubeUrl = `https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${this.settings.youtubeApiKey}`; const youtubeReqParam = { url: youtubeUrl, throw: false }; const youtubeApiRes = yield (0, import_obsidian2.requestUrl)(youtubeReqParam); if (youtubeApiRes.status === 200) { const vidSnippet = youtubeApiRes.json.items[0].snippet; info.authorUrl = "javascript:void(0)"; const channelQueryUrl = `https://youtube.googleapis.com/youtube/v3/channels?part=snippet&id=${vidSnippet.channelId}&key=${this.settings.youtubeApiKey}`; const channelQueryParam = { url: channelQueryUrl, throw: false }; const channelQueryRes = yield (0, import_obsidian2.requestUrl)(channelQueryParam); if (channelQueryRes.status === 200) { const channelSnippet = channelQueryRes.json.items[0].snippet; const channelCustomUrl = channelSnippet.customUrl; const channelUrl = `https://www.youtube.com/${channelCustomUrl}`; info.authorUrl = channelUrl; } info.title = vidSnippet.title; info.author = vidSnippet.channelTitle; info.vidFound = true; } } if (info.vidFound) { if (isYoutube) { const videoId = yield this.getVideoId(url); info.thumbnail = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`; } else { info.thumbnail = res.json.thumbnail_url; } } } catch (error) { console.error(error); info.networkError = true; } return info; }); } getVideoId(url) { return __async(this, null, function* () { let id = ""; for (const type of URL_TYPES.youtube) { if (url.includes(type.match)) { const matches = url.match(type.idPattern); if (matches !== null) { id = matches[1]; } } } const vimeoType = URL_TYPES.vimeo[0]; if (url.includes(vimeoType.match)) { const matches = url.match(vimeoType.idPattern); if (matches !== null) { id = matches[1]; if (!/^[0-9]+$/.exec(id)) { id = yield this.fetchVimeoVideoId(url); } } } return id; }); } fetchVimeoVideoId(url) { return __async(this, null, function* () { let id = ""; try { const reqParam = { url: `https://vimeo.com/api/oembed.json?url=${url}` }; const res = yield (0, import_obsidian2.requestUrl)(reqParam); if (res.status === 200 && res.json.video_id) { id = res.json.video_id.toString(); } } catch (error) { console.error(error); } return id; }); } };