2023-02-10 17:43:14 +08:00

501 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// deno-lint-ignore-file no-window-prefix
import type { DocC, DocU } from "../../src/db.ts"
import type { Aut, Id } from "../../src/eid/typ.ts"
import { adm, adm1_def, adm2_def } from "../../src/ont/adm.ts"
import { utc_medium } from "../../src/ont/utc.ts"
import type { Pas } from "../../src/pra/pas.ts"
import type { PasCode, UsrAct } from "../../src/pra/pos.ts"
import type { Soc, Usr } from "../../src/pra/que.ts"
import { bind } from "./template.ts"
let hash = ""
let pas: Pas | null = null
let utc_etag = Date.now()
const main = document.getElementById("main")!
const pas_a = document.getElementById("pas")! as HTMLAnchorElement
async function que<T>(
q: string
) {
const res = await fetch(`/q/${q}`)
const etag = res.headers.get("etag")?.substring(3)
if (etag) utc_etag = parseInt(etag)
return res.json() as T
}
async function pos<T>(
f: string, b: Record<string, string | number | boolean>
) {
const res = await fetch(`/p/${f}`, {
method: "POST",
body: JSON.stringify(b)
})
return res.json() as T
}
function is_aut(
pas: Pas,
aut: Aut["p"][0],
): boolean {
return pas.aut.includes(aut)
}
function not_aut(
pas: Pas,
aut: Aut["p"][0],
) {
return !is_aut(pas, aut)
}
function is_pro(
{ rej, ref }: Pas,
): boolean {
return rej.length < 2 && ref.length >= 2
}
function not_pro(
pas: Pas,
) {
return !is_pro(pas)
}
function selopt(
sel: HTMLSelectElement,
ts: Iterable<string>,
) {
sel.options.length = 0
for (const t of ts) {
const opt = document.createElement("option")
opt.text = t
sel.add(opt)
}
}
function idanchor(
id: number[],
idnam: Map<Id["_id"], Id["nam"]>,
el: HTMLElement,
pf: "" | "s" | "a",
) {
if (id.length === 0) { el.innerText = "无"; return }
id.forEach(id => {
const a = el.appendChild(document.createElement("a"))
a.href = `#${pf}${id}`
a.innerText = idnam.get(id) ?? `${id}`
})
}
function idmeta(
id: NonNullable<Usr | Soc>,
idnam: Map<Id["_id"], Id["nam"]>,
t: {
id: HTMLElement,
adm: HTMLElement, utc: HTMLElement,
rej: HTMLElement, ref: HTMLElement,
rejc: HTMLElement, refc: HTMLElement, proc: HTMLElement,
},
): boolean {
let pro: null | "rej" | "ref" = null
if (id.rej.length >= 2) pro = "rej"
else if (id.ref.length < 2) pro = "ref"
const pub: boolean = pro === null || (pas !== null && is_aut(pas, "pro_usr"))
if (pro === "rej") {
t.id.classList.add("red")
t.proc.classList.add("red")
} else if (pro === "ref") {
t.id.classList.add("green")
t.proc.classList.add("green")
} else t.proc.classList.add("gray")
t.adm.innerText = `${id.adm1} ${id.adm2}`
t.utc.innerText = `${utc_medium(id.utc)}`
idanchor(id.rej, idnam, t.rej, "")
idanchor(id.ref, idnam, t.ref, "")
if (id.rej.length >= 2) {
t.rej.classList.add("red")
t.rejc.classList.add("red")
} else t.rejc.classList.add("gray")
if (id.ref.length < 2) {
t.ref.classList.add("green")
t.refc.classList.add("green")
} else t.refc.classList.add("gray")
return pub
}
function pasact(
) {
const t = bind("pasact")
const send = async () => {
if (!/^1\d{10}$/.test(t.nbr.value)) { alert("无效手机号"); return }
t.nbr.readOnly = t.send.disabled = true
const sent = await pos<PasCode>("pas", { nbr: t.nbr.value, sms: location.hostname === "ismist.cn" })
if (sent) {
const utc = sent.utc ? `\n上次发送${utc_medium(sent.utc)}` : ""
t.hint.innerText = `验证码已发送,可多次使用\n一小时内不再重复发送${utc}`
t.pas.classList.remove("none")
} else {
t.hint.innerText = `手机号未注册\n输入居住地与注册激活码\n激活码只能使用一次确认手机号无误`
selopt(t.adm1, adm.keys())
t.adm1.value = adm1_def
selopt(t.adm2, adm.get(adm1_def)!)
t.adm2.value = adm2_def
t.adm1.addEventListener("change", () => selopt(t.adm2, adm.get(t.adm1.value)!))
t.adm.classList.remove("none")
t.pre.classList.remove("none")
}
}
t.send.addEventListener("click", send)
t.act.addEventListener("click", async () => {
if (!t.actid.checkValidity()) { alert("无效激活码"); return }
t.actid.readOnly = t.act.disabled = t.adm1.disabled = t.adm2.disabled = true
const uid = await pos<UsrAct>("pre", { actid: t.actid.value, nbr: t.nbr.value, adm1: t.adm1.value, adm2: t.adm2.value })
if (uid) {
await send()
t.pas.classList.remove("none")
} else {
t.actid.readOnly = t.act.disabled = t.adm1.disabled = t.adm2.disabled = false
alert("无效激活码")
}
})
t.issue.addEventListener("click", () => {
if (!t.code.checkValidity()) { alert("无效验证码"); return }
t.code.readOnly = t.issue.disabled = true
pos<Pas>("pas", { nbr: t.nbr.value, code: parseInt(t.code.value) }).then(p => {
if (!p) {
t.code.readOnly = t.issue.disabled = false
alert("无效验证码")
return
}
pas = p
pas_a.innerText = p.nam
pas_a.href = `#${p.id.uid}`
location.hash = `#${p.id.uid}`
})
})
main.append(t.bind)
}
async function usr(
uid: NonNullable<Usr>["_id"]
) {
const u = await que<Usr>(`usr?uid=${uid}`)
if (!u) {
idnull(`${uid}`, `ismist.cn#${uid} 是无效用户`)
return
}
main.innerHTML = ""
const t = bind("usr")
t.idnam.href = `#${uid}`
t.id.innerText = `${uid}`
t.nam.innerText = u.nam
const unam = new Map(u.unam)
let pub = idmeta(u, unam, t)
if (pas && pas.id.uid === uid) pub = true
if (pub) {
t.intro.innerText = u.intro.length > 0 ? u.intro : "无"
const snam = new Map(u.snam)
t.soc.innerText = `${u.snam.length > 0 ? "" : "无"}`
idanchor([...u.snam.keys()], snam, t.soc, "s")
t.rec.innerText = JSON.stringify(u.nrec)
} else {
t.nam.innerText = t.intro.innerText = t.soc.innerText = t.rec.innerText = "【冻结中】"
}
if (pas) {
if (pas.id.uid === uid) {
t.pro.remove()
t.put.addEventListener("click", () => putusr(u))
t.pas.addEventListener("click", async () => {
await pos("pas", { uid })
pas = null
pas_a.innerText = "用户登录"
pas_a.href = "#pas"
location.href = `#pas`
})
if (not_aut(pas, "pre_usr")) t.preusr.remove()
else t.preusr.addEventListener("click", () => pre("创建用户"))
if (not_aut(pas, "pre_soc")) t.presoc.remove()
else t.presoc.addEventListener("click", () => pre("创建社团"))
if (not_aut(pas, "pre_agd")) t.preagd.remove()
else t.preagd.addEventListener("click", () => pre("创建活动"))
} else {
t.pos.remove()
t.pre.remove()
const prorej = !u.rej.includes(pas.id.uid)
const proref = !u.ref.includes(pas.id.uid)
t.prorej.innerText = prorej ? "反对" : "取消反对"
t.proref.innerText = proref ? "推荐" : "取消推荐"
if (not_aut(pas, "pro_usr") || not_pro(pas) || pas.ref.includes(uid)) {
t.prorej.disabled = true
t.proref.disabled = true
} else {
t.prorej.addEventListener("click", async () => {
t.prorej.disabled = true
const c = await pos<DocU>("pro", { re: "rej", uid, pro: prorej })
if (c && c > 0) setTimeout(() => usr(uid), 500)
else t.prorej.disabled = false
})
t.proref.addEventListener("click", async () => {
t.proref.disabled = true
const c = await pos<DocU>("pro", { re: "ref", uid, pro: proref })
if (c && c > 0) setTimeout(() => usr(uid), 500)
else t.proref.disabled = false
})
}
}
} else {
t.pos.remove()
t.pre.remove()
t.pro.remove()
}
main.append(t.bind)
}
async function soc(
sid?: NonNullable<Soc>["_id"]
) {
let ss = sid ? [await que<Soc>(`soc?sid=${sid}`)] : await que<Soc[]>("soc")
ss = ss.filter(s => s)
if (sid && ss.length === 0) {
idnull(`s${sid}`, `ismist.cn#s${sid} 是无效社团`)
return
}
main.innerHTML = ""
for (const s of ss) {
if (!s) continue
const t = bind("soc")
t.idnam.href = `#s${s._id}`
t.id.innerText = `s${s._id}`
if (hash === t.id.innerText) t.id.classList.add("active")
t.nam.innerText = s.nam
const unam = new Map(s.unam)
const pub = idmeta(s, unam, t)
if (pub) {
idanchor(s.sec, unam, t.sec, "")
idanchor(s.uid, unam, t.uid, "")
idanchor(s.res, unam, t.res, "")
t.intro.innerText = s.intro
t.rec.innerText = JSON.stringify(s.nrec)
} else {
t.sec.innerText = t.uid.innerText = t.res.innerText = t.intro.innerText = t.rec.innerText = "【冻结中】"
}
if (pas) {
if (not_aut(pas, "pre_soc")) t.putpre.remove()
else t.putpre.disabled = true
if (!s.sec.includes(pas.id.uid)) t.putsec.remove()
else t.putsec.disabled = true
if (!s.uid.includes(pas.id.uid)) t.putuid.remove()
else t.putuid.addEventListener("click", async () => {
if (confirm("退出社团?")) {
t.putuid.disabled = true
const r = await pos<DocU>("put", { sid: s._id, uid: pas!.id.uid, pro: false })
if (r && r > 0) {
const h = `s${s._id}`
if (hash === h) soc(s._id)
else location.href = `#${h}`
} else t.putuid.disabled = false
}
})
if (s.uid.includes(pas.id.uid)) t.putres.remove()
else {
const res = !s.res.includes(pas.id.uid)
t.putres.innerText = res ? "申请加入" : "取消申请"
if (!res || pub && s.res.length < s.res_max) t.putres.addEventListener("click", async () => {
t.putres.disabled = true
const r = await pos<DocU>("put", { sid: s._id, res })
if (r && r > 0) {
const h = `s${s._id}`
if (hash === h) soc(s._id)
else location.href = `#${h}`
} else t.putuid.disabled = false
}); else t.putres.disabled = true
}
if (not_aut(pas, "pro_soc")) t.pro.remove()
else {
const prorej = !s.rej.includes(pas.id.uid)
const proref = !s.ref.includes(pas.id.uid)
t.prorej.innerText = prorej ? "反对" : "取消反对"
t.proref.innerText = proref ? "推荐" : "取消推荐"
if (not_pro(pas)) t.prorej.disabled = t.proref.disabled = true
else {
t.prorej.addEventListener("click", async () => {
t.prorej.disabled = true
const r = await pos<DocU>("pro", { re: "rej", sid: s._id, pro: prorej })
if (r && r > 0) {
const h = `s${s._id}`
if (hash === h) soc(s._id)
else location.href = `#${h}`
} else t.prorej.disabled = false
})
t.proref.addEventListener("click", async () => {
t.proref.disabled = true
const r = await pos<DocU>("pro", { re: "ref", sid: s._id, pro: proref })
if (r && r > 0) {
const h = `s${s._id}`
if (hash === h) soc(s._id)
else location.href = `#${h}`
} else t.proref.disabled = false
})
}
}
} else {
t.put.remove()
t.pro.remove()
}
main.append(t.bind)
}
}
function pre(
nam: "创建用户" | "创建社团" | "创建活动"
) {
if (!pas) { location.href = `#pasact`; return }
main.innerHTML = ""
const t = bind("pre")
t.idnam.href = `#${pas.id.uid}`
t.id.innerText = `${pas.id.uid}`
t.nam.innerText = nam
t.meta.innerText = `将作为推荐人${nam}`
selopt(t.adm1, adm.keys())
t.adm1.value = adm1_def
selopt(t.adm2, adm.get(adm1_def)!)
t.adm2.value = adm2_def
t.adm1.addEventListener("change", () => selopt(t.adm2, adm.get(t.adm1.value)!))
let param: () => Record<string, string>
let pf = ""
switch (nam) {
case "创建用户": {
t.meta.innerText += `\n新用户可通过手机号登录、编辑用户信息`
t.pnam.parentElement?.remove()
t.intro.parentElement?.remove()
param = () => ({ nbr: t.nbr.value, adm1: t.adm1.value, adm2: t.adm2.value })
break
} case "创建社团": case "创建活动": {
t.nbr.parentElement?.remove()
t.intro.addEventListener("input", () => {
t.intro.style.height = "auto"
t.intro.style.height = `${t.intro.scrollHeight}px`;
(t.intro.previousElementSibling as HTMLElement).innerText = `简介:(${t.intro.value.length} / 2048 个字符)`
})
param = () => ({
...nam === "创建社团" ? { snam: t.pnam.value } : { anam: t.pnam.value },
adm1: t.adm1.value, adm2: t.adm2.value, intro: t.intro.value
})
pf = nam === "创建社团" ? "s" : "a"
break
} default: { usr(pas.id.uid); return }
}
t.pre.addEventListener("click", async () => {
const id = await pos<DocC<Id["_id"]>>("pre", param())
if (id) setTimeout(() => location.hash = `#${pf}${id}`, 500)
else alert(`${nam}失败`)
})
t.cancel.addEventListener("click", () => usr(pas!.id.uid))
main.append(t.bind)
}
function putusr(
u: NonNullable<Usr>
) {
main.innerHTML = ""
const t = bind("putusr")
t.idnam.href = `#${u._id}`
t.id.innerText = `${u._id}`
t.nam.value = u.nam
selopt(t.adm1, adm.keys())
t.adm1.value = u.adm1
selopt(t.adm2, adm.get(u.adm1)!)
t.adm2.value = u.adm2
t.adm1.addEventListener("change", () => selopt(t.adm2, adm.get(t.adm1.value)!))
t.intro.value = u.intro
t.intro.addEventListener("input", () => {
t.intro.style.height = "auto"
t.intro.style.height = `${t.intro.scrollHeight}px`;
(t.intro.previousElementSibling as HTMLElement).innerText = `简介:(${t.intro.value.length} / 2048 个字符)`
})
setTimeout(() => t.intro.dispatchEvent(new Event("input")), 50)
t.put.addEventListener("click", async () => {
t.put.disabled = true
const c = await pos<DocU>("put", {
uid: u._id,
nam: t.nam.value,
adm1: t.adm1.value,
adm2: t.adm2.value,
intro: t.intro.value.trim(),
})
if (c === null || c === 0) {
alert(`无效输入\n或用户名已被占用`)
t.put.disabled = false
} else {
pas_a.innerText = t.nam.value
setTimeout(() => usr(u._id), 500)
}
})
t.cancel.addEventListener("click", () => usr(u._id))
main.append(t.bind)
}
function idnull(
id: string,
meta: string,
) {
main.innerHTML = ""
const t = bind("idnull")
t.id.innerText = id
t.meta.innerText = meta;
main.append(t.bind)
}
window.addEventListener("hashchange", () => {
hash = decodeURI(window.location.hash).substring(1)
main.innerHTML = ""
if (hash === "pas") pasact()
else if (/^\d+$/.test(hash)) usr(parseInt(hash))
else if (hash === "soc") soc()
else if (/^s\d+$/.test(hash)) soc(parseInt(hash.substring(1)))
else if (hash.startsWith("a")) que("agd").then(a => main.innerText = JSON.stringify(a))
})
async function load(
) {
console.log("ismism-20230204")
console.log(`\n主义主义开发小组成员招募中\n\n发送自我介绍至网站维护邮箱或微信联系 728 万大可\n \n`)
pas = await pos<Pas>("pas", {})
if (pas) {
pas_a.innerText = pas.nam
pas_a.href = `#${pas.id.uid}`
}
window.dispatchEvent(new Event("hashchange"))
}
load()