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": {
|
||||
"axios": "^1.5.1",
|
||||
"bulma": "^0.9.4",
|
||||
"humanize-duration": "^3.31.0",
|
||||
"luxon": "^3.4.3",
|
||||
"preact": "^10.13.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-mentions": "^4.4.10",
|
||||
"react-query": "^3.39.3",
|
||||
"tributejs": "^5.1.3",
|
||||
"use-debounce": "^10.0.0",
|
||||
@ -3322,11 +3322,6 @@
|
||||
"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": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
@ -3395,6 +3390,14 @@
|
||||
"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": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||
@ -4075,7 +4078,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -4382,7 +4384,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@ -4484,8 +4485,35 @@
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"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": {
|
||||
"version": "6.13.0",
|
||||
@ -4963,6 +4991,18 @@
|
||||
"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": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
@ -5,8 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"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"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"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 { useRef } from "preact/hooks";
|
||||
import { useMentions } from "../App/useMentions";
|
||||
|
||||
export const Content = () => {
|
||||
const [reminder, setReminder] = useReminder();
|
||||
|
||||
const input = useRef(null);
|
||||
useMentions(input);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label class="is-sr-only">Content</label>
|
||||
@ -12,6 +17,7 @@ export const Content = () => {
|
||||
maxlength={2000}
|
||||
name="content"
|
||||
rows={1}
|
||||
ref={input}
|
||||
value={reminder.content}
|
||||
onInput={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
import { Reminder } from "../../../api";
|
||||
import { useMentions } from "../../App/useMentions";
|
||||
import { useRef } from "preact/hooks";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
@ -8,6 +10,9 @@ type Props = {
|
||||
};
|
||||
|
||||
export const Author = ({ name, icon, setReminder }: Props) => {
|
||||
const input = useRef(null);
|
||||
useMentions(input);
|
||||
|
||||
return (
|
||||
<div class="embed-author-box">
|
||||
<div class="a">
|
||||
@ -34,6 +39,7 @@ export const Author = ({ name, icon, setReminder }: Props) => {
|
||||
class="discord-embed-author message-input autoresize"
|
||||
placeholder="Embed Author..."
|
||||
rows={1}
|
||||
ref={input}
|
||||
maxlength={256}
|
||||
name="embed_author"
|
||||
value={name}
|
||||
|
@ -1,18 +1,27 @@
|
||||
export const Description = ({ description, onInput }) => (
|
||||
<>
|
||||
<label class="is-sr-only" for="embedDescription">
|
||||
Embed Description
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-description message-input autoresize "
|
||||
placeholder="Embed Description..."
|
||||
maxlength={4096}
|
||||
name="embed_description"
|
||||
rows={1}
|
||||
value={description}
|
||||
onInput={(ev) => {
|
||||
onInput(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
||||
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">
|
||||
Embed Description
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-description message-input autoresize "
|
||||
placeholder="Embed Description..."
|
||||
maxlength={4096}
|
||||
name="embed_description"
|
||||
rows={1}
|
||||
ref={input}
|
||||
value={description}
|
||||
onInput={(ev) => {
|
||||
onInput(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { useRef } from "preact/hooks";
|
||||
import { useMentions } from "../../../App/useMentions";
|
||||
|
||||
export const Field = ({ title, value, inline, index, onUpdate }) => {
|
||||
const input = useRef(null);
|
||||
useMentions(input);
|
||||
|
||||
return (
|
||||
<div data-inlined={inline ? "1" : "0"} class="embed-field-box" key={index}>
|
||||
<label class="is-sr-only" for="embedFieldTitle">
|
||||
@ -44,6 +50,7 @@ export const Field = ({ title, value, inline, index, onUpdate }) => {
|
||||
maxlength={1024}
|
||||
name="embed_field_value[]"
|
||||
rows={1}
|
||||
ref={input}
|
||||
value={value}
|
||||
onInput={(ev) =>
|
||||
onUpdate({
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Reminder } from "../../../api";
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
import { useMentions } from "../../App/useMentions";
|
||||
import { useRef } from "preact/hooks";
|
||||
|
||||
type Props = {
|
||||
footer: string;
|
||||
@ -7,37 +9,43 @@ type Props = {
|
||||
setReminder: (r: (reminder: Reminder) => Reminder) => void;
|
||||
};
|
||||
|
||||
export const Footer = ({ footer, icon, setReminder }: Props) => (
|
||||
<div class="embed-footer-box">
|
||||
<p class="image is-20x20 customizable">
|
||||
<ImagePicker
|
||||
class="is-rounded embed_footer_url"
|
||||
url={icon}
|
||||
alt="Footer profile-like image"
|
||||
setImage={(url: string) => {
|
||||
export const Footer = ({ footer, icon, setReminder }: Props) => {
|
||||
const input = useRef(null);
|
||||
useMentions(input);
|
||||
|
||||
return (
|
||||
<div class="embed-footer-box">
|
||||
<p class="image is-20x20 customizable">
|
||||
<ImagePicker
|
||||
class="is-rounded embed_footer_url"
|
||||
url={icon}
|
||||
alt="Footer profile-like image"
|
||||
setImage={(url: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_footer_url: url,
|
||||
}));
|
||||
}}
|
||||
></ImagePicker>
|
||||
</p>
|
||||
<label class="is-sr-only" for="embedFooter">
|
||||
Embed Footer text
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-embed-footer message-input autoresize "
|
||||
placeholder="Embed Footer..."
|
||||
maxlength={2048}
|
||||
name="embed_footer"
|
||||
rows={1}
|
||||
ref={input}
|
||||
value={footer}
|
||||
onInput={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_footer_url: url,
|
||||
embed_footer: ev.currentTarget.value,
|
||||
}));
|
||||
}}
|
||||
></ImagePicker>
|
||||
</p>
|
||||
<label class="is-sr-only" for="embedFooter">
|
||||
Embed Footer text
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-embed-footer message-input autoresize "
|
||||
placeholder="Embed Footer..."
|
||||
maxlength={2048}
|
||||
name="embed_footer"
|
||||
rows={1}
|
||||
value={footer}
|
||||
onInput={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_footer: ev.currentTarget.value,
|
||||
}));
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,18 +1,28 @@
|
||||
export const Title = ({ title, onInput }) => (
|
||||
<>
|
||||
<label class="is-sr-only" for="embedTitle">
|
||||
Embed Title
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-title message-input autoresize"
|
||||
placeholder="Embed Title..."
|
||||
maxlength={256}
|
||||
rows={1}
|
||||
name="embed_title"
|
||||
value={title}
|
||||
onInput={(ev) => {
|
||||
onInput(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
||||
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">
|
||||
Embed Title
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-title message-input autoresize"
|
||||
placeholder="Embed Title..."
|
||||
maxlength={256}
|
||||
rows={1}
|
||||
ref={input}
|
||||
name="embed_title"
|
||||
value={title}
|
||||
onInput={(ev) => {
|
||||
onInput(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -4,3 +4,25 @@
|
||||
flex-direction: column;
|
||||
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