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
65 changes: 55 additions & 10 deletions desktop-app/resources/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ document.addEventListener("DOMContentLoaded", function () {
const EMOJI_API_URL = 'https://api.github.com/emojis';
let emojiLoadPromise = null;
let emojiEntries = [];
let emojiUrlMap = new Map();
let emojiLookupLoaded = false;
let emojiRenderScheduled = false;
let emojiItems = [];
const emojiSelection = new Set();
let symbolItems = [];
Expand Down Expand Up @@ -1493,6 +1496,20 @@ This is a fully client-side application. Your content never leaves your browser
}
}

function scheduleEmojiLookupRefresh() {
if (emojiLookupLoaded || emojiRenderScheduled) return;
emojiRenderScheduled = true;
loadEmojiEntries()
.then(() => {
if (emojiUrlMap.size) {
renderMarkdown();
}
})
.finally(() => {
emojiRenderScheduled = false;
});
}

function processEmojis(element) {
const walker = document.createTreeWalker(
element,
Expand All @@ -1519,36 +1536,59 @@ This is a fully client-side application. Your content never leaves your browser
}
}

let needsEmojiLookup = false;
textNodes.forEach(textNode => {
const text = textNode.nodeValue;
const emojiRegex = /:([\w+-]+):/g;

let match;
let lastIndex = 0;
let result = '';
let hasEmoji = false;
const fragment = document.createDocumentFragment();

while ((match = emojiRegex.exec(text)) !== null) {
const shortcode = match[1];
const emoji = joypixels.shortnameToUnicode(`:${shortcode}:`);

if (emoji !== `:${shortcode}:`) { // If conversion was successful
hasEmoji = true;
result += text.substring(lastIndex, match.index) + emoji;
if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
}
fragment.appendChild(document.createTextNode(emoji));
lastIndex = emojiRegex.lastIndex;
} else {
result += text.substring(lastIndex, emojiRegex.lastIndex);
lastIndex = emojiRegex.lastIndex;
const emojiUrl = emojiUrlMap.get(shortcode);
if (emojiUrl) {
hasEmoji = true;
if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
}
const image = document.createElement('img');
image.className = 'emoji-inline';
image.src = emojiUrl;
image.alt = `:${shortcode}:`;
image.loading = 'lazy';
image.setAttribute('aria-label', `:${shortcode}:`);
fragment.appendChild(image);
Comment on lines +1567 to +1573
lastIndex = emojiRegex.lastIndex;
} else if (!emojiLookupLoaded) {
needsEmojiLookup = true;
}
}
}

if (hasEmoji) {
result += text.substring(lastIndex);
const span = document.createElement('span');
span.innerHTML = result;
textNode.parentNode.replaceChild(span, textNode);
if (lastIndex < text.length) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
}
textNode.parentNode.replaceChild(fragment, textNode);
}
});

if (needsEmojiLookup) {
scheduleEmojiLookupRefresh();
}
}

function debouncedRender() {
Expand Down Expand Up @@ -2115,11 +2155,15 @@ This is a fully client-side application. Your content never leaves your browser
shortcode: `:${name}:`,
search: `${name} :${name}:`.toLowerCase(),
}));
emojiUrlMap = new Map(emojiEntries.map((entry) => [entry.name, entry.url]));
emojiLookupLoaded = true;
return emojiEntries;
})
.catch((error) => {
console.error('Failed to load GitHub emojis:', error);
emojiEntries = [];
emojiUrlMap = new Map();
emojiLookupLoaded = true;
return emojiEntries;
});
return emojiLoadPromise;
Expand Down Expand Up @@ -2511,7 +2555,8 @@ This is a fully client-side application. Your content never leaves your browser

const alertTypes = ['note', 'tip', 'important', 'warning', 'caution'];
let selectedType = alertTypes[0];
const options = alertTypes.map((type) => {
const options = [];
alertTypes.forEach((type) => {
const meta = GITHUB_ALERT_META[type] || { label: type };
const option = document.createElement('button');
option.type = 'button';
Expand All @@ -2531,8 +2576,8 @@ This is a fully client-side application. Your content never leaves your browser
item.setAttribute('aria-pressed', isSelected.toString());
});
});
options.push(option);
grid.appendChild(option);
return option;
});

