mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
ContentSearcher.ts, added to ScriptInstallPrompt.ts and Settings.ts
This commit is contained in:
@@ -46,6 +46,7 @@ import { HotkeyEditor } from "src/shared/Dialogs/HotkeyEditor";
|
||||
import { getExcalidrawViews } from "src/utils/obsidianUtils";
|
||||
import { createSliderWithText } from "src/utils/sliderUtils";
|
||||
import { PDFExportSettingsComponent, PDFExportSettings } from "src/shared/Dialogs/PDFExportSettingsComponent";
|
||||
import { ContentSearcher } from "src/shared/components/ContentSearcher";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
disableDoubleClickTextEditing: boolean;
|
||||
@@ -668,35 +669,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
// Search and Settings to Clipboard
|
||||
// ------------------------------------------------
|
||||
|
||||
const settingActions = containerEl.createDiv("ex-settings-actions");
|
||||
const settingActionsContainer = settingActions.createDiv("setting-item-description ex-setting-actions-container");
|
||||
const clipboardActionEl = settingActionsContainer.createEl("a", {attr: { "aria-label": t("SETTINGS_COPY_TO_CLIPBOARD_ARIA") }});
|
||||
clipboardActionEl.innerHTML = getIcon("clipboard-copy").outerHTML + t("SETTINGS_COPY_TO_CLIPBOARD");
|
||||
clipboardActionEl.onClickEvent(e => {
|
||||
// Get the full HTML content first
|
||||
const fullHtml = containerEl.outerHTML;
|
||||
|
||||
// Find the index of the first <hr> element
|
||||
const startIndex = fullHtml.indexOf('<hr');
|
||||
|
||||
// Extract HTML from the first <hr> element onwards
|
||||
const html = startIndex > -1 ? fullHtml.substring(startIndex) : fullHtml;
|
||||
|
||||
function replaceHeading(html:string,level:number):string {
|
||||
const re = new RegExp(`<summary class="excalidraw-setting-h${level}">([^<]+)<\/summary>`,"g");
|
||||
return html.replaceAll(re,`<summary class="excalidraw-setting-h${level}"><h${level}>$1</h${level}></summary>`);
|
||||
}
|
||||
|
||||
let x = replaceHeading(html,1);
|
||||
x = replaceHeading(x,2);
|
||||
x = replaceHeading(x,3);
|
||||
x = replaceHeading(x,4);
|
||||
x = x.replaceAll(/<div class="setting-item-name">([^<]+)<\/div>/g,"<h5>$1</h5>");
|
||||
|
||||
const md = htmlToMarkdown(x);
|
||||
window.navigator.clipboard.writeText(md);
|
||||
new Notice(t("SETTINGS_COPIED_TO_CLIPBOARD"));
|
||||
});
|
||||
const searcher = new ContentSearcher(containerEl);
|
||||
containerEl.prepend(searcher.getSearchBarWrapper());
|
||||
|
||||
// ------------------------------------------------
|
||||
// Saving
|
||||
|
||||
@@ -192,6 +192,14 @@ export default {
|
||||
SAVE_IS_TAKING_LONG: "Saving your previous file is taking a long time. Please wait...",
|
||||
SAVE_IS_TAKING_VERY_LONG: "For better performance, consider splitting large drawings into several smaller files.",
|
||||
|
||||
//ContentSearcher.ts
|
||||
SEARCH_COPIED_TO_CLIPBOARD: "Markdown ready on clipboard",
|
||||
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.",
|
||||
SEARCH_NEXT: "Next",
|
||||
SEARCH_PREVIOUS: "Previous",
|
||||
|
||||
|
||||
|
||||
//settings.ts
|
||||
LINKS_BUGS_ARIA: "Report bugs and raise feature requsts on the plugin's GitHub page",
|
||||
LINKS_BUGS: "Report Bugs",
|
||||
@@ -205,10 +213,6 @@ export default {
|
||||
LINKS_BOOK_ARIA: "Read Sketch Your Mind, my book on Visual Thinking",
|
||||
LINKS_BOOK: "Read the Book",
|
||||
|
||||
SETTINGS_COPIED_TO_CLIPBOARD: "Markdown ready on clipboard",
|
||||
SETTINGS_COPY_TO_CLIPBOARD: "Copy as Text",
|
||||
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.",
|
||||
|
||||
RELEASE_NOTES_NAME: "Display Release Notes after update",
|
||||
RELEASE_NOTES_DESC:
|
||||
"<b><u>Toggle ON:</u></b> Display release notes each time you update Excalidraw to a newer version.<br>" +
|
||||
|
||||
@@ -25,8 +25,10 @@ export class ReleaseNotes extends Modal {
|
||||
async onClose() {
|
||||
this.contentEl.empty();
|
||||
await this.plugin.loadSettings();
|
||||
this.plugin.settings.previousRelease = PLUGIN_VERSION
|
||||
await this.plugin.saveSettings();
|
||||
if(this.plugin.settings.previousRelease !== PLUGIN_VERSION) {
|
||||
this.plugin.settings.previousRelease = PLUGIN_VERSION;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
@@ -39,7 +41,8 @@ export class ReleaseNotes extends Modal {
|
||||
.slice(0, 10)
|
||||
.join("\n\n---\n")
|
||||
: FIRST_RUN;
|
||||
await MarkdownRenderer.renderMarkdown(
|
||||
await MarkdownRenderer.render(
|
||||
this.app,
|
||||
message,
|
||||
this.contentEl,
|
||||
"",
|
||||
|
||||
@@ -1,80 +1,28 @@
|
||||
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { errorlog, escapeRegExp } from "../../utils/utils";
|
||||
import { errorlog } from "../../utils/utils";
|
||||
import { log } from "src/utils/debugHelper";
|
||||
import { ContentSearcher } from "../components/ContentSearcher";
|
||||
|
||||
const URL =
|
||||
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
|
||||
|
||||
export class ScriptInstallPrompt extends Modal {
|
||||
private contentDiv: HTMLDivElement;
|
||||
|
||||
constructor(private plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
// this.titleEl.setText(t("INSTAL_MODAL_TITLE"));
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
const searchBarWrapper = document.createElement("div");
|
||||
searchBarWrapper.classList.add('search-bar-wrapper');
|
||||
|
||||
|
||||
const searchBar = document.createElement("input");
|
||||
searchBar.type = "text";
|
||||
searchBar.id = "search-bar";
|
||||
searchBar.placeholder = "Search...";
|
||||
//searchBar.style.width = "calc(100% - 120px)"; // space for the buttons and hit count
|
||||
|
||||
const nextButton = document.createElement("button");
|
||||
nextButton.textContent = "→";
|
||||
nextButton.onclick = () => this.navigateSearchResults("next");
|
||||
|
||||
const prevButton = document.createElement("button");
|
||||
prevButton.textContent = "←";
|
||||
prevButton.onclick = () => this.navigateSearchResults("previous");
|
||||
|
||||
const hitCount = document.createElement("span");
|
||||
hitCount.id = "hit-count";
|
||||
hitCount.classList.add('hit-count');
|
||||
|
||||
searchBarWrapper.appendChild(prevButton);
|
||||
searchBarWrapper.appendChild(nextButton);
|
||||
searchBarWrapper.appendChild(searchBar);
|
||||
searchBarWrapper.appendChild(hitCount);
|
||||
|
||||
this.contentEl.prepend(searchBarWrapper);
|
||||
|
||||
searchBar.addEventListener("input", (e) => {
|
||||
this.clearHighlights();
|
||||
const searchTerm = (e.target as HTMLInputElement).value;
|
||||
|
||||
if (searchTerm && searchTerm.length > 0) {
|
||||
this.highlightSearchTerm(searchTerm);
|
||||
const totalHits = this.contentDiv.querySelectorAll("mark.search-highlight").length;
|
||||
hitCount.textContent = totalHits > 0 ? `1/${totalHits}` : "";
|
||||
setTimeout(()=>this.navigateSearchResults("next"));
|
||||
} else {
|
||||
hitCount.textContent = "";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
searchBar.addEventListener("keydown", (e) => {
|
||||
// If Ctrl/Cmd + F is pressed, focus on search bar
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "f") {
|
||||
e.preventDefault();
|
||||
searchBar.focus();
|
||||
}
|
||||
// If Enter is pressed, navigate to next result
|
||||
else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
this.navigateSearchResults(e.shiftKey ? "previous" : "next");
|
||||
}
|
||||
});
|
||||
|
||||
this.contentEl.classList.add("excalidraw-scriptengine-install");
|
||||
this.contentDiv = document.createElement("div");
|
||||
this.contentEl.appendChild(this.contentDiv);
|
||||
|
||||
const searcher = new ContentSearcher(this.contentDiv);
|
||||
this.contentEl.prepend(searcher.getSearchBarWrapper());
|
||||
|
||||
this.containerEl.classList.add("excalidraw-scriptengine-install");
|
||||
try {
|
||||
const source = await request({ url: URL });
|
||||
@@ -111,99 +59,6 @@ export class ScriptInstallPrompt extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
highlightSearchTerm(searchTerm: string): void {
|
||||
// Create a walker to traverse text nodes
|
||||
const walker = document.createTreeWalker(
|
||||
this.contentDiv,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
{
|
||||
acceptNode: (node: Text) => {
|
||||
return node.nodeValue!.toLowerCase().includes(searchTerm.toLowerCase()) ?
|
||||
NodeFilter.FILTER_ACCEPT :
|
||||
NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const nodesToReplace: Text[] = [];
|
||||
while (walker.nextNode()) {
|
||||
nodesToReplace.push(walker.currentNode as Text);
|
||||
}
|
||||
|
||||
nodesToReplace.forEach(node => {
|
||||
const nodeContent = node.nodeValue!;
|
||||
const newNode = document.createDocumentFragment();
|
||||
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
const regex = new RegExp(escapeRegExp(searchTerm), 'gi');
|
||||
|
||||
// Iterate over all matches in the text node
|
||||
while ((match = regex.exec(nodeContent)) !== null) {
|
||||
const before = document.createTextNode(nodeContent.slice(lastIndex, match.index));
|
||||
const highlighted = document.createElement('mark');
|
||||
highlighted.className = 'search-highlight';
|
||||
highlighted.textContent = match[0];
|
||||
highlighted.classList.add('search-result');
|
||||
|
||||
newNode.appendChild(before);
|
||||
newNode.appendChild(highlighted);
|
||||
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
newNode.appendChild(document.createTextNode(nodeContent.slice(lastIndex)));
|
||||
node.replaceWith(newNode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
clearHighlights(): void {
|
||||
this.contentDiv.querySelectorAll("mark.search-highlight").forEach((el) => {
|
||||
el.outerHTML = el.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
navigateSearchResults(direction: "next" | "previous"): void {
|
||||
const highlights: HTMLElement[] = Array.from(
|
||||
this.contentDiv.querySelectorAll("mark.search-highlight")
|
||||
);
|
||||
|
||||
if (highlights.length === 0) return;
|
||||
|
||||
const currentActiveIndex = highlights.findIndex((highlight) =>
|
||||
highlight.classList.contains("active-highlight")
|
||||
);
|
||||
|
||||
if (currentActiveIndex !== -1) {
|
||||
highlights[currentActiveIndex].classList.remove("active-highlight");
|
||||
highlights[currentActiveIndex].style.border = "none";
|
||||
}
|
||||
|
||||
let nextActiveIndex = 0;
|
||||
if (direction === "next") {
|
||||
nextActiveIndex =
|
||||
currentActiveIndex === highlights.length - 1
|
||||
? 0
|
||||
: currentActiveIndex + 1;
|
||||
} else if (direction === "previous") {
|
||||
nextActiveIndex =
|
||||
currentActiveIndex === 0
|
||||
? highlights.length - 1
|
||||
: currentActiveIndex - 1;
|
||||
}
|
||||
|
||||
const nextActiveHighlight = highlights[nextActiveIndex];
|
||||
nextActiveHighlight.classList.add("active-highlight");
|
||||
nextActiveHighlight.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
|
||||
// Update the hit count
|
||||
const hitCount = document.getElementById("hit-count");
|
||||
hitCount.textContent = `${nextActiveIndex + 1}/${highlights.length}`;
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
250
src/shared/components/ContentSearcher.ts
Normal file
250
src/shared/components/ContentSearcher.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { t } from "src/lang/helpers";
|
||||
import { escapeRegExp } from "../../utils/utils";
|
||||
// @ts-ignore
|
||||
import { getIcon, htmlToMarkdown, Notice } from "obsidian";
|
||||
|
||||
export class ContentSearcher {
|
||||
private contentDiv: HTMLElement;
|
||||
private searchBar: HTMLInputElement;
|
||||
private prevButton: HTMLButtonElement;
|
||||
private nextButton: HTMLButtonElement;
|
||||
private exportMarkdown: HTMLButtonElement;
|
||||
private hitCount: HTMLSpanElement;
|
||||
private searchBarWrapper: HTMLDivElement;
|
||||
|
||||
constructor(contentDiv: HTMLElement) {
|
||||
this.contentDiv = contentDiv;
|
||||
this.createSearchElements();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates search UI elements styled like Obsidian's native search
|
||||
*/
|
||||
private createSearchElements(): void {
|
||||
// Outer container
|
||||
this.searchBarWrapper = document.createElement("div");
|
||||
this.searchBarWrapper.classList.add("document-search-container");
|
||||
|
||||
// Main search bar
|
||||
const documentSearch = document.createElement("div");
|
||||
documentSearch.classList.add("document-search");
|
||||
|
||||
// Search input container
|
||||
const inputContainer = document.createElement("div");
|
||||
inputContainer.classList.add("search-input-container", "document-search-input");
|
||||
|
||||
// Search input
|
||||
this.searchBar = document.createElement("input");
|
||||
this.searchBar.type = "text";
|
||||
this.searchBar.placeholder = "Find...";
|
||||
|
||||
// Hit count
|
||||
this.hitCount = document.createElement("div");
|
||||
this.hitCount.classList.add("document-search-count");
|
||||
|
||||
inputContainer.appendChild(this.searchBar);
|
||||
inputContainer.appendChild(this.hitCount);
|
||||
|
||||
// Search buttons (prev/next)
|
||||
const buttonContainer = document.createElement("div");
|
||||
buttonContainer.classList.add("document-search-buttons");
|
||||
|
||||
this.prevButton = document.createElement("button");
|
||||
this.prevButton.classList.add("clickable-icon", "document-search-button");
|
||||
this.prevButton.setAttribute("aria-label", t("SEARCH_PREVIOUS"));
|
||||
this.prevButton.setAttribute("data-tooltip-position", "top");
|
||||
this.prevButton.type = "button";
|
||||
this.prevButton.innerHTML = getIcon("arrow-up").outerHTML;
|
||||
|
||||
this.nextButton = document.createElement("button");
|
||||
this.nextButton.classList.add("clickable-icon", "document-search-button");
|
||||
this.nextButton.setAttribute("aria-label", t("SEARCH_NEXT"));
|
||||
this.nextButton.setAttribute("data-tooltip-position", "top");
|
||||
this.nextButton.type = "button";
|
||||
this.nextButton.innerHTML = getIcon("arrow-down").outerHTML;
|
||||
|
||||
this.exportMarkdown = document.createElement("button");
|
||||
this.exportMarkdown.classList.add("clickable-icon", "document-search-button");
|
||||
this.exportMarkdown.setAttribute("aria-label", t("SEARCH_COPY_TO_CLIPBOARD_ARIA"));
|
||||
this.exportMarkdown.setAttribute("data-tooltip-position", "top");
|
||||
this.exportMarkdown.type = "button";
|
||||
this.exportMarkdown.innerHTML = getIcon("clipboard-copy").outerHTML;
|
||||
|
||||
buttonContainer.appendChild(this.prevButton);
|
||||
buttonContainer.appendChild(this.nextButton);
|
||||
buttonContainer.appendChild(this.exportMarkdown);
|
||||
|
||||
documentSearch.appendChild(inputContainer);
|
||||
documentSearch.appendChild(buttonContainer);
|
||||
|
||||
this.searchBarWrapper.appendChild(documentSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach event listeners to search elements
|
||||
*/
|
||||
private setupEventListeners(): void {
|
||||
this.nextButton.onclick = () => this.navigateSearchResults("next");
|
||||
this.prevButton.onclick = () => this.navigateSearchResults("previous");
|
||||
this.exportMarkdown.onclick = () => {
|
||||
// Get the full HTML content first
|
||||
const fullHtml = this.contentDiv.outerHTML;
|
||||
|
||||
// Find the index of the first <hr> element
|
||||
const startIndex = fullHtml.indexOf('<hr');
|
||||
|
||||
// Extract HTML from the first <hr> element onwards
|
||||
const html = startIndex > -1 ? fullHtml.substring(startIndex) : fullHtml;
|
||||
|
||||
function replaceHeading(html:string,level:number):string {
|
||||
const re = new RegExp(`<summary class="excalidraw-setting-h${level}">([^<]+)<\/summary>`,"g");
|
||||
return html.replaceAll(re,`<summary class="excalidraw-setting-h${level}"><h${level}>$1</h${level}></summary>`);
|
||||
}
|
||||
|
||||
let x = replaceHeading(html,1);
|
||||
x = replaceHeading(x,2);
|
||||
x = replaceHeading(x,3);
|
||||
x = replaceHeading(x,4);
|
||||
x = x.replaceAll(/<div class="setting-item-name">([^<]+)<\/div>/g,"<h5>$1</h5>");
|
||||
|
||||
const md = htmlToMarkdown(x);
|
||||
window.navigator.clipboard.writeText(md);
|
||||
new Notice(t("SEARCH_COPIED_TO_CLIPBOARD"));
|
||||
};
|
||||
|
||||
this.searchBar.addEventListener("input", (e) => {
|
||||
this.clearHighlights();
|
||||
const searchTerm = (e.target as HTMLInputElement).value;
|
||||
|
||||
if (searchTerm && searchTerm.length > 0) {
|
||||
this.highlightSearchTerm(searchTerm);
|
||||
const totalHits = this.contentDiv.querySelectorAll("mark.search-highlight").length;
|
||||
this.hitCount.textContent = totalHits > 0 ? `1 / ${totalHits}` : "";
|
||||
setTimeout(() => this.navigateSearchResults("next"));
|
||||
} else {
|
||||
this.hitCount.textContent = "";
|
||||
}
|
||||
});
|
||||
|
||||
this.searchBar.addEventListener("keydown", (e) => {
|
||||
// If Ctrl/Cmd + F is pressed, focus on search bar
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "f") {
|
||||
e.preventDefault();
|
||||
this.searchBar.focus();
|
||||
}
|
||||
// If Enter is pressed, navigate to next result
|
||||
else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
this.navigateSearchResults(e.shiftKey ? "previous" : "next");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search bar wrapper element to add to the DOM
|
||||
*/
|
||||
public getSearchBarWrapper(): HTMLElement {
|
||||
return this.searchBarWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight all instances of the search term in the content
|
||||
*/
|
||||
public highlightSearchTerm(searchTerm: string): void {
|
||||
// Create a walker to traverse text nodes
|
||||
const walker = document.createTreeWalker(
|
||||
this.contentDiv,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
{
|
||||
acceptNode: (node: Text) => {
|
||||
return node.nodeValue!.toLowerCase().includes(searchTerm.toLowerCase()) ?
|
||||
NodeFilter.FILTER_ACCEPT :
|
||||
NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const nodesToReplace: Text[] = [];
|
||||
while (walker.nextNode()) {
|
||||
nodesToReplace.push(walker.currentNode as Text);
|
||||
}
|
||||
|
||||
nodesToReplace.forEach(node => {
|
||||
const nodeContent = node.nodeValue!;
|
||||
const newNode = document.createDocumentFragment();
|
||||
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
const regex = new RegExp(escapeRegExp(searchTerm), 'gi');
|
||||
|
||||
// Iterate over all matches in the text node
|
||||
while ((match = regex.exec(nodeContent)) !== null) {
|
||||
const before = document.createTextNode(nodeContent.slice(lastIndex, match.index));
|
||||
const highlighted = document.createElement('mark');
|
||||
highlighted.className = 'search-highlight';
|
||||
highlighted.textContent = match[0];
|
||||
highlighted.classList.add('search-result');
|
||||
|
||||
newNode.appendChild(before);
|
||||
newNode.appendChild(highlighted);
|
||||
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
newNode.appendChild(document.createTextNode(nodeContent.slice(lastIndex)));
|
||||
node.replaceWith(newNode);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all search highlights
|
||||
*/
|
||||
public clearHighlights(): void {
|
||||
this.contentDiv.querySelectorAll("mark.search-highlight").forEach((el) => {
|
||||
el.outerHTML = el.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to next or previous search result
|
||||
*/
|
||||
public navigateSearchResults(direction: "next" | "previous"): void {
|
||||
const highlights: HTMLElement[] = Array.from(
|
||||
this.contentDiv.querySelectorAll("mark.search-highlight")
|
||||
);
|
||||
|
||||
if (highlights.length === 0) return;
|
||||
|
||||
const currentActiveIndex = highlights.findIndex((highlight) =>
|
||||
highlight.classList.contains("active-highlight")
|
||||
);
|
||||
|
||||
if (currentActiveIndex !== -1) {
|
||||
highlights[currentActiveIndex].classList.remove("active-highlight");
|
||||
highlights[currentActiveIndex].style.border = "none";
|
||||
}
|
||||
|
||||
let nextActiveIndex = 0;
|
||||
if (direction === "next") {
|
||||
nextActiveIndex =
|
||||
currentActiveIndex === highlights.length - 1
|
||||
? 0
|
||||
: currentActiveIndex + 1;
|
||||
} else if (direction === "previous") {
|
||||
nextActiveIndex =
|
||||
currentActiveIndex === 0
|
||||
? highlights.length - 1
|
||||
: currentActiveIndex - 1;
|
||||
}
|
||||
|
||||
const nextActiveHighlight = highlights[nextActiveIndex];
|
||||
nextActiveHighlight.classList.add("active-highlight");
|
||||
nextActiveHighlight.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
|
||||
// Update the hit count
|
||||
this.hitCount.textContent = `${nextActiveIndex + 1} / ${highlights.length}`;
|
||||
}
|
||||
}
|
||||
122
styles.css
122
styles.css
@@ -431,25 +431,6 @@ div.excalidraw-draginfo {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content.excalidraw-scriptengine-install .search-bar-wrapper {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-wrap: nowrap;
|
||||
z-index: 10;
|
||||
background: var(--background-secondary);
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
float: right;
|
||||
max-width: 28rem;
|
||||
}
|
||||
|
||||
div.search-bar-wrapper input {
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
.modal-content.excalidraw-scriptengine-install .hit-count {
|
||||
margin-left: 0.5em;
|
||||
@@ -640,7 +621,7 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
caret-color: var(--excalidraw-caret-color);
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container, .ex-setting-actions-container {
|
||||
.excalidraw-settings-links-container {
|
||||
display: flex; /* Align SVG and text horizontally */
|
||||
align-items: center; /* Center SVG and text vertically */
|
||||
text-decoration: none; /* Remove underline from links */
|
||||
@@ -649,13 +630,7 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
gap: 0.3em;
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container {
|
||||
background-color: color-mix(in srgb, var(--color-base-100) 7%, transparent);
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container a,
|
||||
.ex-setting-actions-container a {
|
||||
.excalidraw-settings-links-container a {
|
||||
display: flex; /* Align children horizontally */
|
||||
align-items: center; /* Center items vertically */
|
||||
text-align: left;
|
||||
@@ -784,3 +759,96 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
.excalidraw-prompt-buttonbar-bottom > div:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.document-search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px 0 rgba(0,0,0,0.10);
|
||||
padding: 0.5em 0.8em;
|
||||
margin-bottom: 2em;
|
||||
min-width: 18rem;
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.document-search {
|
||||
align-items: center;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.search-input-container.document-search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
background: var(--background-primary);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-input-container .clickable-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.search-input-container input[type="text"] {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--text-normal);
|
||||
font-size: 1em;
|
||||
flex: 1 1 auto;
|
||||
padding: 0.1em 2em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.document-search-count {
|
||||
margin-left: 0.5em;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.95em;
|
||||
white-space: nowrap;
|
||||
min-width: 3.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.document-search-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.document-search-button {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
padding: 0.1em 0.2em;
|
||||
margin: 0 1px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--text-faint);
|
||||
transition: background 0.15s;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.document-search-button:hover, .document-search-button:focus {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.document-search-button svg {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user