Files
cours/.obsidian/plugins/mrj-jump-to-link/main.js
oskar 63d5db3e26 2025-08-27 17:44:02 update from obsidian (10 file·s changed)
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
2025-08-27 17:44:02 +02:00

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 */