-
Untitled
-
-
UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
Pascar - Keyboard Shortcuts
+
+
+
Menu Navigation
+
+
+ | Alt+F |
+ Open File menu |
+
+
+ | Alt+E |
+ Open Edit menu |
+
+
+ | Alt+S |
+ Open Search menu |
+
+
+ | Alt+V |
+ Open View menu |
+
+
+ | Alt+H |
+ Open Help menu |
+
+
+ | ↑ / ↓ |
+ Move through menu items |
+
+
+ | ← / → |
+ Switch between menus |
+
+
+ | Enter |
+ Activate highlighted item |
+
+
+ | Letter key |
+ Activate underlined shortcut |
+
+
+ | Escape |
+ Close menu |
+
+
+
+
File Operations
+
+
+ | Ctrl+N |
+ New file |
+
+
+ | Ctrl+O |
+ Open file from disk |
+
+
+ | Ctrl+S |
+ Save / download file |
+
+
+ | Ctrl+Shift+S |
+ Save As... |
+
+
+ | Ctrl+P |
+ Print |
+
+
+
+
Editing
+
+
+ | Ctrl+Z |
+ Undo |
+
+
+ | Ctrl+Y |
+ Redo |
+
+
+ | Ctrl+X |
+ Cut |
+
+
+ | Ctrl+C |
+ Copy |
+
+
+ | Ctrl+V |
+ Paste |
+
+
+ | Ctrl+A |
+ Select All |
+
+
+ | Del |
+ Delete selection |
+
+
+ | F5 |
+ Insert date/time |
+
+
+ | Insert |
+ Toggle Insert/Overwrite |
+
+
+ | Tab |
+ Indent (4 spaces) |
+
+
+ | Shift+Tab |
+ Unindent |
+
+
+
+
+
Search
+
+
+ | Ctrl+F |
+ Find... |
+
+
+ | Ctrl+H |
+ Find & Replace... |
+
+
+ | F3 |
+ Find Next |
+
+
+ | Shift+F3 |
+ Find Previous |
+
+
+ | Ctrl+G |
+ Go to Line... |
+
+
+
+
View
+
+
+ | Alt+W |
+ Toggle Word Wrap |
+
+
+ | Alt+L |
+ Toggle Line Numbers |
+
+
+ | Ctrl++ |
+ Zoom In |
+
+
+ | Ctrl+- |
+ Zoom Out |
+
+
+ | Ctrl+0 |
+ Reset Zoom |
+
+
+
+
Navigation
+
+
+ | Ctrl+Home |
+ Beginning of file |
+
+
+ | Ctrl+End |
+ End of file |
+
+
+ | Ctrl+Left/Right |
+ Move word by word |
+
+
+ | Home / End |
+ Start / end of line |
+
+
+ | Page Up / Down |
+ Scroll one page |
+
+
+
+
General
+
+
+ | F1 |
+ Show this help screen |
+
+
+ | Escape |
+ Close menus / dialogs |
+
+
+ | Alt+F4 |
+ Exit (close tab) |
+
+
+
+
+
+ Press Escape or F1 to close this
+ screen
+
+
+
+
+
-
-
-
+ editor.addEventListener("keydown", (e) => {
+ if (e.key === "Insert" && !e.ctrlKey && !e.altKey) {
+ insertMode = !insertMode;
+ updateStatus();
+ return;
+ }
+ if (e.key === "Tab" && !e.ctrlKey && !e.altKey) {
+ e.preventDefault();
+ const s = editor.selectionStart,
+ en = editor.selectionEnd;
+ if (e.shiftKey) {
+ const lineStart = editor.value.lastIndexOf("\n", s - 1) + 1;
+ const lineText = editor.value.substring(lineStart, s);
+ const spaces = lineText.match(/^ {1,4}/);
+ if (spaces) {
+ editor.value =
+ editor.value.substring(0, lineStart) +
+ editor.value.substring(lineStart + spaces[0].length);
+ editor.selectionStart = editor.selectionEnd =
+ s - spaces[0].length;
+ isModified = true;
+ updateAll();
+ }
+ } else {
+ editor.value =
+ editor.value.substring(0, s) +
+ " " +
+ editor.value.substring(en);
+ editor.selectionStart = editor.selectionEnd = s + 4;
+ isModified = true;
+ updateAll();
+ }
+ }
+ });
+
+ // -- Menu System
+ const ALT_MENU_MAP = {
+ f: "file",
+ e: "edit",
+ s: "search",
+ v: "view",
+ h: "help",
+ };
+
+ function getOpenDropdown() {
+ return document.querySelector(".dropdown.open") || null;
+ }
+
+ // Position the dropdown directly below its menu-item trigger
+ function positionDropdown(menuName) {
+ const item = document.querySelector(
+ `.menu-item[data-menu="${menuName}"]`,
+ );
+ const dd = document.getElementById("menu-" + menuName);
+ if (!item || !dd) return;
+ const itemRect = item.getBoundingClientRect();
+ const barRect = menuBar.getBoundingClientRect();
+ dd.style.left = itemRect.left - barRect.left + "px";
+ }
+
+ function openMenu(name) {
+ const item = document.querySelector(
+ `.menu-item[data-menu="${name}"]`,
+ );
+ const dd = document.getElementById("menu-" + name);
+ if (!item || !dd) return;
+ document
+ .querySelectorAll(".dropdown")
+ .forEach((d) => d.classList.remove("open"));
+ document
+ .querySelectorAll(".menu-item")
+ .forEach((m) => m.classList.remove("active"));
+ positionDropdown(name);
+ dd.classList.add("open");
+ item.classList.add("active");
+ highlightDDItem(dd, 0);
+ }
+
+ function getDDItems(dd) {
+ return Array.from(dd.querySelectorAll(".dd-item:not(.disabled)"));
+ }
+
+ function highlightDDItem(dd, index) {
+ const items = getDDItems(dd);
+ items.forEach((i) => i.classList.remove("dd-focused"));
+ if (items.length === 0) return;
+ const idx = ((index % items.length) + items.length) % items.length;
+ items[idx].classList.add("dd-focused");
+ }
+
+ function getFocusedIndex(dd) {
+ const items = getDDItems(dd);
+ return items.findIndex((i) => i.classList.contains("dd-focused"));
+ }
+
+ document.querySelectorAll(".menu-item").forEach((item) => {
+ item.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const name = item.dataset.menu;
+ const dd = document.getElementById("menu-" + name);
+ const wasOpen = dd.classList.contains("open");
+ document
+ .querySelectorAll(".dropdown")
+ .forEach((d) => d.classList.remove("open"));
+ document
+ .querySelectorAll(".menu-item")
+ .forEach((m) => m.classList.remove("active"));
+ if (!wasOpen) {
+ positionDropdown(name);
+ dd.classList.add("open");
+ item.classList.add("active");
+ }
+ });
+ item.addEventListener("mouseenter", () => {
+ const anyOpen = document.querySelector(".dropdown.open");
+ if (anyOpen) {
+ const name = item.dataset.menu;
+ document
+ .querySelectorAll(".dropdown")
+ .forEach((d) => d.classList.remove("open"));
+ document
+ .querySelectorAll(".menu-item")
+ .forEach((m) => m.classList.remove("active"));
+ positionDropdown(name);
+ document.getElementById("menu-" + name).classList.add("open");
+ item.classList.add("active");
+ }
+ });
+ });
+
+ document.querySelectorAll(".dd-item:not(.disabled)").forEach((item) => {
+ item.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ closeAll();
+ handleAction(item.dataset.action);
+ });
+ });
+
+ document.addEventListener("click", (e) => {
+ if (e.target.closest("#menu-bar")) closeAll();
+ });
+
+ // -- Global Keyboard Shortcuts
+ document.addEventListener("keydown", (e) => {
+ const ctrl = e.ctrlKey || e.metaKey;
+ const shift = e.shiftKey;
+ const alt = e.altKey;
+ const key = e.key.toLowerCase();
+
+ if (alt && !ctrl && ALT_MENU_MAP[key]) {
+ e.preventDefault();
+ const dd = getOpenDropdown();
+ if (dd && dd.id === "menu-" + ALT_MENU_MAP[key]) {
+ closeAll();
+ } else {
+ openMenu(ALT_MENU_MAP[key]);
+ }
+ return;
+ }
+
+ const openDD = getOpenDropdown();
+ if (openDD) {
+ if (e.key === "ArrowDown") {
+ e.preventDefault();
+ highlightDDItem(openDD, getFocusedIndex(openDD) + 1);
+ return;
+ }
+ if (e.key === "ArrowUp") {
+ e.preventDefault();
+ const idx = getFocusedIndex(openDD);
+ highlightDDItem(
+ openDD,
+ idx <= 0 ? getDDItems(openDD).length - 1 : idx - 1,
+ );
+ return;
+ }
+ if (e.key === "ArrowLeft") {
+ e.preventDefault();
+ const menus = ["file", "edit", "search", "view", "help"];
+ const cur = openDD.id.replace("menu-", "");
+ openMenu(
+ menus[(menus.indexOf(cur) - 1 + menus.length) % menus.length],
+ );
+ return;
+ }
+ if (e.key === "ArrowRight") {
+ e.preventDefault();
+ const menus = ["file", "edit", "search", "view", "help"];
+ const cur = openDD.id.replace("menu-", "");
+ openMenu(menus[(menus.indexOf(cur) + 1) % menus.length]);
+ return;
+ }
+ if (e.key === "Enter") {
+ e.preventDefault();
+ const focused = openDD.querySelector(".dd-item.dd-focused");
+ if (
+ findBar.classList.contains("open") &&
+ gotoBar.classList.contains("open") &&
+ helpOverlay.classList.contains("open") &&
+ dialogOv.classList.contains("open")
+ ) {
+ e.preventDefault();
+ e.stopPropagation();
+ return;
+ }
+ if (focused && focused.dataset.action) {
+ const action = focused.dataset.action;
+ closeAll();
+ handleAction(action);
+ }
+ return;
+ }
+ if (!ctrl && !alt && e.key.length === 1) {
+ const letter = e.key.toLowerCase();
+ const match = getDDItems(openDD).find((item) => {
+ const ul = item.querySelector(".ul");
+ return ul && ul.textContent.toLowerCase() === letter;
+ });
+ if (match && match.dataset.action) {
+ e.preventDefault();
+ const action = match.dataset.action;
+ closeAll();
+ handleAction(action);
+ return;
+ }
+ }
+ }
+
+ if (e.key === "Escape") {
+ e.preventDefault();
+ closeAll();
+ return;
+ }
+ if (e.key === "F1") {
+ e.preventDefault();
+ toggleHelp();
+ return;
+ }
+ if (e.key === "F3") {
+ e.preventDefault();
+ doFindNext(shift ? -1 : 1);
+ return;
+ }
+ if (e.key === "F5" && !ctrl && !alt) {
+ e.preventDefault();
+ insertDateTime();
+ return;
+ }
+
+ if (ctrl && key === "n") {
+ e.preventDefault();
+ doNew();
+ }
+ if (ctrl && key === "o") {
+ e.preventDefault();
+ doOpen();
+ }
+ if (ctrl && key === "s" && shift) {
+ e.preventDefault();
+ doSaveAs();
+ } else if (ctrl && key === "s") {
+ e.preventDefault();
+ doSave();
+ }
+ if (ctrl && key === "p") {
+ e.preventDefault();
+ doPrint();
+ }
+ if (ctrl && key === "f") {
+ e.preventDefault();
+ openFindBar(false);
+ }
+ if (ctrl && key === "h") {
+ e.preventDefault();
+ openFindBar(true);
+ }
+ if (ctrl && key === "g") {
+ e.preventDefault();
+ openGoto();
+ }
+ if (ctrl && (key === "=" || key === "+")) {
+ e.preventDefault();
+ zoomChange(1);
+ }
+ if (ctrl && key === "-") {
+ e.preventDefault();
+ zoomChange(-1);
+ }
+ if (ctrl && key === "0") {
+ e.preventDefault();
+ zoomReset();
+ }
+ if (alt && key === "w") {
+ e.preventDefault();
+ toggleWordWrap();
+ }
+ if (alt && key === "l") {
+ e.preventDefault();
+ toggleLineNumbers();
+ }
+ });
+
+ // -- Close All Overlays
+ function closeAll() {
+ document
+ .querySelectorAll(".dropdown")
+ .forEach((d) => d.classList.remove("open"));
+ document
+ .querySelectorAll(".menu-item")
+ .forEach((m) => m.classList.remove("active"));
+ dialogOv.classList.remove("open");
+ helpOverlay.classList.remove("open");
+ findBar.classList.remove("open");
+ gotoBar.classList.remove("open");
+ editor.focus();
+ }
+
+ function toggleHelp() {
+ if (helpOverlay.classList.contains("open")) closeAll();
+ else {
+ closeAll();
+ helpOverlay.classList.add("open");
+ }
+ }
+
+ // -- Action Dispatcher
+ function handleAction(a) {
+ const map = {
+ new: doNew,
+ open: doOpen,
+ save: doSave,
+ saveas: doSaveAs,
+ print: doPrint,
+ exit: () => showNotif("Close this browser tab to exit."),
+ undo: () => {
+ editor.focus();
+ document.execCommand("undo");
+ updateAll();
+ },
+ redo: () => {
+ editor.focus();
+ document.execCommand("redo");
+ updateAll();
+ },
+ cut: () => {
+ editor.focus();
+ document.execCommand("cut");
+ },
+ copy: () => {
+ editor.focus();
+ document.execCommand("copy");
+ },
+ paste: () => {
+ editor.focus();
+ document.execCommand("paste");
+ },
+ delete: doDelete,
+ selectall: () => {
+ editor.select();
+ },
+ timestamp: insertDateTime,
+ find: () => openFindBar(false),
+ replace: () => openFindBar(true),
+ findnext: () => doFindNext(1),
+ findprev: () => doFindNext(-1),
+ goto: openGoto,
+ wordwrap: toggleWordWrap,
+ linenumbers: toggleLineNumbers,
+ zoomin: () => zoomChange(1),
+ zoomout: () => zoomChange(-1),
+ zoomreset: zoomReset,
+ shortcuts: toggleHelp,
+ about: showAbout,
+ };
+ if (map[a]) map[a]();
+ }
+
+ // -- File Operations
+ let fileHandle = null;
+ const hasFS = typeof window.showOpenFilePicker === "function";
+
+ function doNew() {
+ if (isModified && !confirm("Discard unsaved changes?")) return;
+ editor.value = "";
+ fileName = "Untitled";
+ fileHandle = null;
+ isModified = false;
+ updateAll();
+ editor.focus();
+ }
+
+ async function doOpen() {
+ if (isModified && !confirm("Discard unsaved changes?")) return;
+ if (hasFS) {
+ try {
+ const [handle] = await window.showOpenFilePicker({
+ types: [
+ {
+ description: "Text files",
+ accept: {
+ "text/*": [
+ ".txt",
+ ".md",
+ ".js",
+ ".ts",
+ ".py",
+ ".html",
+ ".css",
+ ".json",
+ ".xml",
+ ".csv",
+ ".log",
+ ".c",
+ ".cpp",
+ ".h",
+ ".java",
+ ".rs",
+ ".go",
+ ".rb",
+ ".sh",
+ ".bat",
+ ".ini",
+ ".cfg",
+ ".yaml",
+ ".yml",
+ ],
+ },
+ },
+ ],
+ multiple: false,
+ });
+ fileHandle = handle;
+ const file = await handle.getFile();
+ fileName = file.name;
+ editor.value = await file.text();
+ isModified = false;
+ updateAll();
+ editor.focus();
+ showNotif(`Opened: ${fileName}`);
+ } catch (err) {
+ if (err.name !== "AbortError") showNotif("Could not open file.");
+ }
+ } else {
+ const inp = document.createElement("input");
+ inp.type = "file";
+ inp.accept =
+ ".txt,.md,.js,.ts,.py,.html,.css,.json,.xml,.csv,.log,.c,.cpp,.h,.java,.rs,.go,.rb,.sh,.bat,.ini,.cfg,.yaml,.yml,text/*";
+ inp.onchange = (ev) => {
+ const file = ev.target.files[0];
+ if (!file) return;
+ fileHandle = null;
+ fileName = file.name;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ editor.value = e.target.result;
+ isModified = false;
+ updateAll();
+ editor.focus();
+ showNotif(`Opened: ${fileName}`);
+ };
+ reader.readAsText(file);
+ };
+ inp.click();
+ }
+ }
+
+ async function doSave() {
+ if (hasFS && fileHandle) {
+ try {
+ const writable = await fileHandle.createWritable();
+ await writable.write(editor.value);
+ await writable.close();
+ isModified = false;
+ updateStatus();
+ showNotif(`Saved: ${fileName}`);
+ } catch (err) {
+ if (err.name !== "AbortError")
+ showNotif("Save failed: " + err.message);
+ }
+ } else if (hasFS && !fileHandle) {
+ await doSaveAs();
+ } else {
+ _downloadFile(fileName === "Untitled" ? "untitled.txt" : fileName);
+ }
+ }
+
+ async function doSaveAs() {
+ if (hasFS) {
+ try {
+ const handle = await window.showSaveFilePicker({
+ suggestedName:
+ fileName === "Untitled" ? "untitled.txt" : fileName,
+ types: [
+ {
+ description: "Text files",
+ accept: {
+ "text/plain": [
+ ".txt",
+ ".md",
+ ".js",
+ ".ts",
+ ".py",
+ ".html",
+ ".css",
+ ".json",
+ ".xml",
+ ".csv",
+ ".log",
+ ],
+ },
+ },
+ ],
+ });
+ fileHandle = handle;
+ fileName = handle.name;
+ const writable = await handle.createWritable();
+ await writable.write(editor.value);
+ await writable.close();
+ isModified = false;
+ updateStatus();
+ showNotif(`Saved: ${fileName}`);
+ } catch (err) {
+ if (err.name !== "AbortError")
+ showNotif("Save failed: " + err.message);
+ }
+ } else {
+ showDialog(
+ "Save As",
+ `
+
+
+
File System Access API not supported in this browser.
File will be downloaded instead.
+
+
+
+
+ `,
+ () => {
+ const inp = document.getElementById("d-savename");
+ inp.focus();
+ inp.select();
+ document
+ .getElementById("d-saveas-ok")
+ .addEventListener("click", () => {
+ const name = inp.value.trim();
+ if (name) {
+ fileName = name;
+ closeAll();
+ _downloadFile(name);
+ }
+ });
+ inp.addEventListener("keydown", (e) => {
+ if (e.key === "Enter")
+ document.getElementById("d-saveas-ok").click();
+ });
+ document
+ .getElementById("d-saveas-cancel")
+ .addEventListener("click", closeAll);
+ },
+ );
+ }
+ }
+
+ function _downloadFile(name) {
+ const blob = new Blob([editor.value], {
+ type: "text/plain;charset=utf-8",
+ });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = name;
+ a.click();
+ URL.revokeObjectURL(url);
+ isModified = false;
+ updateStatus();
+ showNotif(`Downloaded: ${name}`);
+ }
+
+ function doPrint() {
+ window.print();
+ }
+
+ function doDelete() {
+ const s = editor.selectionStart,
+ en = editor.selectionEnd;
+ if (s !== en) {
+ editor.value =
+ editor.value.substring(0, s) + editor.value.substring(en);
+ editor.selectionStart = editor.selectionEnd = s;
+ isModified = true;
+ updateAll();
+ }
+ }
+
+ function insertDateTime() {
+ const now = new Date();
+ const ts = now.toLocaleString();
+ const s = editor.selectionStart,
+ en = editor.selectionEnd;
+ editor.value =
+ editor.value.substring(0, s) + ts + editor.value.substring(en);
+ editor.selectionStart = editor.selectionEnd = s + ts.length;
+ isModified = true;
+ updateAll();
+ editor.focus();
+ }
+
+ // -- View
+ function toggleWordWrap() {
+ wordWrap = !wordWrap;
+ editor.style.whiteSpace = wordWrap ? "pre-wrap" : "pre";
+ showNotif("Word Wrap: " + (wordWrap ? "ON" : "OFF"));
+ }
+
+ function toggleLineNumbers() {
+ showLN = !showLN;
+ updateLineNumbers();
+ showNotif("Line Numbers: " + (showLN ? "ON" : "OFF"));
+ }
+
+ function zoomChange(dir) {
+ fontSize = Math.min(32, Math.max(9, fontSize + dir * 2));
+ applyZoom();
+ }
+ function zoomReset() {
+ fontSize = 15;
+ applyZoom();
+ }
+ function applyZoom() {
+ editor.style.fontSize = fontSize + "px";
+ lineNums.style.fontSize = fontSize + "px";
+ editor.style.lineHeight = lineNums.style.lineHeight = "1.25";
+ updateLineNumbers();
+ showNotif(`Zoom: ${fontSize}px`);
+ }
+
+ // -- Find / Replace Bar
+ function openFindBar(withReplace) {
+ replaceMode = withReplace;
+ closeAll();
+ findBar.classList.add("open");
+ document.getElementById("replace-label").style.display = withReplace
+ ? ""
+ : "none";
+ replInput.style.display = withReplace ? "" : "none";
+ document.getElementById("btn-replace-one").style.display = withReplace
+ ? ""
+ : "none";
+ document.getElementById("btn-replace-all").style.display = withReplace
+ ? ""
+ : "none";
+ findInput.value = lastSearch;
+ replInput.value = lastReplace;
+ findStatus.textContent = "";
+ findInput.focus();
+ findInput.select();
+ }
+
+ findInput.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ e.stopPropagation();
+ syncSearch();
+ doFindNext(e.shiftKey ? -1 : 1);
+ }
+ if (e.key === "Escape") closeAll();
+ });
+ replInput.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") closeAll();
+ });
+
+ document.getElementById("btn-find-next").onclick = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ syncSearch();
+ doFindNext(1);
+ };
+ document.getElementById("btn-find-prev").onclick = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ syncSearch();
+ doFindNext(-1);
+ };
+ document.getElementById("btn-replace-one").onclick = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ syncSearch();
+ doReplaceOne();
+ };
+ document.getElementById("btn-replace-all").onclick = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ syncSearch();
+ doReplaceAll();
+ };
+ document.getElementById("btn-find-close").onclick = closeAll;
+
+ function syncSearch() {
+ lastSearch = findInput.value;
+ lastReplace = replInput.value;
+ }
+
+ function buildRegex(global) {
+ if (!lastSearch) return null;
+ let flags = global ? "g" : "";
+ if (!findCase.checked) flags += "i";
+ let pattern = lastSearch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ if (findWhole.checked) pattern = "\\b" + pattern + "\\b";
+ try {
+ return new RegExp(pattern, flags);
+ } catch {
+ return null;
+ }
+ }
+
+ function doFindNext(dir) {
+ if (!lastSearch) {
+ openFindBar(false);
+ return;
+ }
+ const rx = buildRegex(true);
+ if (!rx) {
+ showNotif("Invalid search pattern.");
+ return;
+ }
+ const val = editor.value;
+ const matches = [];
+ let m;
+ while ((m = rx.exec(val)) !== null)
+ matches.push({ i: m.index, len: m[0].length });
+ if (!matches.length) {
+ findStatus.textContent = "Not found";
+ showNotif(`"${lastSearch}" not found.`);
+ return;
+ }
+ const pos =
+ dir === 1 ? editor.selectionEnd : editor.selectionStart - 1;
+ let found;
+ if (dir === 1) {
+ found = matches.find((x) => x.i >= pos) || matches[0];
+ } else {
+ found =
+ [...matches].reverse().find((x) => x.i <= pos) ||
+ matches[matches.length - 1];
+ }
+ editor.setSelectionRange(found.i, found.i + found.len);
+ editor.focus();
+ findStatus.textContent = `${matches.indexOf(found) + 1} / ${matches.length}`;
+ scrollToSelection();
+ updateStatus();
+ }
+
+ function doReplaceOne() {
+ const s = editor.selectionStart;
+ const en = editor.selectionEnd;
+ const rx = buildRegex(false);
+ if (!rx) return;
+ const sel = editor.value.substring(s, en);
+ if (rx.test(sel)) {
+ editor.value =
+ editor.value.substring(0, s) +
+ lastReplace +
+ editor.value.substring(en);
+ editor.setSelectionRange(s, s + lastReplace.length);
+ isModified = true;
+ updateAll();
+ }
+ doFindNext(1);
+ }
+
+ function doReplaceAll() {
+ const rx = buildRegex(true);
+ if (!rx) return;
+ const orig = editor.value;
+ const count = (orig.match(rx) || []).length;
+ if (!count) {
+ findStatus.textContent = "Not found";
+ showNotif(`"${lastSearch}" not found.`);
+ return;
+ }
+ editor.value = orig.replace(rx, lastReplace);
+ isModified = true;
+ updateAll();
+ findStatus.textContent = `Replaced ${count}`;
+ showNotif(`Replaced ${count} occurrence(s).`);
+ }
+
+ function scrollToSelection() {
+ const lines = editor.value
+ .substring(0, editor.selectionStart)
+ .split("\n");
+ const lineH =
+ editor.scrollHeight / Math.max(1, editor.value.split("\n").length);
+ editor.scrollTop = Math.max(0, (lines.length - 5) * lineH);
+ }
+
+ // -- Go to Line
+ function openGoto() {
+ closeAll();
+ gotoBar.classList.add("open");
+ gotoInput.value = "";
+ gotoInput.focus();
+ }
+
+ document.getElementById("goto-ok").onclick = execGoto;
+ document.getElementById("goto-cancel").onclick = () => {
+ gotoBar.classList.remove("open");
+ editor.focus();
+ };
+ gotoInput.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") execGoto();
+ if (e.key === "Escape") {
+ gotoBar.classList.remove("open");
+ editor.focus();
+ }
+ });
+
+ function execGoto() {
+ const n = parseInt(gotoInput.value, 10);
+ if (isNaN(n) || n < 1) {
+ showNotif("Enter a valid line number.");
+ return;
+ }
+ const lines = editor.value.split("\n");
+ const lineN = Math.min(n, lines.length);
+ let pos = 0;
+ for (let i = 0; i < lineN - 1; i++) pos += lines[i].length + 1;
+ editor.setSelectionRange(pos, pos);
+ editor.focus();
+ gotoBar.classList.remove("open");
+ updateStatus();
+ const lineH = editor.scrollHeight / Math.max(1, lines.length);
+ editor.scrollTop = Math.max(0, (lineN - 5) * lineH);
+ showNotif(`Jumped to line ${lineN}`);
+ }
+
+ // -- Dialog Helper
+ function showDialog(title, bodyHTML, onReady) {
+ dialogTitle.textContent = title;
+ dialogBody.innerHTML = bodyHTML;
+ dialogOv.classList.add("open");
+ dialogOv.addEventListener("keydown", (e) => {
+ e.preventDefault();
+ });
+ dialogOv.addEventListener("click", (e) => {
+ e.preventDefault();
+ });
+ if (onReady) onReady();
+ }
+
+ // -- About Dialog
+ function showAbout(e) {
+ showDialog(
+ "About Pascar",
+ `
+
+
Pascar
+
Browser based offline text editor
+
Inspired by the classic MS-DOS editor
+
+ Runs 100% offline in your browser.
+ No server. No data sent anywhere.
+ Open & save files via your local filesystem.
+
+
+
+
+ `,
+ () => {
+ document
+ .getElementById("d-about-ok")
+ .addEventListener("click", closeAll);
+ },
+ );
+ }
+
+ // -- Utility
+ function escHtml(s) {
+ return s
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+ }
+
+ window.addEventListener(
+ "keydown",
+ (e) => {
+ if (
+ (e.ctrlKey || e.metaKey) &&
+ (e.key === "0" || e.key === "+" || e.key === "=" || e.key === "-")
+ ) {
+ e.preventDefault();
+ }
+ },
+ { capture: true },
+ );
+
+ window.addEventListener("beforeunload", (e) => {
+ if (isModified) {
+ e.preventDefault();
+ e.returnValue = "";
+ }
+ });
+
+ // -- Service Worker to make this application work offline
+ if ("serviceWorker" in navigator) {
+ window.addEventListener("load", () => {
+ navigator.serviceWorker
+ .register("sw.js")
+ .then((reg) => console.log("Service Worker Registered"))
+ .catch((err) => console.log("Registration Failed", err));
+ });
+ }
+ })();
+
-
+