fixes
This commit is contained in:
@@ -11,3 +11,4 @@ README.md
|
|||||||
Dockerfile*
|
Dockerfile*
|
||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
migrate/
|
migrate/
|
||||||
|
scripts/*.txt
|
||||||
+2
-1
@@ -13,4 +13,5 @@ data/files/*
|
|||||||
venv
|
venv
|
||||||
.venv
|
.venv
|
||||||
migrate/etherpad-dry-run-report.json
|
migrate/etherpad-dry-run-report.json
|
||||||
data/garge
|
data/garage
|
||||||
|
scripts/*.txt
|
||||||
Generated
+1
-1
@@ -2433,7 +2433,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.0.15"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# scripts/random_text_generator.py
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def anonymize_text(text: str) -> str:
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for character in text:
|
||||||
|
if character.islower():
|
||||||
|
result.append(random.choice(string.ascii_lowercase))
|
||||||
|
elif character.isupper():
|
||||||
|
result.append(random.choice(string.ascii_uppercase))
|
||||||
|
elif character.isdigit():
|
||||||
|
result.append(random.choice(string.digits))
|
||||||
|
else:
|
||||||
|
result.append(character)
|
||||||
|
|
||||||
|
return "".join(result)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python scripts/random_text_generator.py <input_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_path = Path(sys.argv[1])
|
||||||
|
|
||||||
|
if not input_path.is_file():
|
||||||
|
print(f"File not found: {input_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
source_text = input_path.read_text(encoding="utf-8")
|
||||||
|
anonymized_text = anonymize_text(source_text)
|
||||||
|
|
||||||
|
output_path = input_path.with_name(
|
||||||
|
f"{input_path.stem}_anonymized{input_path.suffix}"
|
||||||
|
)
|
||||||
|
|
||||||
|
output_path.write_text(anonymized_text, encoding="utf-8")
|
||||||
|
|
||||||
|
print(f"Anonymized file saved to: {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+67
-36
@@ -2201,7 +2201,7 @@ dialog::backdrop {
|
|||||||
content: attr(data-source-line);
|
content: attr(data-source-line);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: -50px;
|
left: var(--preview-line-left, -50px);
|
||||||
width: 32px;
|
width: 32px;
|
||||||
color: #596270;
|
color: #596270;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -2304,70 +2304,80 @@ dialog::backdrop {
|
|||||||
color: #b9c2cf;
|
color: #b9c2cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .task-list {
|
/* Task-list layout. */
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body .task-list-item {
|
.markdown-body .task-list-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1em minmax(0, 1fr);
|
grid-template-columns: 1em minmax(0, 1fr);
|
||||||
grid-template-rows: 1.32em;
|
grid-template-rows: auto auto;
|
||||||
column-gap: .45em;
|
column-gap: .45em;
|
||||||
align-items: center;
|
align-items: start;
|
||||||
min-height: 1.32em;
|
min-height: 1.32em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1.32
|
line-height: 1.32;
|
||||||
}
|
list-style: none;
|
||||||
|
|
||||||
.markdown-body .task-list-item::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: calc(100% + 18px);
|
|
||||||
width: 32px;
|
|
||||||
height: 1.32em;
|
|
||||||
line-height: 1.32em;
|
|
||||||
transform: none
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .task-checkbox {
|
.markdown-body .task-checkbox {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
|
align-self: start;
|
||||||
|
appearance: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 0 1em;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
|
min-width: 1em;
|
||||||
|
max-width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin: 0;
|
min-height: 1em;
|
||||||
align-self: center;
|
max-height: 1em;
|
||||||
accent-color: var(--accent);
|
margin: .14em 0 0;
|
||||||
cursor: pointer
|
padding: 0;
|
||||||
|
border: 1px solid #707887;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--surface-2);
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .task-list-item>span {
|
.markdown-body .task-checkbox:checked {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background-color: var(--accent);
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2 6.5 4.7 9 10 3'/%3E%3C/svg%3E");
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: .78em .78em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .task-checkbox:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .task-checkbox:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2px rgba(124, 104, 238, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .task-list-item > .list-item-content {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1.32
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-preview-line-numbers .markdown-body .task-list-item::before {
|
.markdown-body .task-list-item > ul,
|
||||||
display: none
|
.markdown-body .task-list-item > ol {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 2;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compact-editor .markdown-body .task-list-item {
|
.compact-editor .markdown-body .task-list-item {
|
||||||
grid-template-rows: 1.24em;
|
|
||||||
min-height: 1.24em;
|
min-height: 1.24em;
|
||||||
line-height: 1.24
|
line-height: 1.24;
|
||||||
}
|
|
||||||
|
|
||||||
.compact-editor .markdown-body .task-list-item::before,
|
|
||||||
.compact-editor .markdown-body .task-list-item>span {
|
|
||||||
height: 1.24em;
|
|
||||||
line-height: 1.24
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .footnotes {
|
.markdown-body .footnotes {
|
||||||
@@ -3805,3 +3815,24 @@ dialog::backdrop {
|
|||||||
.markdown-body .markdown-toc .toc-level-4,
|
.markdown-body .markdown-toc .toc-level-4,
|
||||||
.markdown-body .markdown-toc .toc-level-5,
|
.markdown-body .markdown-toc .toc-level-5,
|
||||||
.markdown-body .markdown-toc .toc-level-6 { margin-left: 3em; }
|
.markdown-body .markdown-toc .toc-level-6 { margin-left: 3em; }
|
||||||
|
|
||||||
|
/* Nested Markdown lists keep markers and source-line numbers in separate gutters. */
|
||||||
|
.markdown-body ul,
|
||||||
|
.markdown-body ol {
|
||||||
|
padding-left: 1.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .list-source-line::before {
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .list-item-content {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .contains-task-items > .task-list-item {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-18
@@ -92,6 +92,63 @@ function normalizeLanguage(value) {
|
|||||||
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 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);
|
const isPlainText = line => !/[`*_~^=\[\]<>|:#]/.test(line) && !/^\s*(?:[-+*>]|\d+\.)\s/.test(line);
|
||||||
|
|
||||||
|
|
||||||
|
function listLine(line) {
|
||||||
|
const match = line.match(/^(\s*)([-*+]|(\d+)\.)\s+(?:\[([ xX])\]\s+)?(.+)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
const indent = match[1].replace(/\t/g, " ").length;
|
||||||
|
return {
|
||||||
|
indent,
|
||||||
|
whitespace: match[1],
|
||||||
|
type: match[3] ? "ol" : "ul",
|
||||||
|
number: match[3] ? Number(match[3]) : null,
|
||||||
|
checked: match[4] == null ? null : match[4].toLowerCase() === "x",
|
||||||
|
text: match[5],
|
||||||
|
marker: match[3] ? `${match[3]}. ` : `${match[2]} `
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderList(lines, start, lineOffset = 0, baseIndent = null, forcedType = null, depth = 0) {
|
||||||
|
const first = listLine(lines[start]);
|
||||||
|
if (!first) return null;
|
||||||
|
const indent = baseIndent == null ? first.indent : baseIndent;
|
||||||
|
const type = forcedType || first.type;
|
||||||
|
let index = start;
|
||||||
|
let body = "";
|
||||||
|
let hasTask = false;
|
||||||
|
|
||||||
|
while (index < lines.length) {
|
||||||
|
const item = listLine(lines[index]);
|
||||||
|
if (!item || item.indent < indent || item.indent !== indent || item.type !== type) break;
|
||||||
|
|
||||||
|
const sourceLine = index + lineOffset + 1;
|
||||||
|
const prefix = `${item.whitespace}${item.marker}${item.checked == null ? "" : `[${item.checked ? "x" : " "}] `}`;
|
||||||
|
const valueAttr = "";
|
||||||
|
const taskClass = item.checked == null ? "" : " task-list-item";
|
||||||
|
hasTask ||= item.checked != null;
|
||||||
|
const gutterOffset = `${(depth + 1) * 1.75}em`;
|
||||||
|
body += `<li class="preview-source-line list-source-line${taskClass}" data-source-line="${sourceLine}"${valueAttr} style="--list-gutter-offset:${gutterOffset}">`;
|
||||||
|
if (item.checked != null) {
|
||||||
|
body += `<input type="checkbox" class="task-checkbox" data-source-line="${sourceLine}"${item.checked ? " checked" : ""}>`;
|
||||||
|
}
|
||||||
|
body += `<span class="preview-editable list-item-content" contenteditable="true" spellcheck="true" data-source-line="${sourceLine}" data-source-prefix="${escapeHtml(prefix)}">${inline(item.text)}</span>`;
|
||||||
|
index++;
|
||||||
|
|
||||||
|
while (index < lines.length) {
|
||||||
|
const nested = listLine(lines[index]);
|
||||||
|
if (!nested || nested.indent <= indent) break;
|
||||||
|
const rendered = renderList(lines, index, lineOffset, nested.indent, nested.type, depth + 1);
|
||||||
|
if (!rendered) break;
|
||||||
|
body += rendered.html;
|
||||||
|
index = rendered.end;
|
||||||
|
}
|
||||||
|
body += `</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classAttr = hasTask ? ` class="contains-task-items"` : "";
|
||||||
|
return {html: `<${type}${classAttr}>${body}</${type}>`, end: index};
|
||||||
|
}
|
||||||
|
|
||||||
function splitTableRow(line) {
|
function splitTableRow(line) {
|
||||||
let value = line.trim();
|
let value = line.trim();
|
||||||
if (value.startsWith("|")) value = value.slice(1);
|
if (value.startsWith("|")) value = value.slice(1);
|
||||||
@@ -138,8 +195,20 @@ function collectHeadings(lines) {
|
|||||||
return headings;
|
return headings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function alignPreviewLineNumbers(root) {
|
||||||
|
if (!root) return;
|
||||||
|
const styles = getComputedStyle(root);
|
||||||
|
const targetLeft = parseFloat(styles.paddingLeft || "0") - 50;
|
||||||
|
const rootLeft = root.getBoundingClientRect().left;
|
||||||
|
root.querySelectorAll(".preview-source-line").forEach(line => {
|
||||||
|
const lineLeft = line.getBoundingClientRect().left - rootLeft;
|
||||||
|
line.style.setProperty("--preview-line-left", `${targetLeft - lineLeft}px`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function renderMarkdown(source, lineOffset = 0) {
|
export function renderMarkdown(source, lineOffset = 0) {
|
||||||
let html = "", inCode = false, fence = "", language = "", codeLineStart = null, code = [], codeStart = 0, list = null;
|
let html = "", inCode = false, fence = "", language = "", codeLineStart = null, code = [], codeStart = 0;
|
||||||
const lines = String(source).split("\n");
|
const lines = String(source).split("\n");
|
||||||
const headings = collectHeadings(lines);
|
const headings = collectHeadings(lines);
|
||||||
const headingByLine = new Map(headings.map(item => [item.index, item]));
|
const headingByLine = new Map(headings.map(item => [item.index, item]));
|
||||||
@@ -159,7 +228,7 @@ export function renderMarkdown(source, lineOffset = 0) {
|
|||||||
lines[i] = "";
|
lines[i] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeList = () => { if (list) { html += `</${list}>`; list = null; } };
|
const closeList = () => {};
|
||||||
const closeCode = () => {
|
const closeCode = () => {
|
||||||
const body = escapeHtml(code.join("\n"));
|
const body = escapeHtml(code.join("\n"));
|
||||||
const lang = normalizeLanguage(language);
|
const lang = normalizeLanguage(language);
|
||||||
@@ -261,29 +330,19 @@ export function renderMarkdown(source, lineOffset = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const heading = line.match(/^\s{0,4}(#{1,6})\s+(.+?)(?:\s+\{#([A-Za-z][\w:.-]*)\})?\s*$/);
|
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 listItem = listLine(line);
|
||||||
const ul = line.match(/^(\s*)[-*+]\s+(.+)$/);
|
|
||||||
const ol = line.match(/^(\s*)\d+\.\s+(.+)$/);
|
|
||||||
|
|
||||||
if (heading) {
|
if (listItem) {
|
||||||
|
const rendered = renderList(lines, index, lineOffset, listItem.indent, listItem.type);
|
||||||
|
html += rendered.html;
|
||||||
|
index = rendered.end - 1;
|
||||||
|
} else if (heading) {
|
||||||
closeList();
|
closeList();
|
||||||
const n = heading[1].length;
|
const n = heading[1].length;
|
||||||
const resolved = headingByLine.get(index);
|
const resolved = headingByLine.get(index);
|
||||||
const id = resolved?.id || heading[3] || headingSlug(heading[2]);
|
const id = resolved?.id || heading[3] || headingSlug(heading[2]);
|
||||||
const suffix = heading[3] ? ` {#${heading[3]}}` : "";
|
const suffix = heading[3] ? ` {#${heading[3]}}` : "";
|
||||||
html += `<h${n} id="${escapeHtml(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";
|
|
||||||
html += `<li class="preview-source-line task-list-item" data-source-line="${index + lineOffset + 1}"><input type="checkbox" class="task-checkbox" data-source-line="${index + lineOffset + 1}"${checked ? " checked" : ""}><span class="preview-editable" contenteditable="true" spellcheck="true" data-source-line="${index + lineOffset + 1}" data-source-prefix="${escapeHtml(`${task[1]}- [${checked ? "x" : " "}] `)}">${inline(task[3])}</span></li>`;
|
|
||||||
} else if (ul || ol) {
|
|
||||||
const type = ul ? "ul" : "ol";
|
|
||||||
if (list !== type) { closeList(); html += `<${type}>`; list = type; }
|
|
||||||
const match = ul || ol;
|
|
||||||
const indentWidth = match[1].replace(/\t/g, " ").length;
|
|
||||||
const prefix = ul ? `${match[1]}- ` : `${match[1]}${(line.match(/^\s*(\d+)\./)||[])[1] || 1}. `;
|
|
||||||
const indentStyle = indentWidth ? ` style="margin-left:${Math.min(indentWidth, 24) * 0.45}em"` : "";
|
|
||||||
html += `<li${attrs(index, true, prefix, "", lineOffset)}${indentStyle}>${inline(match[2])}</li>`;
|
|
||||||
} else {
|
} else {
|
||||||
closeList();
|
closeList();
|
||||||
const definition = index + 1 < lines.length && /^:\s+/.test(lines[index + 1]);
|
const definition = index + 1 < lines.length && /^:\s+/.test(lines[index + 1]);
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ installGlobalDiagnostics();
|
|||||||
import { api } from "@rustpad/api";
|
import { api } from "@rustpad/api";
|
||||||
import { copyText } from "@rustpad/clipboard";
|
import { copyText } from "@rustpad/clipboard";
|
||||||
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||||
import { renderMarkdown } from "@rustpad/markdown";
|
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
||||||
import { prepareImageFile } from "./image-upload.js";
|
import { prepareImageFile } from "./image-upload.js";
|
||||||
import { getNickname, getAccessToken, getAuthToken, setAccessToken } from "@rustpad/session";
|
import { getNickname, getAccessToken, getAuthToken, setAccessToken } from "@rustpad/session";
|
||||||
import { bindIdentityDialog } from "./auth-ui.js";
|
import { bindIdentityDialog } from "./auth-ui.js";
|
||||||
@@ -132,7 +132,7 @@ function replaceTableCell(line,index,value){
|
|||||||
const cells=body.split("|").map(cell=>cell.trim());while(cells.length<=index)cells.push("");cells[index]=value.replace(/\|/g,"|");
|
const cells=body.split("|").map(cell=>cell.trim());while(cells.length<=index)cells.push("");cells[index]=value.replace(/\|/g,"|");
|
||||||
return `${leading?"| ":""}${cells.join(" | ")}${trailing?" |":""}`;
|
return `${leading?"| ":""}${cells.join(" | ")}${trailing?" |":""}`;
|
||||||
}
|
}
|
||||||
function render(){if(uiState.mode==="markdown"){preview.classList.remove("preview--raw");preview.innerHTML=renderMarkdown(editor.value);document.querySelector("#preview-label").textContent="Markdown + Mermaid preview · text and headings are editable";renderMermaid();renderCodeHighlight();}else{preview.classList.add("preview--raw");preview.innerHTML=editor.value.split("\n").map((line,index)=>`<div class="preview-source-line preview-editable" data-source-line="${index+1}" contenteditable="true" spellcheck="true">${escapeHtml(line)||"<br>"}</div>`).join("");document.querySelector("#preview-label").textContent="Text preview · editable";}document.querySelector("#characters").textContent=`${editor.value.length} characters`;document.querySelector("#words").textContent=`${editor.value.trim()?editor.value.trim().split(/\s+/).length:0} words`;renderGutter();}
|
function render(){if(uiState.mode==="markdown"){preview.classList.remove("preview--raw");preview.innerHTML=renderMarkdown(editor.value);document.querySelector("#preview-label").textContent="Markdown + Mermaid preview · text and headings are editable";renderMermaid();renderCodeHighlight();}else{preview.classList.add("preview--raw");preview.innerHTML=editor.value.split("\n").map((line,index)=>`<div class="preview-source-line preview-editable" data-source-line="${index+1}" contenteditable="true" spellcheck="true">${escapeHtml(line)||"<br>"}</div>`).join("");document.querySelector("#preview-label").textContent="Text preview · editable";}alignPreviewLineNumbers(preview);document.querySelector("#characters").textContent=`${editor.value.length} characters`;document.querySelector("#words").textContent=`${editor.value.trim()?editor.value.trim().split(/\s+/).length:0} words`;renderGutter();}
|
||||||
function applyUi({write=false,replace=false}={}){editorWorkspace.className=`workspace view-${uiState.view} editor-workspace-font-${fontFamily.value}`;editorWorkspace.style.setProperty("--editor-font-size",`${fontSize.value}px`);document.body.classList.toggle("compact-editor",compactToggle.checked);document.querySelectorAll("[data-view]").forEach(b=>{const a=b.dataset.view===uiState.view;b.classList.toggle("active",a);b.setAttribute("aria-pressed",String(a));});const markdown=uiState.mode==="markdown";modeToggle.classList.toggle("active",markdown);modeToggle.textContent=markdown?"Markdown":"Text";render();if(write)writeEditorState(uiState,{replace});updateAddressLabel();}
|
function applyUi({write=false,replace=false}={}){editorWorkspace.className=`workspace view-${uiState.view} editor-workspace-font-${fontFamily.value}`;editorWorkspace.style.setProperty("--editor-font-size",`${fontSize.value}px`);document.body.classList.toggle("compact-editor",compactToggle.checked);document.querySelectorAll("[data-view]").forEach(b=>{const a=b.dataset.view===uiState.view;b.classList.toggle("active",a);b.setAttribute("aria-pressed",String(a));});const markdown=uiState.mode==="markdown";modeToggle.classList.toggle("active",markdown);modeToggle.textContent=markdown?"Markdown":"Text";render();if(write)writeEditorState(uiState,{replace});updateAddressLabel();}
|
||||||
function applyRemote(content,ownerMap){if(content===editor.value&&ownerMap==null)return;const start=editor.selectionStart,end=editor.selectionEnd;applyingRemote=true;editor.value=content;try{owners=JSON.parse(ownerMap||"[]");}catch{owners=[];}editor.setSelectionRange(Math.min(start,content.length),Math.min(end,content.length));applyingRemote=false;render();}
|
function applyRemote(content,ownerMap){if(content===editor.value&&ownerMap==null)return;const start=editor.selectionStart,end=editor.selectionEnd;applyingRemote=true;editor.value=content;try{owners=JSON.parse(ownerMap||"[]");}catch{owners=[];}editor.setSelectionRange(Math.min(start,content.length),Math.min(end,content.length));applyingRemote=false;render();}
|
||||||
function connect(){socket?.stop();socket=new NoteSocket({workspaceSlug,noteSlug,password,accessToken,nickname,color:currentUserColor()||null,sessionToken:getAuthToken(),onStatus:s=>setStatus(s==="online"?"online":s==="offline"?"offline":null,s==="online"?"Connected":s==="offline"?"Reconnecting…":"Connecting…"),onAuthenticated:m=>{if(passwordDialog.open)passwordDialog.close();applyRemote(m.content,m.owner_map);editor.focus();},onDocument:m=>{applyRemote(m.content,m.owner_map);document.querySelector("#save-state").textContent=`${m.author?`${m.author} · `:""}${new Date(m.updated_at).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit"})}`;},onPresence:updatePresence,onLatency:updateLatency,onChat:appendChatMessage,onError:m=>{document.querySelector("#password-error").textContent=m;if(/nickname|session|account/i.test(m)){if(!identityDialog.open)identityDialog.showModal();}else if(info?.protected&&!passwordDialog.open)passwordDialog.showModal();}});socket.connect();}
|
function connect(){socket?.stop();socket=new NoteSocket({workspaceSlug,noteSlug,password,accessToken,nickname,color:currentUserColor()||null,sessionToken:getAuthToken(),onStatus:s=>setStatus(s==="online"?"online":s==="offline"?"offline":null,s==="online"?"Connected":s==="offline"?"Reconnecting…":"Connecting…"),onAuthenticated:m=>{if(passwordDialog.open)passwordDialog.close();applyRemote(m.content,m.owner_map);editor.focus();},onDocument:m=>{applyRemote(m.content,m.owner_map);document.querySelector("#save-state").textContent=`${m.author?`${m.author} · `:""}${new Date(m.updated_at).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit"})}`;},onPresence:updatePresence,onLatency:updateLatency,onChat:appendChatMessage,onError:m=>{document.querySelector("#password-error").textContent=m;if(/nickname|session|account/i.test(m)){if(!identityDialog.open)identityDialog.showModal();}else if(info?.protected&&!passwordDialog.open)passwordDialog.showModal();}});socket.connect();}
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ installGlobalDiagnostics();
|
|||||||
import { api } from "@rustpad/api";
|
import { api } from "@rustpad/api";
|
||||||
import { copyText } from "@rustpad/clipboard";
|
import { copyText } from "@rustpad/clipboard";
|
||||||
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||||
import { renderMarkdown } from "@rustpad/markdown";
|
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
||||||
import { prepareImageFile } from "./image-upload.js";
|
import { prepareImageFile } from "./image-upload.js";
|
||||||
import { getNickname, getAuthToken, getAccessToken, setAccessToken } from "@rustpad/session";
|
import { getNickname, getAuthToken, getAccessToken, setAccessToken } from "@rustpad/session";
|
||||||
import { bindIdentityDialog } from "./auth-ui.js";
|
import { bindIdentityDialog } from "./auth-ui.js";
|
||||||
@@ -128,7 +128,7 @@ function replaceTableCell(line,index,value){
|
|||||||
const cells=body.split("|").map(cell=>cell.trim());while(cells.length<=index)cells.push("");cells[index]=value.replace(/\|/g,"|");
|
const cells=body.split("|").map(cell=>cell.trim());while(cells.length<=index)cells.push("");cells[index]=value.replace(/\|/g,"|");
|
||||||
return `${leading?"| ":""}${cells.join(" | ")}${trailing?" |":""}`;
|
return `${leading?"| ":""}${cells.join(" | ")}${trailing?" |":""}`;
|
||||||
}
|
}
|
||||||
function render(){if(uiState.mode==="markdown"){preview.classList.remove("preview--raw");preview.innerHTML=renderMarkdown(editor.value);document.querySelector("#preview-label").textContent="Markdown + Mermaid preview · text and headings are editable";renderMermaid();renderCodeHighlight();}else{preview.classList.add("preview--raw");preview.innerHTML=editor.value.split("\n").map((line,index)=>`<div class="preview-source-line preview-editable" data-source-line="${index+1}" contenteditable="true" spellcheck="true">${escapeHtml(line)||"<br>"}</div>`).join("");document.querySelector("#preview-label").textContent="Text preview · editable";}document.querySelector("#characters").textContent=`${editor.value.length} characters`;document.querySelector("#words").textContent=`${editor.value.trim()?editor.value.trim().split(/\s+/).length:0} words`;renderGutter();}
|
function render(){if(uiState.mode==="markdown"){preview.classList.remove("preview--raw");preview.innerHTML=renderMarkdown(editor.value);document.querySelector("#preview-label").textContent="Markdown + Mermaid preview · text and headings are editable";renderMermaid();renderCodeHighlight();}else{preview.classList.add("preview--raw");preview.innerHTML=editor.value.split("\n").map((line,index)=>`<div class="preview-source-line preview-editable" data-source-line="${index+1}" contenteditable="true" spellcheck="true">${escapeHtml(line)||"<br>"}</div>`).join("");document.querySelector("#preview-label").textContent="Text preview · editable";}alignPreviewLineNumbers(preview);document.querySelector("#characters").textContent=`${editor.value.length} characters`;document.querySelector("#words").textContent=`${editor.value.trim()?editor.value.trim().split(/\s+/).length:0} words`;renderGutter();}
|
||||||
function applyUi({write=false,replace=false}={}){editorWorkspace.className=`workspace view-${uiState.view} editor-workspace-font-${fontFamily.value}`;editorWorkspace.style.setProperty("--editor-font-size",`${fontSize.value}px`);document.body.classList.toggle("compact-editor",compactToggle.checked);document.querySelectorAll("[data-view]").forEach(b=>{const a=b.dataset.view===uiState.view;b.classList.toggle("active",a);b.setAttribute("aria-pressed",String(a));});const markdown=uiState.mode==="markdown";modeToggle.classList.toggle("active",markdown);modeToggle.textContent=markdown?"Markdown":"Text";render();if(write)writeEditorState(uiState,{replace});updateAddressLabel();}
|
function applyUi({write=false,replace=false}={}){editorWorkspace.className=`workspace view-${uiState.view} editor-workspace-font-${fontFamily.value}`;editorWorkspace.style.setProperty("--editor-font-size",`${fontSize.value}px`);document.body.classList.toggle("compact-editor",compactToggle.checked);document.querySelectorAll("[data-view]").forEach(b=>{const a=b.dataset.view===uiState.view;b.classList.toggle("active",a);b.setAttribute("aria-pressed",String(a));});const markdown=uiState.mode==="markdown";modeToggle.classList.toggle("active",markdown);modeToggle.textContent=markdown?"Markdown":"Text";render();if(write)writeEditorState(uiState,{replace});updateAddressLabel();}
|
||||||
function applyRemote(content,ownerMap){if(content===editor.value&&ownerMap==null)return;const start=editor.selectionStart,end=editor.selectionEnd;applyingRemote=true;editor.value=content;try{owners=JSON.parse(ownerMap||"[]");}catch{owners=[];}editor.setSelectionRange(Math.min(start,content.length),Math.min(end,content.length));applyingRemote=false;render();}
|
function applyRemote(content,ownerMap){if(content===editor.value&&ownerMap==null)return;const start=editor.selectionStart,end=editor.selectionEnd;applyingRemote=true;editor.value=content;try{owners=JSON.parse(ownerMap||"[]");}catch{owners=[];}editor.setSelectionRange(Math.min(start,content.length),Math.min(end,content.length));applyingRemote=false;render();}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ installGlobalDiagnostics();
|
|||||||
|
|
||||||
import { api } from "@rustpad/api";
|
import { api } from "@rustpad/api";
|
||||||
import { copyText } from "@rustpad/clipboard";
|
import { copyText } from "@rustpad/clipboard";
|
||||||
import { renderMarkdown } from "@rustpad/markdown";
|
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
||||||
|
|
||||||
const token=location.pathname.split("/").filter(Boolean)[1];
|
const token=location.pathname.split("/").filter(Boolean)[1];
|
||||||
const content=document.querySelector("#public-content");
|
const content=document.querySelector("#public-content");
|
||||||
@@ -16,7 +16,7 @@ function lockPublicContent(allowTaskUpdates){
|
|||||||
content.querySelectorAll('.preview-editable').forEach(node=>node.classList.remove('preview-editable'));
|
content.querySelectorAll('.preview-editable').forEach(node=>node.classList.remove('preview-editable'));
|
||||||
content.querySelectorAll('.task-checkbox').forEach(box=>{box.disabled=!allowTaskUpdates;box.title=allowTaskUpdates?'Update this task':'Task updates are disabled by the owner';});
|
content.querySelectorAll('.task-checkbox').forEach(box=>{box.disabled=!allowTaskUpdates;box.title=allowTaskUpdates?'Update this task':'Task updates are disabled by the owner';});
|
||||||
}
|
}
|
||||||
async function initialize(){try{const page=await api(`/api/public/${encodeURIComponent(token)}`);document.querySelector("#public-title").textContent=page.title;document.querySelector("#public-meta").textContent=`Updated: ${new Date(page.updated_at).toLocaleString("en-US")}${page.allow_task_updates?" · tasks can be updated":""}`;document.title=`${page.title} · RustPad`;content.innerHTML=renderMarkdown(page.content);lockPublicContent(page.allow_task_updates);await Promise.all([renderMermaid(),renderCodeHighlight()]);}catch(error){content.innerHTML=`<p class="error">${String(error.message)}</p>`;}}
|
async function initialize(){try{const page=await api(`/api/public/${encodeURIComponent(token)}`);document.querySelector("#public-title").textContent=page.title;document.querySelector("#public-meta").textContent=`Updated: ${new Date(page.updated_at).toLocaleString("en-US")}${page.allow_task_updates?" · tasks can be updated":""}`;document.title=`${page.title} · RustPad`;content.innerHTML=renderMarkdown(page.content);alignPreviewLineNumbers(content);lockPublicContent(page.allow_task_updates);await Promise.all([renderMermaid(),renderCodeHighlight()]);}catch(error){content.innerHTML=`<p class="error">${String(error.message)}</p>`;}}
|
||||||
content.addEventListener("change",async event=>{const box=event.target.closest(".task-checkbox");if(!box||box.disabled)return;const previous=!box.checked;box.disabled=true;try{const page=await api(`/api/public/${encodeURIComponent(token)}/tasks`,{method:"POST",body:JSON.stringify({source_line:Number(box.dataset.sourceLine),checked:box.checked})});document.querySelector("#public-meta").textContent=`Updated: ${new Date(page.updated_at).toLocaleString("en-US")} · tasks can be updated`;toast("Task saved");}catch(error){box.checked=previous;toast(error.message);}finally{box.disabled=false;}});
|
content.addEventListener("change",async event=>{const box=event.target.closest(".task-checkbox");if(!box||box.disabled)return;const previous=!box.checked;box.disabled=true;try{const page=await api(`/api/public/${encodeURIComponent(token)}/tasks`,{method:"POST",body:JSON.stringify({source_line:Number(box.dataset.sourceLine),checked:box.checked})});document.querySelector("#public-meta").textContent=`Updated: ${new Date(page.updated_at).toLocaleString("en-US")} · tasks can be updated`;toast("Task saved");}catch(error){box.checked=previous;toast(error.message);}finally{box.disabled=false;}});
|
||||||
lineNumbersToggle.addEventListener("change",()=>{document.body.classList.toggle("hide-preview-line-numbers",!lineNumbersToggle.checked);});
|
lineNumbersToggle.addEventListener("change",()=>{document.body.classList.toggle("hide-preview-line-numbers",!lineNumbersToggle.checked);});
|
||||||
document.querySelector("#copy-public-link").addEventListener("click",async()=>{try{await copyText(location.href);toast("Link copied");}catch(error){toast(error.message);}});
|
document.querySelector("#copy-public-link").addEventListener("click",async()=>{try{await copyText(location.href);toast("Link copied");}catch(error){toast(error.message);}});
|
||||||
|
|||||||
Reference in New Issue
Block a user