mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-24 10:15:37 +03:00
New feature: :shortcode: expander in compose field
Using `innerHTML` because easier to code but the `encodeHTML` function is troublesome
This commit is contained in:
parent
122f6877c9
commit
263e48d019
2 changed files with 107 additions and 22 deletions
|
@ -29,7 +29,7 @@ registerRoute(imageRoute);
|
||||||
|
|
||||||
// Cache /instance because masto.js has to keep calling it while initializing
|
// Cache /instance because masto.js has to keep calling it while initializing
|
||||||
const apiExtendedRoute = new RegExpRoute(
|
const apiExtendedRoute = new RegExpRoute(
|
||||||
/^https?:\/\/[^\/]+\/api\/v\d+\/instance/,
|
/^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis)/,
|
||||||
new StaleWhileRevalidate({
|
new StaleWhileRevalidate({
|
||||||
cacheName: 'api-extended',
|
cacheName: 'api-extended',
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
@ -36,6 +36,10 @@ const expiresInFromExpiresAt = (expiresAt) => {
|
||||||
return expirySeconds.find((s) => s >= delta) || oneDay;
|
return expirySeconds.find((s) => s >= delta) || oneDay;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const menu = document.createElement('ul');
|
||||||
|
menu.role = 'listbox';
|
||||||
|
menu.className = 'text-expander-menu';
|
||||||
|
|
||||||
function Compose({
|
function Compose({
|
||||||
onClose,
|
onClose,
|
||||||
replyToStatus,
|
replyToStatus,
|
||||||
|
@ -82,6 +86,15 @@ function Compose({
|
||||||
const [mediaAttachments, setMediaAttachments] = useState([]);
|
const [mediaAttachments, setMediaAttachments] = useState([]);
|
||||||
const [poll, setPoll] = useState(null);
|
const [poll, setPoll] = useState(null);
|
||||||
|
|
||||||
|
const customEmojis = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const emojis = await masto.customEmojis.fetchAll();
|
||||||
|
console.log({ emojis });
|
||||||
|
customEmojis.current = emojis;
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (replyToStatus) {
|
if (replyToStatus) {
|
||||||
const { spoilerText, visibility, sensitive } = replyToStatus;
|
const { spoilerText, visibility, sensitive } = replyToStatus;
|
||||||
|
@ -167,6 +180,7 @@ function Compose({
|
||||||
// console.log('text-expander-change', e);
|
// console.log('text-expander-change', e);
|
||||||
const { key, provide, text } = e.detail;
|
const { key, provide, text } = e.detail;
|
||||||
textExpanderTextRef.current = text;
|
textExpanderTextRef.current = text;
|
||||||
|
|
||||||
if (text === '') {
|
if (text === '') {
|
||||||
provide(
|
provide(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
|
@ -175,6 +189,34 @@ function Compose({
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === ':') {
|
||||||
|
// const emojis = customEmojis.current.filter((emoji) =>
|
||||||
|
// emoji.shortcode.startsWith(text),
|
||||||
|
// );
|
||||||
|
const emojis = filterShortcodes(customEmojis.current, text);
|
||||||
|
let html = '';
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const { shortcode, url } = emoji;
|
||||||
|
html += `
|
||||||
|
<li role="option" data-value="${encodeHTML(shortcode)}">
|
||||||
|
<img src="${encodeHTML(
|
||||||
|
url,
|
||||||
|
)}" width="16" height="16" alt="" loading="lazy" />
|
||||||
|
:${encodeHTML(shortcode)}:
|
||||||
|
</li>`;
|
||||||
|
});
|
||||||
|
// console.log({ emojis, html });
|
||||||
|
menu.innerHTML = html;
|
||||||
|
provide(
|
||||||
|
Promise.resolve({
|
||||||
|
matched: emojis.length > 0,
|
||||||
|
fragment: menu,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const type = {
|
const type = {
|
||||||
'@': 'accounts',
|
'@': 'accounts',
|
||||||
'#': 'hashtags',
|
'#': 'hashtags',
|
||||||
|
@ -192,9 +234,7 @@ function Compose({
|
||||||
}
|
}
|
||||||
const results = value[type];
|
const results = value[type];
|
||||||
console.log('RESULTS', value, results);
|
console.log('RESULTS', value, results);
|
||||||
const menu = document.createElement('ul');
|
let html = '';
|
||||||
menu.role = 'listbox';
|
|
||||||
menu.className = 'text-expander-menu';
|
|
||||||
results.forEach((result) => {
|
results.forEach((result) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
@ -205,27 +245,29 @@ function Compose({
|
||||||
emojis,
|
emojis,
|
||||||
} = result;
|
} = result;
|
||||||
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
||||||
const item = document.createElement('li');
|
// const item = menuItem.cloneNode();
|
||||||
item.setAttribute('role', 'option');
|
|
||||||
if (acct) {
|
if (acct) {
|
||||||
item.dataset.value = acct;
|
html += `
|
||||||
// Want to use <Avatar /> here, but will need to render to string 😅
|
<li role="option" data-value="${encodeHTML(acct)}">
|
||||||
item.innerHTML = `
|
<span class="avatar">
|
||||||
<span class="avatar">
|
<img src="${encodeHTML(
|
||||||
<img src="${avatarStatic}" width="16" height="16" alt="" loading="lazy" />
|
avatarStatic,
|
||||||
</span>
|
)}" width="16" height="16" alt="" loading="lazy" />
|
||||||
<span>
|
</span>
|
||||||
<b>${displayNameWithEmoji || username}</b>
|
<span>
|
||||||
<br>@${acct}
|
<b>${encodeHTML(displayNameWithEmoji || username)}</b>
|
||||||
</span>
|
<br>@${encodeHTML(acct)}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
item.dataset.value = name;
|
html += `
|
||||||
item.innerHTML = `
|
<li role="option" data-value="${encodeHTML(name)}">
|
||||||
<span>#<b>${name}</b></span>
|
<span>#<b>${encodeHTML(name)}</b></span>
|
||||||
|
</li>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
menu.appendChild(item);
|
menu.innerHTML = html;
|
||||||
});
|
});
|
||||||
console.log('MENU', results, menu);
|
console.log('MENU', results, menu);
|
||||||
resolve({
|
resolve({
|
||||||
|
@ -244,7 +286,11 @@ function Compose({
|
||||||
|
|
||||||
textExpanderRef.current.addEventListener('text-expander-value', (e) => {
|
textExpanderRef.current.addEventListener('text-expander-value', (e) => {
|
||||||
const { key, item } = e.detail;
|
const { key, item } = e.detail;
|
||||||
e.detail.value = key + item.dataset.value;
|
if (key === ':') {
|
||||||
|
e.detail.value = `:${item.dataset.value}:`;
|
||||||
|
} else {
|
||||||
|
e.detail.value = `${key}${item.dataset.value}`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -664,7 +710,7 @@ function Compose({
|
||||||
</select>
|
</select>
|
||||||
</label>{' '}
|
</label>{' '}
|
||||||
</div>
|
</div>
|
||||||
<text-expander ref={textExpanderRef} keys="@ #">
|
<text-expander ref={textExpanderRef} keys="@ # :">
|
||||||
<textarea
|
<textarea
|
||||||
class="large"
|
class="large"
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
|
@ -980,4 +1026,43 @@ function Poll({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterShortcodes(emojis, searchTerm) {
|
||||||
|
searchTerm = searchTerm.toLowerCase();
|
||||||
|
|
||||||
|
// Return an array of shortcodes that start with or contain the search term, sorted by relevance and limited to the first 5
|
||||||
|
return emojis
|
||||||
|
.sort((a, b) => {
|
||||||
|
let aLower = a.shortcode.toLowerCase();
|
||||||
|
let bLower = b.shortcode.toLowerCase();
|
||||||
|
|
||||||
|
let aStartsWith = aLower.startsWith(searchTerm);
|
||||||
|
let bStartsWith = bLower.startsWith(searchTerm);
|
||||||
|
let aContains = aLower.includes(searchTerm);
|
||||||
|
let bContains = bLower.includes(searchTerm);
|
||||||
|
let bothStartWith = aStartsWith && bStartsWith;
|
||||||
|
let bothContain = aContains && bContains;
|
||||||
|
|
||||||
|
return bothStartWith
|
||||||
|
? a.length - b.length
|
||||||
|
: aStartsWith
|
||||||
|
? -1
|
||||||
|
: bStartsWith
|
||||||
|
? 1
|
||||||
|
: bothContain
|
||||||
|
? a.length - b.length
|
||||||
|
: aContains
|
||||||
|
? -1
|
||||||
|
: bContains
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
})
|
||||||
|
.slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeHTML(str) {
|
||||||
|
return str.replace(/[&<>"']/g, function (char) {
|
||||||
|
return '&#' + char.charCodeAt(0) + ';';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default Compose;
|
export default Compose;
|
||||||
|
|
Loading…
Reference in a new issue