commit 08f8feaec50c63ffd2f51d5589b3015b60e8f859
parent 782169da765baf1d651c1b71e5f974f762c92d8e
Author: f0x52 <f0x@cthu.lu>
Date: Fri, 27 Jan 2023 09:09:26 +0100
[feature/frontend] filterable local emoji list (#1385)
Diffstat:
4 files changed, 89 insertions(+), 25 deletions(-)
diff --git a/web/source/css/base.css b/web/source/css/base.css
@@ -419,11 +419,6 @@ label {
display: flex;
flex-direction: column;
- &.scrolling {
- max-height: 40rem;
- overflow: auto;
- }
-
.header, .entry {
padding: 0.5rem;
}
@@ -435,6 +430,17 @@ label {
font-weight: bold;
}
+ .entries {
+ display: flex;
+ flex-direction: column;
+
+ &.scrolling {
+ height: 20rem;
+ max-height: 20rem;
+ overflow: auto;
+ }
+ }
+
input[type=checkbox] {
margin-left: 0.5rem;
}
diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js
@@ -20,13 +20,18 @@
const React = require("react");
const { Link } = require("wouter");
+const syncpipe = require("syncpipe");
+const { matchSorter } = require("match-sorter");
const NewEmojiForm = require("./new-emoji");
+const { useTextInput } = require("../../../lib/form");
const query = require("../../../lib/query");
const { useEmojiByCategory } = require("../category-select");
+
const Loading = require("../../../components/loading");
const { Error } = require("../../../components/error");
+const { TextInput } = require("../../../components/form/inputs");
module.exports = function EmojiOverview({ baseUrl }) {
const {
@@ -53,23 +58,70 @@ module.exports = function EmojiOverview({ baseUrl }) {
return (
<>
- <h1>Custom Emoji (local)</h1>
+ <h1>Local Custom Emoji</h1>
+ <p>
+ To use custom emoji in your toots they have to be 'local' to the instance.
+ You can either upload them here directly, or copy from those already
+ present on other (known) instances through the <Link to={`../remote`}>Remote Emoji</Link> page.
+ </p>
{content}
</>
);
};
function EmojiList({ emoji, baseUrl }) {
+ const filterField = useTextInput("filter");
+ const filter = filterField.value;
+
const emojiByCategory = useEmojiByCategory(emoji);
+ /* Filter emoji based on shortcode match with user input, hiding empty categories */
+ const { filteredEmoji, hidden } = React.useMemo(() => {
+ let hidden = emoji.length;
+ const filteredEmoji = syncpipe(emojiByCategory, [
+ (_) => Object.entries(emojiByCategory),
+ (_) => _.map(([category, entries]) => {
+ let filteredEntries = matchSorter(entries, filter, { keys: ["shortcode"] });
+ if (filteredEntries.length == 0) {
+ return null;
+ } else {
+ hidden -= filteredEntries.length;
+ return [category, filteredEntries];
+ }
+ }),
+ (_) => _.filter((value) => value !== null)
+ ]);
+
+ return { filteredEmoji, hidden };
+ }, [filter, emojiByCategory, emoji.length]);
+
return (
<div>
<h2>Overview</h2>
+ {emoji.length > 0
+ ? <span>{emoji.length} custom emoji {hidden > 0 && `(${hidden} filtered)`}</span>
+ : <span>No custom emoji yet, you can add one below.</span>
+ }
<div className="list emoji-list">
- {emoji.length == 0 && "No local emoji yet, add one below"}
- {Object.entries(emojiByCategory).map(([category, entries]) => {
- return <EmojiCategory key={category} category={category} entries={entries} baseUrl={baseUrl} />;
- })}
+ <div className="header">
+ <TextInput
+ field={filterField}
+ name="emoji-shortcode"
+ placeholder="Search"
+ />
+ </div>
+ <div className="entries scrolling">
+ {filteredEmoji.length > 0
+ ? (
+ <div className="entries scrolling">
+ {filteredEmoji.map(([category, entries]) => {
+ return <EmojiCategory key={category} category={category} entries={entries} baseUrl={baseUrl} />;
+ })}
+ </div>
+ )
+ : <div className="entry">No local emoji matched your filter.</div>
+ }
+ </div>
</div>
</div>
);
diff --git a/web/source/settings/admin/federation/overview.js b/web/source/settings/admin/federation/overview.js
@@ -76,21 +76,23 @@ module.exports = function InstanceOverview({ baseUrl }) {
<span>
{blockedInstancesList.length} blocked instance{blockedInstancesList.length != 1 ? "s" : ""} {filtered > 0 && `(${filtered} filtered by search)`}
</span>
- <div className="list scrolling">
- {filteredInstances.map((entry) => {
- return (
- <Link key={entry.domain} to={`${baseUrl}/${entry.domain}`}>
- <a className="entry nounderline">
- <span id="domain">
- {entry.domain}
- </span>
- <span id="date">
- {new Date(entry.created_at).toLocaleString()}
- </span>
- </a>
- </Link>
- );
- })}
+ <div className="list">
+ <div className="entries scrolling">
+ {filteredInstances.map((entry) => {
+ return (
+ <Link key={entry.domain} to={`${baseUrl}/${entry.domain}`}>
+ <a className="entry nounderline">
+ <span id="domain">
+ {entry.domain}
+ </span>
+ <span id="date">
+ {new Date(entry.created_at).toLocaleString()}
+ </span>
+ </a>
+ </Link>
+ );
+ })}
+ </div>
</div>
</div>
</div>
diff --git a/web/source/settings/style.css b/web/source/settings/style.css
@@ -405,6 +405,10 @@ span.form-info {
.emoji-list {
background: $list-entry-bg;
+ .header .form-field {
+ flex: 1 1 auto;
+ }
+
.entry {
flex-direction: column;