generated from obsidianmd/obsidian-sample-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.ts
258 lines (223 loc) · 9.57 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// main.ts
import { Plugin, MarkdownView, Editor, Notice } from "obsidian";
export default class SupSubPlugin extends Plugin {
styleEl: HTMLElement | null = null;
isWrapping: boolean = false; // Flag to prevent re-triggering
selectionStart: { line: number; ch: number } | null = null;
selectionEnd: { line: number; ch: number } | null = null;
onload() {
console.log('SupSub Plugin loaded');
// Register commands
this.addCommand({
id: "wrap-sup",
name: "Wrap with <sup> tags",
editorCallback: (editor, view) => this.wrapSelection("sup", editor),
hotkeys: [
{
modifiers: ["Mod", "Alt"],
key: "=",
},
],
});
this.addCommand({
id: "wrap-sub",
name: "Wrap with <sub> tags",
editorCallback: (editor, view) => this.wrapSelection("sub", editor),
hotkeys: [
{
modifiers: ["Mod", "Alt"],
key: "-",
},
],
});
// Inject CSS
const style = `
.supsub-popup {
position: absolute;
background: var(--background-primary);
border: 1px solid var(--border);
padding: 5px;
border-radius: 8px; /* Enhanced rounding */
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 10000; /* Increased z-index */
display: flex;
gap: 5px;
transition: opacity 0.1s ease; /* Shorter transition */
opacity: 0;
pointer-events: none;
}
.supsub-popup.visible {
opacity: 1;
pointer-events: auto;
}
.supsub-popup button {
background: var(--background-modifier-hover);
border: none;
padding: 5px 10px;
border-radius: 4px; /* Enhanced rounding */
cursor: pointer;
font-size: 12px;
transition: background 0.2s ease;
}
.supsub-popup button:hover {
background: var(--background-modifier-hover-active);
}
`;
this.styleEl = document.createElement("style");
this.styleEl.innerText = style;
document.head.appendChild(this.styleEl);
this.register(() => {
if (this.styleEl) {
this.styleEl.remove();
}
});
// Register selection change event
this.registerEvent(this.app.workspace.on('editor-selection-change', (editor: Editor) => {
if (this.isWrapping) return; // Prevent if wrapping is in progress
const selection = editor.getSelection();
if (selection) {
this.showSupSubButtons(editor);
} else {
this.hideSupSubButtons();
}
}));
// Hide popup on external click
this.registerDomEvent(document, "click", (evt) => {
const target = evt.target as HTMLElement;
if (!target.closest(".supsub-popup")) {
this.hideSupSubButtons();
}
});
}
onunload() {
console.log("SupSub Plugin unloaded");
this.hideSupSubButtons();
if (this.styleEl) {
this.styleEl.remove();
}
}
showSupSubButtons(editor: Editor) {
this.hideSupSubButtons(); // Remove all existing buttons
const selection = editor.getSelection();
if (!selection) return;
const cursorStart = editor.getCursor('from');
const cursorEnd = editor.getCursor('to');
this.selectionStart = { ...cursorStart };
this.selectionEnd = { ...cursorEnd };
const currentTag = this.getCurrentTag(selection);
const buttonContainer = document.createElement("div");
buttonContainer.className = "supsub-popup";
if (currentTag === "sup" || currentTag === "sub") {
// Only show 'Normal (n)' button to remove the tag
const normalButton = document.createElement("button");
normalButton.innerText = "Normal (n)"; // You can consider "Clear (n)" or "Plain (n)"
normalButton.setAttribute("aria-label", "Remove superscript/subscript");
normalButton.addEventListener("mousedown", (e) => {
e.preventDefault();
e.stopPropagation();
this.wrapSelection(currentTag, editor); // This will remove the tag
});
buttonContainer.appendChild(normalButton);
} else {
// Show both 'Sup (ⁿ)' and 'Sub (ₙ)' buttons
const supButton = document.createElement("button");
supButton.innerText = "Sup (ⁿ)"; // Updated label with superscript n
supButton.setAttribute("aria-label", "Wrap selected text with superscript");
supButton.addEventListener("mousedown", (e) => { // Changed to mousedown
e.preventDefault();
e.stopPropagation();
this.wrapSelection("sup", editor);
});
const subButton = document.createElement("button");
subButton.innerText = "Sub (ₙ)"; // Updated label with subscript n
subButton.setAttribute("aria-label", "Wrap selected text with subscript");
subButton.addEventListener("mousedown", (e) => { // Changed to mousedown
e.preventDefault();
e.stopPropagation();
this.wrapSelection("sub", editor);
});
buttonContainer.appendChild(supButton);
buttonContainer.appendChild(subButton);
}
// Append to document.body to avoid CSS constraints
buttonContainer.style.position = "absolute";
document.body.appendChild(buttonContainer);
this.positionPopup(buttonContainer, editor);
// Make the popup visible by adding the 'visible' class after positioning
requestAnimationFrame(() => {
buttonContainer.classList.add("visible");
});
}
hideSupSubButtons() {
const buttonContainers = document.querySelectorAll(".supsub-popup");
buttonContainers.forEach((buttonContainer) => {
// Add transition for fading out
buttonContainer.classList.remove("visible");
// Remove the popup after a short delay to allow transition
setTimeout(() => {
buttonContainer.remove();
}, 100); // Match the CSS transition duration (0.1s)
});
}
positionPopup(popup: HTMLElement, editor: Editor) {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
const top = rect.bottom + window.scrollY + 5; // 5px offset
const left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);
// Prevent the popup from going outside the viewport's boundaries
const maxLeft = window.innerWidth - popup.offsetWidth - 10; // 10px padding
const calculatedLeft = Math.max(10, Math.min(left, maxLeft));
popup.style.top = `${top}px`;
popup.style.left = `${calculatedLeft}px`;
console.log(`Popup positioned at top: ${top}px, left: ${calculatedLeft}px`);
}
}
wrapSelection(tag: string, editor: Editor) {
this.isWrapping = true; // Indicate that wrapping is in progress
// Restore the original selection
if (this.selectionStart && this.selectionEnd) {
editor.setSelection(this.selectionStart, this.selectionEnd);
}
// Focus the editor to ensure it's ready
editor.focus();
// Perform wrapping after a short delay to ensure selection is restored
setTimeout(() => {
const selection = editor.getSelection();
console.log(`Wrapping selection: "${selection}" with tag: <${tag}>`);
if (selection) {
const regex = new RegExp(`<${tag}>(.*?)<\/${tag}>`, "s"); // 's' flag for multiline
const matches = regex.exec(selection);
if (matches) {
const debracketedSelection = matches[1];
editor.replaceSelection(debracketedSelection);
new Notice(`${tag} tags removed`);
} else {
const wrappedSelection = `<${tag}>${selection}</${tag}>`;
editor.replaceSelection(wrappedSelection);
new Notice(`${tag} tags added`);
}
// Hide the popup after wrapping
this.hideSupSubButtons();
} else {
// No selection, do not hide the popup
}
// Clear the selection by setting both start and end to the new cursor position
const currentCursor = editor.getCursor();
editor.setSelection(currentCursor, currentCursor);
this.isWrapping = false; // Reset the wrapping flag
}, 50); // Increased delay to 50ms for better reliability
}
private getCurrentTag(selection: string): string | null {
const supRegex = /^<sup>([\s\S]+)<\/sup>$/i;
const subRegex = /^<sub>([\s\S]+)<\/sub>$/i;
if (supRegex.test(selection)) {
return "sup";
} else if (subRegex.test(selection)) {
return "sub";
} else {
return null;
}
}
}