new funtions and fixes
This commit is contained in:
@@ -11,10 +11,10 @@ export async function prepareImageFile(file){
|
||||
const dialog=document.createElement("dialog");
|
||||
dialog.className="image-editor-dialog";
|
||||
dialog.innerHTML=`<form method="dialog" class="image-editor-panel">
|
||||
<div class="image-editor-head"><div><h2>Adjust image</h2><p>Drag to crop, use zoom and choose output size.</p></div><button class="icon-button" value="cancel" aria-label="Close">×</button></div>
|
||||
<div class="image-editor-head"><div><h2>Adjust image</h2><p>Keep the whole image or choose a crop, then select output size.</p></div><button class="icon-button" value="cancel" aria-label="Close">×</button></div>
|
||||
<div class="image-crop-stage"><canvas></canvas></div>
|
||||
<div class="image-editor-controls">
|
||||
<label>Crop<select data-aspect><option value="free">Free</option><option value="1">Square</option><option value="1.333333">4:3</option><option value="1.777778">16:9</option></select></label>
|
||||
<label>Crop<select data-aspect><option value="original" selected>Whole image</option><option value="free">Free</option><option value="1">Square</option><option value="1.333333">4:3</option><option value="1.777778">16:9</option></select></label>
|
||||
<label>Zoom<input data-zoom type="range" min="1" max="3" value="1" step="0.01"></label>
|
||||
<label>Max size<select data-size><option value="320">320 px</option><option value="480">480 px</option><option value="640">640 px</option><option value="800">800 px</option><option value="1000">1000 px</option><option value="1200">1200 px</option><option value="1600" selected>1600 px</option><option value="2000">2000 px</option><option value="0">Original</option></select></label>
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@ export async function prepareImageFile(file){
|
||||
function cropBox(){
|
||||
const rect=stage.getBoundingClientRect();
|
||||
let width=Math.max(280,rect.width),height=Math.min(520,Math.max(260,rect.height));
|
||||
const aspect=aspectSelect.value==="free"?width/height:Number(aspectSelect.value);
|
||||
const aspect=aspectSelect.value==="original"?image.naturalWidth/image.naturalHeight:aspectSelect.value==="free"?width/height:Number(aspectSelect.value);
|
||||
if(width/height>aspect)width=height*aspect;else height=width/aspect;
|
||||
return {width:Math.round(width),height:Math.round(height)};
|
||||
}
|
||||
@@ -35,7 +35,9 @@ export async function prepareImageFile(file){
|
||||
const box=cropBox(),dpr=Math.min(devicePixelRatio||1,2);
|
||||
canvas.width=Math.round(box.width*dpr);canvas.height=Math.round(box.height*dpr);canvas.style.width=`${box.width}px`;canvas.style.height=`${box.height}px`;
|
||||
ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,box.width,box.height);
|
||||
const base=Math.max(box.width/image.naturalWidth,box.height/image.naturalHeight),scale=base*Number(zoomInput.value);
|
||||
const wholeImage=aspectSelect.value==="original";
|
||||
zoomInput.disabled=wholeImage;
|
||||
const base=wholeImage?Math.min(box.width/image.naturalWidth,box.height/image.naturalHeight):Math.max(box.width/image.naturalWidth,box.height/image.naturalHeight),scale=base*(wholeImage?1:Number(zoomInput.value));
|
||||
const drawW=image.naturalWidth*scale,drawH=image.naturalHeight*scale;
|
||||
const maxX=Math.max(0,(drawW-box.width)/2),maxY=Math.max(0,(drawH-box.height)/2);
|
||||
offsetX=clamp(offsetX,-maxX,maxX);offsetY=clamp(offsetY,-maxY,maxY);
|
||||
@@ -50,8 +52,16 @@ export async function prepareImageFile(file){
|
||||
const result=new Promise(resolve=>{
|
||||
dialog.addEventListener("close",()=>{URL.revokeObjectURL(url);dialog.remove();resolve(accepted);},{once:true});
|
||||
dialog.querySelector("[data-apply]").addEventListener("click",async()=>{
|
||||
const box=cropBox(),maxSize=Number(sizeSelect.value),ratio=Math.min(1,maxSize?maxSize/Math.max(box.width,box.height):1),out=document.createElement("canvas");
|
||||
out.width=Math.max(1,Math.round(box.width*ratio));out.height=Math.max(1,Math.round(box.height*ratio));out.getContext("2d").drawImage(canvas,0,0,out.width,out.height);
|
||||
const box=cropBox(),maxSize=Number(sizeSelect.value),out=document.createElement("canvas");
|
||||
if(aspectSelect.value==="original"){
|
||||
const ratio=Math.min(1,maxSize?maxSize/Math.max(image.naturalWidth,image.naturalHeight):1);
|
||||
out.width=Math.max(1,Math.round(image.naturalWidth*ratio));out.height=Math.max(1,Math.round(image.naturalHeight*ratio));
|
||||
out.getContext("2d").drawImage(image,0,0,out.width,out.height);
|
||||
}else{
|
||||
const ratio=Math.min(1,maxSize?maxSize/Math.max(box.width,box.height):1);
|
||||
out.width=Math.max(1,Math.round(box.width*ratio));out.height=Math.max(1,Math.round(box.height*ratio));
|
||||
out.getContext("2d").drawImage(canvas,0,0,out.width,out.height);
|
||||
}
|
||||
const mime=file.type==="image/png"?"image/png":"image/jpeg";
|
||||
const blob=await new Promise(r=>out.toBlob(r,mime,mime==="image/jpeg"?.88:undefined));
|
||||
const ext=mime==="image/png"?"png":"jpg";
|
||||
|
||||
+120
-8
@@ -54,6 +54,41 @@ function inline(value) {
|
||||
return html.replace(/\u0000T(\d+)\u0000/g, (_, index) => tokens[Number(index)] || "");
|
||||
}
|
||||
|
||||
|
||||
const languageAliases = {
|
||||
js: "javascript", javascript: "javascript", jsx: "javascript",
|
||||
ts: "typescript", typescript: "typescript", tsx: "typescript",
|
||||
py: "python", python: "python",
|
||||
rb: "ruby", ruby: "ruby",
|
||||
rs: "rust", rust: "rust",
|
||||
php: "php",
|
||||
sh: "bash", shell: "bash", bash: "bash", zsh: "bash",
|
||||
c: "c", h: "c",
|
||||
cpp: "cpp", "c++": "cpp", cxx: "cpp", hpp: "cpp",
|
||||
cs: "csharp", "c#": "csharp", csharp: "csharp",
|
||||
java: "java", kotlin: "kotlin", kt: "kotlin",
|
||||
go: "go", golang: "go",
|
||||
swift: "swift", dart: "dart", scala: "scala",
|
||||
html: "html", htm: "html", xml: "xml", svg: "xml",
|
||||
css: "css", scss: "scss", sass: "scss", less: "less",
|
||||
json: "json", jsonc: "json", yaml: "yaml", yml: "yaml", toml: "ini", ini: "ini",
|
||||
sql: "sql", graphql: "graphql", gql: "graphql",
|
||||
md: "markdown", markdown: "markdown",
|
||||
dockerfile: "dockerfile", docker: "dockerfile",
|
||||
makefile: "makefile", make: "makefile",
|
||||
powershell: "powershell", ps1: "powershell",
|
||||
lua: "lua", perl: "perl", pl: "perl", r: "r", matlab: "matlab",
|
||||
nginx: "nginx", apache: "apache", diff: "diff", patch: "diff",
|
||||
text: "plaintext", txt: "plaintext", plaintext: "plaintext", none: "plaintext",
|
||||
mermaid: "mermaid"
|
||||
};
|
||||
|
||||
function normalizeLanguage(value) {
|
||||
const language = String(value || "").trim().toLowerCase();
|
||||
if (!language) return "";
|
||||
return languageAliases[language] || language.replace(/[^a-z0-9_-]/g, "");
|
||||
}
|
||||
|
||||
const attrs = (line, editable = false, prefix = "", suffix = "", lineOffset = 0) => ` class="preview-source-line${editable ? " preview-editable" : ""}" data-source-line="${line + lineOffset + 1}"${editable ? ` contenteditable="true" spellcheck="true" data-source-prefix="${escapeHtml(prefix)}" data-source-suffix="${escapeHtml(suffix)}"` : ""}`;
|
||||
const isPlainText = line => !/[`*_~^=\[\]<>|:#]/.test(line) && !/^\s*(?:[-+*>]|\d+\.)\s/.test(line);
|
||||
|
||||
@@ -70,9 +105,44 @@ function tableDelimiter(line) {
|
||||
return cells.map(cell => cell.startsWith(":") && cell.endsWith(":") ? "center" : cell.endsWith(":") ? "right" : "left");
|
||||
}
|
||||
|
||||
function headingSlug(value) {
|
||||
return String(value)
|
||||
.replace(/\{#[A-Za-z][\w:.-]*\}\s*$/, "")
|
||||
.replace(/[`*_~^=<>]/g, "")
|
||||
.replace(/:([a-z0-9_+-]+):/gi, "$1")
|
||||
.toLowerCase().trim()
|
||||
.replace(/[^a-z0-9\u00c0-\u024f\u1e00-\u1eff]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "") || "section";
|
||||
}
|
||||
|
||||
function collectHeadings(lines) {
|
||||
const used = new Map();
|
||||
const headings = [];
|
||||
let fence = null;
|
||||
lines.forEach((line, index) => {
|
||||
const fenceMatch = line.match(/^\s*(```+|~~~+)/);
|
||||
if (fenceMatch) {
|
||||
if (!fence) fence = fenceMatch[1];
|
||||
else if (fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) fence = null;
|
||||
return;
|
||||
}
|
||||
if (fence) return;
|
||||
const match = line.match(/^\s{0,4}(#{1,6})\s+(.+?)(?:\s+\{#([A-Za-z][\w:.-]*)\})?\s*$/);
|
||||
if (!match) return;
|
||||
const base = match[3] || headingSlug(match[2]);
|
||||
const count = used.get(base) || 0;
|
||||
used.set(base, count + 1);
|
||||
const id = count ? `${base}-${count + 1}` : base;
|
||||
headings.push({level: match[1].length, text: match[2], id, index});
|
||||
});
|
||||
return headings;
|
||||
}
|
||||
|
||||
export function renderMarkdown(source, lineOffset = 0) {
|
||||
let html = "", inCode = false, fence = "", language = "", code = [], codeStart = 0, list = null;
|
||||
let html = "", inCode = false, fence = "", language = "", codeLineStart = null, code = [], codeStart = 0, list = null;
|
||||
const lines = String(source).split("\n");
|
||||
const headings = collectHeadings(lines);
|
||||
const headingByLine = new Map(headings.map(item => [item.index, item]));
|
||||
const footnotes = new Map();
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
@@ -92,10 +162,18 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
const closeList = () => { if (list) { html += `</${list}>`; list = null; } };
|
||||
const closeCode = () => {
|
||||
const body = escapeHtml(code.join("\n"));
|
||||
html += language.toLowerCase() === "mermaid"
|
||||
? `<div class="mermaid preview-source-line" data-source-line="${codeStart + lineOffset + 1}">${body}</div>`
|
||||
: `<pre${attrs(codeStart, false, "", "", lineOffset)}><code class="language-${escapeHtml(language)}">${body}</code></pre>`;
|
||||
code = []; language = ""; fence = "";
|
||||
const lang = normalizeLanguage(language);
|
||||
if (lang === "mermaid") {
|
||||
html += `<div class="mermaid preview-source-line" data-source-line="${codeStart + lineOffset + 1}">${body}</div>`;
|
||||
} else if (codeLineStart !== null) {
|
||||
const numbered = body.split("\n").map((line, index) => `<span class="code-line" data-line="${codeLineStart + index}">${line || " "}</span>`).join("\n");
|
||||
const languageClass = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
||||
html += `<pre${attrs(codeStart, false, "", "", lineOffset).replace(' class="', ' class="code-with-lines ')}><code${languageClass}>${numbered}</code></pre>`;
|
||||
} else {
|
||||
const languageClass = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
||||
html += `<pre${attrs(codeStart, false, "", "", lineOffset)}><code${languageClass}>${body}</code></pre>`;
|
||||
}
|
||||
code = []; language = ""; codeLineStart = null; fence = "";
|
||||
};
|
||||
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
@@ -104,7 +182,16 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
if (fenceMatch) {
|
||||
closeList();
|
||||
if (inCode && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) closeCode();
|
||||
else if (!inCode) { fence = fenceMatch[1]; language = fenceMatch[2] || ""; codeStart = index; }
|
||||
else if (!inCode) {
|
||||
fence = fenceMatch[1];
|
||||
const info = fenceMatch[2] || "";
|
||||
// Generic syntax for every fenced code block:
|
||||
// ```rust=, ```python=101, ```= or ```=101.
|
||||
const numbered = info.match(/^(.*?)=(\d*)$/);
|
||||
language = numbered ? numbered[1] : info;
|
||||
codeLineStart = numbered ? Number(numbered[2] || 1) : null;
|
||||
codeStart = index;
|
||||
}
|
||||
inCode = !inCode;
|
||||
continue;
|
||||
}
|
||||
@@ -149,6 +236,30 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
if (/^\s*\[TOC\]\s*$/i.test(line)) {
|
||||
closeList();
|
||||
if (headings.length) {
|
||||
html += `<nav class="markdown-toc preview-source-line" data-source-line="${index + lineOffset + 1}" aria-label="Table of contents"><ol>`;
|
||||
headings.forEach(item => html += `<li class="toc-level-${item.level}"><a href="#${escapeHtml(item.id)}">${inline(item.text)}</a></li>`);
|
||||
html += `</ol></nav>`;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const alertStart = line.match(/^\s*:::(success|info|warning|danger)\s*$/i);
|
||||
if (alertStart) {
|
||||
closeList();
|
||||
let end = index + 1;
|
||||
while (end < lines.length && !/^\s*:::\s*$/.test(lines[end])) end++;
|
||||
if (end < lines.length) {
|
||||
const type = alertStart[1].toLowerCase();
|
||||
const body = lines.slice(index + 1, end).join("\n");
|
||||
html += `<aside class="markdown-alert markdown-alert--${type}" role="note">${renderMarkdown(body, lineOffset + index + 1)}</aside>`;
|
||||
index = end;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const heading = line.match(/^\s{0,4}(#{1,6})\s+(.+?)(?:\s+\{#([A-Za-z][\w:.-]*)\})?\s*$/);
|
||||
const task = line.match(/^(\s*)[-*+]\s+\[([ xX])\]\s+(.+)$/);
|
||||
const ul = line.match(/^(\s*)[-*+]\s+(.+)$/);
|
||||
@@ -157,9 +268,10 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
if (heading) {
|
||||
closeList();
|
||||
const n = heading[1].length;
|
||||
const id = heading[3] ? ` id="${escapeHtml(heading[3])}"` : "";
|
||||
const resolved = headingByLine.get(index);
|
||||
const id = resolved?.id || heading[3] || headingSlug(heading[2]);
|
||||
const suffix = heading[3] ? ` {#${heading[3]}}` : "";
|
||||
html += `<h${n}${id}${attrs(index, true, `${heading[1]} `, suffix, lineOffset)}>${inline(heading[2])}</h${n}>`;
|
||||
html += `<h${n} id="${escapeHtml(id)}"${attrs(index, true, `${heading[1]} `, suffix, lineOffset)}>${inline(heading[2])}</h${n}>`;
|
||||
} else if (task) {
|
||||
if (list !== "ul") { closeList(); html += `<ul class="task-list">`; list = "ul"; }
|
||||
const checked = task[2].toLowerCase() === "x";
|
||||
|
||||
+7
-4
@@ -20,7 +20,9 @@ let unreadChat=0;
|
||||
const compactToggle=document.querySelector("#compact-toggle"), publicTaskUpdates=document.querySelector("#public-task-updates"), fontFamily=document.querySelector("#font-family"), fontSize=document.querySelector("#font-size"), currentUser=document.querySelector("#current-user"), userColorPicker=document.querySelector("#user-color-picker");
|
||||
const shareToken=new URLSearchParams(location.search).get("share");if(shareToken)setAccessToken("workspace",workspaceSlug,shareToken);
|
||||
let accessToken=shareToken||getAuthToken()||getAccessToken("workspace",workspaceSlug), password="", nickname=getNickname(), info, socket, saveTimer, applyingRemote=false, uiState=readEditorState(), owners=[];
|
||||
const lineToggle=document.querySelector("#line-numbers-toggle"); lineToggle.checked=localStorage.getItem("rustpad:line-numbers")!=="off";
|
||||
const lineToggle=document.querySelector("#line-numbers-toggle"), previewLineToggle=document.querySelector("#preview-line-numbers-toggle");
|
||||
lineToggle.checked=localStorage.getItem("rustpad:line-numbers")!=="off";
|
||||
previewLineToggle.checked=localStorage.getItem("rustpad:preview-line-numbers")==="on";
|
||||
compactToggle.checked=localStorage.getItem("rustpad:compact")!=="off";
|
||||
fontFamily.value=localStorage.getItem("rustpad:font-family")||"mono";
|
||||
fontSize.value=localStorage.getItem("rustpad:font-size")||"14";
|
||||
@@ -42,7 +44,7 @@ function clearUnread(){unreadChat=0;chatUnread.hidden=true;chatUnread.textConten
|
||||
function setStatus(kind,text){document.querySelector("#status-dot").className=`status__dot${kind?` is-${kind}`:""}`;document.querySelector("#status-text").textContent=text;}
|
||||
function updateAddressLabel(){document.querySelector("#note-url").textContent=`${location.pathname}${location.search}`;}
|
||||
async function renderMermaid(){const nodes=preview.querySelectorAll(".mermaid");if(!nodes.length)return;try{const {default:mermaid}=await import("https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs");mermaid.initialize({startOnLoad:false,theme:"dark",securityLevel:"strict"});await mermaid.run({nodes:[...nodes]});}catch{nodes.forEach(n=>n.insertAdjacentHTML("beforebegin",'<p class="error">Failed to load Mermaid.</p>'));}}
|
||||
async function renderCodeHighlight(){const nodes=preview.querySelectorAll('pre code[class^="language-"]:not(.language-mermaid)');if(!nodes.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");nodes.forEach(node=>hljs.default.highlightElement(node));}catch{}}
|
||||
async function renderCodeHighlight(){const nodes=preview.querySelectorAll('pre code[class^="language-"]:not(.language-mermaid)');if(!nodes.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");nodes.forEach(node=>{const lines=node.querySelectorAll(".code-line");if(!lines.length){hljs.default.highlightElement(node);return;}const language=[...node.classList].find(name=>name.startsWith("language-"))?.slice(9);lines.forEach(line=>{try{line.innerHTML=hljs.default.highlight(line.textContent,{language,ignoreIllegals:true}).value;}catch{line.innerHTML=hljs.default.highlightAuto(line.textContent).value;}});node.classList.add("hljs");});}catch{}}
|
||||
function renderGutter(){
|
||||
const lineCount=Math.max(1,(editor.value.match(/\n/g)||[]).length+1);
|
||||
const lines=Array.from({length:lineCount});
|
||||
@@ -59,7 +61,8 @@ function renderGutter(){
|
||||
const label=owner!==owners[i-1]?`<span class="owner-label" style="top:${top}px;--owner:${colorFor(owner)}">${escapeHtml(ownerName(owner))}</span>`:"";
|
||||
return `<span class="owner-line" style="top:${top}px;--owner:${colorFor(owner)}"></span>${label}`;
|
||||
}).join("");
|
||||
document.body.classList.toggle("hide-line-numbers",!lineToggle.checked);
|
||||
document.body.classList.toggle("hide-editor-line-numbers",!lineToggle.checked);
|
||||
document.body.classList.toggle("hide-preview-line-numbers",!previewLineToggle.checked);
|
||||
}
|
||||
function escapeHtml(v){return String(v).replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));}function formatDate(value){const raw=String(value??"").trim();let normalized=raw;if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}$/.test(normalized))normalized=normalized.replace(" ","T")+":00";else if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}:\d{2}$/.test(normalized))normalized=normalized.replace(" ","T");else if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(normalized))normalized=normalized.replace(" ","T")+"Z";const date=new Date(normalized);return Number.isNaN(date.getTime())?raw:date.toLocaleString("pl-PL",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"});}
|
||||
|
||||
@@ -148,7 +151,7 @@ bindIdentityDialog({dialog:identityDialog,onIdentity:async value=>{nickname=valu
|
||||
identityDialog.addEventListener("close",()=>{if(!nickname)queueMicrotask(()=>{if(!identityDialog.open)identityDialog.showModal();});});
|
||||
async function initialize(){try{info=await api(`/api/workspaces/${encodeURIComponent(workspaceSlug)}/notes/${encodeURIComponent(noteSlug)}`);document.querySelector("#back-workspace").href=`/w/${encodeURIComponent(workspaceSlug)}`;document.title=`${info.title} · ${info.workspace_title}`;publicTaskUpdates.checked=Boolean(info.allow_public_task_updates);applyUi({write:true,replace:true});if(!nickname){identityDialog.showModal();return;}updateCurrentUser();document.querySelector("#delete-note").hidden=info.note_protected;if(info.protected&&!accessToken)passwordDialog.showModal();else{loadFiles();connect();}}catch(e){document.body.innerHTML=`<main class="error-page"><div><h1>Note not found</h1><p>${escapeHtml(e.message)}</p></div></main>`;}}
|
||||
|
||||
document.querySelectorAll("[data-view]").forEach(b=>b.addEventListener("click",()=>{uiState={...uiState,view:b.dataset.view};applyUi({write:true});}));modeToggle.addEventListener("click",()=>{uiState={...uiState,mode:uiState.mode==="markdown"?"text":"markdown"};applyUi({write:true});});lineToggle.addEventListener("change",()=>{localStorage.setItem("rustpad:line-numbers",lineToggle.checked?"on":"off");renderGutter();});compactToggle.addEventListener("change",()=>{localStorage.setItem("rustpad:compact",compactToggle.checked?"on":"off");applyUi();});fontFamily.addEventListener("change",()=>{localStorage.setItem("rustpad:font-family",fontFamily.value);applyUi();});fontSize.addEventListener("change",()=>{localStorage.setItem("rustpad:font-size",fontSize.value);applyUi();});
|
||||
document.querySelectorAll("[data-view]").forEach(b=>b.addEventListener("click",()=>{uiState={...uiState,view:b.dataset.view};applyUi({write:true});}));modeToggle.addEventListener("click",()=>{uiState={...uiState,mode:uiState.mode==="markdown"?"text":"markdown"};applyUi({write:true});});lineToggle.addEventListener("change",()=>{localStorage.setItem("rustpad:line-numbers",lineToggle.checked?"on":"off");renderGutter();});previewLineToggle.addEventListener("change",()=>{localStorage.setItem("rustpad:preview-line-numbers",previewLineToggle.checked?"on":"off");renderGutter();});compactToggle.addEventListener("change",()=>{localStorage.setItem("rustpad:compact",compactToggle.checked?"on":"off");applyUi();});fontFamily.addEventListener("change",()=>{localStorage.setItem("rustpad:font-family",fontFamily.value);applyUi();});fontSize.addEventListener("change",()=>{localStorage.setItem("rustpad:font-size",fontSize.value);applyUi();});
|
||||
window.addEventListener("popstate",()=>{uiState=readEditorState();applyUi();});window.addEventListener("rustpad:urlchange",updateAddressLabel);document.querySelector("#copy-link").addEventListener("click",async()=>{try{await copyText(currentShareUrl(uiState));toast("Link copied");}catch(e){toast(e.message);}});document.querySelectorAll("[data-format]").forEach(b=>b.addEventListener("click",()=>{applyFormat(editor,b.dataset.format);b.closest("details")?.removeAttribute("open");}));bindFormatShortcuts(editor);document.querySelector("#shortcuts-button").addEventListener("click",()=>document.querySelector("#shortcuts-dialog").showModal());document.querySelector("#close-shortcuts").addEventListener("click",()=>document.querySelector("#shortcuts-dialog").close());preview.addEventListener("change",event=>{const checkbox=event.target.closest(".task-checkbox");if(!checkbox)return;const lineIndex=Number(checkbox.dataset.sourceLine)-1;const lines=editor.value.split("\n");if(lineIndex<0||lineIndex>=lines.length)return;lines[lineIndex]=lines[lineIndex].replace(/^(\s*[-*+]\s+\[)[ xX](\])/,`$1${checkbox.checked?"x":" "}$2`);editor.value=lines.join("\n");editor.dispatchEvent(new Event("input",{bubbles:true}));});preview.addEventListener("keydown",event=>{const target=event.target.closest(".preview-editable");if(!target)return;if(event.key==="Enter"){event.preventDefault();target.blur();return;}if(event.key==="ArrowUp"||event.key==="ArrowDown"){if(movePreviewCaret(target,event.key==="ArrowUp"?-1:1))event.preventDefault();}});preview.addEventListener("blur",event=>{const target=event.target.closest(".preview-editable");if(!target)return;const lineIndex=Number(target.dataset.sourceLine)-1;if(lineIndex<0)return;const lines=editor.value.split("\n");const value=markdownFromPreview(target);let next;if(target.dataset.tableCell!==undefined)next=replaceTableCell(lines[lineIndex],Number(target.dataset.tableCell),value);else{const prefix=target.dataset.sourcePrefix||"",suffix=target.dataset.sourceSuffix||"";next=prefix+value+suffix;}if(lines[lineIndex]===next)return;lines[lineIndex]=next;editor.value=lines.join("\n");editor.setSelectionRange(editor.value.length,editor.value.length);editor.dispatchEvent(new Event("input",{bubbles:true}));},{capture:true});
|
||||
publicTaskUpdates.addEventListener("change",async()=>{publicTaskUpdates.disabled=true;try{await api(`/api/workspaces/${encodeURIComponent(workspaceSlug)}/notes/${encodeURIComponent(noteSlug)}/publish`,{method:"POST",body:JSON.stringify({access_token:accessToken||null,allow_task_updates:publicTaskUpdates.checked})});toast(publicTaskUpdates.checked?"Public task updates enabled":"Public task updates disabled");}catch(error){publicTaskUpdates.checked=!publicTaskUpdates.checked;toast(error.message);}finally{publicTaskUpdates.disabled=false;}});document.querySelector("#publish-page").addEventListener("click",async()=>{try{const result=await api(`/api/workspaces/${encodeURIComponent(workspaceSlug)}/notes/${encodeURIComponent(noteSlug)}/publish`,{method:"POST",body:JSON.stringify({access_token:accessToken||null,allow_task_updates:publicTaskUpdates.checked})});const url=new URL(result.url,location.origin).href;await copyText(url);toast("Page link copied");window.open(url,"_blank","noopener");}catch(error){toast(error.message);}});
|
||||
roomDetails.addEventListener("toggle",()=>{if(roomDetails.open){clearUnread();chatInput.focus();if("Notification" in window&&Notification.permission==="default")Notification.requestPermission().catch(()=>{});}});
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ function clearUnread(){unreadChat=0;chatUnread.hidden=true;chatUnread.textConten
|
||||
function setStatus(kind,text){document.querySelector("#status-dot").className=`status__dot${kind?` is-${kind}`:""}`;document.querySelector("#status-text").textContent=text;}
|
||||
function updateAddressLabel(){document.querySelector("#pad-url").textContent=`${location.pathname}${location.search}`;}
|
||||
async function renderMermaid(){const nodes=preview.querySelectorAll(".mermaid");if(!nodes.length)return;try{const {default:mermaid}=await import("https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs");mermaid.initialize({startOnLoad:false,theme:"dark",securityLevel:"strict"});await mermaid.run({nodes:[...nodes]});}catch{nodes.forEach(n=>n.insertAdjacentHTML("beforebegin",'<p class="error">Failed to load Mermaid.</p>'));}}
|
||||
async function renderCodeHighlight(){const nodes=preview.querySelectorAll('pre code[class^="language-"]:not(.language-mermaid)');if(!nodes.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");nodes.forEach(node=>hljs.default.highlightElement(node));}catch{}}
|
||||
async function renderCodeHighlight(){const nodes=preview.querySelectorAll('pre code[class^="language-"]:not(.language-mermaid)');if(!nodes.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");nodes.forEach(node=>{const lines=node.querySelectorAll(".code-line");if(!lines.length){hljs.default.highlightElement(node);return;}const language=[...node.classList].find(name=>name.startsWith("language-"))?.slice(9);lines.forEach(line=>{try{line.innerHTML=hljs.default.highlight(line.textContent,{language,ignoreIllegals:true}).value;}catch{line.innerHTML=hljs.default.highlightAuto(line.textContent).value;}});node.classList.add("hljs");});}catch{}}
|
||||
function renderGutter(){
|
||||
const lineCount=Math.max(1,(editor.value.match(/\n/g)||[]).length+1);
|
||||
const lines=Array.from({length:lineCount});
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ const token=location.pathname.split("/").filter(Boolean)[1];
|
||||
const content=document.querySelector("#public-content");
|
||||
function toast(text){const el=document.querySelector("#toast");el.textContent=text;el.classList.add("visible");setTimeout(()=>el.classList.remove("visible"),1800);}
|
||||
async function renderMermaid(){const nodes=content.querySelectorAll(".mermaid");if(!nodes.length)return;try{const {default:mermaid}=await import("https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs");mermaid.initialize({startOnLoad:false,theme:"dark",securityLevel:"strict"});await mermaid.run({nodes:[...nodes]});}catch{nodes.forEach(n=>n.insertAdjacentHTML("beforebegin",'<p class="error">Failed to load Mermaid.</p>'));}}
|
||||
async function renderCodeHighlight(){const blocks=content.querySelectorAll('pre code[class^="language-"]');if(!blocks.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");blocks.forEach(block=>hljs.default.highlightElement(block));}catch{}}
|
||||
async function renderCodeHighlight(){const blocks=content.querySelectorAll('pre code[class^="language-"]');if(!blocks.length)return;try{const hljs=await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm");blocks.forEach(block=>{const lines=block.querySelectorAll(".code-line");if(!lines.length){hljs.default.highlightElement(block);return;}const language=[...block.classList].find(name=>name.startsWith("language-"))?.slice(9);lines.forEach(line=>{try{line.innerHTML=hljs.default.highlight(line.textContent,{language,ignoreIllegals:true}).value;}catch{line.innerHTML=hljs.default.highlightAuto(line.textContent).value;}});block.classList.add("hljs");});}catch{}}
|
||||
function lockPublicContent(allowTaskUpdates){
|
||||
content.querySelectorAll('[contenteditable]').forEach(node=>node.removeAttribute('contenteditable'));
|
||||
content.querySelectorAll('.preview-editable').forEach(node=>node.classList.remove('preview-editable'));
|
||||
|
||||
Reference in New Issue
Block a user