init
This commit is contained in:
154
announcements/desktop/DesktopAnnouncementsFeed.js
Normal file
154
announcements/desktop/DesktopAnnouncementsFeed.js
Normal file
@@ -0,0 +1,154 @@
|
||||
class DesktopAnnouncementsFeed extends Shadow {
|
||||
constructor(announcements, selectedId, searchText, onSelect, onSearch) {
|
||||
super()
|
||||
this.announcements = announcements
|
||||
this.selectedId = selectedId
|
||||
this.searchText = searchText
|
||||
this.onSelect = onSelect
|
||||
this.onSearch = onSearch
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
// Search bar
|
||||
HStack(() => {
|
||||
p("🔍")
|
||||
.margin(0).fontSize(0.78, em).opacity(0.38).flexShrink(0)
|
||||
input()
|
||||
.attr({ type: "text", placeholder: "Search announcements…", value: this.searchText })
|
||||
.flex(1).border("none").outline("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(0.85, em)
|
||||
.onInput((e) => this.onSearch(e.target.value))
|
||||
})
|
||||
.gap(0.5, em).paddingHorizontal(0.85, em).paddingVertical(0.58, em)
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.borderRadius(0.5, em).alignItems("center")
|
||||
.marginHorizontal(0.75, em).marginTop(0.75, em).marginBottom(0.5, em)
|
||||
.flexShrink(0)
|
||||
|
||||
// Count
|
||||
p(`${this.announcements.length} announcement${this.announcements.length !== 1 ? "s" : ""}`)
|
||||
.margin(0).paddingHorizontal(1.1, em).paddingBottom(0.35, em)
|
||||
.fontSize(0.68, em).fontWeight("700").letterSpacing("0.05em")
|
||||
.color("var(--headertext)").opacity(0.32)
|
||||
.flexShrink(0)
|
||||
|
||||
// List
|
||||
VStack(() => {
|
||||
if (this.announcements.length === 0) {
|
||||
VStack(() => {
|
||||
p(this.searchText ? "No results" : "No announcements yet")
|
||||
.margin(0).fontSize(0.85, em)
|
||||
.color("var(--headertext)").opacity(0.32).textAlign("center")
|
||||
})
|
||||
.flex(1).justifyContent("center").alignItems("center").paddingTop(3, em)
|
||||
} else {
|
||||
this.announcements.forEach(ann => this.renderRow(ann))
|
||||
}
|
||||
})
|
||||
.flex(1).overflowY("auto").gap(0).paddingBottom(0.75, em)
|
||||
})
|
||||
.height(100, pct).width(100, pct).boxSizing("border-box")
|
||||
}
|
||||
|
||||
renderRow(ann) {
|
||||
const isSelected = ann.id === this.selectedId
|
||||
const isEdited = ann.created !== ann.updated_at
|
||||
const isMe = ann.creator_id === global.profile.id
|
||||
const author = this.getAuthor(ann.creator_id)
|
||||
const authorName = isMe ? "You" : author
|
||||
const initials = this.getInitials(ann.creator_id)
|
||||
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
// Avatar
|
||||
VStack(() => {
|
||||
p(initials)
|
||||
.margin(0).fontSize(0.6, em).fontWeight("700")
|
||||
.color("white").lineHeight("1")
|
||||
})
|
||||
.width(2.1, em).height(2.1, em).borderRadius(50, pct)
|
||||
.background(this.avatarColor(author))
|
||||
.justifyContent("center").alignItems("center").flexShrink(0)
|
||||
|
||||
VStack(() => {
|
||||
// Author + date
|
||||
HStack(() => {
|
||||
p(authorName)
|
||||
.margin(0).fontSize(0.8, em).fontWeight("600")
|
||||
.color(isMe ? "var(--quillred)" : "var(--headertext)")
|
||||
.flex(1).minWidth(0)
|
||||
.overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
p(this.relativeDate(ann.created))
|
||||
.margin(0).fontSize(0.68, em)
|
||||
.color("var(--headertext)").opacity(0.38).flexShrink(0)
|
||||
})
|
||||
.alignItems("center").gap(0.4, em)
|
||||
|
||||
// Preview text
|
||||
p(ann.text)
|
||||
.margin(0).marginTop(0.12, em).fontSize(0.78, em)
|
||||
.color("var(--headertext)")
|
||||
.opacity(isSelected ? 0.75 : 0.45)
|
||||
.overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
.lineHeight("1.4")
|
||||
})
|
||||
.flex(1).minWidth(0)
|
||||
})
|
||||
.gap(0.62, em).alignItems("flex-start")
|
||||
|
||||
if (isEdited) {
|
||||
p("edited")
|
||||
.margin(0).marginTop(0.3, em)
|
||||
.fontSize(0.62, em).fontStyle("italic")
|
||||
.color("var(--headertext)").opacity(0.28)
|
||||
.alignSelf("flex-end")
|
||||
}
|
||||
})
|
||||
.paddingHorizontal(0.85, em).paddingVertical(0.7, em)
|
||||
.marginHorizontal(0.4, em)
|
||||
.borderRadius(0.55, em)
|
||||
.background(isSelected ? "var(--accent)" : "transparent")
|
||||
.cursor("pointer")
|
||||
.width("calc(100% - 0.8em)").boxSizing("border-box")
|
||||
.onClick((done) => {
|
||||
if(done) this.onSelect(ann.id)
|
||||
})
|
||||
}
|
||||
|
||||
getAuthor(creatorId) {
|
||||
const members = global.currentNetwork.data?.members || []
|
||||
const m = members.find(m => m.id === creatorId)
|
||||
return m ? `${m.first_name} ${m.last_name}` : "Unknown"
|
||||
}
|
||||
|
||||
getInitials(creatorId) {
|
||||
const members = global.currentNetwork.data?.members || []
|
||||
const m = members.find(m => m.id === creatorId)
|
||||
if (!m) return "?"
|
||||
return [m.first_name?.[0], m.last_name?.[0]].filter(Boolean).join("").toUpperCase()
|
||||
}
|
||||
|
||||
relativeDate(raw) {
|
||||
const d = new Date(raw)
|
||||
const diff = Date.now() - d
|
||||
const mins = Math.floor(diff / 60000)
|
||||
const hours = Math.floor(diff / 3600000)
|
||||
const days = Math.floor(diff / 86400000)
|
||||
if (mins < 1) return "just now"
|
||||
if (mins < 60) return `${mins}m ago`
|
||||
if (hours < 24) return `${hours}h ago`
|
||||
if (days === 1) return "yesterday"
|
||||
if (days < 7) return `${days}d ago`
|
||||
return d.toLocaleDateString([], { month: "short", day: "numeric" })
|
||||
}
|
||||
|
||||
avatarColor(name) {
|
||||
const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"]
|
||||
let hash = 0
|
||||
for (let i = 0; i < (name || "").length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
||||
return colors[Math.abs(hash) % colors.length]
|
||||
}
|
||||
}
|
||||
|
||||
register(DesktopAnnouncementsFeed)
|
||||
Reference in New Issue
Block a user