-
Notifications
You must be signed in to change notification settings - Fork 25
fix(site): refresh landing demos and mobile scroll trigger #499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ export default defineConfig({ | |
| react(), | ||
| starlight({ | ||
| title: 'TouchAI', | ||
| disable404Route: true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Astro custom 404 route candidates:"
fd -a '^404\.(astro|md|mdx)$' apps/site/src/pages || true
echo
echo "Starlight docs 404 candidates:"
fd -a '^404\.(md|mdx)$' apps/site/src/content/docs || trueRepository: TouchAI-org/TouchAI Length of output: 277 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Astro/Starlight config and 404-related files:"
git ls-files 'apps/site/**' | rg '(^|/)(astro\.config\.mjs|404\.(astro|md|mdx)|starlight|pages/404|content/docs/404)' || true
echo
echo "Search for disable404Route usage:"
rg -n "disable404Route|404\\.md|404\\.astro" apps/site || true
echo
echo "Inspect the site astro config:"
cat -n apps/site/astro.config.mjsRepository: TouchAI-org/TouchAI Length of output: 972 Keep Starlight’s 404 route enabled 🤖 Prompt for AI Agents |
||
| social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/TouchAI-org/TouchAI' }], | ||
| sidebar: [ | ||
| ], | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,253 @@ | ||||||||||||||||||||||||
| (function () { | ||||||||||||||||||||||||
| const renderer = window.TouchAILiteRenderer; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function escapeHtml(value) { | ||||||||||||||||||||||||
| return renderer ? renderer.escapeHtml(value) : String(value); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+4
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Keep escaping safe when the base renderer is unavailable. The fallback currently returns raw 🛡️ Proposed fix function escapeHtml(value) {
- return renderer ? renderer.escapeHtml(value) : String(value);
+ if (renderer) return renderer.escapeHtml(value);
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function normalizeFormula(source) { | ||||||||||||||||||||||||
| return String(source) | ||||||||||||||||||||||||
| .replace(/\\left/g, '') | ||||||||||||||||||||||||
| .replace(/\\right/g, '') | ||||||||||||||||||||||||
| .replace(/\\,/g, ' ') | ||||||||||||||||||||||||
| .replace(/\\:/g, ' ') | ||||||||||||||||||||||||
| .replace(/\\;/g, ' ') | ||||||||||||||||||||||||
| .replace(/\\quad/g, ' ') | ||||||||||||||||||||||||
| .replace(/\\qquad/g, ' ') | ||||||||||||||||||||||||
| .replace(/\\!/g, ''); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function renderFormula(source, displayMode) { | ||||||||||||||||||||||||
| if (window.katex && typeof window.katex.renderToString === 'function') { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| return window.katex.renderToString(String(source), { | ||||||||||||||||||||||||
| displayMode, | ||||||||||||||||||||||||
| throwOnError: false, | ||||||||||||||||||||||||
| strict: 'ignore', | ||||||||||||||||||||||||
| trust: false, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } catch (error) {} | ||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Make the KaTeX fallback catch lint-clean. The empty catch with an unused 🧰 Tools🪛 ESLint[error] 29-29: 'error' is defined but never used. ( [error] 29-29: Empty block statement. (no-empty) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const input = normalizeFormula(source); | ||||||||||||||||||||||||
| let index = 0; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const commandMap = { | ||||||||||||||||||||||||
| theta: 'θ', | ||||||||||||||||||||||||
| cos: 'cos', | ||||||||||||||||||||||||
| sin: 'sin', | ||||||||||||||||||||||||
| max: 'max', | ||||||||||||||||||||||||
| min: 'min', | ||||||||||||||||||||||||
| in: '∈', | ||||||||||||||||||||||||
| Rightarrow: '⇒', | ||||||||||||||||||||||||
| to: '→', | ||||||||||||||||||||||||
| cdot: '·', | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const readCommandName = () => { | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| let name = ''; | ||||||||||||||||||||||||
| while (index < input.length && /[A-Za-z]/.test(input[index])) { | ||||||||||||||||||||||||
| name += input[index]; | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!name && index < input.length) { | ||||||||||||||||||||||||
| name = input[index]; | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return name; | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const readRawGroup = () => { | ||||||||||||||||||||||||
| if (input[index] !== '{') { | ||||||||||||||||||||||||
| const start = index; | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| return input.slice(start, index); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| let depth = 1; | ||||||||||||||||||||||||
| let value = ''; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| while (index < input.length && depth > 0) { | ||||||||||||||||||||||||
| const char = input[index]; | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (char === '{') { | ||||||||||||||||||||||||
| depth += 1; | ||||||||||||||||||||||||
| value += char; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (char === '}') { | ||||||||||||||||||||||||
| depth -= 1; | ||||||||||||||||||||||||
| if (depth > 0) value += char; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| value += char; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return value; | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const readArgument = () => { | ||||||||||||||||||||||||
| while (input[index] === ' ') index += 1; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (index >= input.length) return ''; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (input[index] === '{') { | ||||||||||||||||||||||||
| const raw = readRawGroup(); | ||||||||||||||||||||||||
| return renderFormula(raw, false); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (input[index] === '\\') { | ||||||||||||||||||||||||
| const command = readCommandName(); | ||||||||||||||||||||||||
| if (command === 'text') { | ||||||||||||||||||||||||
| return `<span class="op">${escapeHtml(readRawGroup())}</span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (command === 'frac') { | ||||||||||||||||||||||||
| const num = readArgument(); | ||||||||||||||||||||||||
| const den = readArgument(); | ||||||||||||||||||||||||
| return `<span class="frac"><span class="num">${num}</span><span class="den">${den}</span></span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (command === 'sqrt') { | ||||||||||||||||||||||||
| const radicand = readArgument(); | ||||||||||||||||||||||||
| return `<span class="root"><span class="radicand">${radicand}</span></span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (command === 'boxed') { | ||||||||||||||||||||||||
| return `<span class="boxed-formula">${readArgument()}</span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const mapped = commandMap[command] ?? command; | ||||||||||||||||||||||||
| return `<span class="${/^[A-Za-z]+$/.test(mapped) ? 'op' : 'punct'}">${escapeHtml(mapped)}</span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const char = input[index]; | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| return escapeHtml(char); | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const parse = (stopChar) => { | ||||||||||||||||||||||||
| let html = ''; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| while (index < input.length) { | ||||||||||||||||||||||||
| const char = input[index]; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (stopChar && char === stopChar) { | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (char === '{') { | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| html += parse('}'); | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (char === '^' || char === '_') { | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| const tag = char === '^' ? 'sup' : 'sub'; | ||||||||||||||||||||||||
| html += `<${tag}>${readArgument()}</${tag}>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (char === '\\') { | ||||||||||||||||||||||||
| const command = readCommandName(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (command === 'frac') { | ||||||||||||||||||||||||
| const num = readArgument(); | ||||||||||||||||||||||||
| const den = readArgument(); | ||||||||||||||||||||||||
| html += `<span class="frac"><span class="num">${num}</span><span class="den">${den}</span></span>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (command === 'sqrt') { | ||||||||||||||||||||||||
| const radicand = readArgument(); | ||||||||||||||||||||||||
| html += `<span class="root"><span class="radicand">${radicand}</span></span>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (command === 'boxed') { | ||||||||||||||||||||||||
| html += `<span class="boxed-formula">${readArgument()}</span>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (command === 'text') { | ||||||||||||||||||||||||
| html += `<span class="op">${escapeHtml(readRawGroup())}</span>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||
| command === ',' || | ||||||||||||||||||||||||
| command === ':' || | ||||||||||||||||||||||||
| command === ';' || | ||||||||||||||||||||||||
| command === 'quad' || | ||||||||||||||||||||||||
| command === 'qquad' | ||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||
| html += ' '; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const mapped = commandMap[command] ?? command; | ||||||||||||||||||||||||
| const className = /^[A-Za-z]+$/.test(mapped) ? 'op' : 'punct'; | ||||||||||||||||||||||||
| html += `<span class="${className}">${escapeHtml(mapped)}</span>`; | ||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| html += escapeHtml(char); | ||||||||||||||||||||||||
| index += 1; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return html; | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const inner = parse(); | ||||||||||||||||||||||||
| const className = displayMode | ||||||||||||||||||||||||
| ? 'formula-module' | ||||||||||||||||||||||||
| : 'formula-module inline-formula'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return `<span class="${className}">${inner}</span>`; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function renderMarkdownWithMath(markdown) { | ||||||||||||||||||||||||
| const mathTokens = []; | ||||||||||||||||||||||||
| const protectedMarkdown = String(markdown) | ||||||||||||||||||||||||
| .replace(/\$\$([\s\S]+?)\$\$/g, (_, formula) => { | ||||||||||||||||||||||||
| const token = `@@MATH_BLOCK_${mathTokens.length}@@`; | ||||||||||||||||||||||||
| mathTokens.push({ token, formula: formula.trim(), display: true }); | ||||||||||||||||||||||||
| return `\n\n${token}\n\n`; | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| .replace(/\$([^$\n]+?)\$/g, (_, formula) => { | ||||||||||||||||||||||||
| const token = `@@MATH_INLINE_${mathTokens.length}@@`; | ||||||||||||||||||||||||
| mathTokens.push({ token, formula: formula.trim(), display: false }); | ||||||||||||||||||||||||
| return token; | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| let html = renderer | ||||||||||||||||||||||||
| ? renderer.renderMarkdownContent(protectedMarkdown) | ||||||||||||||||||||||||
| : escapeHtml(protectedMarkdown); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mathTokens.forEach(({ token, formula, display }) => { | ||||||||||||||||||||||||
| const rendered = renderFormula(formula, display); | ||||||||||||||||||||||||
| const replacement = display | ||||||||||||||||||||||||
| ? `<div class="math-block"><span class="math-display-frame">${rendered}</span></div>` | ||||||||||||||||||||||||
| : `<span class="katex-formula">${rendered}</span>`; | ||||||||||||||||||||||||
| html = html | ||||||||||||||||||||||||
| .replace( | ||||||||||||||||||||||||
| new RegExp(`<p class="paragraph-node">${token}</p>`, 'g'), | ||||||||||||||||||||||||
| replacement | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| .replace(new RegExp(token, 'g'), replacement); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return html; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| window.TouchAILiteMathRenderer = { | ||||||||||||||||||||||||
| renderFormula, | ||||||||||||||||||||||||
| renderMarkdownWithMath, | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| (function () { | ||
| function escapeHtml(value) { | ||
| return String(value) | ||
| .replace(/&/g, '&') | ||
| .replace(/</g, '<') | ||
| .replace(/>/g, '>') | ||
| .replace(/"/g, '"'); | ||
| } | ||
|
|
||
| function stripImageMarkdown(value) { | ||
| return String(value) | ||
| .replace(/\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)/g, '') | ||
| .replace(/!\[[^\]]*\]\([^)]*\)/g, ''); | ||
| } | ||
|
|
||
| function renderInline(source) { | ||
| const segments = stripImageMarkdown(source).split(/(`[^`]+`)/g); | ||
|
|
||
| return segments | ||
| .map((segment) => { | ||
| if (!segment) return ''; | ||
| if (segment.startsWith('`') && segment.endsWith('`')) { | ||
| return `<span class="kbd">${escapeHtml(segment.slice(1, -1))}</span>`; | ||
| } | ||
|
|
||
| return escapeHtml(segment).replace( | ||
| /\*\*([^*]+)\*\*/g, | ||
| '<strong>$1</strong>' | ||
| ); | ||
|
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Apply the formatter to satisfy Prettier. Static analysis reports Prettier errors in these two wrapped expressions; this can fail lint even though behavior is correct. Also applies to: 51-53 🧰 Tools🪛 ESLint[error] 26-29: Replace (prettier/prettier) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| }) | ||
| .join(''); | ||
| } | ||
|
|
||
| function renderMarkdownContent(markdown) { | ||
| const lines = String(markdown).split(/\r?\n/); | ||
| const html = []; | ||
| let paragraphLines = []; | ||
| let listItems = []; | ||
|
|
||
| const flushParagraph = () => { | ||
| if (!paragraphLines.length) return; | ||
| html.push( | ||
| `<p class="paragraph-node">${paragraphLines.map(renderInline).join('<br>')}</p>` | ||
| ); | ||
| paragraphLines = []; | ||
| }; | ||
|
|
||
| const flushList = () => { | ||
| if (!listItems.length) return; | ||
| html.push( | ||
| `<ul>${listItems | ||
| .map((item) => `<li>${renderInline(item)}</li>`) | ||
| .join('')}</ul>` | ||
| ); | ||
| listItems = []; | ||
| }; | ||
|
|
||
| lines.forEach((line) => { | ||
| const trimmed = stripImageMarkdown(line).trim(); | ||
|
|
||
| if (!trimmed) { | ||
| flushParagraph(); | ||
| flushList(); | ||
| return; | ||
| } | ||
|
|
||
| if (trimmed === '---') { | ||
| flushParagraph(); | ||
| flushList(); | ||
| html.push(`<p class="response-divider" aria-hidden="true"></p>`); | ||
| return; | ||
| } | ||
|
|
||
| if (trimmed.startsWith('## ')) { | ||
| flushParagraph(); | ||
| flushList(); | ||
| html.push(`<h2>${renderInline(trimmed.slice(3))}</h2>`); | ||
| return; | ||
| } | ||
|
|
||
| if (trimmed.startsWith('- ')) { | ||
| flushParagraph(); | ||
| listItems.push(trimmed.slice(2)); | ||
| return; | ||
| } | ||
|
|
||
| flushList(); | ||
| paragraphLines.push(trimmed); | ||
| }); | ||
|
|
||
| flushParagraph(); | ||
| flushList(); | ||
|
|
||
| return html.join('\n'); | ||
| } | ||
|
|
||
| window.TouchAILiteRenderer = { | ||
| escapeHtml, | ||
| renderInline, | ||
| renderMarkdownContent, | ||
| }; | ||
| })(); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
Warn that the analytics endpoint is client-visible.
index.astroemits this value into the page, so tokens embedded in the URL would be exposed to every visitor.Proposed doc tweak
# Generic analytics collector endpoint that accepts JSON POST payloads. +# This value is exposed to browsers; do not include secrets or private write tokens. # Leave empty to disable the built-in pageview and click tracking. PUBLIC_ANALYTICS_ENDPOINT=📝 Committable suggestion
🤖 Prompt for AI Agents