function insertAlert() {
Expand Down
6 changes: 6 additions & 0 deletions desktop-app/resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ body {
padding: 0.2em 0.4em;
}

.markdown-body img.emoji-inline {
width: 1em;
height: 1em;
vertical-align: -0.1em;
}

.markdown-body .markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
Expand Down
60 changes: 52 additions & 8 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ document.addEventListener("DOMContentLoaded", function () {
const EMOJI_API_URL = 'https://api.github.com/emojis';
let emojiLoadPromise = null;
let emojiEntries = [];
let emojiUrlMap = new Map();
let emojiLookupLoaded = false;
let emojiRenderScheduled = false;
let emojiItems = [];
const emojiSelection = new Set();
let symbolItems = [];
Expand Down Expand Up @@ -1493,6 +1496,20 @@ This is a fully client-side application. Your content never leaves your browser
}
}

function scheduleEmojiLookupRefresh() {
if (emojiLookupLoaded || emojiRenderScheduled) return;
emojiRenderScheduled = true;
loadEmojiEntries()
.then(() => {
if (emojiUrlMap.size) {
renderMarkdown();
}
})
.finally(() => {
emojiRenderScheduled = false;
});
}

function processEmojis(element) {
const walker = document.createTreeWalker(
element,
Expand All @@ -1519,36 +1536,59 @@ This is a fully client-side application. Your content never leaves your browser
}
}

let needsEmojiLookup = false;
textNodes.forEach(textNode => {
const text = textNode.nodeValue;
const emojiRegex = /:([\w+-]+):/g;

let match;
let lastIndex = 0;
let result = '';
let hasEmoji = false;
const fragment = document.createDocumentFragment();

while ((match = emojiRegex.exec(text)) !== null) {
const shortcode = match[1];
const emoji = joypixels.shortnameToUnicode(`:${shortcode}:`);

if (emoji !== `:${shortcode}:`) { // If conversion was successful
hasEmoji = true;
result += text.substring(lastIndex, match.index) + emoji;
if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
}
fragment.appendChild(document.createTextNode(emoji));
lastIndex = emojiRegex.lastIndex;
} else {
result += text.substring(lastIndex, emojiRegex.lastIndex);
lastIndex = emojiRegex.lastIndex;
const emojiUrl = emojiUrlMap.get(shortcode);
if (emojiUrl) {
hasEmoji = true;
if (match.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
}
const image = document.createElement('img');
image.className = 'emoji-inline';
image.src = emojiUrl;
image.alt = `:${shortcode}:`;
image.loading = 'lazy';
image.setAttribute('aria-label', `:${shortcode}:`);
fragment.appendChild(image);
Comment on lines +1567 to +1573
lastIndex = emojiRegex.lastIndex;
} else if (!emojiLookupLoaded) {
needsEmojiLookup = true;
}
}
}

if (hasEmoji) {
result += text.substring(lastIndex);
const span = document.createElement('span');
span.innerHTML = result;
textNode.parentNode.replaceChild(span, textNode);
if (lastIndex < text.length) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
}
textNode.parentNode.replaceChild(fragment, textNode);
}
});

if (needsEmojiLookup) {
scheduleEmojiLookupRefresh();
}
}

function debouncedRender() {
Expand Down Expand Up @@ -2115,11 +2155,15 @@ This is a fully client-side application. Your content never leaves your browser
shortcode: `:${name}:`,
search: `${name} :${name}:`.toLowerCase(),
}));
emojiUrlMap = new Map(emojiEntries.map((entry) => [entry.name, entry.url]));
emojiLookupLoaded = true;
return emojiEntries;
})
.catch((error) => {
console.error('Failed to load GitHub emojis:', error);
emojiEntries = [];
emojiUrlMap = new Map();
emojiLookupLoaded = true;
return emojiEntries;
});
return emojiLoadPromise;
Expand Down
6 changes: 6 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ body {
padding: 0.2em 0.4em;
}

.markdown-body img.emoji-inline {
width: 1em;
height: 1em;
vertical-align: -0.1em;
}

.markdown-body .markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
Expand Down
Loading