Skip to content

Commit 207fea3

Browse files
committed
ContentSearcher.ts, added to ScriptInstallPrompt.ts and Settings.ts
1 parent 972fe1b commit 207fea3

6 files changed

Lines changed: 368 additions & 214 deletions

File tree

src/core/settings.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { HotkeyEditor } from "src/shared/Dialogs/HotkeyEditor";
4646
import { getExcalidrawViews } from "src/utils/obsidianUtils";
4747
import { createSliderWithText } from "src/utils/sliderUtils";
4848
import { PDFExportSettingsComponent, PDFExportSettings } from "src/shared/Dialogs/PDFExportSettingsComponent";
49+
import { ContentSearcher } from "src/shared/components/ContentSearcher";
4950

5051
export interface ExcalidrawSettings {
5152
disableDoubleClickTextEditing: boolean;
@@ -668,35 +669,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
668669
// Search and Settings to Clipboard
669670
// ------------------------------------------------
670671

671-
const settingActions = containerEl.createDiv("ex-settings-actions");
672-
const settingActionsContainer = settingActions.createDiv("setting-item-description ex-setting-actions-container");
673-
const clipboardActionEl = settingActionsContainer.createEl("a", {attr: { "aria-label": t("SETTINGS_COPY_TO_CLIPBOARD_ARIA") }});
674-
clipboardActionEl.innerHTML = getIcon("clipboard-copy").outerHTML + t("SETTINGS_COPY_TO_CLIPBOARD");
675-
clipboardActionEl.onClickEvent(e => {
676-
// Get the full HTML content first
677-
const fullHtml = containerEl.outerHTML;
678-
679-
// Find the index of the first <hr> element
680-
const startIndex = fullHtml.indexOf('<hr');
681-
682-
// Extract HTML from the first <hr> element onwards
683-
const html = startIndex > -1 ? fullHtml.substring(startIndex) : fullHtml;
684-
685-
function replaceHeading(html:string,level:number):string {
686-
const re = new RegExp(`<summary class="excalidraw-setting-h${level}">([^<]+)<\/summary>`,"g");
687-
return html.replaceAll(re,`<summary class="excalidraw-setting-h${level}"><h${level}>$1</h${level}></summary>`);
688-
}
689-
690-
let x = replaceHeading(html,1);
691-
x = replaceHeading(x,2);
692-
x = replaceHeading(x,3);
693-
x = replaceHeading(x,4);
694-
x = x.replaceAll(/<div class="setting-item-name">([^<]+)<\/div>/g,"<h5>$1</h5>");
695-
696-
const md = htmlToMarkdown(x);
697-
window.navigator.clipboard.writeText(md);
698-
new Notice(t("SETTINGS_COPIED_TO_CLIPBOARD"));
699-
});
672+
const searcher = new ContentSearcher(containerEl);
673+
containerEl.prepend(searcher.getSearchBarWrapper());
700674

701675
// ------------------------------------------------
702676
// Saving

src/lang/locale/en.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ export default {
192192
SAVE_IS_TAKING_LONG: "Saving your previous file is taking a long time. Please wait...",
193193
SAVE_IS_TAKING_VERY_LONG: "For better performance, consider splitting large drawings into several smaller files.",
194194

195+
//ContentSearcher.ts
196+
SEARCH_COPIED_TO_CLIPBOARD: "Markdown ready on clipboard",
197+
SEARCH_COPY_TO_CLIPBOARD_ARIA: "Copy the entire dialog to the clipboard as Markdown. Ideal for use with tools like ChatGPT to search and understand.",
198+
SEARCH_NEXT: "Next",
199+
SEARCH_PREVIOUS: "Previous",
200+
201+
202+
195203
//settings.ts
196204
LINKS_BUGS_ARIA: "Report bugs and raise feature requsts on the plugin's GitHub page",
197205
LINKS_BUGS: "Report Bugs",
@@ -205,10 +213,6 @@ export default {
205213
LINKS_BOOK_ARIA: "Read Sketch Your Mind, my book on Visual Thinking",
206214
LINKS_BOOK: "Read the Book",
207215

208-
SETTINGS_COPIED_TO_CLIPBOARD: "Markdown ready on clipboard",
209-
SETTINGS_COPY_TO_CLIPBOARD: "Copy as Text",
210-
SETTINGS_COPY_TO_CLIPBOARD_ARIA: "Copy the entire settings dialog to the clipboard as Markdown. Ideal for use with tools like ChatGPT to search and understand the settings.",
211-
212216
RELEASE_NOTES_NAME: "Display Release Notes after update",
213217
RELEASE_NOTES_DESC:
214218
"<b><u>Toggle ON:</u></b> Display release notes each time you update Excalidraw to a newer version.<br>" +

src/shared/Dialogs/ReleaseNotes.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ export class ReleaseNotes extends Modal {
2525
async onClose() {
2626
this.contentEl.empty();
2727
await this.plugin.loadSettings();
28-
this.plugin.settings.previousRelease = PLUGIN_VERSION
29-
await this.plugin.saveSettings();
28+
if(this.plugin.settings.previousRelease !== PLUGIN_VERSION) {
29+
this.plugin.settings.previousRelease = PLUGIN_VERSION;
30+
await this.plugin.saveSettings();
31+
}
3032
}
3133

3234
async createForm() {
@@ -39,7 +41,8 @@ export class ReleaseNotes extends Modal {
3941
.slice(0, 10)
4042
.join("\n\n---\n")
4143
: FIRST_RUN;
42-
await MarkdownRenderer.renderMarkdown(
44+
await MarkdownRenderer.render(
45+
this.app,
4346
message,
4447
this.contentEl,
4548
"",

src/shared/Dialogs/ScriptInstallPrompt.ts

Lines changed: 6 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,28 @@
11
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
22
import ExcalidrawPlugin from "../../core/main";
3-
import { errorlog, escapeRegExp } from "../../utils/utils";
3+
import { errorlog } from "../../utils/utils";
44
import { log } from "src/utils/debugHelper";
5+
import { ContentSearcher } from "../components/ContentSearcher";
56

67
const URL =
78
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
89

910
export class ScriptInstallPrompt extends Modal {
1011
private contentDiv: HTMLDivElement;
12+
1113
constructor(private plugin: ExcalidrawPlugin) {
1214
super(plugin.app);
1315
// this.titleEl.setText(t("INSTAL_MODAL_TITLE"));
1416
}
1517

1618
async onOpen(): Promise<void> {
17-
const searchBarWrapper = document.createElement("div");
18-
searchBarWrapper.classList.add('search-bar-wrapper');
19-
20-
21-
const searchBar = document.createElement("input");
22-
searchBar.type = "text";
23-
searchBar.id = "search-bar";
24-
searchBar.placeholder = "Search...";
25-
//searchBar.style.width = "calc(100% - 120px)"; // space for the buttons and hit count
26-
27-
const nextButton = document.createElement("button");
28-
nextButton.textContent = "→";
29-
nextButton.onclick = () => this.navigateSearchResults("next");
30-
31-
const prevButton = document.createElement("button");
32-
prevButton.textContent = "←";
33-
prevButton.onclick = () => this.navigateSearchResults("previous");
34-
35-
const hitCount = document.createElement("span");
36-
hitCount.id = "hit-count";
37-
hitCount.classList.add('hit-count');
38-
39-
searchBarWrapper.appendChild(prevButton);
40-
searchBarWrapper.appendChild(nextButton);
41-
searchBarWrapper.appendChild(searchBar);
42-
searchBarWrapper.appendChild(hitCount);
43-
44-
this.contentEl.prepend(searchBarWrapper);
45-
46-
searchBar.addEventListener("input", (e) => {
47-
this.clearHighlights();
48-
const searchTerm = (e.target as HTMLInputElement).value;
49-
50-
if (searchTerm && searchTerm.length > 0) {
51-
this.highlightSearchTerm(searchTerm);
52-
const totalHits = this.contentDiv.querySelectorAll("mark.search-highlight").length;
53-
hitCount.textContent = totalHits > 0 ? `1/${totalHits}` : "";
54-
setTimeout(()=>this.navigateSearchResults("next"));
55-
} else {
56-
hitCount.textContent = "";
57-
}
58-
});
59-
60-
61-
searchBar.addEventListener("keydown", (e) => {
62-
// If Ctrl/Cmd + F is pressed, focus on search bar
63-
if ((e.ctrlKey || e.metaKey) && e.key === "f") {
64-
e.preventDefault();
65-
searchBar.focus();
66-
}
67-
// If Enter is pressed, navigate to next result
68-
else if (e.key === "Enter") {
69-
e.preventDefault();
70-
this.navigateSearchResults(e.shiftKey ? "previous" : "next");
71-
}
72-
});
73-
7419
this.contentEl.classList.add("excalidraw-scriptengine-install");
7520
this.contentDiv = document.createElement("div");
7621
this.contentEl.appendChild(this.contentDiv);
7722

23+
const searcher = new ContentSearcher(this.contentDiv);
24+
this.contentEl.prepend(searcher.getSearchBarWrapper());
25+
7826
this.containerEl.classList.add("excalidraw-scriptengine-install");
7927
try {
8028
const source = await request({ url: URL });
@@ -111,99 +59,6 @@ export class ScriptInstallPrompt extends Modal {
11159
}
11260
}
11361

114-
highlightSearchTerm(searchTerm: string): void {
115-
// Create a walker to traverse text nodes
116-
const walker = document.createTreeWalker(
117-
this.contentDiv,
118-
NodeFilter.SHOW_TEXT,
119-
{
120-
acceptNode: (node: Text) => {
121-
return node.nodeValue!.toLowerCase().includes(searchTerm.toLowerCase()) ?
122-
NodeFilter.FILTER_ACCEPT :
123-
NodeFilter.FILTER_REJECT;
124-
}
125-
}
126-
);
127-
128-
const nodesToReplace: Text[] = [];
129-
while (walker.nextNode()) {
130-
nodesToReplace.push(walker.currentNode as Text);
131-
}
132-
133-
nodesToReplace.forEach(node => {
134-
const nodeContent = node.nodeValue!;
135-
const newNode = document.createDocumentFragment();
136-
137-
let lastIndex = 0;
138-
let match;
139-
const regex = new RegExp(escapeRegExp(searchTerm), 'gi');
140-
141-
// Iterate over all matches in the text node
142-
while ((match = regex.exec(nodeContent)) !== null) {
143-
const before = document.createTextNode(nodeContent.slice(lastIndex, match.index));
144-
const highlighted = document.createElement('mark');
145-
highlighted.className = 'search-highlight';
146-
highlighted.textContent = match[0];
147-
highlighted.classList.add('search-result');
148-
149-
newNode.appendChild(before);
150-
newNode.appendChild(highlighted);
151-
152-
lastIndex = regex.lastIndex;
153-
}
154-
newNode.appendChild(document.createTextNode(nodeContent.slice(lastIndex)));
155-
node.replaceWith(newNode);
156-
});
157-
}
158-
159-
160-
clearHighlights(): void {
161-
this.contentDiv.querySelectorAll("mark.search-highlight").forEach((el) => {
162-
el.outerHTML = el.innerHTML;
163-
});
164-
}
165-
166-
navigateSearchResults(direction: "next" | "previous"): void {
167-
const highlights: HTMLElement[] = Array.from(
168-
this.contentDiv.querySelectorAll("mark.search-highlight")
169-
);
170-
171-
if (highlights.length === 0) return;
172-
173-
const currentActiveIndex = highlights.findIndex((highlight) =>
174-
highlight.classList.contains("active-highlight")
175-
);
176-
177-
if (currentActiveIndex !== -1) {
178-
highlights[currentActiveIndex].classList.remove("active-highlight");
179-
highlights[currentActiveIndex].style.border = "none";
180-
}
181-
182-
let nextActiveIndex = 0;
183-
if (direction === "next") {
184-
nextActiveIndex =
185-
currentActiveIndex === highlights.length - 1
186-
? 0
187-
: currentActiveIndex + 1;
188-
} else if (direction === "previous") {
189-
nextActiveIndex =
190-
currentActiveIndex === 0
191-
? highlights.length - 1
192-
: currentActiveIndex - 1;
193-
}
194-
195-
const nextActiveHighlight = highlights[nextActiveIndex];
196-
nextActiveHighlight.classList.add("active-highlight");
197-
nextActiveHighlight.scrollIntoView({
198-
behavior: "smooth",
199-
block: "nearest",
200-
});
201-
202-
// Update the hit count
203-
const hitCount = document.getElementById("hit-count");
204-
hitCount.textContent = `${nextActiveIndex + 1}/${highlights.length}`;
205-
}
206-
20762
onClose(): void {
20863
this.contentEl.empty();
20964
}

0 commit comments

Comments
 (0)