From 355822aa96f8c21db7c1d2bf751dde975c093695 Mon Sep 17 00:00:00 2001
From: chetsCMD <104981866+chetsCMD@users.noreply.github.com>
Date: Thu, 5 Feb 2026 14:21:02 +0300
Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20=D0=BF?=
=?UTF-8?q?=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20?=
=?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
—Добавлена возможность закреплять подсказку с помощью ЛКМ
—Изменена работа с Markdown
—Адаптивный дизайн
—Работа с API CodeMirror 6
—Работа с предварительным просмотром
—Изменена обработка переноса строк
---
main.js | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 232 insertions(+), 4 deletions(-)
diff --git a/main.js b/main.js
index e9f458f..181090a 100644
--- a/main.js
+++ b/main.js
@@ -1,15 +1,169 @@
const { Plugin, Modal } = require('obsidian');
+const { EditorView, Decoration, ViewPlugin, WidgetType } = require('@codemirror/view');
+const { RangeSetBuilder } = require('@codemirror/state');
+
+class TooltipWidget extends WidgetType {
+ constructor(plugin, word, tooltip) {
+ super();
+ this.plugin = plugin;
+ this.word = word;
+ this.tooltip = tooltip;
+ }
+
+ toDOM() {
+ const span = document.createElement('span');
+ span.className = 'tooltip-word';
+ span.textContent = this.word;
+ span.setAttribute('data-tooltip', this.tooltip);
+
+ span.addEventListener('mouseenter', (e) => {
+ this.plugin.showTooltip(span, this.tooltip);
+ });
+
+ span.addEventListener('mouseleave', (e) => {
+ this.plugin.hideTooltip();
+ });
+
+ return span;
+ }
+
+ eq(other) {
+ return other.word === this.word && other.tooltip === this.tooltip;
+ }
+
+ ignoreEvent() {
+ return false;
+ }
+}
+
+const tooltipViewPlugin = (plugin) => {
+ return ViewPlugin.fromClass(
+ class {
+ constructor(view) {
+ this.view = view;
+ this.decorations = this.buildDecorations(view, plugin);
+ }
+
+ update(update) {
+ if (update.docChanged || update.viewportChanged || update.selectionSet) {
+ this.decorations = this.buildDecorations(update.view, plugin);
+ }
+ }
+
+ buildDecorations(view, plugin) {
+ const builder = new RangeSetBuilder();
+ const regex = /\{\/\{([^\/\n]+)\/([^}\n]+)\}\/\}/g;
+ const cursorPos = view.state.selection.main.head;
+ const text = view.state.doc.toString();
+
+ let match;
+ while ((match = regex.exec(text)) !== null) {
+ const start = match.index;
+ const end = start + match[0].length;
+
+ if (cursorPos < start || cursorPos > end) {
+ const word = match[1].trim();
+ let tooltip = match[2].trim().replace(/\}$/, "");
+
+ const lines = tooltip.split('\n').map(line => line.trim()).filter(line => line.length > 0);
+ tooltip = lines.join(' / ');
+
+ builder.add(start, end, Decoration.replace({
+ widget: new TooltipWidget(plugin, word, tooltip),
+ }));
+ }
+ }
+
+ return builder.finish();
+ }
+ },
+ { decorations: (v) => v.decorations }
+ );
+};
class TooltipPlugin extends Plugin {
+ processMarkdownLine(line) {
+ line = line.replace(/`([^`]+)`/g, '$1');
+ line = line.replace(/\*\*([^\*]+)\*\*/g, '$1');
+ line = line.replace(/\*([^\*]+)\*/g, '$1');
+ line = line.replace(/==([^=]+)==/g, '$1');
+
+ line = line.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, (match, text, url) => {
+ return `${text}`;
+ });
+
+ line = line.replace(/\[\[([^\]]+)\]\]/g, (match, link) => {
+ const parts = link.split('|');
+ const href = parts[0];
+ const text = parts[1] || href;
+ return `${text}`;
+ });
+
+ return line;
+ }
+
+ escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
onload() {
console.log("Tooltip Plugin loaded");
+ this.tooltipElement = null;
+ this.pinnedTooltip = null;
+
+ this.registerEditorExtension(tooltipViewPlugin(this));
+
this.registerMarkdownPostProcessor((el) => {
- const regex = /\{\/\{([^\/]+)\/([^}]+)\}\/\}/g;
+ const regex = /\{\/\{([^\/\n]+)\/([^}\n]+)\}\/\}/g;
el.innerHTML = el.innerHTML.replace(regex, (_, word, tooltip) => {
tooltip = tooltip.trim().replace(/\}$/, "");
- return `${word}${tooltip}`;
+ const uniqueId = `tooltip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+
+ const lines = tooltip.split('\n').map(line => line.trim()).filter(line => line.length > 0);
+ const formattedTooltip = lines.join(' / ');
+
+ return `
+
+ ${word}
+
+ `;
});
+
+ el.querySelectorAll('.tooltip-word').forEach(wordElement => {
+ wordElement.addEventListener('mouseenter', (e) => {
+ if (this.pinnedTooltip) return;
+ this.showTooltip(e.target, e.target.getAttribute('data-tooltip'));
+ });
+
+ wordElement.addEventListener('mouseleave', (e) => {
+ if (this.pinnedTooltip) return;
+ this.hideTooltip();
+ });
+
+ wordElement.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const tooltipContent = e.target.getAttribute('data-tooltip');
+
+ if (this.pinnedTooltip === e.target) {
+ this.pinnedTooltip = null;
+ this.hideTooltip();
+ } else {
+ this.pinnedTooltip = e.target;
+ this.showTooltip(e.target, tooltipContent);
+ }
+ });
+ });
+ });
+
+ this.registerDomEvent(document, 'click', (e) => {
+ if (this.pinnedTooltip && !this.pinnedTooltip.contains(e.target) &&
+ (!this.tooltipElement || !this.tooltipElement.contains(e.target))) {
+ this.pinnedTooltip = null;
+ this.hideTooltip();
+ }
});
this.addCommand({
@@ -19,12 +173,83 @@ class TooltipPlugin extends Plugin {
const selection = editor.getSelection();
const word = selection || "word";
const description = await this.promptForDescription(word);
- const formatted = `{/{${word}/${description}}/}`;
+
+ const lines = description.split('\n')
+ .map(line => line.trim())
+ .filter(line => line.length > 0);
+
+ const formattedDescription = lines.join(' / ');
+ const formatted = `{/{${word}/${formattedDescription}}/}`;
editor.replaceSelection(formatted);
},
});
}
+ showTooltip(element, content) {
+ if (!this.tooltipElement) {
+ this.tooltipElement = document.createElement('div');
+ this.tooltipElement.className = 'tooltip-text';
+ this.tooltipElement.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
+ document.body.appendChild(this.tooltipElement);
+ }
+
+ const lines = content.split(' / ').map(line => line.trim());
+
+ const processedLines = lines.map(line => {
+ const processedLine = this.processMarkdownLine(line);
+ return processedLine;
+ });
+
+ const tooltipHTML = processedLines.join('
');
+
+ this.tooltipElement.innerHTML = tooltipHTML;
+
+ const rect = element.getBoundingClientRect();
+ const tooltipRect = this.tooltipElement.getBoundingClientRect();
+
+ let left = rect.left + (rect.width / 2) - (tooltipRect.width / 2);
+ let top = rect.top - tooltipRect.height - 5;
+
+ if (left < 5) left = 5;
+ if (left + tooltipRect.width > window.innerWidth - 5) {
+ left = window.innerWidth - tooltipRect.width - 5;
+ }
+
+ if (top < 5) {
+ top = rect.bottom + 5;
+ }
+
+ this.tooltipElement.style.left = left + 'px';
+ this.tooltipElement.style.top = top + 'px';
+ this.tooltipElement.classList.add('visible');
+
+ const internalLinks = this.tooltipElement.querySelectorAll('a.internal-link');
+ const app = this.app;
+ internalLinks.forEach(link => {
+ link.addEventListener('click', (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ const linkText = link.getAttribute('href');
+ app.workspace.openLinkText(linkText, '', false);
+ });
+ });
+
+ const externalLinks = this.tooltipElement.querySelectorAll('a.external-link');
+ externalLinks.forEach(link => {
+ link.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
+ });
+ }
+
+ hideTooltip() {
+ if (this.tooltipElement) {
+ this.tooltipElement.classList.remove('visible');
+ }
+ }
+
async promptForDescription(word) {
return new Promise((resolve) => {
const modal = new TooltipPromptModal(this.app, word, resolve);
@@ -34,6 +259,9 @@ class TooltipPlugin extends Plugin {
onunload() {
console.log("Tooltip Plugin unloaded");
+ if (this.tooltipElement) {
+ this.tooltipElement.remove();
+ }
}
}
@@ -112,4 +340,4 @@ class TooltipPromptModal extends Modal {
}
}
-module.exports = TooltipPlugin;
\ No newline at end of file
+module.exports = TooltipPlugin;