(function () {
'use strict';
class MarkMikuHighlighter {
constructor() {}
escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
wrapSpan(text, className) {
return '' + text + '';
}
highlight(code) {
if (!code) return '';
var result = this.escapeHtml(code);
var i = 0;
var out = '';
while (i < result.length) {
// Fenced code blocks: ```lang\n...\n```
if (result.substr(i, 3) === '```') {
var lineEnd = result.indexOf('\n', i);
if (lineEnd === -1) lineEnd = result.length;
var firstLine = result.substring(i, lineEnd);
var blockEnd = result.indexOf('\n```', lineEnd);
if (blockEnd !== -1) {
var lang = firstLine.substring(3);
out += this.wrapSpan('```', 'miku-syntax-html-tag');
if (lang) out += this.wrapSpan(lang, 'miku-syntax-html-attribute');
out += '\n';
var content = result.substring(lineEnd + 1, blockEnd);
if (content) out += this.wrapSpan(content, 'miku-syntax-html-string');
out += '\n' + this.wrapSpan('```', 'miku-syntax-html-tag');
i = blockEnd + 4;
continue;
}
}
// Spoiler: ||text||
if (result.substr(i, 2) === '||') {
var spoilerEnd = result.indexOf('||', i + 2);
if (spoilerEnd !== -1) {
out += this.wrapSpan(result.substring(i, spoilerEnd + 2), 'markmiku-syntax-spoiler');
i = spoilerEnd + 2;
continue;
}
}
// Bold: **text**
if (result.substr(i, 2) === '**') {
var boldEnd = result.indexOf('**', i + 2);
if (boldEnd !== -1) {
out += this.wrapSpan(result.substring(i, boldEnd + 2), 'miku-syntax-js-keyword');
i = boldEnd + 2;
continue;
}
}
// Underline: __text__
if (result.substr(i, 2) === '__') {
var underlineEnd = result.indexOf('__', i + 2);
if (underlineEnd !== -1) {
out += this.wrapSpan(result.substring(i, underlineEnd + 2), 'miku-syntax-js-function');
i = underlineEnd + 2;
continue;
}
}
// Strikethrough: ~~text~~
if (result.substr(i, 2) === '~~') {
var strikeEnd = result.indexOf('~~', i + 2);
if (strikeEnd !== -1) {
out += this.wrapSpan(result.substring(i, strikeEnd + 2), 'miku-syntax-html-comment');
i = strikeEnd + 2;
continue;
}
}
// Inline code: `text`
if (result[i] === '`' && result.substr(i, 3) !== '```') {
var codeEnd = result.indexOf('`', i + 1);
if (codeEnd !== -1 && result.indexOf('\n', i + 1) > codeEnd) {
out += this.wrapSpan(result.substring(i, codeEnd + 1), 'miku-syntax-html-string');
i = codeEnd + 1;
continue;
}
if (codeEnd !== -1 && result.indexOf('\n', i + 1) === -1) {
out += this.wrapSpan(result.substring(i, codeEnd + 1), 'miku-syntax-html-string');
i = codeEnd + 1;
continue;
}
}
// Italic: *text* (single, not preceded by *)
if (result[i] === '*' && (i === 0 || result[i - 1] !== '*') &&
(i + 1 < result.length && result[i + 1] !== '*')) {
var italicEnd = result.indexOf('*', i + 1);
if (italicEnd !== -1 && result[italicEnd + 1] !== '*') {
out += this.wrapSpan(result.substring(i, italicEnd + 1), 'markmiku-syntax-italic');
i = italicEnd + 1;
continue;
}
}
// Emoji: :name:
if (result[i] === ':') {
var emojiMatch = result.substring(i).match(/^:([a-z0-9_]+):/);
if (emojiMatch) {
out += this.wrapSpan(emojiMatch[0], 'markmiku-syntax-emoji');
i += emojiMatch[0].length;
continue;
}
}
// Link: [text](url)
if (result[i] === '[') {
var linkMatch = result.substring(i).match(/^\[([^\]]+)\]\(([^)]+)\)/);
if (linkMatch) {
out += this.wrapSpan('[', 'miku-syntax-html-tag');
out += linkMatch[1];
out += this.wrapSpan(']', 'miku-syntax-html-tag');
out += this.wrapSpan('(', 'miku-syntax-html-tag');
out += this.wrapSpan(linkMatch[2], 'miku-syntax-css-value');
out += this.wrapSpan(')', 'miku-syntax-html-tag');
i += linkMatch[0].length;
continue;
}
}
// Line-start tokens
if (i === 0 || result[i - 1] === '\n') {
// Headings: # ## ###
var headingMatch = result.substring(i).match(/^(#{1,3}) /);
if (headingMatch) {
out += this.wrapSpan(headingMatch[1], 'miku-syntax-css-selector');
out += ' ';
i += headingMatch[0].length;
continue;
}
// Blockquote: >
if (result.substr(i, 2) === '>' || result.substr(i, 5) === '> ') {
var gtLen = result.substr(i, 5) === '> ' ? 5 : 4;
out += this.wrapSpan(result.substring(i, i + gtLen), 'miku-syntax-html-attribute');
i += gtLen;
continue;
}
// List items: - or *
if ((result[i] === '-' || result[i] === '*') && result[i + 1] === ' ') {
out += this.wrapSpan(result[i], 'miku-syntax-css-property');
i++;
continue;
}
// Ordered list: 1. 2. etc.
var olMatch = result.substring(i).match(/^(\d+)\. /);
if (olMatch) {
out += this.wrapSpan(olMatch[1] + '.', 'miku-syntax-css-property');
out += ' ';
i += olMatch[0].length;
continue;
}
}
out += result[i];
i++;
}
return out;
}
}
class MarkMikuEmojiPicker {
constructor(editor) {
this.editor = editor;
this.emojis = [];
this.pickerEl = null;
this.visible = false;
}
setEmojis(emojiData) {
this.emojis = emojiData || [];
}
createPicker() {
if (this.pickerEl) return;
this.pickerEl = document.createElement('div');
this.pickerEl.className = 'markmiku-emoji-picker';
this.pickerEl.style.display = 'none';
var searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.className = 'markmiku-emoji-search';
searchInput.placeholder = 'Search emojis...';
searchInput.addEventListener('input', function () {
this.filterEmojis(searchInput.value);
}.bind(this));
var grid = document.createElement('div');
grid.className = 'markmiku-emoji-grid';
this.pickerEl.appendChild(searchInput);
this.pickerEl.appendChild(grid);
this.gridEl = grid;
document.body.appendChild(this.pickerEl);
this.renderGrid(this.emojis);
document.addEventListener('click', function (e) {
if (this.visible && !this.pickerEl.contains(e.target) &&
!e.target.closest('.markmiku-btn-emoji')) {
this.hide();
}
}.bind(this));
}
renderGrid(emojis) {
this.gridEl.innerHTML = '';
for (var i = 0; i < emojis.length; i++) {
var emoji = emojis[i];
var item = document.createElement('div');
item.className = 'markmiku-emoji-item';
item.title = ':' + emoji.name + ':';
item.innerHTML = '';
item.addEventListener('click', (function (em) {
return function () {
this.insertEmoji(em);
this.hide();
}.bind(this);
}.bind(this))(emoji));
this.gridEl.appendChild(item);
}
if (emojis.length === 0) {
this.gridEl.innerHTML = '
' + this.escapeHtml(codeContent.join('\n')) + '';
inCodeBlock = false;
codeLang = '';
codeContent = [];
i++;
continue;
}
}
if (inCodeBlock) {
codeContent.push(line);
i++;
continue;
}
// Blank line
if (line.trim() === '') {
html += this.closeListStack(listStack);
i++;
continue;
}
// Headings
var headingMatch = line.match(/^(#{1,3}) (.+)/);
if (headingMatch) {
html += this.closeListStack(listStack);
var level = headingMatch[1].length;
html += '' + this.inlineMarkdown(quoteLines.join(''; continue; } // Unordered list var ulMatch = line.match(/^[\-\*] (.+)/); if (ulMatch) { if (listStack.length === 0 || listStack[listStack.length - 1] !== 'ul') { html += this.closeListStack(listStack); html += '
')) + '
' + this.inlineMarkdown(line) + '
'; i++; } // Close any remaining open code block if (inCodeBlock) { var langAttr2 = codeLang ? ' class="language-' + this.escapeAttr(codeLang) + '"' : ''; html += '' + this.escapeHtml(codeContent.join('\n')) + '';
}
html += this.closeListStack(listStack);
return html;
}
closeListStack(stack) {
var html = '';
while (stack.length > 0) {
var tag = stack.pop();
html += '' + tag + '>';
}
return html;
}
inlineMarkdown(text) {
// Inline code first (protect from other replacements)
var codeSegments = [];
text = text.replace(/`([^`]+)`/g, function (m, p1) {
var idx = codeSegments.length;
codeSegments.push('' + p1 + '');
return '\x00CODE' + idx + '\x00';
});
// Spoiler: ||text||
text = text.replace(/\|\|(.+?)\|\|/g, '$1');
// Bold: **text**
text = text.replace(/\*\*(.+?)\*\*/g, '$1');
// Underline: __text__
text = text.replace(/__(.+?)__/g, '$1');
// Strikethrough: ~~text~~
text = text.replace(/~~(.+?)~~/g, '