ismism/ismism.ts/ui/bind/article.ts
2023-03-08 10:55:29 +08:00

522 lines
16 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.

import type { Fund, Id, Work } from "../../src/eid/typ.ts"
import type { Pas, PasCode, PreUsr } from "../../src/pra/pos.ts"
import type { DocC, DocD, DocU } from "../../src/db.ts"
import type * as Q from "../../src/pra/que.ts"
import { is_pre_agd, is_pre_soc, is_pre_usr, is_pro_usr, is_re, is_ref, is_rej, is_sec, is_uid } from "../../src/pra/con.ts"
import { nav, navhash, navnid, navpas } from "./nav.ts"
import { acct, btn, cover, goal, idnam, meta, putpro, putrel, re, rec as srec, rel, rolref, seladm, txt, ida } from "./section.ts"
import { bind, main, pas_a, pos, que } from "./template.ts"
import { is_actid, is_goal, is_img, is_msg, is_nam, is_nbr, is_url, } from "../../src/eid/is.ts"
import { utc_medium, utc_short } from "../../src/ont/utc.ts"
export function pas(
) {
if (navhash("pas")) return
if (nav.pas) {
pos("pas", { uid: nav.pas.uid })
navpas(null)
}
navnid()
main.innerHTML = ""
const t = bind("pas")
const send = async () => {
if (!is_nbr(t.nbr.value)) return alert("无效手机号")
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激活码只能使用一次确认手机号无误`
seladm(t)
t.adm.classList.remove("none")
t.pre.classList.remove("none")
}
}
t.send.addEventListener("click", send)
t.act.addEventListener("click", async () => {
if (!is_actid(t.actid.value)) return alert("无效激活码")
t.actid.readOnly = t.act.disabled = t.adm1.disabled = t.adm2.disabled = true
const uid = await pos<PreUsr>("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", async () => {
if (!t.code.checkValidity()) return alert("无效验证码")
t.code.readOnly = t.issue.disabled = true
const p = await pos<Pas>("pas", { nbr: t.nbr.value, code: parseInt(t.code.value) })
if (!p) {
t.code.readOnly = t.issue.disabled = false
return alert("无效验证码")
}
navpas(p)
usr(p.uid)
})
main.append(t.bind)
}
export type Usr = Omit<NonNullable<Q.Usr>, "unam" | "snam" | "anam"> & {
unam: Map<Id["_id"], Id["nam"]>,
snam: Map<Id["_id"], Id["nam"]>,
anam: Map<Id["_id"], Id["nam"]>,
}
export async function usr(
uid: number
) {
if (navhash(`${uid}`)) return
const q = await que<Q.Usr>(`usr?uid=${uid}`)
if (!q) return idn(`${uid}`, "用户")
const u: Usr = { ...q, unam: new Map(q.unam), snam: new Map(q.snam), anam: new Map(q.anam) }
const froze = is_rej(u) && !(nav.pas && (nav.pas.aut || is_sec(nav.pas)))
navnid()
main.innerHTML = ""
const t = bind("usr")
idnam(t, `${uid}`, froze ? "" : u.nam, meta(t, u))
rolref(t.rolref, u)
re(t, u)
srec(t, "uid", u, froze)
if (froze) [t.nam, t.intro].forEach(el => el.classList.add("froze"))
else t.intro.innerText = u.intro
if (nav.pas) {
if (nav.pas.uid === uid) {
pas_a.innerText = u.nam
t.put.addEventListener("click", () => put("用户", u))
t.pas.addEventListener("click", pas)
if (is_pre_usr(nav.pas)) t.preusr.addEventListener("click", () => pre("用户"))
else t.preusr.remove()
if (is_pre_soc(nav.pas)) t.presoc.addEventListener("click", () => pre("社团"))
else t.presoc.remove()
if (is_pre_agd(nav.pas)) t.preagd.addEventListener("click", () => pre("活动"))
else t.preagd.remove()
btn(t.prefund, t.prefund.innerText, {
prompt1: "输入订单号",
confirm: "订单号只能激活或绑定一位用户。确认使用?",
alert: "无效订单号,或订单号已被使用",
pos: (actid) => actid ? pos("pre", { actid }) : null,
refresh: () => usr(u._id),
})
t.putpro.remove()
} else {
t.pos.remove()
t.pre.remove()
putpro(t, "uid", u, is_pro_usr(nav.pas, "rej", u._id) ? () => usr(u._id) : undefined)
}
} else {
t.pos.remove()
t.pre.remove()
t.putpro.remove()
}
main.append(t.bind)
}
function doc<T>(
q: "soc" | "agd",
adm: string,
) {
const [a1, a2] = (adm).split("-")
if (a2) {
navnid(q, a1, a2)
return que<T[]>(`${q}?adm2=${a2}`)
} else if (a1) {
navnid(q, a1)
return que<T[]>(`${q}?adm1=${a1}`)
} else {
navnid(q)
return que<T[]>(q)
}
}
export type Soc = Omit<NonNullable<Q.Soc>, "unam"> & {
unam: Map<Id["_id"], Id["nam"]>,
}
export async function soc(
sidadm?: number | string
) {
if (navhash(typeof sidadm === "number" ? `s${sidadm}` : `soc${sidadm ?? ""}`)) return
let ss: Q.Soc[]
if (typeof sidadm === "number") {
ss = [await que<Q.Soc>(`soc?sid=${sidadm}`)]
navnid()
} else ss = await doc<Q.Soc>("soc", sidadm ?? "")
ss = ss.filter(s => s)
if (typeof sidadm === "number" && ss.length === 0) return idn(`s${sidadm}`, "社团")
main.innerHTML = ""
for (const d of ss) {
if (!d) continue
const s: Soc = { ...d, unam: new Map(d.unam) }
const froze = is_rej(s) && !(nav.pas && (nav.pas.aut || is_sec(nav.pas, { sid: s._id })))
const t = bind("soc")
idnam(t, `s${s._id}`, s.nam, meta(t, s))
rel(t, s)
srec(t, "sid", s, froze)
if (froze) [t.nam, t.intro].forEach(el => el.classList.add("froze"))
else t.intro.innerText = s.intro
if (nav.pas) {
if (nav.pas.aut || is_sec(nav.pas, { sid: s._id }))
t.put.addEventListener("click", () => put("社团", s))
else t.put.remove()
putrel(t, "sid", s, () => soc(s._id))
putpro(t, "sid", s, nav.pas.aut ? () => soc(s._id) : undefined)
} else {
t.pos.remove()
t.putrel.remove()
t.putpro.remove()
}
main.append(t.bind)
}
}
export type Agd = Omit<NonNullable<Q.Agd>, "unam"> & {
unam: Map<Id["_id"], Id["nam"]>,
}
export async function agd(
aidadm?: number | string
) {
if (navhash(typeof aidadm === "number" ? `a${aidadm}` : `agd${aidadm ?? ""}`)) return
let aa: Q.Agd[]
if (typeof aidadm === "number") {
aa = [await que<Q.Agd>(`agd?aid=${aidadm}`)]
navnid()
} else aa = await doc<Q.Agd>("agd", aidadm ?? "")
aa = aa.filter(a => a)
if (typeof aidadm === "number" && aa.length === 0) return idn(`a${aidadm}`, "活动")
main.innerHTML = ""
for (const d of aa) {
if (!d) continue
const a: Agd = { ...d, unam: new Map(d.unam) }
const froze = is_rej(a) && !(nav.pas && (nav.pas.aut || is_sec(nav.pas, { aid: a._id })))
const t = bind("agd")
idnam(t, `a${a._id}`, a.nam, meta(t, a))
rel(t, a)
cover(t, a)
acct(t, a)
goal(t.goal, a)
srec(t, "aid", a, froze)
if (froze) [t.nam, t.intro].forEach(el => el.classList.add("froze"))
else t.intro.innerText = a.intro
if (nav.pas) {
if (nav.pas.aut || is_sec(nav.pas, { aid: a._id }))
t.put.addEventListener("click", () => put("活动", a))
else t.put.remove()
if (is_sec(nav.pas, { aid: a._id })) {
btn(t.putimg, t.putimg.innerText, {
prompt1: "输入要增删或编辑的图片名2-16个中文字符",
prompt2: "输入图片外链,或留空以删除图片",
alert: "无效输入或图片数已达上限9张",
pos: (nam, src) => {
if (!is_nam(nam!)) return null
let img = a.img.filter(m => m.nam !== nam)
if (src && src.length > 0) img = [{ nam, src }, ...img]
return is_img(img) ? pos("put", { aid: a._id, img }) : null
},
refresh: () => agd(a._id),
})
btn(t.putgoal, t.putgoal.innerText, {
prompt1: "输入要增删或编辑的目标名2-16个中文字符",
prompt2: "输入目标进度0-100或留空以删除目标",
alert: "无效输入或目标数已达上限9个",
pos: (nam, pct) => {
if (!is_nam(nam!)) return null
let g = a.goal.filter(m => m.nam !== nam)
if (pct && pct.length > 0) g = [{ nam, pct: parseInt(pct) }, ...g]
return is_goal(g) ? pos("put", { aid: a._id, goal: g }) : null
},
refresh: () => agd(a._id),
})
btn(t.prevideo, t.prevideo.innerText, {
prompt1: "输入视频标题2 - 256 个字符)",
prompt2: "输入视频外链",
alert: "无效输入",
pos: (nam, src) => {
if (!is_msg(nam!) || !is_url(src!)) return null
return pos("pre", { aid: a._id, nam, src })
},
refresh: () => agd(a._id),
})
} else {
t.putimg.remove()
t.putgoal.remove()
t.prevideo.remove()
}
if (is_uid(nav.pas, { aid: a._id })) btn(t.prework, t.prework.innerText, {
prompt1: "输入工作日志2-256 个字符,\\n 为换行符)",
pos: msg => {
if (!is_msg(msg!)) return null
msg = msg.replaceAll("\\n", "\n")
return pos("pre", { aid: a._id, msg })
},
alert: "无效输入",
refresh: () => agd(a._id),
}); else t.prework.remove()
putrel(t, "aid", a, () => agd(a._id))
putpro(t, "aid", a, nav.pas.aut ? () => agd(a._id) : undefined)
} else {
t.pos.remove()
t.putrel.remove()
t.putpro.remove()
}
main.append(t.bind)
}
}
export type Rec = Omit<NonNullable<Q.Rec>, "unam" | "anam"> & {
unam: Map<Id["_id"], Id["nam"]>,
anam: Map<Id["_id"], Id["nam"]>,
}
export function rec(
c: "work" | "fund",
r: Rec,
d: Rec["rec"][0],
): DocumentFragment {
const t = bind("rec")
const aid = d._id.aid
t.unam.innerText = r.unam.get(d._id.uid)!
t.unam.href = `#${d._id.uid}`
t.anam.innerText = r.anam.get(aid)!
t.anam.href = `#a${aid}`
t.meta.innerText = utc_short(d._id.utc)
if (c === "work") {
const w = d as Work
const froze = !is_re(w) && !(nav.pas && (nav.pas.aut || is_sec(nav.pas, { aid }) || is_uid(nav.pas, { aid })))
if (is_rej(w)) {
t.meta.innerText += "(反对者达两名,不公示)";
[t.meta, t.msg].forEach(el => el.classList.add("rej2"))
} else if (!is_ref(w)) {
t.meta.innerText += "(推荐人未达两名,不公示)";
[t.meta, t.msg].forEach(el => el.classList.add("ref2"))
}
if (w.ref.length !== 0 && w._id.uid === nav.pas?.uid) t.meta.innerText += "(有推荐人,不可编辑)"
if (!froze) switch (w.work) {
case "work": {
t.msg.innerText = w.msg
break
} case "video": {
t.msg.innerText = "发布了视频:"
const a = t.msg.appendChild(document.createElement("a"))
a.innerText = w.nam
a.href = w.src
break
}
}
ida(t.rej, w.rej.map(uid => [`${uid}`, r.unam.get(uid)!]))
ida(t.ref, w.ref.map(uid => [`${uid}`, r.unam.get(uid)!]))
if (nav.pas) {
const refresh = async () => {
const q = await que<Q.Rec>(`rec?c=work&uid=${w._id.uid}&aid=${w._id.aid}&utc=${w._id.utc}`)
if (q) t.rec.replaceWith(rec(c, { ...q, unam: new Map(q.unam), anam: new Map(q.anam) }, q.rec[0]))
}
putpro(t, "workid", w, refresh)
if (!is_uid(nav.pas, { aid })) t.putrej.remove()
if (!is_sec(nav.pas, { aid })) t.putref.remove()
if (w.ref.length === 0 && w._id.uid === nav.pas.uid) {
const prompt1 = w.work === "work"
? "输入工作日志2-256 个字符,\\n 为换行符,留空以删除日志)"
: "输入视频标题2 - 256 个字符,留空以删除日志)"
let del = false
btn(t.put, t.put.innerText, {
prompt1, prompt2: w.work === "work" ? undefined : "输入视频外链",
pos: (p1, p2) => {
del = !p1
return pos("put", del ? { workid: w._id } : w.work === "work"
? { workid: w._id, msg: p1!.replaceAll("\\n", "\n") }
: { workid: w._id, nam: p1!, src: p2! }
)
},
alert: "无效输入",
refresh: () => del ? t.rec.remove() : refresh()
})
} else t.put.remove()
} else t.putpro.remove()
} else if (c === "fund") {
const f = d as Fund
const amount = f.fund > 0 ? `提供支持:+${f.fund}\n` : ""
t.msg.innerText = `${amount}${f.msg}`
t.re.remove()
}
return t.bind
}
export function wsl(
) {
}
export function lit(
) {
}
function pre(
nam: "用户" | "社团" | "活动",
) {
if (!nav.pas) return
navnid()
main.innerHTML = ""
const t = bind("pre")
idnam(t, `${nav.pas.uid}`, `添加${nam}`)
seladm(t)
// deno-lint-ignore no-explicit-any
let p: () => any
let r: (id: Id["_id"]) => void
switch (nam) {
case "用户": {
t.pnam.parentElement?.remove()
p = () => ({ nbr: t.nbr.value, adm1: t.adm1.value, adm2: t.adm2.value })
r = usr
break
} case "社团": {
t.nbr.parentElement?.remove()
p = () => ({ snam: t.pnam.value, adm1: t.adm1.value, adm2: t.adm2.value })
r = soc
break
} case "活动": {
t.nbr.parentElement?.remove()
p = () => ({ anam: t.pnam.value, adm1: t.adm1.value, adm2: t.adm2.value })
r = agd
break
}
}
btn(t.pre, t.pre.innerText, {
pos: () => pos<DocC<Id["_id"]>>("pre", p()),
alert: `无效输入\n或${nam === "用户" ? "手机号" : "名称"}已被占用`,
refresh: r,
})
t.cancel.addEventListener("click", () => usr(nav.pas!.uid))
main.append(t.bind)
}
function put(
nam: "用户" | "社团" | "活动",
id: Usr | Soc | Agd,
) {
if (!nav.pas) return
navnid()
main.innerHTML = ""
const t = bind("put")
idnam(t, `${id._id}`, `编辑${nam}信息`)
t.pnam.value = id.nam
seladm(t, id.adm1, id.adm2)
txt(t.intro, "简介", id.intro)
const pid = () => ({ nam: t.pnam.value, adm1: t.adm1.value, adm2: t.adm2.value, })
const paut = () => ({ uidlim: parseInt(t.uidlim.value) })
const psoc = () => ({ intro: t.intro.value.trim(), reslim: parseInt(t.reslim.value) })
const pagd = () => ({
account: t.account.value.trim(), budget: parseInt(t.budget.value),
fund: parseInt(t.fund.value), expense: parseInt(t.expense.value)
}) // deno-lint-ignore no-explicit-any
let p: () => any[]
let r
switch (nam) {
case "用户": {
[t.uidlim, t.reslim, t.account, t.budget, t.fund, t.expense].forEach(el => el.parentElement?.remove())
p = () => [{ uid: id._id, ...pid(), intro: t.intro.value.trim() }]
r = () => usr(id._id)
break
} case "社团": {
[t.account, t.budget, t.fund, t.expense].forEach(el => el.parentElement?.remove())
const s = id as Soc
t.uidlim.value = `${s.uidlim}`
t.reslim.value = `${s.reslim}`
const [isaut, issec] = [nav.pas.aut, is_sec(nav.pas, { sid: id._id })]
t.pnam.readOnly = t.adm1.disabled = t.adm2.disabled = t.uidlim.readOnly = !isaut
t.intro.readOnly = t.reslim.readOnly = !issec
p = () => [
...isaut ? [{ sid: id._id, ...pid(), ...paut() }] : [],
...issec ? [{ sid: id._id, ...psoc() }] : [],
]
r = () => soc(id._id)
break
} case "活动": {
const a = id as Agd
t.uidlim.value = `${a.uidlim}`
t.reslim.value = `${a.reslim}`
t.account.value = a.account
t.budget.value = `${a.budget}`
t.fund.value = `${a.fund}`
t.expense.value = `${a.expense}`
const [isaut, issec] = [nav.pas.aut, is_sec(nav.pas, { aid: id._id })]
t.pnam.readOnly = t.adm1.disabled = t.adm2.disabled = t.uidlim.readOnly = !isaut;
[t.intro, t.reslim, t.account, t.budget, t.fund, t.expense].forEach(el => el.readOnly = !issec)
p = () => [
...isaut ? [{ aid: id._id, ...pid(), ...paut() }] : [],
...issec ? [{ aid: id._id, ...psoc(), ...pagd() }] : [],
]
r = () => agd(id._id)
break
}
}
if (nam === "用户" || !nav.pas.aut || !is_re(nav.pas)) t.putn.remove()
else btn(t.putn, `删除${nam}`, {
prompt1: `确认要删除的${nam}名称\n只能删除${nam === "社团" ? "无志愿者的社团" : "无工作日志,无支持记录的活动"}`,
confirm: `确认要删除${nam}?`,
pos: p1 => {
if (p1 !== id.nam) return null
return pos<DocD>("put", { [nam === "社团" ? "sid" : "aid"]: id._id })
},
alert: `${nam}名称有误\n${nam === "社团" ? "或社团仍有志愿者" : "或活动仍有工作日志或支持记录"}`,
refresh: () => navhash(nam === "社团" ? "soc" : "agd"),
})
btn(t.put, t.put.innerText, {
pos: async () => {
const rs = await Promise.all(p().map(b => pos<DocU>("put", b)))
return rs.some(r => r === null) ? null : 1
},
alert: "无效输入\n或名称已被占用",
refresh: r,
})
t.cancel.addEventListener("click", r)
main.append(t.bind)
}
export function idn(
id: string, nam: string
) {
navnid()
main.innerHTML = ""
const t = bind("idn")
t.id.innerText = id
t.meta.innerText = `ismist#${id} 是无效${nam}`
main.append(t.bind)
}