From 71db941e6d772c4b6b2849d2db91decd7431262e Mon Sep 17 00:00:00 2001 From: 728 Date: Wed, 15 Feb 2023 16:53:02 +0800 Subject: [PATCH] ui #39 --- ismism.ts/src/eid/is.ts | 11 ++ ismism.ts/src/eid/usr.ts | 3 +- ismism.ts/src/ont/sms.ts | 11 -- ismism.ts/tst/eid.test.ts | 7 +- ismism.ts/tst/ont.test.ts | 6 - ismism.ts/ui/bind/article.ts | 285 +++++++++++++++---------------- ismism.ts/ui/bind/section.ts | 117 ++++++++----- ismism.ts/ui/bind/template.ts | 19 ++- ismism.ts/ui/index/style.css | 44 +++-- ismism.ts/ui/index/template.html | 6 +- 10 files changed, 272 insertions(+), 237 deletions(-) diff --git a/ismism.ts/src/eid/is.ts b/ismism.ts/src/eid/is.ts index 1c4522d..dcf0fe7 100644 --- a/ismism.ts/src/eid/is.ts +++ b/ismism.ts/src/eid/is.ts @@ -22,6 +22,17 @@ export function not_nam( return !is_nam(nam) } +export function is_nbr( + nbr?: null | string +): nbr is string { + return typeof nbr === "string" && /^1\d{10}$/.test(nbr) +} +export function not_nbr( + nbr?: null | string +) { + return !is_nbr(nbr) +} + export function is_intro( intro?: null | Id["intro"] ) { diff --git a/ismism.ts/src/eid/usr.ts b/ismism.ts/src/eid/usr.ts index 344d5f0..d397237 100644 --- a/ismism.ts/src/eid/usr.ts +++ b/ismism.ts/src/eid/usr.ts @@ -1,8 +1,7 @@ import { Usr } from "./typ.ts" import { coll, DocC, DocD, DocR, DocU, Update } from "../db.ts" -import { is_nbr, not_nbr } from "../ont/sms.ts" import { not_adm } from "../ont/adm.ts" -import { is_id, not_id, not_nam, not_intro } from "./is.ts" +import { is_id, not_id, not_nam, is_nbr, not_nbr, not_intro } from "./is.ts" export async function usr_c( nbr: NonNullable, diff --git a/ismism.ts/src/ont/sms.ts b/ismism.ts/src/ont/sms.ts index 3f8c5b6..acee90e 100644 --- a/ismism.ts/src/ont/sms.ts +++ b/ismism.ts/src/ont/sms.ts @@ -2,17 +2,6 @@ import { to_hex } from "./base.ts" import { digest, key, sign } from "./crypt.ts" import { utc_date } from "./utc.ts" -export function is_nbr( - nbr?: null | string -): nbr is string { - return typeof nbr === "string" && /^1\d{10}$/.test(nbr) -} -export function not_nbr( - nbr?: null | string -) { - return !is_nbr(nbr) -} - const tc: { id: string, key: string, diff --git a/ismism.ts/tst/eid.test.ts b/ismism.ts/tst/eid.test.ts index feb23d8..701d5c8 100644 --- a/ismism.ts/tst/eid.test.ts +++ b/ismism.ts/tst/eid.test.ts @@ -4,7 +4,7 @@ import { act_c, act_d, act_r, act_u } from "../src/eid/act.ts" import { agd_c, agd_d, agd_r, agd_u } from "../src/eid/agd.ts" import { aut_c, aut_d, aut_r, aut_u } from "../src/eid/aut.ts" import { id, idnam, nid_of_adm } from "../src/eid/id.ts" -import { is_id, is_intro, is_nam, not_id, not_intro, not_nam, is_rol, not_rol } from "../src/eid/is.ts" +import { is_id, is_intro, is_nam, not_id, not_intro, not_nam, is_rol, not_rol, is_nbr, not_nbr } from "../src/eid/is.ts" import { nrec, rec_c, rec_d, rec_r, rec_u, rol } from "../src/eid/rec.ts" import { soc_c, soc_d, sidnam, soc_r, soc_u } from "../src/eid/soc.ts" import { usr_c, usr_r, usr_u, usr_d } from "../src/eid/usr.ts" @@ -46,6 +46,11 @@ Deno.test("id", async () => { await Promise.all(sid.map(soc_d)) }) +Deno.test("nbr", () => { + assert(is_nbr("11111111111")) + assert(not_nbr(undefined) && not_nbr(null) && not_nbr("") && not_nbr("123")) +}) + Deno.test("usr", async () => { const nbr = "11111111114" assert(null === await usr_r({ _id: 1 }, { nbr: 1 })) diff --git a/ismism.ts/tst/ont.test.ts b/ismism.ts/tst/ont.test.ts index df06fd9..f9da0d6 100644 --- a/ismism.ts/tst/ont.test.ts +++ b/ismism.ts/tst/ont.test.ts @@ -3,7 +3,6 @@ import { is_adm, is_adm1, is_adm2, not_adm, not_adm1, not_adm2 } from "../src/on import { from_base64, from_hex, from_u8, to_base64, to_hex, to_u8 } from "../src/ont/base.ts" import { digest } from "../src/ont/crypt.ts" import { jwk_load, jwk_set, jwt_sign, jwt_verify } from "../src/ont/jwt.ts" -import { is_nbr, not_nbr } from "../src/ont/sms.ts" import { utc_date, utc_h, utc_medium, utc_short } from "../src/ont/utc.ts" Deno.test("base", () => { @@ -39,11 +38,6 @@ Deno.test("adm", () => { assert(not_adm2(undefined) && not_adm2(null) && not_adm2("") && not_adm2("四川")) }) -Deno.test("nbr", () => { - assert(is_nbr("11111111111")) - assert(not_nbr(undefined) && not_nbr(null) && not_nbr("") && not_nbr("123")) -}) - Deno.test("dig", async () => { const h = `${Math.floor(Date.now() % utc_h * Math.random())}` const h1000 = await digest(h, 1000) diff --git a/ismism.ts/ui/bind/article.ts b/ismism.ts/ui/bind/article.ts index 1956b81..c49a5fc 100644 --- a/ismism.ts/ui/bind/article.ts +++ b/ismism.ts/ui/bind/article.ts @@ -1,22 +1,40 @@ // deno-lint-ignore-file no-window-prefix import type { DocC, DocU } from "../../src/db.ts" -import type { Id } from "../../src/eid/typ.ts"; +import type { Id } from "../../src/eid/typ.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 { admsel, id, idanchor } from "./section.ts" -import { bind, main, pas_a, pos, que } from "./template.ts" +import type * as Q from "../../src/pra/que.ts" +import { admsel, idnam, ida, idmeta, pro, label } from "./section.ts" +import { bind, main, pas_a, pos, que, utc_refresh } from "./template.ts" import { not_aut, not_pro } from "../../src/pra/con.ts" +import { not_actid, not_nbr } from "../../src/eid/is.ts" -let hash = "" +export let hash = "" let pas: Pas | null = null +function hashchange( + h: string +): boolean { + if (hash === h || hash === "" && h === "agd") return false + location.href = `#${h}` + return true +} + function pasact( ) { + if (hashchange("pas")) return + + if (pas) pos("pas", { uid: pas.id.uid }) + pas = null + pas_a.innerText = "用户登录" + pas_a.href = "#pas" + + main.innerHTML = "" const t = bind("pasact") + const send = async () => { - if (!/^1\d{10}$/.test(t.nbr.value)) { alert("无效手机号"); return } + if (not_nbr(t.nbr.value)) return alert("无效手机号") t.nbr.readOnly = t.send.disabled = true const sent = await pos("pas", { nbr: t.nbr.value, sms: location.hostname === "ismist.cn" }) if (sent) { @@ -33,9 +51,8 @@ function pasact( t.send.addEventListener("click", send) t.act.addEventListener("click", async () => { - if (!t.actid.checkValidity()) { alert("无效激活码"); return } + if (not_actid(t.actid.value)) return alert("无效激活码") t.actid.readOnly = t.act.disabled = t.adm1.disabled = t.adm2.disabled = true - const uid = await pos("pre", { actid: t.actid.value, nbr: t.nbr.value, adm1: t.adm1.value, adm2: t.adm2.value }) if (uid) { await send() @@ -46,91 +63,66 @@ function pasact( } }) - t.issue.addEventListener("click", () => { - if (!t.code.checkValidity()) { alert("无效验证码"); return } + t.issue.addEventListener("click", async () => { + if (!t.code.checkValidity()) return alert("无效验证码") t.code.readOnly = t.issue.disabled = true - pos("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}` - }) + const p = await pos("pas", { nbr: t.nbr.value, code: parseInt(t.code.value) }) + if (!p) { + t.code.readOnly = t.issue.disabled = false + return alert("无效验证码") + } + pas = p + pas_a.innerText = p.nam + pas_a.href = `#${p.id.uid}` + usr(p.id.uid) }) main.append(t.bind) } +export type Usr = Omit, "unam" | "snam"> & { + unam: Map, + snam: Map, +} + async function usr( - uid: NonNullable["_id"], + uid: Usr["_id"], ) { - const d = await que(`usr?uid=${uid}`) - if (!d) { - idnull(`${uid}`, `ismist.cn#${uid} 是无效用户`) - return - } - const u = { ...d, unam: new Map(d.unam) } + if (hashchange(`${uid}`)) return + + const q = await que(`usr?uid=${uid}`) + if (!q) return idnull(`${uid}`, "用户") + const u: Usr = { ...q, unam: new Map(q.unam), snam: new Map(q.snam) } main.innerHTML = "" - const t = bind("usr") - const pub = id(pas, "", u, t) + + const pub = idmeta(pas, t, u) + idnam(t, `${uid}`, pub ? u.nam : "-冻结中-") 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") + ida(t.soc, "s", u.snam) t.rec.innerText = JSON.stringify(u.nrec) - } else { - t.nam.innerText = t.intro.innerText = t.soc.innerText = t.rec.innerText = "【冻结中】" - } + } else 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` - }) + t.pas.addEventListener("click", () => pasact()) if (not_aut(pas.aut, "pre_usr")) t.preusr.remove() else t.preusr.addEventListener("click", () => pre("创建用户")) if (not_aut(pas.aut, "pre_soc")) t.presoc.remove() else t.presoc.addEventListener("click", () => pre("创建社团")) if (not_aut(pas.aut, "pre_agd")) t.preagd.remove() else t.preagd.addEventListener("click", () => pre("创建活动")) + t.pro.remove() } 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.aut, "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("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("pro", { re: "ref", uid, pro: proref }) - if (c && c > 0) setTimeout(() => usr(uid), 500) - else t.proref.disabled = false - }) - } + if (not_aut(pas.aut, "pre_usr") || not_pro(pas) || pas.ref.includes(uid)) + pro(pas, t, u) + else pro(pas, t, u, usr) } } else { t.pos.remove() @@ -141,16 +133,18 @@ async function usr( main.append(t.bind) } -async function soc( - sid?: NonNullable["_id"] -) { - let ss = sid ? [await que(`soc?sid=${sid}`)] : await que("soc") - ss = ss.filter(s => s) +export type Soc = Omit, "unam"> & { + unam: Map, +} - if (sid && ss.length === 0) { - idnull(`s${sid}`, `ismist.cn#s${sid} 是无效社团`) - return - } +async function soc( + sid?: Soc["_id"] +) { + if (hashchange(`s${sid ?? "oc"}`)) return + + let ss = sid ? [await que(`soc?sid=${sid}`)] : await que("soc") + ss = ss.filter(s => s) + if (sid && ss.length === 0) return idnull(`s${sid}`, "社团") main.innerHTML = "" @@ -160,16 +154,18 @@ async function soc( const t = bind("soc") const s = { ...d, unam: new Map(d.unam) } - const pub = id(pas, "s", s, t) + const pub = idmeta(pas, t, s) + idnam(t, `s${s._id}`, s.nam) + + ida(t.sec, "", s.unam, s.sec) + ida(t.uid, "", s.unam, s.uid) + label(t.res, `申请加入(${s.res.length}/${s.res_max}):`) + ida(t.res, "", s.unam, s.res) + if (pub) { - idanchor(s.sec, s.unam, t.sec, "") - idanchor(s.uid, s.unam, t.uid, "") - idanchor(s.res, s.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 = "【冻结中】" - } + } else t.sec.innerText = t.uid.innerText = t.res.innerText = t.intro.innerText = t.rec.innerText = "-冻结中-" if (pas) { if (not_aut(pas.aut, "pre_soc")) t.putpre.remove() @@ -181,11 +177,8 @@ async function soc( if (confirm("退出社团?")) { t.putuid.disabled = true const r = await pos("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 (r && r > 0) soc(s._id) + else t.putuid.disabled = false } }) if (s.uid.includes(pas.id.uid)) t.putres.remove() @@ -195,41 +188,12 @@ async function soc( if (!res || pub && s.res.length < s.res_max) t.putres.addEventListener("click", async () => { t.putres.disabled = true const r = await pos("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 + if (r && r > 0) soc(s._id) + else t.putuid.disabled = false }); else t.putres.disabled = true } - if (not_aut(pas.aut, "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("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("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 - }) - } - } + if (not_aut(pas.aut, "pre_soc") || not_pro(pas)) pro(pas, t, s) + else pro(pas, t, s, soc) } else { t.put.remove() t.pro.remove() @@ -239,51 +203,70 @@ async function soc( } } +export type Agd = Omit, "unam"> & { + unam: Map, +} + +async function agd( + aid?: Agd["_id"] +) { + if (hashchange(`a${aid ?? "gd"}`)) return + + let aa = aid ? [await que(`agd?aid=${aid}`)] : await que("agd") + aa = aa.filter(a => a) + if (aid && aa.length === 0) return idnull(`a${aid}`, "活动") + + main.innerText = JSON.stringify(aa, null, 2) +} + function pre( nam: "创建用户" | "创建社团" | "创建活动" ) { - if (!pas) { location.href = `#pasact`; return } + if (!pas || hashchange("pas")) return main.innerHTML = "" - const t = bind("pre") - t.idnam.href = `#${pas.id.uid}` - t.id.innerText = `${pas.id.uid}` - t.nam.innerText = nam + idnam(t, `${pas.id.uid}`, nam) t.meta.innerText = `将作为推荐人${nam}` - admsel(t) - let param: () => Record - 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", () => { + label(t.intro, `简介:(${t.intro.value.length}/2048 个字符)`) t.intro.style.height = "auto" - t.intro.style.height = `${t.intro.scrollHeight}px`; - (t.intro.previousElementSibling as HTMLElement).innerText = `简介:(${t.intro.value.length} / 2048 个字符)` + t.intro.style.height = `${t.intro.scrollHeight}px` }) - 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 } } - + const { p, f } = { + p: () => ({ + nbr: t.nbr.value, adm1: t.adm1.value, adm2: t.adm2.value + }), f: usr, + ...nam === "创建社团" ? { + p: () => ({ + snam: t.pnam.value, adm1: t.adm1.value, adm2: t.adm2.value, intro: t.intro.value + }), f: soc, + } : {}, + ...nam === "创建活动" ? { + p: () => ({ + anam: t.pnam.value, adm1: t.adm1.value, adm2: t.adm2.value, intro: t.intro.value + }), f: agd, + } : {}, + } t.pre.addEventListener("click", async () => { - const id = await pos>("pre", param()) - if (id) setTimeout(() => location.hash = `#${pf}${id}`, 500) - else alert(`${nam}失败`) + t.pre.disabled = true + const id = await pos>("pre", p()) + if (id) setTimeout(() => f(id), utc_refresh) + else { alert("无效输入\n或名称已被占用"); t.pre.disabled = false } }) t.cancel.addEventListener("click", () => usr(pas!.id.uid)) @@ -291,10 +274,9 @@ function pre( } function putusr( - u: Omit, "unam"> & { unam: Map }, + u: Usr ) { main.innerHTML = "" - const t = bind("putusr") t.idnam.href = `#${u._id}` @@ -305,9 +287,9 @@ function putusr( t.intro.value = u.intro t.intro.addEventListener("input", () => { + label(t.intro, `简介:(${t.intro.value.length}/2048 个字符)`) t.intro.style.height = "auto" - t.intro.style.height = `${t.intro.scrollHeight}px`; - (t.intro.previousElementSibling as HTMLElement).innerText = `简介:(${t.intro.value.length} / 2048 个字符)` + t.intro.style.height = `${t.intro.scrollHeight}px` }) setTimeout(() => t.intro.dispatchEvent(new Event("input")), 50) @@ -320,12 +302,12 @@ function putusr( adm2: t.adm2.value, intro: t.intro.value.trim(), }) - if (c === null || c === 0) { - alert(`无效输入\n或用户名已被占用`) + if (c === null) { + alert("无效输入\n或名称已被占用") t.put.disabled = false } else { pas_a.innerText = t.nam.value - setTimeout(() => usr(u._id), 500) + setTimeout(() => usr(u._id), utc_refresh) } }) t.cancel.addEventListener("click", () => usr(u._id)) @@ -335,25 +317,26 @@ function putusr( function idnull( id: string, - meta: string, + nam: string, ) { main.innerHTML = "" const t = bind("idnull") t.id.innerText = id - t.meta.innerText = meta + t.meta.innerText = `ismist#${id} 是无效${nam}` main.append(t.bind) } window.addEventListener("hashchange", () => { - hash = decodeURI(window.location.hash).substring(1) - main.innerHTML = "" + hash = decodeURI(location.hash).substring(1) 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)) + else if (hash === "" || hash === "agd") agd() + else if (/^a\d+$/.test(hash)) agd(parseInt(hash.substring(1))) + else idnull(hash, "链接") }) export async function load( diff --git a/ismism.ts/ui/bind/section.ts b/ismism.ts/ui/bind/section.ts index 04e6869..0eb086e 100644 --- a/ismism.ts/ui/bind/section.ts +++ b/ismism.ts/ui/bind/section.ts @@ -1,10 +1,19 @@ -import type { Id } from "../../src/eid/typ.ts" +import type { Aut, Id } from "../../src/eid/typ.ts" import type { Pas } from "../../src/pra/pas.ts" -import type { Usr, Soc } from "../../src/pra/que.ts" import { utc_medium } from "../../src/ont/utc.ts" import { adm } from "../../src/ont/adm.ts" -import { Template } from "./template.ts" +import { pos, Template, utc_refresh } from "./template.ts" import { is_aut } from "../../src/pra/con.ts" +import { Usr, Soc, hash } from "./article.ts" +import type { DocU } from "../../src/db.ts" + +export function label( + el: HTMLElement, + s: string, +) { + const l = el.previousElementSibling as HTMLLabelElement + l.innerText = s +} function selopt( sel: HTMLSelectElement, @@ -30,63 +39,83 @@ export function admsel( t.adm1.addEventListener("change", () => selopt(t.adm2, adm.get(t.adm1.value)!)) } -export function idanchor( - id: number[], - idnam: Map, - el: HTMLElement, +export function ida( + t: HTMLElement, pf: "" | "s" | "a", + nam: Map, + id?: number[], ) { - if (id.length === 0) { el.innerText = "无"; return } + if (!id) id = [...nam.keys()] + if (id.length === 0) return id.forEach(id => { - const a = el.appendChild(document.createElement("a")) + const a = t.appendChild(document.createElement("a")) a.href = `#${pf}${id}` - a.innerText = idnam.get(id) ?? `${id}` + a.innerText = nam.get(id) ?? `${pf}${id}` }) } -function idmeta( +export function idmeta( pas: Pas | null, - id: Omit, "unam"> & { unam: Map }, t: Template["usr" | "soc"], + id: Usr | Soc, ): 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.aut, "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") + const [rej, ref] = [id.rej.length >= 2, id.ref.length < 2] + const re: "rej" | "ref" | null = rej ? "rej" : ref ? "ref" : null + const { aut }: { aut?: Aut["aut"][0] } = { + ...t.tid === "usr" ? { aut: "pre_usr" } : {}, + ...t.tid === "soc" ? { aut: "pre_soc" } : {}, + } + const pub: boolean = re === null || (pas !== null && aut !== undefined && is_aut(pas.aut, aut)) t.adm.innerText = `${id.adm1} ${id.adm2}` t.utc.innerText = `${utc_medium(id.utc)}` - idanchor(id.rej, id.unam, t.rej, "") - idanchor(id.ref, id.unam, t.ref, "") + ida(t.rej, "", id.unam, id.rej) + ida(t.ref, "", id.unam, id.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") + if (rej) [t.rej, t.rejc].forEach(el => el.classList.add("red")) + if (ref) [t.ref, t.refc].forEach(el => el.classList.add("green")) + if (re === "rej") [t.id, t.proc].forEach(el => el.classList.add("red")) + else if (re === "ref") [t.id, t.proc].forEach(el => el.classList.add("green")) return pub } -export function id( - pas: Pas | null, - ph: "" | "s", - id: Omit, "unam"> & { unam: Map }, - t: Template["usr" | "soc"], -): boolean { - t.idnam.href = `#${ph}${id._id}` - t.id.innerText = `${ph}${id._id}` - t.nam.innerText = id.nam - return idmeta(pas, id, t) || ph === "" && pas !== null && pas.id.uid === id._id +export function idnam( + t: Template["usr" | "soc" | "pre"], + id: string, + nam?: string, +) { + t.idnam.href = `#${id}` + t.id.innerText = id + if (hash === id) t.id.classList.add("active") + if (nam) t.nam.innerText = nam +} + +export function pro( + pas: Pas, + t: Template["usr" | "soc"], + id: Usr | Soc, + re?: (r: Id["_id"]) => void, +) { + const [prorej, proref] = [!id.rej.includes(pas.id.uid), !id.ref.includes(pas.id.uid)] + t.prorej.innerText = prorej ? "反对" : "取消反对" + t.proref.innerText = proref ? "推荐" : "取消推荐" + if (re) { + const pid = { + ...t.tid === "usr" ? { uid: id._id } : {}, + ...t.tid === "soc" ? { sid: id._id } : {}, + } + t.prorej.addEventListener("click", async () => { + t.prorej.disabled = true + const c = await pos("pro", { re: "rej", ...pid, pro: prorej }) + if (c && c > 0) setTimeout(() => re(id._id), utc_refresh) + else t.prorej.disabled = false + }) + t.proref.addEventListener("click", async () => { + t.proref.disabled = true + const c = await pos("pro", { re: "ref", ...pid, pro: proref }) + if (c && c > 0) setTimeout(() => re(id._id), utc_refresh) + else t.proref.disabled = false + }) + } else t.prorej.disabled = t.proref.disabled = true } diff --git a/ismism.ts/ui/bind/template.ts b/ismism.ts/ui/bind/template.ts index feba281..6324db0 100644 --- a/ismism.ts/ui/bind/template.ts +++ b/ismism.ts/ui/bind/template.ts @@ -1,14 +1,15 @@ export async function que( q: string ) { - const res = await fetch(`/q/${q}`) - const etag = res.headers.get("etag")?.substring(3) + const r = await fetch(`/q/${q}`) + const etag = r.headers.get("etag")?.substring(3) if (etag) utc_etag = parseInt(etag) - return res.json() as T + return r.json() as T } export async function pos( - f: string, b: Record + f: string, + b: Record, ) { const res = await fetch(`/p/${f}`, { method: "POST", @@ -17,14 +18,16 @@ export async function pos( return res.json() as T } +export const utc_refresh = 500 export let utc_etag = Date.now() -export const main = document.getElementById("main")! +export const main = document.getElementById("main")! as HTMLDivElement export const pas_a = document.getElementById("pas")! as HTMLAnchorElement const t: typeof document.createElement = (s: string) => document.createElement(s) const template = { pasact: { + tid: "pasact" as const, nbr: t("input"), send: t("button"), adm: t("section"), adm1: t("select"), adm2: t("select"), pre: t("section"), actid: t("input"), act: t("button"), @@ -33,6 +36,7 @@ const template = { }, usr: { + tid: "usr" as const, idnam: t("a"), id: t("code"), nam: t("span"), adm: t("span"), utc: t("span"), rej: t("span"), ref: t("span"), @@ -46,6 +50,7 @@ const template = { }, soc: { + tid: "soc" as const, idnam: t("a"), id: t("code"), nam: t("span"), adm: t("span"), utc: t("span"), rej: t("span"), ref: t("span"), @@ -56,6 +61,7 @@ const template = { }, pre: { + tid: "pre" as const, idnam: t("a"), id: t("code"), nam: t("span"), meta: t("section"), pnam: t("input"), nbr: t("input"), adm: t("section"), adm1: t("select"), adm2: t("select"), @@ -64,6 +70,7 @@ const template = { }, putusr: { + tid: "putusr" as const, idnam: t("a"), id: t("code"), nam: t("input"), adm1: t("select"), adm2: t("select"), @@ -72,6 +79,7 @@ const template = { }, idnull: { + tid: "idnull" as const, id: t("cod"), meta: t("section") } @@ -90,5 +98,6 @@ export function bind< c, t.querySelector(`.${c}`) as HTMLElement ]) ]) as Template[T] & { bind: DocumentFragment } + b.tid = tid return b } diff --git a/ismism.ts/ui/index/style.css b/ismism.ts/ui/index/style.css index e79c580..03e3fbb 100644 --- a/ismism.ts/ui/index/style.css +++ b/ismism.ts/ui/index/style.css @@ -155,6 +155,12 @@ section>p { padding: 1ch; } +section>span:empty::before, +section>p:empty::before { + color: var(--gray); + content: "无"; +} + section>textarea { box-sizing: border-box; display: block; @@ -180,6 +186,20 @@ section>select { padding: 0.2ch 1ch; } +section>span>a:not(:first-child):before, +section>p>a:not(:first-child):before { + content: "、"; + color: initial; +} + +@media (hover: hover) { + + section>span>a:hover, + section>p>a:hover { + color: var(--amber); + } +} + div#main { max-width: 1080px; margin: 0 auto; @@ -279,25 +299,19 @@ section.meta>span { color: var(--black) } -section.meta>span.red { - color: var(--red); -} - -section.meta>span.green { - color: var(--green); -} - -section.meta>span.gray { +section.meta>span.rejc, +section.meta>span.refc, +section.meta>span.proc { color: var(--gray); } -section.meta>span>a:hover { - color: var(--amber); +section.meta>span.red { + color: var(--red) !important; } -section.meta>span>a:not(:first-child):before { - content: "、"; - color: initial; +section.meta>span.green, +section.meta>span.green:empty::before { + color: var(--green) !important; } a.idnam { @@ -318,10 +332,12 @@ a.idnam>code.id.active { } a.idnam>code.id.red { + border: none; background: var(--red) !important; } a.idnam>code.id.green { + border: none; background: var(--green) !important; } diff --git a/ismism.ts/ui/index/template.html b/ismism.ts/ui/index/template.html index a60542d..9f29ac8 100644 --- a/ismism.ts/ui/index/template.html +++ b/ismism.ts/ui/index/template.html @@ -1,7 +1,7 @@