- Added a new file renaming functionality in the editor, allowing users to rename files and directories in place. - Introduced notification settings for team inbox messages and task clarifications, enabling users to receive native OS notifications for important updates. - Updated the README to reflect the new features and provide a clearer overview of the task management capabilities. - Improved the application icon handling for notifications across different platforms.
158 lines
4.2 KiB
TypeScript
158 lines
4.2 KiB
TypeScript
/**
|
|
* Standalone syntax highlighter using highlight.js.
|
|
*
|
|
* Highlights code without a full CodeMirror EditorView.
|
|
* Outputs HTML strings with `hljs-*` CSS classes (already styled in index.css).
|
|
*/
|
|
|
|
import hljs from 'highlight.js';
|
|
|
|
// =============================================================================
|
|
// File extension → highlight.js language mapping
|
|
// =============================================================================
|
|
|
|
const EXT_TO_LANG: Record<string, string> = {
|
|
'.js': 'javascript',
|
|
'.mjs': 'javascript',
|
|
'.cjs': 'javascript',
|
|
'.jsx': 'javascript',
|
|
'.ts': 'typescript',
|
|
'.tsx': 'typescript',
|
|
'.py': 'python',
|
|
'.rb': 'ruby',
|
|
'.go': 'go',
|
|
'.rs': 'rust',
|
|
'.java': 'java',
|
|
'.kt': 'kotlin',
|
|
'.swift': 'swift',
|
|
'.c': 'c',
|
|
'.cpp': 'cpp',
|
|
'.cc': 'cpp',
|
|
'.h': 'c',
|
|
'.hpp': 'cpp',
|
|
'.cs': 'csharp',
|
|
'.php': 'php',
|
|
'.sh': 'bash',
|
|
'.bash': 'bash',
|
|
'.zsh': 'bash',
|
|
'.json': 'json',
|
|
'.yaml': 'yaml',
|
|
'.yml': 'yaml',
|
|
'.xml': 'xml',
|
|
'.html': 'xml',
|
|
'.htm': 'xml',
|
|
'.css': 'css',
|
|
'.scss': 'scss',
|
|
'.less': 'less',
|
|
'.sql': 'sql',
|
|
'.md': 'markdown',
|
|
'.toml': 'ini',
|
|
'.ini': 'ini',
|
|
'.lua': 'lua',
|
|
'.r': 'r',
|
|
'.scala': 'scala',
|
|
'.dart': 'dart',
|
|
'.ex': 'elixir',
|
|
'.exs': 'elixir',
|
|
'.erl': 'erlang',
|
|
'.hs': 'haskell',
|
|
'.pl': 'perl',
|
|
'.pm': 'perl',
|
|
'.m': 'objectivec',
|
|
'.mm': 'objectivec',
|
|
'.makefile': 'makefile',
|
|
'.cmake': 'cmake',
|
|
'.dockerfile': 'dockerfile',
|
|
'.tf': 'hcl',
|
|
'.proto': 'protobuf',
|
|
'.graphql': 'graphql',
|
|
'.gql': 'graphql',
|
|
'.vue': 'xml',
|
|
'.svelte': 'xml',
|
|
};
|
|
|
|
function getLanguage(fileName: string): string | undefined {
|
|
const dotIndex = fileName.lastIndexOf('.');
|
|
if (dotIndex === -1) return undefined;
|
|
const ext = fileName.slice(dotIndex).toLowerCase();
|
|
|
|
// Explicit map first, then try extension as hljs alias (e.g. 'rb', 'py')
|
|
const mapped = EXT_TO_LANG[ext];
|
|
if (mapped) return mapped;
|
|
|
|
const bare = ext.slice(1); // '.ts' → 'ts'
|
|
if (bare && hljs.getLanguage(bare)) return bare;
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// =============================================================================
|
|
// HTML line splitting
|
|
// =============================================================================
|
|
|
|
/** Escape HTML and split into plain-text lines (fallback for unknown languages). */
|
|
function escapeAndSplit(code: string): string[] {
|
|
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
return escaped.split('\n');
|
|
}
|
|
|
|
/**
|
|
* Split highlight.js HTML output into per-line strings with balanced tags.
|
|
* Multi-line spans (comments, strings) are properly closed/reopened at line breaks.
|
|
*/
|
|
function splitHtmlByLines(html: string): string[] {
|
|
const rawLines = html.split('\n');
|
|
const result: string[] = [];
|
|
const openTags: string[] = [];
|
|
|
|
for (const rawLine of rawLines) {
|
|
// Prefix with any spans still open from previous lines
|
|
const prefix = openTags.join('');
|
|
const fullLine = prefix + rawLine;
|
|
|
|
// Update open tags stack by scanning this line's tags
|
|
const tagRegex = /<span[^>]*>|<\/span>/g;
|
|
let match;
|
|
while ((match = tagRegex.exec(rawLine)) !== null) {
|
|
if (match[0] === '</span>') {
|
|
if (openTags.length > 0) openTags.pop();
|
|
} else {
|
|
openTags.push(match[0]);
|
|
}
|
|
}
|
|
|
|
// Close any unclosed spans for this line
|
|
const suffix = '</span>'.repeat(openTags.length);
|
|
result.push(fullLine + suffix);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Public API
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Highlight code and return per-line HTML strings with `hljs-*` CSS classes.
|
|
* Uses highlight.js (same library as rehype-highlight in markdown rendering).
|
|
*/
|
|
export function highlightLines(code: string, fileName: string): string[] {
|
|
if (!code) return [''];
|
|
|
|
const lang = getLanguage(fileName);
|
|
|
|
let highlighted: string;
|
|
if (!lang) {
|
|
// Unknown extension — plain text is safer than unreliable auto-detection
|
|
return escapeAndSplit(code);
|
|
}
|
|
|
|
try {
|
|
highlighted = hljs.highlight(code, { language: lang }).value;
|
|
} catch {
|
|
return escapeAndSplit(code);
|
|
}
|
|
|
|
return splitHtmlByLines(highlighted);
|
|
}
|