Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/reusable-playground-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ jobs:

- run: vp run build

- run: |
mkdir -p playground/packages/cherry-markdown
- name: Prepare playground
run: |
mkdir -p playground/packages/cherry-markdown/src
cp -r packages/cherry-markdown/dist playground/packages/cherry-markdown
cp packages/cherry-markdown/src/Cherry.config.js playground/packages/cherry-markdown/src/
cp -r examples/assets playground
cp examples/*.html playground
[ -d examples/config_helper ] && cp -r examples/config_helper playground
Expand Down
101 changes: 66 additions & 35 deletions examples/config_helper/code-highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,78 @@ export function findChangedRange(before, after) {
beforeEnd--;
afterEnd--;
}
const changedText = after.slice(start, afterEnd);
return changedText.length <= 160 && !changedText.includes('\n') ? { start, end: afterEnd } : null;
return { start, end: afterEnd };
}

export function highlightCode(el, focusLine = -1, focusRange = null) {
let code = el.textContent;
if (focusRange) {
code = `${code.slice(0, focusRange.start)}\uE000${code.slice(focusRange.start, focusRange.end)}\uE001${code.slice(focusRange.end)}`;
} else if (focusLine >= 0) {
const lines = code.split('\n');
const line = lines[focusLine];
const separator = line.indexOf(':');
if (separator !== -1) {
const valueStart = separator + 1 + (line.slice(separator + 1).match(/^\s*/) || [''])[0].length;
const valueEnd = line.endsWith(',') ? line.length - 1 : line.length;
const isContainer = /^[\[{]$/.test(line.slice(valueStart, valueEnd));
const start = isContainer ? line.search(/\S/) : valueStart;
lines[focusLine] = `${line.slice(0, start)}\uE000${line.slice(start, valueEnd)}\uE001${line.slice(valueEnd)}`;
const CODE_TOKEN = /(\/\*[\s\S]*?\*\/|\/\/[^\n]*)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|([A-Za-z_$][\w$]*(?=\s*:))|\b(const|let|var|new|true|false|function|return|if|else|export|import|default)\b|\b(\d+(?:\.\d+)?)\b/g;

function highlightText(code, focusRange = null) {
let html = '';
let offset = 0;
let focusStarted = false;
let focusOpen = false;
const syncFocus = position => {
if (!focusRange) return;
if (!focusStarted && position === focusRange.start) {
html += '<span class="code-focus-token">';
focusStarted = true;
focusOpen = true;
}
if (focusOpen && position === focusRange.end) {
html += '</span>';
focusOpen = false;
}
code = lines.join('\n');
};
const appendSegment = (text, start, className) => {
const end = start + text.length;
const boundaries = [start, end];
if (focusRange?.start > start && focusRange.start < end) boundaries.push(focusRange.start);
if (focusRange?.end > start && focusRange.end < end) boundaries.push(focusRange.end);
boundaries.sort((a, b) => a - b);
for (let i = 0; i < boundaries.length - 1; i++) {
const from = boundaries[i];
const to = boundaries[i + 1];
syncFocus(from);
const escaped = escapeHtml(text.slice(from - start, to - start));
html += className ? `<span class="${className}">${escaped}</span>` : escaped;
}
};
CODE_TOKEN.lastIndex = 0;
for (const match of code.matchAll(CODE_TOKEN)) {
appendSegment(code.slice(offset, match.index), offset);
const className = match[1] ? 'source-comment' : match[2] ? 'source-string'
: match[3] ? 'source-property' : match[4] ? 'source-keyword' : 'source-number';
appendSegment(match[0], match.index, className);
offset = match.index + match[0].length;
}
let html = escapeHtml(code);
// 关键字
html = html.replace(/\b(const|let|var|new|true|false|function|return|if|else|export|import|default)\b/g,
'<span class="source-keyword">$1</span>');
// 字符串
html = html.replace(/'([^']*)'/g, '<span class="source-string">\'$1\'</span>');
// 数字
html = html.replace(/\b(\d+)\b/g, '<span class="source-number">$1</span>');
// 注释
html = html.replace(/(\/\/.*)/g, '<span class="source-comment">$1</span>');
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="source-comment">$1</span>');
// 属性名
html = html.replace(/(\w+)(?=\s*:)/g, '<span class="source-property">$1</span>');
appendSegment(code.slice(offset), offset);
syncFocus(code.length);
return html;
}

html = html.replace('\uE000', '<span class="code-focus-token">').replace('\uE001', '</span>');
el.innerHTML = html;
function findLineValueRange(code, lineIndex) {
if (lineIndex < 0) return null;
const lines = code.split('\n');
const line = lines[lineIndex];
if (!line) return null;
const separator = line.indexOf(':');
if (separator === -1) return null;
const valueStart = separator + 1 + (line.slice(separator + 1).match(/^\s*/) || [''])[0].length;
const valueEnd = line.endsWith(',') ? line.length - 1 : line.length;
const isContainer = /^[\[{]$/.test(line.slice(valueStart, valueEnd));
const start = isContainer ? line.search(/\S/) : valueStart;
const lineOffset = lines.slice(0, lineIndex).reduce((length, previous) => length + previous.length + 1, 0);
return { start: lineOffset + start, end: lineOffset + valueEnd };
}

export function highlightCode(el, focusLine = -1, focusRange = null) {
const code = el.textContent;
const range = focusRange || findLineValueRange(code, focusLine);
el.innerHTML = highlightText(code, range);
}

export function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
return str.replace(/[&<>"']/g, char => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
})[char]);
}
Loading
Loading