You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
27 KiB

3 years ago
/*
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugins github repository
*/
'use strict';
var obsidian = require('obsidian');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
const DEFAULT_SETTINGS = {
regex: /^(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,})$/i,
lineRegex: /(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,})/ig,
linkRegex: /^\[([^\[\]]*)\]\((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,})\)$/i,
linkLineRegex: /\[([^\[\]]*)\]\((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,})\)/ig,
imageRegex: /\.(gif|jpe?g|tiff?|png|webp|bmp|tga|psd|ai)/i
};
class EditorExtensions {
static getSelectedText(editor) {
if (!editor.somethingSelected()) {
let wordBoundaries = this.getWordBoundaries(editor);
editor.setSelection(wordBoundaries.start, wordBoundaries.end);
}
return editor.getSelection();
}
static cursorWithinBoundaries(cursor, match) {
let startIndex = match.index;
let endIndex = match.index + match[0].length;
return startIndex <= cursor.ch && cursor.ch <= endIndex;
}
static getWordBoundaries(editor) {
let cursor = editor.getCursor();
// If its a normal URL token this is not a markdown link
// In this case we can simply overwrite the link boundaries as-is
let lineText = editor.getLine(cursor.line);
// First check if we're in a link
let linksInLine = lineText.matchAll(DEFAULT_SETTINGS.linkLineRegex);
for (let match of linksInLine) {
if (this.cursorWithinBoundaries(cursor, match)) {
return {
start: { line: cursor.line, ch: match.index },
end: { line: cursor.line, ch: match.index + match[0].length },
};
}
}
// If not, check if we're in just a standard ol' URL.
let urlsInLine = lineText.matchAll(DEFAULT_SETTINGS.lineRegex);
for (let match of urlsInLine) {
if (this.cursorWithinBoundaries(cursor, match)) {
return {
start: { line: cursor.line, ch: match.index },
end: { line: cursor.line, ch: match.index + match[0].length },
};
}
}
return {
start: cursor,
end: cursor,
};
}
static getEditorPositionFromIndex(content, index) {
let substr = content.substr(0, index);
let l = 0;
let offset = -1;
let r = -1;
for (; (r = substr.indexOf("\n", r + 1)) !== -1; l++, offset = r)
;
offset += 1;
let ch = content.substr(offset, index - offset).length;
return { line: l, ch: ch };
}
}
class CheckIf {
static isMarkdownLinkAlready(editor) {
let cursor = editor.getCursor();
// Check if the characters before the url are ]( to indicate a markdown link
var titleEnd = editor.getRange({ ch: cursor.ch - 2, line: cursor.line }, { ch: cursor.ch, line: cursor.line });
return titleEnd == "](";
}
static isAfterQuote(editor) {
let cursor = editor.getCursor();
// Check if the characters before the url are " or ' to indicate we want the url directly
// This is common in elements like <a href="linkhere"></a>
var beforeChar = editor.getRange({ ch: cursor.ch - 1, line: cursor.line }, { ch: cursor.ch, line: cursor.line });
return beforeChar == "\"" || beforeChar == "'";
}
static isUrl(text) {
let urlRegex = new RegExp(DEFAULT_SETTINGS.regex);
return urlRegex.test(text);
}
static isImage(text) {
let imageRegex = new RegExp(DEFAULT_SETTINGS.imageRegex);
return imageRegex.test(text);
}
static isLinkedUrl(text) {
let urlRegex = new RegExp(DEFAULT_SETTINGS.linkRegex);
return urlRegex.test(text);
}
}
const electronPkg = require("electron");
function getPageTitle(url) {
// If we're on Desktop use the Electron scraper
if (electronPkg != null) {
const { remote, ipcRenderer } = electronPkg;
const { BrowserWindow, ipcMain } = remote;
return new Promise((resolve) => {
try {
const window = new BrowserWindow({
width: 1000,
height: 600,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
images: false,
},
show: false,
});
// After page finishes loading send the title via a pageloaded event
window.webContents.on("did-finish-load", () => __awaiter(this, void 0, void 0, function* () {
try {
ipcRenderer.send('pageloaded', window.webContents.getTitle());
}
catch (ex) {
resolve("Title Unknown");
}
}));
window.loadURL(url);
// Clean up the title and remove the BrowserWindow
ipcMain.on("pageloaded", (_event, title) => {
window.destroy();
if (title != null && title != '') {
resolve(title);
}
else {
resolve("Title Unknown");
}
});
}
catch (ex) {
resolve("Site Unreachable");
}
});
// Otherwise if we're on mobile use a CORS proxy
}
else {
return new Promise((resolve) => {
// console.log(`Fetching ${text} for title`);
//Because of CORS you can't fetch the site directly
var corsed = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`;
fetch(corsed)
.then((response) => {
return response.text();
})
.then((html) => {
const doc = new DOMParser().parseFromString(html, "text/html");
const title = doc.querySelectorAll("title")[0];
if (title == null || title.innerText.length == 0) {
// If site is javascript based and has a no-title attribute when unloaded, use it.
var noTitle = title.getAttr("no-title");
if (noTitle != null)
return noTitle;
// Otherwise if the site has no title/requires javascript simply return Title Unknown
return resolve("Title Unknown");
}
return resolve(title.innerText);
})
.catch((error) => {
console.error(error);
return resolve("Site Unreachable");
});
});
}
}
class AutoLinkTitle extends obsidian.Plugin {
onload() {
return __awaiter(this, void 0, void 0, function* () {
console.log("loading obsidian-auto-link-title");
yield this.loadSettings();
// Listen to paste event
this.pasteFunction = this.pasteUrlWithTitle.bind(this);
this.app.workspace.containerEl.addEventListener("paste", this.pasteFunction, true);
this.addCommand({
id: "enhance-url-with-title",
name: "Enhance existing URL with link and title",
callback: () => this.addTitleToLink(),
hotkeys: [
{
modifiers: ["Mod", "Shift"],
key: "e",
},
],
});
});
}
addTitleToLink() {
// Only attempt fetch if online
if (!navigator.onLine)
return;
let editor = this.getEditor();
if (editor == null)
return;
let selectedText = (EditorExtensions.getSelectedText(editor) || "").trim();
// If the cursor is on a raw html link, convert to a markdown link and fetch title
if (CheckIf.isUrl(selectedText)) {
this.convertUrlToTitledLink(editor, selectedText);
}
// If the cursor is on the URL part of a markdown link, fetch title and replace existing link title
else if (CheckIf.isLinkedUrl(selectedText)) {
var link = this.getUrlFromLink(selectedText);
this.convertUrlToTitledLink(editor, link);
}
}
pasteUrlWithTitle(clipboard) {
// Only attempt fetch if online
if (!navigator.onLine)
return;
let editor = this.getEditor();
let clipboardText = clipboard.clipboardData.getData("text/plain");
if (clipboardText == null || clipboardText == "")
return;
// If its not a URL, we return false to allow the default paste handler to take care of it.
// Similarly, image urls don't have a meaningful <title> attribute so downloading it
// to fetch the title is a waste of bandwidth.
if (!CheckIf.isUrl(clipboardText) || CheckIf.isImage(clipboardText)) {
return;
}
// We've decided to handle the paste, stop propagation to the default handler.
clipboard.stopPropagation();
clipboard.preventDefault();
// If it looks like we're pasting the url into a markdown link already, don't fetch title
// as the user has already probably put a meaningful title, also it would lead to the title
// being inside the link.
if (CheckIf.isMarkdownLinkAlready(editor) || CheckIf.isAfterQuote(editor)) {
editor.replaceSelection(clipboardText);
return;
}
// At this point we're just pasting a link in a normal fashion, fetch its title.
this.convertUrlToTitledLink(editor, clipboardText);
return;
}
convertUrlToTitledLink(editor, text) {
// Generate a unique id for find/replace operations for the title.
let pasteId = `Fetching Title#${this.createBlockHash()}`;
// Instantly paste so you don't wonder if paste is broken
editor.replaceSelection(`[${pasteId}](${text})`);
// Fetch title from site, replace Fetching Title with actual title
this.fetchUrlTitle(text).then((title) => {
let text = editor.getValue();
let start = text.indexOf(pasteId);
let end = start + pasteId.length;
let startPos = EditorExtensions.getEditorPositionFromIndex(text, start);
let endPos = EditorExtensions.getEditorPositionFromIndex(text, end);
editor.replaceRange(title, startPos, endPos);
});
}
fetchUrlTitle(text) {
return getPageTitle(text).then(title => {
if (title == null || title == "") {
return "Title Unknown";
}
return title.trim();
}).catch((error) => {
// console.error(error)
return "Site Unreachable";
});
}
getEditor() {
let activeLeaf = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
if (activeLeaf == null)
return;
return activeLeaf.editor;
}
getUrlFromLink(text) {
let urlRegex = new RegExp(DEFAULT_SETTINGS.linkRegex);
return urlRegex.exec(text)[2];
}
// Custom hashid by @shabegom
createBlockHash() {
let result = "";
var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < 4; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
onunload() {
console.log("unloading obsidian-auto-link-title");
this.app.workspace.containerEl.removeEventListener("paste", this.pasteFunction, true);
}
loadSettings() {
return __awaiter(this, void 0, void 0, function* () {
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
});
}
saveSettings() {
return __awaiter(this, void 0, void 0, function* () {
yield this.saveData(this.settings);
});
}
}
module.exports = AutoLinkTitle;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNldHRpbmdzLnRzIiwiZWRpdG9yLWVuaGFuY2VtZW50cy50cyIsImNoZWNraWYudHMiLCJzY3JhcGVyLnRzIiwibWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6bnVsbCwibmFtZXMiOlsiUGx1Z2luIiwiTWFya2Rvd25WaWV3Il0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBdURBO0FBQ08sU0FBUyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFO0FBQzdELElBQUksU0FBUyxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsT0FBTyxLQUFLLFlBQVksQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxVQUFVLE9BQU8sRUFBRSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFO0FBQ2hILElBQUksT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsVUFBVSxPQUFPLEVBQUUsTUFBTSxFQUFFO0FBQy9ELFFBQVEsU0FBUyxTQUFTLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtBQUNuRyxRQUFRLFNBQVMsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVB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