Add mention support
Allow vertical resizing of inputs
This commit is contained in:
parent
66135ecd08
commit
329492b244
60
reminder-dashboard/package-lock.json
generated
60
reminder-dashboard/package-lock.json
generated
@ -8,10 +8,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"bulma": "^0.9.4",
|
"bulma": "^0.9.4",
|
||||||
"humanize-duration": "^3.31.0",
|
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.3",
|
||||||
"preact": "^10.13.1",
|
"preact": "^10.13.1",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
|
"react-mentions": "^4.4.10",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"tributejs": "^5.1.3",
|
"tributejs": "^5.1.3",
|
||||||
"use-debounce": "^10.0.0",
|
"use-debounce": "^10.0.0",
|
||||||
@ -3322,11 +3322,6 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/humanize-duration": {
|
|
||||||
"version": "3.31.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.31.0.tgz",
|
|
||||||
"integrity": "sha512-fRrehgBG26NNZysRlTq1S+HPtDpp3u+Jzdc/d5A4cEzOD86YLAkDaJyJg8krSdCi7CJ+s7ht3fwRj8Dl+Btd0w=="
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
@ -3395,6 +3390,14 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/invariant": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||||
@ -4075,7 +4078,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -4382,7 +4384,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -4484,8 +4485,35 @@
|
|||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
},
|
||||||
|
"node_modules/react-mentions": {
|
||||||
|
"version": "4.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.4.10.tgz",
|
||||||
|
"integrity": "sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "7.4.5",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"prop-types": "^15.5.8",
|
||||||
|
"substyle": "^9.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.3",
|
||||||
|
"react-dom": ">=16.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-mentions/node_modules/@babel/runtime": {
|
||||||
|
"version": "7.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
|
||||||
|
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.13.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-mentions/node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||||
},
|
},
|
||||||
"node_modules/react-onclickoutside": {
|
"node_modules/react-onclickoutside": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
@ -4963,6 +4991,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/substyle": {
|
||||||
|
"version": "9.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/substyle/-/substyle-9.4.1.tgz",
|
||||||
|
"integrity": "sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.3.4",
|
||||||
|
"invariant": "^2.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch --mode development",
|
"dev": "vite build --watch --mode development",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview"
|
||||||
"package": "mkdir -p reminder-dashboard/usr/share/reminder-dashboard && vite build --mode production --outDir reminder-dashboard/usr/share/reminder-dashboard && dpkg-deb --build reminder-dashboard"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
|
6
reminder-dashboard/src/components/App/useGuild.tsx
Normal file
6
reminder-dashboard/src/components/App/useGuild.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { useParams } from "wouter";
|
||||||
|
|
||||||
|
export const useGuild = () => {
|
||||||
|
const { guild } = useParams() as { guild: string };
|
||||||
|
return guild;
|
||||||
|
};
|
28
reminder-dashboard/src/components/App/useMentions.tsx
Normal file
28
reminder-dashboard/src/components/App/useMentions.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect, useMemo } from "preact/hooks";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { fetchGuildRoles } from "../../api";
|
||||||
|
import Tribute from "tributejs";
|
||||||
|
import { useGuild } from "./useGuild";
|
||||||
|
|
||||||
|
export const useMentions = (input) => {
|
||||||
|
const guild = useGuild();
|
||||||
|
|
||||||
|
const { data: roles } = useQuery(fetchGuildRoles(guild));
|
||||||
|
|
||||||
|
const tribute = useMemo(() => {
|
||||||
|
return new Tribute({
|
||||||
|
values: (roles || []).map(({ id, name }) => ({ key: name, value: id })),
|
||||||
|
allowSpaces: true,
|
||||||
|
selectTemplate: (item) => {
|
||||||
|
return `<@&${item.original.value}>`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [roles]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
tribute.detach(input.current);
|
||||||
|
if (input.current !== null) {
|
||||||
|
tribute.attach(input.current);
|
||||||
|
}
|
||||||
|
}, [tribute]);
|
||||||
|
};
|
@ -1,8 +1,13 @@
|
|||||||
import { useReminder } from "./ReminderContext";
|
import { useReminder } from "./ReminderContext";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
import { useMentions } from "../App/useMentions";
|
||||||
|
|
||||||
export const Content = () => {
|
export const Content = () => {
|
||||||
const [reminder, setReminder] = useReminder();
|
const [reminder, setReminder] = useReminder();
|
||||||
|
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label class="is-sr-only">Content</label>
|
<label class="is-sr-only">Content</label>
|
||||||
@ -12,6 +17,7 @@ export const Content = () => {
|
|||||||
maxlength={2000}
|
maxlength={2000}
|
||||||
name="content"
|
name="content"
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
value={reminder.content}
|
value={reminder.content}
|
||||||
onInput={(ev) => {
|
onInput={(ev) => {
|
||||||
setReminder((reminder) => ({
|
setReminder((reminder) => ({
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { ImagePicker } from "../ImagePicker";
|
import { ImagePicker } from "../ImagePicker";
|
||||||
import { Reminder } from "../../../api";
|
import { Reminder } from "../../../api";
|
||||||
|
import { useMentions } from "../../App/useMentions";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -8,6 +10,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Author = ({ name, icon, setReminder }: Props) => {
|
export const Author = ({ name, icon, setReminder }: Props) => {
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="embed-author-box">
|
<div class="embed-author-box">
|
||||||
<div class="a">
|
<div class="a">
|
||||||
@ -34,6 +39,7 @@ export const Author = ({ name, icon, setReminder }: Props) => {
|
|||||||
class="discord-embed-author message-input autoresize"
|
class="discord-embed-author message-input autoresize"
|
||||||
placeholder="Embed Author..."
|
placeholder="Embed Author..."
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
maxlength={256}
|
maxlength={256}
|
||||||
name="embed_author"
|
name="embed_author"
|
||||||
value={name}
|
value={name}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
export const Description = ({ description, onInput }) => (
|
import { useMentions } from "../../App/useMentions";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
|
||||||
|
export const Description = ({ description, onInput }) => {
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<label class="is-sr-only" for="embedDescription">
|
<label class="is-sr-only" for="embedDescription">
|
||||||
Embed Description
|
Embed Description
|
||||||
@ -9,6 +16,7 @@ export const Description = ({ description, onInput }) => (
|
|||||||
maxlength={4096}
|
maxlength={4096}
|
||||||
name="embed_description"
|
name="embed_description"
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
value={description}
|
value={description}
|
||||||
onInput={(ev) => {
|
onInput={(ev) => {
|
||||||
onInput(ev.currentTarget.value);
|
onInput(ev.currentTarget.value);
|
||||||
@ -16,3 +24,4 @@ export const Description = ({ description, onInput }) => (
|
|||||||
></textarea>
|
></textarea>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
import { useMentions } from "../../../App/useMentions";
|
||||||
|
|
||||||
export const Field = ({ title, value, inline, index, onUpdate }) => {
|
export const Field = ({ title, value, inline, index, onUpdate }) => {
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-inlined={inline ? "1" : "0"} class="embed-field-box" key={index}>
|
<div data-inlined={inline ? "1" : "0"} class="embed-field-box" key={index}>
|
||||||
<label class="is-sr-only" for="embedFieldTitle">
|
<label class="is-sr-only" for="embedFieldTitle">
|
||||||
@ -44,6 +50,7 @@ export const Field = ({ title, value, inline, index, onUpdate }) => {
|
|||||||
maxlength={1024}
|
maxlength={1024}
|
||||||
name="embed_field_value[]"
|
name="embed_field_value[]"
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
value={value}
|
value={value}
|
||||||
onInput={(ev) =>
|
onInput={(ev) =>
|
||||||
onUpdate({
|
onUpdate({
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Reminder } from "../../../api";
|
import { Reminder } from "../../../api";
|
||||||
import { ImagePicker } from "../ImagePicker";
|
import { ImagePicker } from "../ImagePicker";
|
||||||
|
import { useMentions } from "../../App/useMentions";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
footer: string;
|
footer: string;
|
||||||
@ -7,7 +9,11 @@ type Props = {
|
|||||||
setReminder: (r: (reminder: Reminder) => Reminder) => void;
|
setReminder: (r: (reminder: Reminder) => Reminder) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Footer = ({ footer, icon, setReminder }: Props) => (
|
export const Footer = ({ footer, icon, setReminder }: Props) => {
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
|
return (
|
||||||
<div class="embed-footer-box">
|
<div class="embed-footer-box">
|
||||||
<p class="image is-20x20 customizable">
|
<p class="image is-20x20 customizable">
|
||||||
<ImagePicker
|
<ImagePicker
|
||||||
@ -31,6 +37,7 @@ export const Footer = ({ footer, icon, setReminder }: Props) => (
|
|||||||
maxlength={2048}
|
maxlength={2048}
|
||||||
name="embed_footer"
|
name="embed_footer"
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
value={footer}
|
value={footer}
|
||||||
onInput={(ev) => {
|
onInput={(ev) => {
|
||||||
setReminder((reminder) => ({
|
setReminder((reminder) => ({
|
||||||
@ -41,3 +48,4 @@ export const Footer = ({ footer, icon, setReminder }: Props) => (
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
export const Title = ({ title, onInput }) => (
|
import { useParams } from "wouter";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
import { useMentions } from "../../App/useMentions";
|
||||||
|
|
||||||
|
export const Title = ({ title, onInput }) => {
|
||||||
|
const input = useRef(null);
|
||||||
|
useMentions(input);
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<label class="is-sr-only" for="embedTitle">
|
<label class="is-sr-only" for="embedTitle">
|
||||||
Embed Title
|
Embed Title
|
||||||
@ -8,6 +16,7 @@ export const Title = ({ title, onInput }) => (
|
|||||||
placeholder="Embed Title..."
|
placeholder="Embed Title..."
|
||||||
maxlength={256}
|
maxlength={256}
|
||||||
rows={1}
|
rows={1}
|
||||||
|
ref={input}
|
||||||
name="embed_title"
|
name="embed_title"
|
||||||
value={title}
|
value={title}
|
||||||
onInput={(ev) => {
|
onInput={(ev) => {
|
||||||
@ -16,3 +25,4 @@ export const Title = ({ title, onInput }) => (
|
|||||||
></textarea>
|
></textarea>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -4,3 +4,25 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tribute-container {
|
||||||
|
background-color: #2b2d31;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
box-shadow: 0 0 5px 0 rgba(0,0,0,0.75);
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #35373c;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.autoresize {
|
||||||
|
resize: vertical !important;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user