mirror of
https://github.com/MathMan05/Fermi.git
synced 2026-08-28 02:54:08 +00:00
add poll support
This commit is contained in:
+11
-6
@@ -9,6 +9,7 @@ import {InfiniteScroller} from "./infiniteScroller.js";
|
||||
import {SnowFlake} from "./snowflake.js";
|
||||
import {
|
||||
channeljson,
|
||||
creatPollJSON,
|
||||
embedjson,
|
||||
filejson,
|
||||
memberjson,
|
||||
@@ -3619,12 +3620,14 @@ class Channel extends SnowFlake {
|
||||
embeds = [],
|
||||
sticker_ids = [],
|
||||
nonce = undefined,
|
||||
poll = undefined,
|
||||
}: {
|
||||
attachments: Blob[];
|
||||
embeds: embedjson[];
|
||||
replyingto: Message | null;
|
||||
sticker_ids: string[];
|
||||
attachments?: Blob[];
|
||||
embeds?: embedjson[];
|
||||
replyingto?: Message | null;
|
||||
sticker_ids?: string[];
|
||||
nonce?: string;
|
||||
poll?: creatPollJSON;
|
||||
},
|
||||
onRes = (_e: "Ok" | "NotOk") => {},
|
||||
) {
|
||||
@@ -3634,7 +3637,8 @@ class Channel extends SnowFlake {
|
||||
content.trim() === "" &&
|
||||
attachments.length === 0 &&
|
||||
embeds.length == 0 &&
|
||||
sticker_ids.length === 0
|
||||
sticker_ids.length === 0 &&
|
||||
!poll
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -3717,6 +3721,7 @@ class Channel extends SnowFlake {
|
||||
message_reference: undefined,
|
||||
sticker_ids,
|
||||
embeds,
|
||||
poll,
|
||||
};
|
||||
if (replyjson) {
|
||||
body.message_reference = replyjson;
|
||||
@@ -3758,6 +3763,7 @@ class Channel extends SnowFlake {
|
||||
message_reference: undefined,
|
||||
sticker_ids,
|
||||
embeds,
|
||||
poll,
|
||||
};
|
||||
if (replyjson) {
|
||||
body.message_reference = replyjson;
|
||||
@@ -3974,4 +3980,3 @@ class Channel extends SnowFlake {
|
||||
}
|
||||
Channel.setupcontextmenu();
|
||||
export {Channel};
|
||||
|
||||
|
||||
@@ -235,15 +235,17 @@ type contextCluster<X, Y> = [Contextmenu<X, Y>, X, Y];
|
||||
class LayeredEvent extends CustomEvent<unknown> {
|
||||
menus: contextCluster<unknown, unknown>[];
|
||||
primary?: contextCluster<unknown, unknown>;
|
||||
constructor(mouse: MouseEvent, menus: LayeredEvent["menus"]) {
|
||||
side: "top" | "bottom";
|
||||
constructor(mouse: MouseEvent, menus: LayeredEvent["menus"], side: "top" | "bottom") {
|
||||
super("layered", {bubbles: true});
|
||||
this.side = side;
|
||||
this.menus = menus;
|
||||
queueMicrotask(() => {
|
||||
console.log(this);
|
||||
const pop = this.primary || menus.pop();
|
||||
if (!pop) return;
|
||||
const [menu, addinfo, other] = pop;
|
||||
menu.makemenu(mouse.clientX, mouse.clientY, addinfo, other, undefined, menus);
|
||||
menu.makemenu(mouse.clientX, mouse.clientY, addinfo, other, undefined, menus, this.side);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -340,7 +342,11 @@ class Contextmenu<x, y> {
|
||||
other: y,
|
||||
keep: boolean | HTMLElement = false,
|
||||
layered: LayeredEvent["menus"] = [],
|
||||
side: "top" | "bottom" = "top",
|
||||
) {
|
||||
if (side === "bottom") {
|
||||
y = y - window.innerHeight;
|
||||
}
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("contextmenu", "flexttb");
|
||||
const processed = new WeakSet<menuPart<unknown, unknown>>();
|
||||
@@ -386,6 +392,7 @@ class Contextmenu<x, y> {
|
||||
touchDrag: (x: number, y: number) => unknown = () => {},
|
||||
touchEnd: (x: number, y: number) => unknown = () => {},
|
||||
click: "right" | "left" = "right",
|
||||
side: "top" | "bottom" = "top",
|
||||
) {
|
||||
const func = (event: MouseEvent) => {
|
||||
const selectedText = window.getSelection();
|
||||
@@ -406,7 +413,7 @@ class Contextmenu<x, y> {
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
const layered = new LayeredEvent(event, []);
|
||||
const layered = new LayeredEvent(event, [], side);
|
||||
obj.dispatchEvent(layered);
|
||||
};
|
||||
obj.addEventListener("layered", (layered) => {
|
||||
|
||||
+15
-2
@@ -423,7 +423,11 @@ if (window.location.pathname.startsWith("/channels")) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
};
|
||||
(document.getElementById("upload") as HTMLElement).onclick = () => {
|
||||
const umenu = new Contextmenu<void, void>("upload");
|
||||
umenu.addButton(I18n.makePoll(), () => {
|
||||
thisUser.makePoll();
|
||||
});
|
||||
umenu.addButton(I18n.upload(), () => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.click();
|
||||
@@ -441,7 +445,16 @@ if (window.location.pathname.startsWith("/channels")) {
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
umenu.bindContextmenu(
|
||||
document.getElementById("upload")!,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
"left",
|
||||
"bottom",
|
||||
);
|
||||
const emojiTB = document.getElementById("emojiTB") as HTMLElement;
|
||||
emojiTB.onmousedown = (e) => e.stopImmediatePropagation();
|
||||
emojiTB.onclick = (e) => {
|
||||
|
||||
@@ -448,6 +448,37 @@ type emojijson = {
|
||||
animated?: boolean;
|
||||
emoji?: string;
|
||||
};
|
||||
interface pollMedia {
|
||||
text: string;
|
||||
emoji?: emojijson;
|
||||
}
|
||||
export interface creatPollJSON {
|
||||
question: pollMedia;
|
||||
answers: {
|
||||
id?: number;
|
||||
poll_media: pollMedia;
|
||||
}[];
|
||||
duration: number;
|
||||
allow_multiselect?: boolean;
|
||||
}
|
||||
export interface polljson {
|
||||
question: pollMedia;
|
||||
answers: {
|
||||
answer_id?: number;
|
||||
poll_media: pollMedia;
|
||||
}[];
|
||||
expiry: string;
|
||||
allow_multiselect?: boolean;
|
||||
layout_type: 1;
|
||||
results?: {
|
||||
is_finalized: boolean;
|
||||
answer_counts: {
|
||||
id: number;
|
||||
count: number;
|
||||
me_voted: boolean;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
type emojipjson = emojijson & {
|
||||
available: boolean;
|
||||
guild_id: string;
|
||||
@@ -835,6 +866,7 @@ type messagejson = {
|
||||
sticker_items: stickerJson[];
|
||||
message_reference?: string;
|
||||
referenced_message?: messagejson;
|
||||
poll?: polljson;
|
||||
};
|
||||
|
||||
export interface threadMetadata {
|
||||
@@ -939,6 +971,18 @@ type messageCreateJson = {
|
||||
s: number;
|
||||
t: "MESSAGE_CREATE";
|
||||
};
|
||||
export type pollUpdateJson = {
|
||||
op: 0;
|
||||
d: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
answer_id: number;
|
||||
};
|
||||
s: number;
|
||||
t: "MESSAGE_POLL_VOTE_ADD" | "MESSAGE_POLL_VOTE_REMOVE";
|
||||
};
|
||||
export interface relationJson {
|
||||
id: string;
|
||||
type: 0 | 1 | 2 | 3 | 4;
|
||||
@@ -1018,6 +1062,7 @@ type wsjson =
|
||||
}
|
||||
| messageCreateJson
|
||||
| readyjson
|
||||
| pollUpdateJson
|
||||
| {
|
||||
op: 11;
|
||||
s: undefined;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
readyjson,
|
||||
startTypingjson,
|
||||
wsjson,
|
||||
pollUpdateJson,
|
||||
} from "./jsontypes.js";
|
||||
import {Member} from "./member.js";
|
||||
import {Dialog, Form, FormError, Options, Settings} from "./settings.js";
|
||||
@@ -773,6 +774,14 @@ class Localuser {
|
||||
this.conectionChange();
|
||||
break;
|
||||
}
|
||||
case "MESSAGE_POLL_VOTE_ADD":
|
||||
case "MESSAGE_POLL_VOTE_REMOVE": {
|
||||
const m = this.messages.get(temp.d.message_id);
|
||||
m?.pollUpdate(temp);
|
||||
const f = this.pollUpdateSubMap.get(temp.d.message_id);
|
||||
f?.(temp);
|
||||
break;
|
||||
}
|
||||
case "MESSAGE_DELETE": {
|
||||
temp.d.guild_id ??= "@me";
|
||||
const channel = this.channelids.get(temp.d.channel_id);
|
||||
@@ -4527,7 +4536,77 @@ class Localuser {
|
||||
const searchBox = document.getElementById("searchBox") as HTMLDivElement;
|
||||
searchBox.style.setProperty("--hint-text", JSON.stringify(I18n.search.search()));
|
||||
}
|
||||
makePoll() {
|
||||
const d = new Dialog(I18n.makePoll());
|
||||
const opt = d.options;
|
||||
const q = opt.addTextInput(I18n.poll.question(), () => {});
|
||||
opt.addText(I18n.poll.answers());
|
||||
const ansField = document.createElement("div");
|
||||
ansField.classList.add("flexttb", "pollAnsM");
|
||||
const answers = ["", ""] as string[];
|
||||
const genAnswerField = () => {
|
||||
ansField.textContent = "";
|
||||
for (let i = 0; i < answers.length; i++) {
|
||||
const si = i;
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("flexltr");
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.value = answers[i];
|
||||
input.onchange = () => {
|
||||
answers[si] = input.value;
|
||||
};
|
||||
|
||||
const del = document.createElement("span");
|
||||
del.classList.add("svg-delete", "svgicon");
|
||||
div.append(input, del);
|
||||
del.onclick = () => {
|
||||
answers.splice(si, 1);
|
||||
genAnswerField();
|
||||
};
|
||||
ansField.append(div);
|
||||
}
|
||||
};
|
||||
genAnswerField();
|
||||
opt.addHTMLArea(ansField);
|
||||
opt.addButtonInput("", I18n.poll.newAnswer(), () => {
|
||||
answers.push("");
|
||||
genAnswerField();
|
||||
});
|
||||
const hours = [1, 4, 8, 24, 72, 168, 336] as const;
|
||||
const h = opt.addSelect(
|
||||
I18n.poll.duration(),
|
||||
() => {},
|
||||
//@ts-ignore-error this is fine :P
|
||||
hours.map((_) => I18n.poll.durCount[_ + ""]()),
|
||||
{
|
||||
defaultIndex: 3,
|
||||
},
|
||||
);
|
||||
const c = opt.addCheckboxInput(I18n.poll.mult(), () => {});
|
||||
opt.addButtonInput("", I18n.submit(), () => {
|
||||
const chan = this.channelfocus;
|
||||
if (!chan) return;
|
||||
chan.sendMessage("", {
|
||||
poll: {
|
||||
question: {
|
||||
text: q.value,
|
||||
},
|
||||
answers: answers.map((_) => ({poll_media: {text: _}})),
|
||||
duration: hours[h.index] as number,
|
||||
allow_multiselect: c.value,
|
||||
},
|
||||
});
|
||||
d.hide();
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
curSearch?: Symbol;
|
||||
pollUpdateSubMap = new Map<string, (u: pollUpdateJson) => void>();
|
||||
subToPollUpdate(m: string, func: ((u: pollUpdateJson) => void) | null) {
|
||||
if (func) this.pollUpdateSubMap.set(m, func);
|
||||
else this.pollUpdateSubMap.delete(m);
|
||||
}
|
||||
mSearch(query: string) {
|
||||
const searchy = Symbol("search");
|
||||
this.curSearch = searchy;
|
||||
|
||||
+26
-16
@@ -968,7 +968,10 @@ class MarkDown {
|
||||
return span;
|
||||
}
|
||||
static relTime(date: Date, nextUpdate?: () => void): string {
|
||||
const time = Date.now() - +date;
|
||||
const r = Date.now() - +date;
|
||||
if (isNaN(r)) return "NaN";
|
||||
const up = r < 0;
|
||||
const time = Math.abs(r);
|
||||
|
||||
let seconds = Math.round(time / 1000);
|
||||
const round = time % 1000;
|
||||
@@ -983,25 +986,32 @@ class MarkDown {
|
||||
|
||||
const formatter = new Intl.RelativeTimeFormat(I18n.lang, {style: "short"});
|
||||
if (years) {
|
||||
if (nextUpdate)
|
||||
setTimeout(
|
||||
nextUpdate,
|
||||
round + (seconds + (minutes + (hours + days * 24) * 60) * 60) * 1000,
|
||||
);
|
||||
return formatter.format(-years, "year");
|
||||
if (nextUpdate) {
|
||||
const ti = round + (seconds + (minutes + (hours + days * 24) * 60) * 60) * 1000;
|
||||
setTimeout(nextUpdate, up ? 1000 * 60 * 60 * 24 * 365 - ti : ti);
|
||||
}
|
||||
return formatter.format(up ? years : -years, "year");
|
||||
} else if (days) {
|
||||
if (nextUpdate)
|
||||
setTimeout(nextUpdate, round + (seconds + (minutes + hours * 60) * 60) * 1000);
|
||||
return formatter.format(-days, "days");
|
||||
if (nextUpdate) {
|
||||
const ti = round + (seconds + (minutes + hours * 60) * 60) * 1000;
|
||||
setTimeout(nextUpdate, up ? 1000 * 60 * 60 * 24 - ti : ti);
|
||||
}
|
||||
return formatter.format(up ? days : -days, "days");
|
||||
} else if (hours) {
|
||||
if (nextUpdate) setTimeout(nextUpdate, round + (seconds + minutes * 60) * 1000);
|
||||
return formatter.format(-hours, "hours");
|
||||
if (nextUpdate) {
|
||||
const ti = round + (seconds + minutes * 60) * 1000;
|
||||
setTimeout(nextUpdate, up ? 1000 * 60 * 60 - ti : ti);
|
||||
}
|
||||
return formatter.format(up ? hours : -hours, "hours");
|
||||
} else if (minutes) {
|
||||
if (nextUpdate) setTimeout(nextUpdate, round + seconds * 1000);
|
||||
return formatter.format(-minutes, "minutes");
|
||||
if (nextUpdate) {
|
||||
const ti = round + seconds * 1000;
|
||||
setTimeout(nextUpdate, up ? 1000 * 60 - ti : ti);
|
||||
}
|
||||
return formatter.format(up ? minutes : -minutes, "minutes");
|
||||
} else {
|
||||
if (nextUpdate) setTimeout(nextUpdate, round);
|
||||
return formatter.format(-seconds, "seconds");
|
||||
if (nextUpdate) setTimeout(nextUpdate, up ? 1000 - round : round);
|
||||
return formatter.format(up ? seconds : -seconds, "seconds");
|
||||
}
|
||||
}
|
||||
static unspoil(e: any): void {
|
||||
|
||||
+236
-2
@@ -14,6 +14,8 @@ import {
|
||||
interactionEvents,
|
||||
memberjson,
|
||||
messagejson,
|
||||
polljson,
|
||||
pollUpdateJson,
|
||||
userjson,
|
||||
} from "./jsontypes.js";
|
||||
import {Emoji} from "./emoji.js";
|
||||
@@ -68,6 +70,7 @@ class Message extends SnowFlake {
|
||||
}[] = [];
|
||||
pinned!: boolean;
|
||||
flags: number = 0;
|
||||
poll?: polljson;
|
||||
getTimeStamp() {
|
||||
return new Date(this.timestamp).getTime();
|
||||
}
|
||||
@@ -253,6 +256,22 @@ class Message extends SnowFlake {
|
||||
color: "red",
|
||||
},
|
||||
);
|
||||
Message.contextmenu.addButton(
|
||||
() => I18n.message.endPoll(),
|
||||
function (this: Message) {
|
||||
this.confirmDeletePoll();
|
||||
},
|
||||
{
|
||||
visible: function () {
|
||||
return (
|
||||
this.author.id === this.localuser.user.id &&
|
||||
!!this.poll &&
|
||||
!this.poll.results?.is_finalized
|
||||
);
|
||||
},
|
||||
color: "red",
|
||||
},
|
||||
);
|
||||
Message.contextmenu.addButton(
|
||||
() => I18n.message.report(),
|
||||
async function () {
|
||||
@@ -642,6 +661,22 @@ class Message extends SnowFlake {
|
||||
}
|
||||
console.log("deleted done");
|
||||
}
|
||||
pollUpdate(update: pollUpdateJson) {
|
||||
if (!this.poll) return;
|
||||
if (!this.poll.results) this.poll.results = {is_finalized: false, answer_counts: []};
|
||||
let ans = this.poll.results.answer_counts.find((_) => _.id === update.d.answer_id);
|
||||
if (!ans) {
|
||||
ans = {id: update.d.answer_id, count: 0, me_voted: false};
|
||||
this.poll.results.answer_counts.push(ans);
|
||||
}
|
||||
if (update.t === "MESSAGE_POLL_VOTE_ADD") {
|
||||
ans.count++;
|
||||
if (update.d.user_id === this.localuser.user.id) ans.me_voted = true;
|
||||
} else {
|
||||
ans.count--;
|
||||
if (update.d.user_id === this.localuser.user.id) ans.me_voted = false;
|
||||
}
|
||||
}
|
||||
reactdiv!: WeakRef<HTMLDivElement>;
|
||||
blockedPropigate() {
|
||||
const previd = this.channel.idToPrev.get(this.id);
|
||||
@@ -792,7 +827,8 @@ class Message extends SnowFlake {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.message_reference && this.type !== 6 && this.type !== 18) {
|
||||
|
||||
if (this.message_reference && this.type !== 6 && this.type !== 18 && this.type !== 46) {
|
||||
const replyline = document.createElement("div");
|
||||
|
||||
const minipfp = document.createElement("img");
|
||||
@@ -1137,6 +1173,76 @@ class Message extends SnowFlake {
|
||||
time.classList.add("timestamp");
|
||||
text.append(time);
|
||||
div.classList.add("topMessage");
|
||||
} else if (this.type === 46) {
|
||||
build.classList.remove("flexltr");
|
||||
build.classList.add("flexttb");
|
||||
const t = I18n.message.pollRes("$$$$", "||||");
|
||||
const content = document.createElement("div");
|
||||
content.classList.add("flexltr", "pollRes");
|
||||
const username = document.createElement("span");
|
||||
this.author.bind(username, this.guild);
|
||||
username.classList.add("username");
|
||||
const [before, after] = t.split("$$$$");
|
||||
username.textContent = this.author.name;
|
||||
const [b1, a1] = before.split("||||") as [string, undefined | string];
|
||||
const [b2, a2] = after.split("||||") as [string, undefined | string];
|
||||
const b1s = document.createElement("span");
|
||||
b1s.textContent = b1;
|
||||
content.append(b1s);
|
||||
const poll = document.createElement("span");
|
||||
poll.classList.add("pollText", "username");
|
||||
poll.onclick = () => {
|
||||
this.channel.focus(this.message_reference?.message_id!, true);
|
||||
};
|
||||
this.channel.getmessage(this.message_reference?.message_id!).then((_) => {
|
||||
if (!_) return;
|
||||
poll.textContent = _!.poll!.question!.text;
|
||||
});
|
||||
|
||||
if (a1) {
|
||||
content.append(poll);
|
||||
const a1s = document.createElement("span");
|
||||
a1s.textContent = a1;
|
||||
content.append(a1s);
|
||||
}
|
||||
content.append(username);
|
||||
const b2s = document.createElement("span");
|
||||
b2s.textContent = b2;
|
||||
content.append(b2s);
|
||||
if (a2) {
|
||||
content.append(poll);
|
||||
const a2s = document.createElement("span");
|
||||
a2s.textContent = a2;
|
||||
content.append(a2s);
|
||||
}
|
||||
build.append(content);
|
||||
|
||||
const resBody = document.createElement("div");
|
||||
resBody.classList.add("embed", "flexltr", "pollresembed");
|
||||
const res = document.createElement("div");
|
||||
res.classList.add("flexttb");
|
||||
const m = new Map((this.embeds[0].json.fields ?? []).map((f) => [f.name, f.value] as const));
|
||||
if (m.has("victor_answer_text")) {
|
||||
const ans = document.createElement("span");
|
||||
ans.textContent = m.get("victor_answer_text") + "";
|
||||
const winning = document.createElement("span");
|
||||
const per = Number(m.get("victor_answer_votes")) / Number(m.get("total_votes"));
|
||||
winning.textContent = I18n.poll.winningAnswer(Math.round(per * 100) + "");
|
||||
res.append(ans, winning);
|
||||
} else {
|
||||
res.textContent = I18n.poll.tie();
|
||||
}
|
||||
const view = document.createElement("button");
|
||||
view.textContent = I18n.poll.view();
|
||||
view.onclick = () => {
|
||||
this.channel.focus(this.message_reference?.message_id!, true);
|
||||
};
|
||||
resBody.append(res, view);
|
||||
|
||||
build.append(resBody);
|
||||
|
||||
this.bindButtonEvent();
|
||||
return div;
|
||||
}
|
||||
build.appendChild(text);
|
||||
const stickerArea = document.createElement("div");
|
||||
@@ -1145,6 +1251,118 @@ class Message extends SnowFlake {
|
||||
stickerArea.append(sticker.getHTML());
|
||||
}
|
||||
div.append(stickerArea);
|
||||
|
||||
if (this.poll) {
|
||||
const pollbody = document.createElement("div");
|
||||
pollbody.classList.add("flexttb", "pollBody");
|
||||
let voted = false;
|
||||
const genPoll = () => {
|
||||
if (!this.poll) return;
|
||||
pollbody.textContent = "";
|
||||
const d = new Date(this.poll.expiry);
|
||||
const expired = +d < Date.now() || this.poll.results?.is_finalized || false;
|
||||
const nupdate = () => {
|
||||
fetch(`${this.info.api}/channels/${this.channel.id}/polls/${this.id}/answers/@me`, {
|
||||
method: "PUT",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
answer_ids: [...r.values()]
|
||||
.filter((_) => _.me_voted)
|
||||
.map(({id}) => id)
|
||||
.filter((_) => _),
|
||||
}),
|
||||
});
|
||||
voted = !!r.values.length;
|
||||
};
|
||||
if (!this.poll.results) this.poll.results = {is_finalized: false, answer_counts: []};
|
||||
const r = new Map((this.poll.results?.answer_counts ?? []).map((_) => [_.id, _] as const));
|
||||
const question = document.createElement("h3");
|
||||
question.textContent = this.poll.question.text;
|
||||
pollbody.append(question);
|
||||
let ccount = [...r.values()].reduce((e, l) => e + +l.me_voted, 0);
|
||||
if (this.poll.allow_multiselect) voted = !!ccount;
|
||||
for (const a of this.poll.answers) {
|
||||
const aarea = document.createElement("div");
|
||||
aarea.classList.add("flexltr", "answerArea");
|
||||
const span = document.createElement("span");
|
||||
span.textContent = a.poll_media.text;
|
||||
const check = document.createElement("input");
|
||||
check.type = "checkbox";
|
||||
check.disabled = expired;
|
||||
check.checked = !!r.get(a.answer_id ?? -1)?.me_voted;
|
||||
|
||||
aarea.append(span, check);
|
||||
if (!expired)
|
||||
check.onclick = (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
if (ccount && check.checked && !this.poll?.allow_multiselect) {
|
||||
check.checked = false;
|
||||
return;
|
||||
} else ccount += check.checked ? 1 : -1;
|
||||
if (this.poll?.allow_multiselect && ccount) voted = true;
|
||||
let g = r.get(a.answer_id ?? -1);
|
||||
if (!g) {
|
||||
g = {count: 0, id: a.answer_id ?? -1, me_voted: false};
|
||||
this.poll?.results?.answer_counts.push(g);
|
||||
r.set(a.answer_id ?? -1, g);
|
||||
}
|
||||
g.me_voted = check.checked;
|
||||
if (this.poll?.allow_multiselect) nupdate();
|
||||
};
|
||||
if (voted || expired) {
|
||||
const total = [...r.values()].reduce((e, l) => e + l.count, 0);
|
||||
const count = document.createElement("span");
|
||||
count.classList.add("countpollspan");
|
||||
let c = r.get(a.answer_id ?? -1)?.count ?? 0;
|
||||
if (isNaN(c)) c = 0;
|
||||
let per = Math.round((c / total) * 100);
|
||||
if (isNaN(per)) per = 0;
|
||||
count.textContent = I18n.poll.count("" + c, per + "");
|
||||
aarea.append(count);
|
||||
if (per)
|
||||
aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${100 - per}%)`;
|
||||
}
|
||||
aarea.onclick = () => check.click();
|
||||
pollbody.append(aarea);
|
||||
}
|
||||
|
||||
if (!this.poll.allow_multiselect) {
|
||||
const submit = document.createElement("button");
|
||||
submit.textContent = I18n.submit();
|
||||
pollbody.append(submit);
|
||||
if (expired) {
|
||||
submit.disabled = true;
|
||||
} else {
|
||||
submit.onclick = () => {
|
||||
nupdate();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
} else {
|
||||
const expiresAt = document.createElement("span");
|
||||
const updatePollTime = async () => {
|
||||
if (document.contains(expiresAt))
|
||||
expiresAt.textContent = I18n.poll.expires(MarkDown.relTime(d, updatePollTime));
|
||||
};
|
||||
expiresAt.textContent = I18n.poll.expires(MarkDown.relTime(d));
|
||||
queueMicrotask(() => MarkDown.relTime(d, updatePollTime));
|
||||
pollbody.append(expiresAt);
|
||||
}
|
||||
};
|
||||
genPoll();
|
||||
this.localuser.subToPollUpdate(this.id, () => {
|
||||
if (document.contains(div)) {
|
||||
genPoll();
|
||||
} else {
|
||||
this.localuser.subToPollUpdate(this.id, null);
|
||||
}
|
||||
});
|
||||
|
||||
div.append(pollbody);
|
||||
}
|
||||
|
||||
if (!dupe) {
|
||||
if (this.components && this.components.components.length) {
|
||||
const cdiv = this.components.getHTML();
|
||||
@@ -1304,7 +1522,7 @@ class Message extends SnowFlake {
|
||||
});
|
||||
};
|
||||
}
|
||||
if (this.author === this.localuser.user) {
|
||||
if (this.author === this.localuser.user && this.type !== 46) {
|
||||
const container = document.createElement("button");
|
||||
const edit = document.createElement("span");
|
||||
edit.classList.add("svg-edit", "svgicon");
|
||||
@@ -1353,6 +1571,22 @@ class Message extends SnowFlake {
|
||||
});
|
||||
diaolog.show();
|
||||
}
|
||||
confirmDeletePoll() {
|
||||
const diaolog = new Dialog("");
|
||||
diaolog.options.addTitle(I18n.deleteConfirmPoll());
|
||||
const options = diaolog.options.addOptions("", {ltr: true});
|
||||
options.addButtonInput("", I18n.yes(), () => {
|
||||
fetch(`${this.info.api}/channels/${this.channel.id}/polls/${this.id}/expire`, {
|
||||
headers: this.headers,
|
||||
method: "POST",
|
||||
});
|
||||
diaolog.hide();
|
||||
});
|
||||
options.addButtonInput("", I18n.no(), () => {
|
||||
diaolog.hide();
|
||||
});
|
||||
diaolog.show();
|
||||
}
|
||||
updateReactions() {
|
||||
const reactdiv = this.reactdiv.deref();
|
||||
if (!reactdiv) return;
|
||||
|
||||
@@ -183,6 +183,9 @@ body {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
}
|
||||
.pollRes {
|
||||
padding-left: 50px;
|
||||
}
|
||||
.amsCont {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -257,6 +260,39 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pollBody {
|
||||
background: #00000059;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 2px;
|
||||
margin-left: 52px;
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.answerArea {
|
||||
--bg: #0000004a;
|
||||
background: var(--bg);
|
||||
margin-top: 3px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.pollAnsM {
|
||||
.svg-delete {
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.flexltr {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.reses {
|
||||
span {
|
||||
background: #0000007a;
|
||||
@@ -2733,6 +2769,9 @@ span.instanceStatus {
|
||||
position: relative;
|
||||
background-image: var(--userbg, linear-gradient(var(--primary-text), var(--primary-text)));
|
||||
}
|
||||
.pollText {
|
||||
margin: 0px 4px;
|
||||
}
|
||||
.roleIcon {
|
||||
display: inline-block;
|
||||
padding: 1px;
|
||||
@@ -3057,6 +3096,17 @@ span .quote:last-of-type .quoteline {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
.countpollspan {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
}
|
||||
.pollresembed {
|
||||
margin-left: 50px;
|
||||
margin-top: 6px;
|
||||
button {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.linkembed {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
+27
-1
@@ -213,6 +213,7 @@
|
||||
"createAccount": "Create Account",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Are you sure you want to delete this?",
|
||||
"deleteConfirmPoll":"Are you sure you want to end the poll early?",
|
||||
"devSettings": {
|
||||
"badUser": "Enable logging of bad user objects that send too much information:",
|
||||
"cache": "Enable Service Worker Caching map files:",
|
||||
@@ -621,6 +622,7 @@
|
||||
"andMore": "$1, and more!",
|
||||
"attached": "sent an attachment",
|
||||
"delete": "Delete message",
|
||||
"endPoll":"End poll",
|
||||
"report":"Report message",
|
||||
"deleted": "Deleted message",
|
||||
"edit": "Edit message",
|
||||
@@ -635,7 +637,8 @@
|
||||
"reactions": "View reactions",
|
||||
"reactionsTitle": "Reactions",
|
||||
"retry": "Resend errored message",
|
||||
"viewrest": "View rest"
|
||||
"viewrest": "View rest",
|
||||
"pollRes":"$1's poll $2 has closed."
|
||||
},
|
||||
"report":{
|
||||
"back":"Back",
|
||||
@@ -818,6 +821,29 @@
|
||||
"roleFileIcon": "Role icon:",
|
||||
"roles": "Roles"
|
||||
},
|
||||
"upload":"Upload files",
|
||||
"makePoll":"Make poll",
|
||||
"poll":{
|
||||
"question":"Question:",
|
||||
"answers":"Answers:",
|
||||
"newAnswer":"New Answer",
|
||||
"duration":"Duration:",
|
||||
"durCount":{
|
||||
"1":"1 hour",
|
||||
"4":"4 hours",
|
||||
"8":"8 hours",
|
||||
"24":"24 hours",
|
||||
"72":"3 days",
|
||||
"168":"7 days",
|
||||
"336":"14 days"
|
||||
},
|
||||
"mult":"Allow multiple answers:",
|
||||
"expires":"Expires: $1",
|
||||
"tie":"It was a tie!",
|
||||
"winningAnswer":"Winning answer: $1%",
|
||||
"view":"View poll",
|
||||
"count":"$1 {{PLURAL:$1|vote|votes}} $2%"
|
||||
},
|
||||
"search": {
|
||||
"back": "Back",
|
||||
"new": "New",
|
||||
|
||||
Reference in New Issue
Block a user