emoji uploader
This commit is contained in:
parent
b984641108
commit
dd9a46a412
|
@ -42,6 +42,7 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
|
||||||
assetsPathPrefix + "/dist/_colors.css",
|
assetsPathPrefix + "/dist/_colors.css",
|
||||||
assetsPathPrefix + "/dist/base.css",
|
assetsPathPrefix + "/dist/base.css",
|
||||||
assetsPathPrefix + "/dist/profile.css",
|
assetsPathPrefix + "/dist/profile.css",
|
||||||
|
assetsPathPrefix + "/dist/status.css",
|
||||||
assetsPathPrefix + "/dist/settings-panel-style.css",
|
assetsPathPrefix + "/dist/settings-panel-style.css",
|
||||||
},
|
},
|
||||||
"javascript": []string{
|
"javascript": []string{
|
||||||
|
|
|
@ -278,6 +278,13 @@ section.error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: $error1;
|
||||||
|
background: $error2;
|
||||||
|
border-radius: 0.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 0.15rem solid $input-border;
|
border: 0.15rem solid $input-border;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"browserlist": "^1.0.1",
|
"browserlist": "^1.0.1",
|
||||||
"create-error": "^0.3.1",
|
"create-error": "^0.3.1",
|
||||||
"css-extract": "^2.0.0",
|
"css-extract": "^2.0.0",
|
||||||
|
"default-value": "^1.0.0",
|
||||||
"dotty": "^0.1.2",
|
"dotty": "^0.1.2",
|
||||||
"eslint-plugin-react": "^7.24.0",
|
"eslint-plugin-react": "^7.24.0",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
|
@ -35,6 +36,8 @@
|
||||||
"postcss-nested": "^5.0.6",
|
"postcss-nested": "^5.0.6",
|
||||||
"postcss-scss": "^4.0.4",
|
"postcss-scss": "^4.0.4",
|
||||||
"postcss-strip-inline-comments": "^0.1.5",
|
"postcss-strip-inline-comments": "^0.1.5",
|
||||||
|
"prettier-bytes": "^1.0.4",
|
||||||
|
"pretty-bytes": "4",
|
||||||
"react": "18",
|
"react": "18",
|
||||||
"react-dom": "18",
|
"react-dom": "18",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
|
|
|
@ -21,36 +21,192 @@
|
||||||
const Promise = require("bluebird");
|
const Promise = require("bluebird");
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const Redux = require("react-redux");
|
const Redux = require("react-redux");
|
||||||
|
const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter");
|
||||||
|
|
||||||
|
const Submit = require("../components/submit");
|
||||||
|
const FakeToot = require("../components/fake-toot");
|
||||||
|
const { formFields } = require("../components/form-fields");
|
||||||
|
|
||||||
const api = require("../lib/api");
|
const api = require("../lib/api");
|
||||||
const adminActions = require("../redux/reducers/admin").actions;
|
const adminActions = require("../redux/reducers/admin").actions;
|
||||||
|
const submit = require("../lib/submit");
|
||||||
|
|
||||||
|
const base = "/settings/admin/custom-emoji";
|
||||||
|
|
||||||
module.exports = function CustomEmoji() {
|
module.exports = function CustomEmoji() {
|
||||||
return (
|
return (
|
||||||
<>
|
<Switch>
|
||||||
<h1>Custom Emoji</h1>
|
<Route path={`${base}/:emojiId`}>
|
||||||
<div>
|
<EmojiDetailWrapped />
|
||||||
<EmojiOverview/>
|
</Route>
|
||||||
</div>
|
<EmojiOverview />
|
||||||
<div>
|
</Switch>
|
||||||
<h2>Upload</h2>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function EmojiOverview() {
|
function EmojiOverview() {
|
||||||
const dispatch = Redux.useDispatch();
|
const dispatch = Redux.useDispatch();
|
||||||
const emoji = Redux.useSelector((state) => state.admin.emoji);
|
const [loaded, setLoaded] = React.useState(false);
|
||||||
console.log(emoji);
|
|
||||||
|
const [errorMsg, setError] = React.useState("");
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
dispatch(api.admin.fetchCustomEmoji());
|
if (!loaded) {
|
||||||
|
Promise.try(() => {
|
||||||
|
return dispatch(api.admin.fetchCustomEmoji());
|
||||||
|
}).then(() => {
|
||||||
|
setLoaded(true);
|
||||||
|
}).catch((e) => {
|
||||||
|
setLoaded(true);
|
||||||
|
setError(e.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Custom Emoji</h1>
|
||||||
|
Loading...
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<h1>Custom Emoji</h1>
|
||||||
|
<EmojiList/>
|
||||||
|
<NewEmoji/>
|
||||||
|
{errorMsg.length > 0 &&
|
||||||
|
<div className="error accent">{errorMsg}</div>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NewEmojiForm = formFields(adminActions.updateNewEmojiVal, (state) => state.admin.newEmoji);
|
||||||
|
function NewEmoji() {
|
||||||
|
const dispatch = Redux.useDispatch();
|
||||||
|
const newEmojiForm = Redux.useSelector((state) => state.admin.newEmoji);
|
||||||
|
|
||||||
|
const [errorMsg, setError] = React.useState("");
|
||||||
|
const [statusMsg, setStatus] = React.useState("");
|
||||||
|
|
||||||
|
const uploadEmoji = submit(
|
||||||
|
() => dispatch(api.admin.newEmoji()),
|
||||||
|
{
|
||||||
|
setStatus, setError,
|
||||||
|
onSuccess: function() {
|
||||||
|
URL.revokeObjectURL(newEmojiForm.image);
|
||||||
|
return Promise.all([
|
||||||
|
dispatch(adminActions.updateNewEmojiVal(["image", undefined])),
|
||||||
|
dispatch(adminActions.updateNewEmojiVal(["imageFile", undefined])),
|
||||||
|
dispatch(adminActions.updateNewEmojiVal(["shortcode", ""])),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (newEmojiForm.shortcode.length == 0) {
|
||||||
|
if (newEmojiForm.imageFile != undefined) {
|
||||||
|
let [name, ext] = newEmojiForm.imageFile.name.split(".");
|
||||||
|
dispatch(adminActions.updateNewEmojiVal(["shortcode", name]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let emojiOrShortcode = `:${newEmojiForm.shortcode}:`;
|
||||||
|
|
||||||
|
if (newEmojiForm.image != undefined) {
|
||||||
|
emojiOrShortcode = <img
|
||||||
|
className="emoji"
|
||||||
|
src={newEmojiForm.image}
|
||||||
|
title={`:${newEmojiForm.shortcode}:`}
|
||||||
|
alt={newEmojiForm.shortcode}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Add new custom emoji</h2>
|
||||||
|
|
||||||
|
<FakeToot content="bazinga">
|
||||||
|
Look at this new custom emoji {emojiOrShortcode} isn't it cool?
|
||||||
|
</FakeToot>
|
||||||
|
|
||||||
|
<NewEmojiForm.File
|
||||||
|
id="image"
|
||||||
|
name="Image"
|
||||||
|
fileType="image/png,image/gif"
|
||||||
|
showSize={true}
|
||||||
|
maxSize={50 * 1000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NewEmojiForm.TextInput
|
||||||
|
id="shortcode"
|
||||||
|
name="Shortcode (without : :), must be unique on the instance"
|
||||||
|
placeHolder="blobcat"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Submit onClick={uploadEmoji} label="Upload" errorMsg={errorMsg} statusMsg={statusMsg} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmojiList() {
|
||||||
|
const emoji = Redux.useSelector((state) => state.admin.emoji);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Overview</h2>
|
||||||
|
<div className="list emoji-list">
|
||||||
|
{Object.entries(emoji).map(([category, entries]) => {
|
||||||
|
return <EmojiCategory key={category} category={category} entries={entries}/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmojiCategory({category, entries}) {
|
||||||
|
return (
|
||||||
|
<div className="entry">
|
||||||
|
<b>{category}</b>
|
||||||
|
<div className="emoji-group">
|
||||||
|
{entries.map((e) => {
|
||||||
|
return (
|
||||||
|
// <Link key={e.static_url} to={`${base}/${e.shortcode}`}>
|
||||||
|
<Link key={e.static_url} to={`${base}`}>
|
||||||
|
<a>
|
||||||
|
<img src={e.static_url} alt={e.shortcode} title={`:${e.shortcode}:`}/>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmojiDetailWrapped() {
|
||||||
|
/* We wrap the component to generate formFields with a setter depending on the domain
|
||||||
|
if formFields() is used inside the same component that is re-rendered with their state,
|
||||||
|
inputs get re-created on every change, causing them to lose focus, and bad performance
|
||||||
|
*/
|
||||||
|
let [_match, {emojiId}] = useRoute(`${base}/:emojiId`);
|
||||||
|
|
||||||
|
function alterEmoji([key, val]) {
|
||||||
|
return adminActions.updateDomainBlockVal([emojiId, key, val]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = formFields(alterEmoji, (state) => state.admin.blockedInstances[emojiId]);
|
||||||
|
|
||||||
|
return <EmojiDetail id={emojiId} Form={fields} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmojiDetail({id, Form}) {
|
||||||
|
return (
|
||||||
|
"Not implemented yet"
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -287,7 +287,6 @@ function BackButton() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function InstancePageWrapped() {
|
function InstancePageWrapped() {
|
||||||
/* We wrap the component to generate formFields with a setter depending on the domain
|
/* We wrap the component to generate formFields with a setter depending on the domain
|
||||||
if formFields() is used inside the same component that is re-rendered with their state,
|
if formFields() is used inside the same component that is re-rendered with their state,
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
const Redux = require("react-redux");
|
||||||
|
|
||||||
|
module.exports = function FakeToot({children}) {
|
||||||
|
const account = Redux.useSelector((state) => state.user.profile);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="toot expanded">
|
||||||
|
<div className="contentgrid">
|
||||||
|
<span className="avatar">
|
||||||
|
<img src="http://localhost:8080/assets/default_avatars/GoToSocial_icon6.png" alt=""/>
|
||||||
|
</span>
|
||||||
|
<span className="displayname">{account.display_name.trim().length > 0 ? account.display_name : account.username}</span>
|
||||||
|
<span className="username">@{account.username}</span>
|
||||||
|
<div className="text">
|
||||||
|
<div className="content">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -21,6 +21,7 @@
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const Redux = require("react-redux");
|
const Redux = require("react-redux");
|
||||||
const d = require("dotty");
|
const d = require("dotty");
|
||||||
|
const prettierBytes = require("prettier-bytes");
|
||||||
|
|
||||||
function eventListeners(dispatch, setter, obj) {
|
function eventListeners(dispatch, setter, obj) {
|
||||||
return {
|
return {
|
||||||
|
@ -78,7 +79,7 @@ module.exports = {
|
||||||
formFields: function formFields(setter, selector) {
|
formFields: function formFields(setter, selector) {
|
||||||
function FormField({
|
function FormField({
|
||||||
type, id, name, className="", placeHolder="", fileType="", children=null,
|
type, id, name, className="", placeHolder="", fileType="", children=null,
|
||||||
options=null, inputProps={}, withPreview=true
|
options=null, inputProps={}, withPreview=true, showSize=false, maxSize=Infinity
|
||||||
}) {
|
}) {
|
||||||
const dispatch = Redux.useDispatch();
|
const dispatch = Redux.useDispatch();
|
||||||
let state = Redux.useSelector(selector);
|
let state = Redux.useSelector(selector);
|
||||||
|
@ -105,10 +106,22 @@ module.exports = {
|
||||||
} else if (type == "file") {
|
} else if (type == "file") {
|
||||||
defaultLabel = false;
|
defaultLabel = false;
|
||||||
let file = get(state, `${id}File`);
|
let file = get(state, `${id}File`);
|
||||||
|
|
||||||
|
let size = null;
|
||||||
|
if (showSize && file) {
|
||||||
|
size = `(${prettierBytes(file.size)})`;
|
||||||
|
|
||||||
|
if (file.size > maxSize) {
|
||||||
|
size = <span className="error-text">{size}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
field = (
|
field = (
|
||||||
<>
|
<>
|
||||||
<label htmlFor={id} className="file-input button">Browse</label>
|
<label htmlFor={id} className="file-input button">Browse</label>
|
||||||
<span>{file ? file.name : "no file selected"}</span>
|
<span>
|
||||||
|
{file ? file.name : "no file selected"} {size}
|
||||||
|
</span>
|
||||||
{/* <a onClick={removeFile("header")}>remove</a> */}
|
{/* <a onClick={removeFile("header")}>remove</a> */}
|
||||||
<input className="hidden" id={id} type="file" accept={fileType} onChange={onFileChange(id, withPreview)} {...inputProps}/>
|
<input className="hidden" id={id} type="file" accept={fileType} onChange={onFileChange(id, withPreview)} {...inputProps}/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -92,7 +92,7 @@ function App() {
|
||||||
e.message = "Stored OAUTH token no longer valid, please log in again.";
|
e.message = "Stored OAUTH token no longer valid, please log in again.";
|
||||||
}
|
}
|
||||||
setErrorMsg(e);
|
setErrorMsg(e);
|
||||||
console.error(e.message);
|
console.error(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -157,6 +157,23 @@ module.exports = function ({ apiCall, getChanges }) {
|
||||||
return dispatch(admin.setEmoji(emoji));
|
return dispatch(admin.setEmoji(emoji));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
newEmoji: function newEmoji() {
|
||||||
|
return function (dispatch, getState) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
const state = getState().admin.newEmoji;
|
||||||
|
|
||||||
|
const update = getChanges(state, {
|
||||||
|
formKeys: ["shortcode"],
|
||||||
|
fileKeys: ["image"]
|
||||||
|
});
|
||||||
|
|
||||||
|
return dispatch(apiCall("POST", "/api/v1/admin/custom_emojis", update, "form"));
|
||||||
|
}).then((emoji) => {
|
||||||
|
return dispatch(admin.addEmoji(emoji));
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return adminAPI;
|
return adminAPI;
|
||||||
|
|
|
@ -37,7 +37,9 @@ function apiCall(method, route, payload, type = "json") {
|
||||||
let url = new URL(base);
|
let url = new URL(base);
|
||||||
let [path, query] = route.split("?");
|
let [path, query] = route.split("?");
|
||||||
url.pathname = path;
|
url.pathname = path;
|
||||||
url.search = query;
|
if (query != undefined) {
|
||||||
|
url.search = query;
|
||||||
|
}
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
let headers = {
|
let headers = {
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
|
||||||
|
module.exports = function submit(func, {
|
||||||
|
setStatus, setError,
|
||||||
|
startStatus="PATCHing", successStatus="Saved!",
|
||||||
|
onSuccess,
|
||||||
|
onError
|
||||||
|
}) {
|
||||||
|
return function() {
|
||||||
|
setStatus(startStatus);
|
||||||
|
setError("");
|
||||||
|
return Promise.try(() => {
|
||||||
|
return func();
|
||||||
|
}).then(() => {
|
||||||
|
setStatus(successStatus);
|
||||||
|
if (onSuccess != undefined) {
|
||||||
|
console.log("running", onSuccess);
|
||||||
|
return onSuccess();
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
setError(e.message);
|
||||||
|
setStatus("");
|
||||||
|
console.error(e);
|
||||||
|
if (onError != undefined) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
|
@ -19,6 +19,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { createSlice } = require("@reduxjs/toolkit");
|
const { createSlice } = require("@reduxjs/toolkit");
|
||||||
|
const defaultValue = require("default-value");
|
||||||
|
|
||||||
function sortBlocks(blocks) {
|
function sortBlocks(blocks) {
|
||||||
return blocks.sort((a, b) => { // alphabetical sort
|
return blocks.sort((a, b) => { // alphabetical sort
|
||||||
|
@ -34,6 +35,12 @@ function emptyBlock() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emptyEmojiForm() {
|
||||||
|
return {
|
||||||
|
shortcode: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = createSlice({
|
module.exports = createSlice({
|
||||||
name: "admin",
|
name: "admin",
|
||||||
initialState: {
|
initialState: {
|
||||||
|
@ -44,7 +51,8 @@ module.exports = createSlice({
|
||||||
exportType: "plain",
|
exportType: "plain",
|
||||||
...emptyBlock()
|
...emptyBlock()
|
||||||
},
|
},
|
||||||
emoji: []
|
emoji: {},
|
||||||
|
newEmoji: emptyEmojiForm()
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setBlockedInstances: (state, { payload }) => {
|
setBlockedInstances: (state, { payload }) => {
|
||||||
|
@ -90,7 +98,26 @@ module.exports = createSlice({
|
||||||
},
|
},
|
||||||
|
|
||||||
setEmoji: (state, {payload}) => {
|
setEmoji: (state, {payload}) => {
|
||||||
state.emoji = payload;
|
state.emoji = {};
|
||||||
}
|
payload.forEach((emoji) => {
|
||||||
|
if (emoji.category == undefined) {
|
||||||
|
emoji.category = "Unsorted";
|
||||||
|
}
|
||||||
|
state.emoji[emoji.category] = defaultValue(state.emoji[emoji.category], []);
|
||||||
|
state.emoji[emoji.category].push(emoji);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNewEmojiVal: (state, { payload: [key, val] }) => {
|
||||||
|
state.newEmoji[key] = val;
|
||||||
|
},
|
||||||
|
|
||||||
|
addEmoji: (state, {payload: emoji}) => {
|
||||||
|
if (emoji.category == undefined) {
|
||||||
|
emoji.category = "Unsorted";
|
||||||
|
}
|
||||||
|
state.emoji[emoji.category] = defaultValue(state.emoji[emoji.category], []);
|
||||||
|
state.emoji[emoji.category].push(emoji);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -192,9 +192,14 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
button, .button {
|
button, .button {
|
||||||
margin-top: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messagebutton > div {
|
||||||
|
button, .button {
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,6 +364,23 @@ section.with-sidebar > div {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
max-height: 40rem;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
background: $settings-entry-bg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $settings-entry-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.instance-list {
|
.instance-list {
|
||||||
.filter {
|
.filter {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -370,20 +392,9 @@ section.with-sidebar > div {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
max-height: 40rem;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
padding: 0.3rem;
|
padding: 0.3rem;
|
||||||
margin: 0.2rem 0;
|
margin: 0.2rem 0;
|
||||||
background: $settings-entry-bg;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
#domain {
|
#domain {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -391,10 +402,6 @@ section.with-sidebar > div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $settings-entry-hover-bg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,3 +409,42 @@ section.with-sidebar > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji-list {
|
||||||
|
background: $settings-entry-bg;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
padding: 0.5rem;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.emoji-group {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
a {
|
||||||
|
border-radius: $br;
|
||||||
|
padding: 0.4rem;
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $settings-entry-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toot {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
.contentgrid {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4492,11 +4492,16 @@ prelude-ls@~1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
||||||
|
|
||||||
prettier-bytes@^1.0.3:
|
prettier-bytes@^1.0.3, prettier-bytes@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6"
|
resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6"
|
||||||
integrity sha512-dLbWOa4xBn+qeWeIF60qRoB6Pk2jX5P3DIVgOQyMyvBpu931Q+8dXz8X0snJiFkQdohDDLnZQECjzsAj75hgZQ==
|
integrity sha512-dLbWOa4xBn+qeWeIF60qRoB6Pk2jX5P3DIVgOQyMyvBpu931Q+8dXz8X0snJiFkQdohDDLnZQECjzsAj75hgZQ==
|
||||||
|
|
||||||
|
pretty-bytes@4:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
|
||||||
|
integrity sha512-yJAF+AjbHKlxQ8eezMd/34Mnj/YTQ3i6kLzvVsH4l/BfIFtp444n0wVbnsn66JimZ9uBofv815aRp1zCppxlWw==
|
||||||
|
|
||||||
pretty-ms@^2.1.0:
|
pretty-ms@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc"
|
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc"
|
||||||
|
|
Loading…
Reference in New Issue