Affected files: .obsidian/community-plugins.json .obsidian/plugins/darlal-switcher-plus/manifest.json .obsidian/plugins/mrj-jump-to-link/main.js .obsidian/plugins/mrj-jump-to-link/manifest.json .obsidian/plugins/obsidian-git/main.js .obsidian/plugins/obsidian-git/manifest.json .obsidian/plugins/obsidian-git/styles.css .obsidian/plugins/url-into-selection/main.js .obsidian/plugins/url-into-selection/manifest.json flashcards anglais.md
930 lines
38 KiB
JavaScript
930 lines
38 KiB
JavaScript
'use strict';
|
|
|
|
var obsidian = require('obsidian');
|
|
var state = require('@codemirror/state');
|
|
var view = require('@codemirror/view');
|
|
|
|
/******************************************************************************
|
|
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());
|
|
});
|
|
}
|
|
|
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
var e = new Error(message);
|
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
};
|
|
|
|
class Settings {
|
|
constructor() {
|
|
// Defaults as in Vimium extension for browsers
|
|
this.letters = 'sadfjklewcmpgh';
|
|
this.jumpToAnywhereRegex = '\\b\\w{3,}\\b';
|
|
this.lightspeedCaseSensitive = false;
|
|
this.jumpToLinkIfOneLinkOnly = true;
|
|
this.lightspeedJumpToStartOfWord = true;
|
|
this.lightspeedCharacterCount = 2;
|
|
}
|
|
}
|
|
|
|
class MarkWidget extends view.WidgetType {
|
|
constructor(mark, type, matchedEventKey) {
|
|
super();
|
|
this.mark = mark;
|
|
this.type = type;
|
|
this.matchedEventKey = matchedEventKey;
|
|
}
|
|
eq(other) {
|
|
return other.mark === this.mark && other.matchedEventKey == this.matchedEventKey;
|
|
}
|
|
toDOM() {
|
|
const mark = activeDocument.createElement("span");
|
|
mark.innerText = this.mark;
|
|
const wrapper = activeDocument.createElement("div");
|
|
wrapper.style.display = "inline-block";
|
|
wrapper.style.position = "absolute";
|
|
wrapper.classList.add('jl');
|
|
wrapper.classList.add('jl-' + this.type);
|
|
wrapper.classList.add('popover');
|
|
if (this.matchedEventKey && this.mark.toUpperCase().startsWith(this.matchedEventKey.toUpperCase())) {
|
|
wrapper.classList.add('matched');
|
|
}
|
|
wrapper.append(mark);
|
|
return wrapper;
|
|
}
|
|
ignoreEvent() {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class MarkPlugin {
|
|
constructor(_view) {
|
|
this.links = [];
|
|
this.matchedEventKey = undefined;
|
|
this.links = [];
|
|
this.matchedEventKey = undefined;
|
|
this.decorations = view.Decoration.none;
|
|
}
|
|
setLinks(links) {
|
|
this.links = links;
|
|
this.matchedEventKey = undefined;
|
|
}
|
|
clean() {
|
|
this.links = [];
|
|
this.matchedEventKey = undefined;
|
|
}
|
|
filterWithEventKey(eventKey) {
|
|
if (eventKey.length != 1)
|
|
return;
|
|
this.links = this.links.filter(v => {
|
|
return v.letter.length == 2 && v.letter[0].toUpperCase() == eventKey.toUpperCase();
|
|
});
|
|
this.matchedEventKey = eventKey;
|
|
}
|
|
get visible() {
|
|
return this.links.length > 0;
|
|
}
|
|
update(_update) {
|
|
const widgets = this.links.map((x) => view.Decoration.widget({
|
|
widget: new MarkWidget(x.letter, x.type, this.matchedEventKey),
|
|
side: 1,
|
|
}).range(x.index));
|
|
this.decorations = view.Decoration.set(widgets);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get only visible content
|
|
* @param cmEditor
|
|
* @returns Letter offset and visible content as a string
|
|
*/
|
|
function getVisibleLineText(cmEditor) {
|
|
const scrollInfo = cmEditor.getScrollInfo();
|
|
const { line: from } = cmEditor.coordsChar({ left: 0, top: 0 }, 'page');
|
|
const { line: to } = cmEditor.coordsChar({ left: scrollInfo.left, top: scrollInfo.top + scrollInfo.height });
|
|
const indOffset = cmEditor.indexFromPos({ ch: 0, line: from });
|
|
const strs = cmEditor.getRange({ ch: 0, line: from }, { ch: 0, line: to + 1 });
|
|
return { indOffset, strs };
|
|
}
|
|
/**
|
|
*
|
|
* @param alphabet - Letters which used to produce hints
|
|
* @param numLinkHints - Count of needed links
|
|
*/
|
|
function getLinkHintLetters(alphabet, numLinkHints) {
|
|
const alphabetUppercase = alphabet.toUpperCase();
|
|
let prefixCount = Math.ceil((numLinkHints - alphabetUppercase.length) / (alphabetUppercase.length - 1));
|
|
// ensure 0 <= prefixCount <= alphabet.length
|
|
prefixCount = Math.max(prefixCount, 0);
|
|
prefixCount = Math.min(prefixCount, alphabetUppercase.length);
|
|
const prefixes = ['', ...Array.from(alphabetUppercase.slice(0, prefixCount))];
|
|
const linkHintLetters = [];
|
|
for (let i = 0; i < prefixes.length; i++) {
|
|
const prefix = prefixes[i];
|
|
for (let j = 0; j < alphabetUppercase.length; j++) {
|
|
if (linkHintLetters.length < numLinkHints) {
|
|
const letter = alphabetUppercase[j];
|
|
if (prefix === '') {
|
|
if (!prefixes.contains(letter)) {
|
|
linkHintLetters.push(letter);
|
|
}
|
|
}
|
|
else {
|
|
linkHintLetters.push(prefix + letter);
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return linkHintLetters;
|
|
}
|
|
function getMDHintLinks(content, offset, letters) {
|
|
var _a;
|
|
// expecting either [[Link]] or [[Link|Title]]
|
|
const regExInternal = /\[\[(.+?)(\|.+?)?]]/g;
|
|
// expecting [Title](../example.md)
|
|
const regExMdInternal = /\[[^\[\]]+?\]\(((\.\.|\w|\d).+?)\)/g;
|
|
// expecting [Title](file://link), [Title](https://link) or any other [Jira-123](jira://bla-bla) link
|
|
const regExExternal = /\[[^\[\]]+?\]\((.+?:\/\/.+?)\)/g;
|
|
// expecting http://hogehoge or https://hogehoge
|
|
const regExUrl = /( |\n|^)(https?:\/\/[^ \n]+)/g;
|
|
let indexes = new Set();
|
|
let linksWithIndex = [];
|
|
let regExResult;
|
|
const addLinkToArray = (link) => {
|
|
if (indexes.has(link.index))
|
|
return;
|
|
indexes.add(link.index);
|
|
linksWithIndex.push(link);
|
|
};
|
|
while (regExResult = regExInternal.exec(content)) {
|
|
const linkText = (_a = regExResult[1]) === null || _a === void 0 ? void 0 : _a.trim();
|
|
addLinkToArray({ index: regExResult.index + offset, type: 'internal', linkText });
|
|
}
|
|
// External Link above internal, to prefer type external over interal in case of a dupe
|
|
while (regExResult = regExExternal.exec(content)) {
|
|
const linkText = regExResult[1];
|
|
addLinkToArray({ index: regExResult.index + offset, type: 'external', linkText });
|
|
}
|
|
while (regExResult = regExMdInternal.exec(content)) {
|
|
const linkText = regExResult[1];
|
|
addLinkToArray({ index: regExResult.index + offset, type: 'internal', linkText });
|
|
}
|
|
while (regExResult = regExUrl.exec(content)) {
|
|
const linkText = regExResult[2];
|
|
addLinkToArray({ index: regExResult.index + offset + 1, type: 'external', linkText });
|
|
}
|
|
const linkHintLetters = getLinkHintLetters(letters, linksWithIndex.length);
|
|
const linksWithLetter = [];
|
|
linksWithIndex
|
|
.sort((x, y) => x.index - y.index)
|
|
.forEach((linkHint, i) => {
|
|
linksWithLetter.push(Object.assign({ letter: linkHintLetters[i] }, linkHint));
|
|
});
|
|
return linksWithLetter.filter(link => link.letter);
|
|
}
|
|
function createWidgetElement(content, type) {
|
|
const linkHintEl = activeDocument.createElement('div');
|
|
linkHintEl.classList.add('jl');
|
|
linkHintEl.classList.add('jl-' + type);
|
|
linkHintEl.classList.add('popover');
|
|
linkHintEl.innerHTML = content;
|
|
return linkHintEl;
|
|
}
|
|
function displaySourcePopovers(cmEditor, linkKeyMap) {
|
|
const drawWidget = (cmEditor, linkHint) => {
|
|
const pos = cmEditor.posFromIndex(linkHint.index);
|
|
// the fourth parameter is undocumented. it specifies where the widget should be place
|
|
return cmEditor.addWidget(pos, createWidgetElement(linkHint.letter, linkHint.type), false, 'over');
|
|
};
|
|
linkKeyMap.forEach(x => drawWidget(cmEditor, x));
|
|
}
|
|
|
|
class CM6LinkProcessor {
|
|
constructor(editor, alphabet) {
|
|
this.getSourceLinkHints = () => {
|
|
const { letters } = this;
|
|
const { index, content } = this.getVisibleLines();
|
|
return getMDHintLinks(content, index, letters);
|
|
};
|
|
this.cmEditor = editor;
|
|
this.letters = alphabet;
|
|
}
|
|
init() {
|
|
return this.getSourceLinkHints();
|
|
}
|
|
getVisibleLines() {
|
|
var _a, _b, _c;
|
|
const { cmEditor } = this;
|
|
let { from, to } = cmEditor.viewport;
|
|
// For CM6 get real visible lines top
|
|
// @ts-ignore
|
|
if ((_b = (_a = cmEditor.viewState) === null || _a === void 0 ? void 0 : _a.pixelViewport) === null || _b === void 0 ? void 0 : _b.top) {
|
|
// @ts-ignore
|
|
const pixelOffsetTop = cmEditor.viewState.pixelViewport.top;
|
|
// @ts-ignore
|
|
const lines = cmEditor.viewState.viewportLines;
|
|
// @ts-ignore
|
|
from = (_c = lines.filter(line => line.top > pixelOffsetTop)[0]) === null || _c === void 0 ? void 0 : _c.from;
|
|
}
|
|
const content = cmEditor.state.sliceDoc(from, to);
|
|
return { index: from, content };
|
|
}
|
|
}
|
|
|
|
function extractRegexpBlocks(content, offset, regexp, letters, caseSensitive) {
|
|
const regExUrl = caseSensitive ? new RegExp(regexp, 'g') : new RegExp(regexp, 'ig');
|
|
let linksWithIndex = [];
|
|
let regExResult;
|
|
while ((regExResult = regExUrl.exec(content))) {
|
|
const linkText = regExResult[1];
|
|
linksWithIndex.push({
|
|
index: regExResult.index + offset,
|
|
type: "regex",
|
|
linkText,
|
|
});
|
|
}
|
|
const linkHintLetters = getLinkHintLetters(letters, linksWithIndex.length);
|
|
const linksWithLetter = [];
|
|
linksWithIndex
|
|
.sort((x, y) => x.index - y.index)
|
|
.forEach((linkHint, i) => {
|
|
linksWithLetter.push(Object.assign({ letter: linkHintLetters[i] }, linkHint));
|
|
});
|
|
return linksWithLetter.filter(link => link.letter);
|
|
}
|
|
|
|
class CM6RegexProcessor extends CM6LinkProcessor {
|
|
constructor(editor, alphabet, regexp, caseSensitive) {
|
|
super(editor, alphabet);
|
|
this.regexp = regexp;
|
|
this.caseSensitive = caseSensitive;
|
|
}
|
|
init() {
|
|
const { letters, regexp } = this;
|
|
const { index, content } = this.getVisibleLines();
|
|
return extractRegexpBlocks(content, index, regexp, letters, this.caseSensitive);
|
|
}
|
|
}
|
|
|
|
class LegacyRegexpProcessor {
|
|
constructor(cmEditor, regexp, alphabet, caseSensitive) {
|
|
this.cmEditor = cmEditor;
|
|
this.regexp = regexp;
|
|
this.letters = alphabet;
|
|
this.caseSensitive = caseSensitive;
|
|
}
|
|
init() {
|
|
const [content, offset] = this.getVisibleContent();
|
|
const links = this.getLinks(content, offset);
|
|
this.display(links);
|
|
return links;
|
|
}
|
|
getVisibleContent() {
|
|
const { cmEditor } = this;
|
|
const { indOffset, strs } = getVisibleLineText(cmEditor);
|
|
return [strs, indOffset];
|
|
}
|
|
getLinks(content, offset) {
|
|
const { regexp, letters } = this;
|
|
return extractRegexpBlocks(content, offset, regexp, letters, this.caseSensitive);
|
|
}
|
|
display(links) {
|
|
const { cmEditor } = this;
|
|
displaySourcePopovers(cmEditor, links);
|
|
}
|
|
}
|
|
|
|
class LegacySourceLinkProcessor {
|
|
constructor(editor, alphabet) {
|
|
this.getSourceLinkHints = (cmEditor) => {
|
|
const { letters } = this;
|
|
const { indOffset, strs } = getVisibleLineText(cmEditor);
|
|
return getMDHintLinks(strs, indOffset, letters);
|
|
};
|
|
this.cmEditor = editor;
|
|
this.letters = alphabet;
|
|
}
|
|
init() {
|
|
const { cmEditor } = this;
|
|
const linkHints = this.getSourceLinkHints(cmEditor);
|
|
displaySourcePopovers(cmEditor, linkHints);
|
|
return linkHints;
|
|
}
|
|
}
|
|
|
|
function getPreviewLinkHints(previewViewEl, letters) {
|
|
const anchorEls = previewViewEl.querySelectorAll('a, .metadata-link-inner');
|
|
const embedEls = previewViewEl.querySelectorAll('.internal-embed');
|
|
const linkHints = [];
|
|
anchorEls.forEach((anchorEl, _i) => {
|
|
var _a;
|
|
if (checkIsPreviewElOnScreen(previewViewEl, anchorEl)) {
|
|
return;
|
|
}
|
|
const linkType = anchorEl.classList.contains('internal-link')
|
|
? 'internal'
|
|
: 'external';
|
|
const linkText = linkType === 'internal'
|
|
? (_a = anchorEl.dataset['href']) !== null && _a !== void 0 ? _a : anchorEl.href
|
|
: anchorEl.href;
|
|
let offsetParent = anchorEl.offsetParent;
|
|
let top = anchorEl.offsetTop;
|
|
let left = anchorEl.offsetLeft;
|
|
while (offsetParent) {
|
|
if (offsetParent == previewViewEl) {
|
|
offsetParent = undefined;
|
|
}
|
|
else {
|
|
top += offsetParent.offsetTop;
|
|
left += offsetParent.offsetLeft;
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
}
|
|
linkHints.push({
|
|
linkElement: anchorEl,
|
|
letter: '',
|
|
linkText: linkText,
|
|
type: linkType,
|
|
top: top,
|
|
left: left,
|
|
});
|
|
});
|
|
embedEls.forEach((embedEl, _i) => {
|
|
const linkText = embedEl.getAttribute('src');
|
|
const linkEl = embedEl.querySelector('.markdown-embed-link');
|
|
if (linkText && linkEl) {
|
|
if (checkIsPreviewElOnScreen(previewViewEl, linkEl)) {
|
|
return;
|
|
}
|
|
let offsetParent = linkEl.offsetParent;
|
|
let top = linkEl.offsetTop;
|
|
let left = linkEl.offsetLeft;
|
|
while (offsetParent) {
|
|
if (offsetParent == previewViewEl) {
|
|
offsetParent = undefined;
|
|
}
|
|
else {
|
|
top += offsetParent.offsetTop;
|
|
left += offsetParent.offsetLeft;
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
}
|
|
linkHints.push({
|
|
linkElement: linkEl,
|
|
letter: '',
|
|
linkText: linkText,
|
|
type: 'internal',
|
|
top: top,
|
|
left: left,
|
|
});
|
|
}
|
|
});
|
|
const sortedLinkHints = linkHints.sort((a, b) => {
|
|
if (a.top > b.top) {
|
|
return 1;
|
|
}
|
|
else if (a.top === b.top) {
|
|
if (a.left > b.left) {
|
|
return 1;
|
|
}
|
|
else if (a.left === b.left) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
});
|
|
const linkHintLetters = getLinkHintLetters(letters, sortedLinkHints.length);
|
|
sortedLinkHints.forEach((linkHint, i) => {
|
|
linkHint.letter = linkHintLetters[i];
|
|
});
|
|
return sortedLinkHints;
|
|
}
|
|
function checkIsPreviewElOnScreen(parent, el) {
|
|
el = el.closest('[data-view-type="table"], table') || el;
|
|
return el.offsetTop < parent.scrollTop || el.offsetTop > parent.scrollTop + parent.offsetHeight;
|
|
}
|
|
function displayPreviewPopovers(linkHints) {
|
|
const linkHintHtmlElements = [];
|
|
for (let linkHint of linkHints) {
|
|
const popoverElement = linkHint.linkElement.createEl('span');
|
|
linkHint.linkElement.style.position = 'relative';
|
|
popoverElement.style.top = '0px';
|
|
popoverElement.style.left = '0px';
|
|
popoverElement.textContent = linkHint.letter;
|
|
popoverElement.classList.add('jl');
|
|
popoverElement.classList.add('jl-' + linkHint.type);
|
|
popoverElement.classList.add('popover');
|
|
linkHintHtmlElements.push(popoverElement);
|
|
}
|
|
return linkHintHtmlElements;
|
|
}
|
|
|
|
class PreviewLinkProcessor {
|
|
constructor(view, alphabet) {
|
|
this.view = view;
|
|
this.alphabet = alphabet;
|
|
}
|
|
init() {
|
|
const { view, alphabet } = this;
|
|
const links = getPreviewLinkHints(view, alphabet);
|
|
displayPreviewPopovers(links);
|
|
return links;
|
|
}
|
|
}
|
|
|
|
class LivePreviewLinkProcessor {
|
|
constructor(view, editor, alphabet) {
|
|
this.getSourceLinkHints = () => {
|
|
const { alphabet } = this;
|
|
const { index, content } = this.getVisibleLines();
|
|
return getMDHintLinks(content, index, alphabet);
|
|
};
|
|
this.view = view;
|
|
this.cmEditor = editor;
|
|
this.alphabet = alphabet;
|
|
}
|
|
init() {
|
|
const { view, alphabet } = this;
|
|
const links = getPreviewLinkHints(view, alphabet);
|
|
const sourceLinks = this.getSourceLinkHints();
|
|
const linkHintLetters = getLinkHintLetters(alphabet, links.length + sourceLinks.length);
|
|
const linksRemapped = links.map((link, idx) => (Object.assign(Object.assign({}, link), { letter: linkHintLetters[idx] }))).filter(link => link.letter);
|
|
const sourceLinksRemapped = sourceLinks.map((link, idx) => (Object.assign(Object.assign({}, link), { letter: linkHintLetters[idx + links.length] }))).filter(link => link.letter);
|
|
const linkHintHtmlElements = displayPreviewPopovers(linksRemapped);
|
|
return [linksRemapped, sourceLinksRemapped, linkHintHtmlElements];
|
|
}
|
|
getVisibleLines() {
|
|
var _a, _b, _c;
|
|
const { cmEditor } = this;
|
|
let { from, to } = cmEditor.viewport;
|
|
// For CM6 get real visible lines top
|
|
// @ts-ignore
|
|
if ((_b = (_a = cmEditor.viewState) === null || _a === void 0 ? void 0 : _a.pixelViewport) === null || _b === void 0 ? void 0 : _b.top) {
|
|
// @ts-ignore
|
|
const pixelOffsetTop = cmEditor.viewState.pixelViewport.top;
|
|
// @ts-ignore
|
|
const lines = cmEditor.viewState.viewportLines;
|
|
// @ts-ignore
|
|
from = (_c = lines.filter(line => line.top > pixelOffsetTop)[0]) === null || _c === void 0 ? void 0 : _c.from;
|
|
}
|
|
const content = cmEditor.state.sliceDoc(from, to);
|
|
return { index: from, content };
|
|
}
|
|
}
|
|
|
|
var VIEW_MODE;
|
|
(function (VIEW_MODE) {
|
|
VIEW_MODE[VIEW_MODE["SOURCE"] = 0] = "SOURCE";
|
|
VIEW_MODE[VIEW_MODE["PREVIEW"] = 1] = "PREVIEW";
|
|
VIEW_MODE[VIEW_MODE["LEGACY"] = 2] = "LEGACY";
|
|
VIEW_MODE[VIEW_MODE["LIVE_PREVIEW"] = 3] = "LIVE_PREVIEW";
|
|
})(VIEW_MODE || (VIEW_MODE = {}));
|
|
class JumpToLink extends obsidian.Plugin {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.isLinkHintActive = false;
|
|
this.prefixInfo = undefined;
|
|
this.currentCursor = {};
|
|
this.cursorBeforeJump = {};
|
|
this.handleJumpToLink = () => {
|
|
const { settings: { letters } } = this;
|
|
const { mode, currentView } = this;
|
|
switch (mode) {
|
|
case VIEW_MODE.LEGACY: {
|
|
const cmEditor = this.cmEditor;
|
|
const sourceLinkHints = new LegacySourceLinkProcessor(cmEditor, letters).init();
|
|
this.handleActions(sourceLinkHints);
|
|
break;
|
|
}
|
|
case VIEW_MODE.LIVE_PREVIEW: {
|
|
const cm6Editor = this.cmEditor;
|
|
const previewViewEl = currentView.currentMode.editor.containerEl;
|
|
const [previewLinkHints, sourceLinkHints, linkHintHtmlElements] = new LivePreviewLinkProcessor(previewViewEl, cm6Editor, letters).init();
|
|
cm6Editor.plugin(this.markViewPlugin).setLinks(sourceLinkHints);
|
|
this.app.workspace.updateOptions();
|
|
this.handleActions([...previewLinkHints, ...sourceLinkHints], linkHintHtmlElements);
|
|
break;
|
|
}
|
|
case VIEW_MODE.PREVIEW: {
|
|
const previewViewEl = currentView.previewMode.containerEl.querySelector('div.markdown-preview-view');
|
|
const previewLinkHints = new PreviewLinkProcessor(previewViewEl, letters).init();
|
|
this.handleActions(previewLinkHints);
|
|
break;
|
|
}
|
|
case VIEW_MODE.SOURCE: {
|
|
const cm6Editor = this.cmEditor;
|
|
const livePreviewLinks = new CM6LinkProcessor(cm6Editor, letters).init();
|
|
cm6Editor.plugin(this.markViewPlugin).setLinks(livePreviewLinks);
|
|
this.app.workspace.updateOptions();
|
|
this.handleActions(livePreviewLinks);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
/*
|
|
* caseSensitive is only for lightspeed and shall not affect jumpToAnywhere, so it is true
|
|
* by default
|
|
*/
|
|
this.handleJumpToRegex = (stringToSearch, caseSensitive = true) => {
|
|
const { settings: { letters, jumpToAnywhereRegex } } = this;
|
|
const whatToLookAt = stringToSearch || jumpToAnywhereRegex;
|
|
const { mode } = this;
|
|
switch (mode) {
|
|
case VIEW_MODE.SOURCE:
|
|
this.handleMarkdownRegex(letters, whatToLookAt, caseSensitive);
|
|
break;
|
|
case VIEW_MODE.LIVE_PREVIEW:
|
|
this.handleMarkdownRegex(letters, whatToLookAt, caseSensitive);
|
|
break;
|
|
case VIEW_MODE.PREVIEW:
|
|
break;
|
|
case VIEW_MODE.LEGACY:
|
|
const cmEditor = this.cmEditor;
|
|
const links = new LegacyRegexpProcessor(cmEditor, whatToLookAt, letters, caseSensitive).init();
|
|
this.handleActions(links);
|
|
break;
|
|
}
|
|
};
|
|
this.handleMarkdownRegex = (letters, whatToLookAt, caseSensitive) => {
|
|
const cm6Editor = this.cmEditor;
|
|
const livePreviewLinks = new CM6RegexProcessor(cm6Editor, letters, whatToLookAt, caseSensitive).init();
|
|
cm6Editor.plugin(this.markViewPlugin).setLinks(livePreviewLinks);
|
|
this.app.workspace.updateOptions();
|
|
this.handleActions(livePreviewLinks);
|
|
};
|
|
}
|
|
onload() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.settings = (yield this.loadData()) || new Settings();
|
|
this.addSettingTab(new SettingTab(this.app, this));
|
|
const markViewPlugin = this.markViewPlugin = view.ViewPlugin.fromClass(MarkPlugin, {
|
|
decorations: v => v.decorations
|
|
});
|
|
this.registerEditorExtension([markViewPlugin]);
|
|
this.watchForSelectionChange();
|
|
this.addCommand({
|
|
id: 'activate-jump-to-link',
|
|
name: 'Jump to Link',
|
|
callback: this.action.bind(this, 'link'),
|
|
hotkeys: [{ modifiers: ['Ctrl'], key: `'` }],
|
|
});
|
|
this.addCommand({
|
|
id: "activate-jump-to-anywhere",
|
|
name: "Jump to Anywhere Regex",
|
|
callback: this.action.bind(this, 'regexp'),
|
|
hotkeys: [{ modifiers: ["Ctrl"], key: ";" }],
|
|
});
|
|
this.addCommand({
|
|
id: "activate-lightspeed-jump",
|
|
name: "Lightspeed Jump",
|
|
callback: this.action.bind(this, 'lightspeed'),
|
|
hotkeys: [],
|
|
});
|
|
});
|
|
}
|
|
onunload() {
|
|
console.log('unloading jump to links plugin');
|
|
}
|
|
action(type) {
|
|
if (this.isLinkHintActive) {
|
|
return;
|
|
}
|
|
const activeViewOfType = app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
|
const currentView = this.currentView = activeViewOfType.leaf.view;
|
|
const mode = this.mode = this.getMode(this.currentView);
|
|
this.contentElement = activeViewOfType.contentEl;
|
|
this.cursorBeforeJump = this.currentCursor;
|
|
switch (mode) {
|
|
case VIEW_MODE.LEGACY:
|
|
this.cmEditor = currentView.sourceMode.cmEditor;
|
|
break;
|
|
case VIEW_MODE.LIVE_PREVIEW:
|
|
case VIEW_MODE.SOURCE:
|
|
this.cmEditor = currentView.editor.cm;
|
|
break;
|
|
}
|
|
switch (type) {
|
|
case "link":
|
|
this.handleJumpToLink();
|
|
return;
|
|
case "regexp":
|
|
this.handleJumpToRegex();
|
|
return;
|
|
case "lightspeed":
|
|
this.handleLightspeedJump();
|
|
return;
|
|
}
|
|
}
|
|
getMode(currentView) {
|
|
// @ts-ignore
|
|
const isLegacy = this.app.vault.getConfig("legacyEditor");
|
|
if (currentView.getState().mode === 'preview') {
|
|
return VIEW_MODE.PREVIEW;
|
|
}
|
|
else if (isLegacy) {
|
|
return VIEW_MODE.LEGACY;
|
|
}
|
|
else if (currentView.getState().mode === 'source') {
|
|
const isLivePreview = currentView.editor.cm.state.field(obsidian.editorLivePreviewField);
|
|
if (isLivePreview)
|
|
return VIEW_MODE.LIVE_PREVIEW;
|
|
return VIEW_MODE.SOURCE;
|
|
}
|
|
}
|
|
// adapted from: https://github.com/mrjackphil/obsidian-jump-to-link/issues/35#issuecomment-1085905668
|
|
handleLightspeedJump() {
|
|
// get all text color
|
|
const { contentEl } = app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
|
if (!contentEl) {
|
|
return;
|
|
}
|
|
// this element doesn't exist in cm5/has a different class, so lightspeed will not work in cm5
|
|
const contentContainerColor = contentEl.getElementsByClassName("cm-contentContainer");
|
|
const originalColor = contentContainerColor[0].style.color;
|
|
// change all text color to gray
|
|
contentContainerColor[0].style.color = 'var(--jump-to-link-lightspeed-color)';
|
|
const keyArray = [];
|
|
const grabKey = (event) => {
|
|
event.preventDefault();
|
|
// handle Escape to reject the mode
|
|
if (event.key === 'Escape') {
|
|
contentEl.removeEventListener("keydown", grabKey, { capture: true });
|
|
contentContainerColor[0].style.color = originalColor;
|
|
}
|
|
// test if keypress is capitalized
|
|
if (/^[\w\S\W]$/i.test(event.key)) {
|
|
const isCapital = event.shiftKey;
|
|
if (isCapital) {
|
|
// capture uppercase
|
|
keyArray.push((event.key).toUpperCase());
|
|
}
|
|
else {
|
|
// capture lowercase
|
|
keyArray.push(event.key);
|
|
}
|
|
}
|
|
// stop when length of array is equal to lightspeedCharacterCount
|
|
if (keyArray.length === this.settings.lightspeedCharacterCount) {
|
|
const stringToSearch = this.settings.lightspeedJumpToStartOfWord ? "\\b" + keyArray.join("") : keyArray.join("");
|
|
this.handleJumpToRegex(stringToSearch, this.settings.lightspeedCaseSensitive);
|
|
// removing eventListener after proceeded
|
|
contentEl.removeEventListener("keydown", grabKey, { capture: true });
|
|
contentContainerColor[0].style.color = originalColor;
|
|
}
|
|
};
|
|
contentEl.addEventListener('keydown', grabKey, { capture: true });
|
|
}
|
|
handleHotkey(heldShiftKey, link) {
|
|
if ((link.linkText === undefined || link.linkText === '') && link.linkElement) {
|
|
const event = new MouseEvent("click", {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
view: window,
|
|
metaKey: heldShiftKey,
|
|
});
|
|
link.linkElement.dispatchEvent(event);
|
|
}
|
|
else if (link.type === 'internal') {
|
|
const file = this.app.workspace.getActiveFile();
|
|
if (file) {
|
|
// the second argument is for the link resolution
|
|
this.app.workspace.openLinkText(decodeURI(link.linkText), file.path, heldShiftKey, { active: true });
|
|
}
|
|
}
|
|
else if (link.type === 'external') {
|
|
window.open(link.linkText);
|
|
}
|
|
else {
|
|
const editor = this.cmEditor;
|
|
if (editor instanceof view.EditorView) {
|
|
const index = link.index;
|
|
const { vimMode, anchor } = this.cursorBeforeJump;
|
|
const useSelection = heldShiftKey || (vimMode === 'visual' || vimMode === 'visual block');
|
|
if (useSelection && anchor !== undefined) {
|
|
editor.dispatch({ selection: state.EditorSelection.range(anchor, index) });
|
|
}
|
|
else {
|
|
editor.dispatch({ selection: state.EditorSelection.cursor(index) });
|
|
}
|
|
}
|
|
else {
|
|
editor.setCursor(editor.posFromIndex(link.index));
|
|
}
|
|
}
|
|
}
|
|
removePopovers(linkHintHtmlElements = []) {
|
|
const currentView = this.contentElement;
|
|
currentView.removeEventListener('click', () => this.removePopovers(linkHintHtmlElements));
|
|
linkHintHtmlElements === null || linkHintHtmlElements === void 0 ? void 0 : linkHintHtmlElements.forEach(e => e.remove());
|
|
currentView.querySelectorAll('.jl.popover').forEach(e => e.remove());
|
|
this.prefixInfo = undefined;
|
|
if (this.mode == VIEW_MODE.SOURCE || this.mode == VIEW_MODE.LIVE_PREVIEW) {
|
|
this.cmEditor.plugin(this.markViewPlugin).clean();
|
|
}
|
|
this.app.workspace.updateOptions();
|
|
this.isLinkHintActive = false;
|
|
}
|
|
removePopoversWithoutPrefixEventKey(eventKey, linkHintHtmlElements = []) {
|
|
const currentView = this.contentElement;
|
|
linkHintHtmlElements === null || linkHintHtmlElements === void 0 ? void 0 : linkHintHtmlElements.forEach(e => {
|
|
if (e.innerHTML.length == 2 && e.innerHTML[0] == eventKey) {
|
|
e.classList.add("matched");
|
|
return;
|
|
}
|
|
e.remove();
|
|
});
|
|
currentView.querySelectorAll('.jl.popover').forEach(e => {
|
|
if (e.innerHTML.length == 2 && e.innerHTML[0] == eventKey) {
|
|
e.classList.add("matched");
|
|
return;
|
|
}
|
|
e.remove();
|
|
});
|
|
if (this.mode == VIEW_MODE.SOURCE || this.mode == VIEW_MODE.LIVE_PREVIEW) {
|
|
this.cmEditor.plugin(this.markViewPlugin).filterWithEventKey(eventKey);
|
|
}
|
|
this.app.workspace.updateOptions();
|
|
}
|
|
handleActions(linkHints, linkHintHtmlElements) {
|
|
var _a;
|
|
const contentElement = this.contentElement;
|
|
if (!linkHints.length) {
|
|
return;
|
|
}
|
|
const linkHintMap = {};
|
|
linkHints.forEach(x => linkHintMap[x.letter] = x);
|
|
const handleKeyDown = (event) => {
|
|
var _a;
|
|
if (['Shift', 'Control', 'CapsLock', 'ScrollLock', 'GroupNext', 'Meta'].includes(event.key)) {
|
|
return;
|
|
}
|
|
const eventKey = event.key.toUpperCase();
|
|
const prefixes = new Set(Object.keys(linkHintMap).filter(x => x.length > 1).map(x => x[0]));
|
|
let linkHint;
|
|
if (this.prefixInfo) {
|
|
linkHint = linkHintMap[this.prefixInfo.prefix + eventKey];
|
|
}
|
|
else {
|
|
linkHint = linkHintMap[eventKey];
|
|
if (!linkHint && prefixes && prefixes.has(eventKey)) {
|
|
this.prefixInfo = { prefix: eventKey, shiftKey: event.shiftKey };
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
this.removePopoversWithoutPrefixEventKey(eventKey, linkHintHtmlElements);
|
|
return;
|
|
}
|
|
}
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
const heldShiftKey = ((_a = this.prefixInfo) === null || _a === void 0 ? void 0 : _a.shiftKey) || event.shiftKey;
|
|
linkHint && this.handleHotkey(heldShiftKey, linkHint);
|
|
this.removePopovers(linkHintHtmlElements);
|
|
contentElement.removeEventListener('keydown', handleKeyDown, { capture: true });
|
|
};
|
|
if (linkHints.length === 1 && this.settings.jumpToLinkIfOneLinkOnly) {
|
|
const heldShiftKey = (_a = this.prefixInfo) === null || _a === void 0 ? void 0 : _a.shiftKey;
|
|
this.handleHotkey(heldShiftKey, linkHints[0]);
|
|
this.removePopovers(linkHintHtmlElements);
|
|
return;
|
|
}
|
|
contentElement.addEventListener('click', () => this.removePopovers(linkHintHtmlElements));
|
|
contentElement.addEventListener('keydown', handleKeyDown, { capture: true });
|
|
this.isLinkHintActive = true;
|
|
}
|
|
/**
|
|
* CodeMirror's vim automatically exits visual mode when executing a command.
|
|
* This keeps track of selection changes so we can restore the selection.
|
|
*
|
|
* This is the same approach taken by the obsidian-vimrc-plugin
|
|
*/
|
|
watchForSelectionChange() {
|
|
const updateSelection = this.updateSelection.bind(this);
|
|
const watchForChanges = () => {
|
|
var _a, _b;
|
|
const editor = (_a = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView)) === null || _a === void 0 ? void 0 : _a.editor;
|
|
const cm = (_b = editor === null || editor === void 0 ? void 0 : editor.cm) === null || _b === void 0 ? void 0 : _b.cm;
|
|
if (cm && !cm._handlers.cursorActivity.includes(updateSelection)) {
|
|
cm.on("cursorActivity", updateSelection);
|
|
this.register(() => cm.off("cursorActivity", updateSelection));
|
|
}
|
|
};
|
|
this.registerEvent(this.app.workspace.on("active-leaf-change", watchForChanges));
|
|
this.registerEvent(this.app.workspace.on("file-open", watchForChanges));
|
|
watchForChanges();
|
|
}
|
|
updateSelection(editor) {
|
|
var _a, _b;
|
|
const anchor = (_a = editor.listSelections()[0]) === null || _a === void 0 ? void 0 : _a.anchor;
|
|
this.currentCursor = {
|
|
anchor: anchor ? editor.indexFromPos(anchor) : undefined,
|
|
vimMode: (_b = editor.state.vim) === null || _b === void 0 ? void 0 : _b.mode
|
|
};
|
|
}
|
|
}
|
|
class SettingTab extends obsidian.PluginSettingTab {
|
|
constructor(app, plugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
display() {
|
|
let { containerEl } = this;
|
|
containerEl.empty();
|
|
containerEl.createEl('h2', { text: 'Settings for Jump To Link.' });
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Characters used for link hints')
|
|
.setDesc('The characters placed next to each link after enter link-hint mode.')
|
|
.addText(cb => {
|
|
cb.setValue(this.plugin.settings.letters)
|
|
.onChange((value) => {
|
|
this.plugin.settings.letters = value;
|
|
this.plugin.saveData(this.plugin.settings);
|
|
});
|
|
});
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Jump To Anywhere')
|
|
.setDesc("Regex based navigating in editor mode")
|
|
.addText((text) => text
|
|
.setPlaceholder('Custom Regex')
|
|
.setValue(this.plugin.settings.jumpToAnywhereRegex)
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.jumpToAnywhereRegex = value;
|
|
yield this.plugin.saveData(this.plugin.settings);
|
|
})));
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Lightspeed regex case sensitivity')
|
|
.setDesc('If enabled, the regex for matching will be case sensitive.')
|
|
.addToggle((toggle) => {
|
|
toggle.setValue(this.plugin.settings.lightspeedCaseSensitive)
|
|
.onChange((state) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.lightspeedCaseSensitive = state;
|
|
yield this.plugin.saveData(this.plugin.settings);
|
|
}));
|
|
});
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Jump to Link If Only One Link In Page')
|
|
.setDesc('If enabled, auto jump to link if there is only one link in page')
|
|
.addToggle((toggle) => {
|
|
toggle.setValue(this.plugin.settings.jumpToLinkIfOneLinkOnly)
|
|
.onChange((state) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.jumpToLinkIfOneLinkOnly = state;
|
|
yield this.plugin.saveData(this.plugin.settings);
|
|
}));
|
|
});
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Lightspeed only jumps to start of words')
|
|
.setDesc('If enabled, lightspeed jumps will only target characters occuring at the start of words.')
|
|
.addToggle((toggle) => {
|
|
toggle.setValue(this.plugin.settings.lightspeedJumpToStartOfWord)
|
|
.onChange((state) => __awaiter(this, void 0, void 0, function* () {
|
|
this.plugin.settings.lightspeedJumpToStartOfWord = state;
|
|
yield this.plugin.saveData(this.plugin.settings);
|
|
}));
|
|
});
|
|
new obsidian.Setting(containerEl)
|
|
.setName('Number of characters for Lightspeed jump')
|
|
.setDesc('Determines how many characters you need to type to perform a Lightspeed jump.')
|
|
.addText((text) => (text
|
|
.setValue(String(this.plugin.settings.lightspeedCharacterCount))
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
const num = Number(value);
|
|
if (!isNaN(num)) {
|
|
this.plugin.settings.lightspeedCharacterCount = num;
|
|
yield this.plugin.saveData(this.plugin.settings);
|
|
}
|
|
})).inputEl.type = "number"));
|
|
}
|
|
}
|
|
|
|
module.exports = JumpToLink;
|
|
|
|
|
|
/* nosourcemap */ |