Popover displaying clickable usernames in voting value
This commit is contained in:
parent
75e0ec90dd
commit
37b8d96a78
7 changed files with 227 additions and 172 deletions
|
@ -1,7 +1,8 @@
|
||||||
{%- for vote in votes -%}
|
{% from 'macros/forum-username.html' import forum_username with context -%}
|
||||||
|
{% for vote in votes -%}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3 text-nowrap text-start">{{ loop.revindex|localize }}.</div>
|
<div class="col-3 text-nowrap text-start">{{ loop.revindex|localize }}.</div>
|
||||||
<div class="col-6 text-nowrap text-start">{{ vote.user.username }}</div>
|
<div class="col-6 text-nowrap text-start">{{ forum_username(user=vote.user) }}</div>
|
||||||
<div class="col-3 text-nowrap text-end {% if vote.value > 0 -%}
|
<div class="col-3 text-nowrap text-end {% if vote.value > 0 -%}
|
||||||
text-success
|
text-success
|
||||||
{%- elif vote.value < 0 -%}
|
{%- elif vote.value < 0 -%}
|
||||||
|
|
|
@ -14,7 +14,8 @@ class VotingValueDetailsView(TemplateView):
|
||||||
obj = super().get_context_data()
|
obj = super().get_context_data()
|
||||||
votes = CommentVote.objects.filter(comment=comment_pk).order_by('-pk')
|
votes = CommentVote.objects.filter(comment=comment_pk).order_by('-pk')
|
||||||
user_pks = set(x.user_id for x in votes)
|
user_pks = set(x.user_id for x in votes)
|
||||||
users = User.objects.only('username').in_bulk(id_list=user_pks)
|
users = User.objects.only(
|
||||||
|
*User.F_USERNAME_DISPLAYED).in_bulk(id_list=user_pks)
|
||||||
for vote in votes:
|
for vote in votes:
|
||||||
vote.user = users[vote.user_id]
|
vote.user = users[vote.user_id]
|
||||||
obj.update(votes=votes)
|
obj.update(votes=votes)
|
||||||
|
|
|
@ -88,10 +88,60 @@ export interface TopicBoundFunctionsType {
|
||||||
getALink: (scrollToPk: number) => string
|
getALink: (scrollToPk: number) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let urlTemplates: TopicCommentListingUrltemplateType
|
||||||
|
|
||||||
|
function initializeUrls(options: TopicCommentListingOptionsType): void {
|
||||||
|
const urls = options.urls
|
||||||
|
urlTemplates = {
|
||||||
|
commentListing: template(
|
||||||
|
urls.commentListing.backend
|
||||||
|
.replace(urls.commentListing.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.commentListing.commentPk, '{commentPk}')
|
||||||
|
),
|
||||||
|
expandCommentsDown: template(
|
||||||
|
urls.expandCommentsDown.backend
|
||||||
|
.replace(urls.expandCommentsDown.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.expandCommentsDown.commentPk, '{commentPk}')
|
||||||
|
.replace(urls.expandCommentsDown.scrollToPk, '{scrollToPk}')
|
||||||
|
),
|
||||||
|
expandCommentsUp: template(
|
||||||
|
urls.expandCommentsUp.backend
|
||||||
|
.replace(urls.expandCommentsUp.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.expandCommentsUp.commentPk, '{commentPk}')
|
||||||
|
.replace(urls.expandCommentsUp.scrollToPk, '{scrollToPk}')
|
||||||
|
),
|
||||||
|
expandCommentsUpRecursive: template(
|
||||||
|
urls.expandCommentsUpRecursive.backend
|
||||||
|
.replace(urls.expandCommentsUpRecursive.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.expandCommentsUpRecursive.commentPk, '{commentPk}')
|
||||||
|
.replace(urls.expandCommentsUpRecursive.scrollToPk, '{scrollToPk}')
|
||||||
|
),
|
||||||
|
expandCommentsEntireThread: template(
|
||||||
|
urls.expandCommentsEntireThread.backend
|
||||||
|
.replace(urls.expandCommentsEntireThread.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.expandCommentsEntireThread.commentPk, '{commentPk}')
|
||||||
|
.replace(urls.expandCommentsEntireThread.scrollToPk, '{scrollToPk}')
|
||||||
|
),
|
||||||
|
getTopicSlugByPk: template(
|
||||||
|
urls.getTopicSlugByPk.backend
|
||||||
|
.replace(urls.getTopicSlugByPk.commentPk, '{commentPk}')
|
||||||
|
),
|
||||||
|
getVotingValueDetails: template(
|
||||||
|
urls.getVotingValueDetails.backend
|
||||||
|
.replace(urls.getVotingValueDetails.commentPk, '{commentPk}')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (urls.commentListingPageNo) {
|
||||||
|
urlTemplates.commentListingPageNo = template(
|
||||||
|
urls.commentListingPageNo.backend
|
||||||
|
.replace(urls.commentListingPageNo.exampleSlug, '{topicSlug}')
|
||||||
|
.replace(urls.commentListingPageNo.pageId, '{pageId}')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TopicCommentListing {
|
class TopicCommentListing {
|
||||||
private readonly options: TopicCommentListingOptionsType
|
private readonly options: TopicCommentListingOptionsType
|
||||||
private urlTemplates!: TopicCommentListingUrltemplateType
|
|
||||||
|
|
||||||
private boundFunctions!: TopicBoundFunctionsType
|
private boundFunctions!: TopicBoundFunctionsType
|
||||||
private root!: HTMLElement
|
private root!: HTMLElement
|
||||||
private readonly comments = new Map<number, OneComment>()
|
private readonly comments = new Map<number, OneComment>()
|
||||||
|
@ -170,29 +220,29 @@ class TopicCommentListing {
|
||||||
private getALink(scrollToPk: number): string {
|
private getALink(scrollToPk: number): string {
|
||||||
switch (this.options.listingMode) {
|
switch (this.options.listingMode) {
|
||||||
case 'commentListing':
|
case 'commentListing':
|
||||||
return this.urlTemplates.commentListing({
|
return urlTemplates.commentListing({
|
||||||
topicSlug: this.options.topicSlugOriginal, commentPk: scrollToPk
|
topicSlug: this.options.topicSlugOriginal, commentPk: scrollToPk
|
||||||
})
|
})
|
||||||
case 'expandCommentsDown':
|
case 'expandCommentsDown':
|
||||||
return this.urlTemplates.expandCommentsDown({
|
return urlTemplates.expandCommentsDown({
|
||||||
topicSlug: this.options.topicSlugOriginal,
|
topicSlug: this.options.topicSlugOriginal,
|
||||||
commentPk: this.options.commentPk,
|
commentPk: this.options.commentPk,
|
||||||
scrollToPk
|
scrollToPk
|
||||||
})
|
})
|
||||||
case 'expandCommentsUp':
|
case 'expandCommentsUp':
|
||||||
return this.urlTemplates.expandCommentsUp({
|
return urlTemplates.expandCommentsUp({
|
||||||
topicSlug: this.options.topicSlugOriginal,
|
topicSlug: this.options.topicSlugOriginal,
|
||||||
commentPk: this.options.commentPk,
|
commentPk: this.options.commentPk,
|
||||||
scrollToPk
|
scrollToPk
|
||||||
})
|
})
|
||||||
case 'expandCommentsUpRecursive':
|
case 'expandCommentsUpRecursive':
|
||||||
return this.urlTemplates.expandCommentsUpRecursive({
|
return urlTemplates.expandCommentsUpRecursive({
|
||||||
topicSlug: this.options.topicSlugOriginal,
|
topicSlug: this.options.topicSlugOriginal,
|
||||||
commentPk: this.options.commentPk,
|
commentPk: this.options.commentPk,
|
||||||
scrollToPk
|
scrollToPk
|
||||||
})
|
})
|
||||||
case 'expandCommentsEntireThread':
|
case 'expandCommentsEntireThread':
|
||||||
return this.urlTemplates.expandCommentsEntireThread({
|
return urlTemplates.expandCommentsEntireThread({
|
||||||
topicSlug: this.options.topicSlugOriginal,
|
topicSlug: this.options.topicSlugOriginal,
|
||||||
commentPk: this.options.commentPk,
|
commentPk: this.options.commentPk,
|
||||||
scrollToPk
|
scrollToPk
|
||||||
|
@ -206,64 +256,13 @@ class TopicCommentListing {
|
||||||
history.replaceState(null, '', path)
|
history.replaceState(null, '', path)
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeUrls(): void {
|
|
||||||
const urls = this.options.urls
|
|
||||||
this.urlTemplates = {
|
|
||||||
commentListing: template(
|
|
||||||
urls.commentListing.backend
|
|
||||||
.replace(urls.commentListing.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.commentListing.commentPk, '{commentPk}')
|
|
||||||
),
|
|
||||||
expandCommentsDown: template(
|
|
||||||
urls.expandCommentsDown.backend
|
|
||||||
.replace(urls.expandCommentsDown.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.expandCommentsDown.commentPk, '{commentPk}')
|
|
||||||
.replace(urls.expandCommentsDown.scrollToPk, '{scrollToPk}')
|
|
||||||
),
|
|
||||||
expandCommentsUp: template(
|
|
||||||
urls.expandCommentsUp.backend
|
|
||||||
.replace(urls.expandCommentsUp.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.expandCommentsUp.commentPk, '{commentPk}')
|
|
||||||
.replace(urls.expandCommentsUp.scrollToPk, '{scrollToPk}')
|
|
||||||
),
|
|
||||||
expandCommentsUpRecursive: template(
|
|
||||||
urls.expandCommentsUpRecursive.backend
|
|
||||||
.replace(urls.expandCommentsUpRecursive.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.expandCommentsUpRecursive.commentPk, '{commentPk}')
|
|
||||||
.replace(urls.expandCommentsUpRecursive.scrollToPk, '{scrollToPk}')
|
|
||||||
),
|
|
||||||
expandCommentsEntireThread: template(
|
|
||||||
urls.expandCommentsEntireThread.backend
|
|
||||||
.replace(urls.expandCommentsEntireThread.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.expandCommentsEntireThread.commentPk, '{commentPk}')
|
|
||||||
.replace(urls.expandCommentsEntireThread.scrollToPk, '{scrollToPk}')
|
|
||||||
),
|
|
||||||
getTopicSlugByPk: template(
|
|
||||||
urls.getTopicSlugByPk.backend
|
|
||||||
.replace(urls.getTopicSlugByPk.commentPk, '{commentPk}')
|
|
||||||
),
|
|
||||||
getVotingValueDetails: template(
|
|
||||||
urls.getVotingValueDetails.backend
|
|
||||||
.replace(urls.getVotingValueDetails.commentPk, '{commentPk}')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (urls.commentListingPageNo) {
|
|
||||||
this.urlTemplates.commentListingPageNo = template(
|
|
||||||
urls.commentListingPageNo.backend
|
|
||||||
.replace(urls.commentListingPageNo.exampleSlug, '{topicSlug}')
|
|
||||||
.replace(urls.commentListingPageNo.pageId, '{pageId}')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeOneComment(rootEl: HTMLElement): void {
|
private initializeOneComment(rootEl: HTMLElement): void {
|
||||||
const commentPk = parseInt(<string>rootEl.dataset.forumCommentPk)
|
const commentPk = parseInt(<string>rootEl.dataset.forumCommentPk)
|
||||||
this.comments.set(commentPk, new OneComment({
|
this.comments.set(commentPk, new OneComment({
|
||||||
root: rootEl,
|
root: rootEl,
|
||||||
commentPk,
|
commentPk,
|
||||||
boundFunctions: this.boundFunctions,
|
boundFunctions: this.boundFunctions,
|
||||||
strings: this.options.strings,
|
strings: this.options.strings
|
||||||
urlTemplates: this.urlTemplates
|
|
||||||
}))
|
}))
|
||||||
addOnremoveCallback(rootEl, this.teardownOneComment.bind(this))
|
addOnremoveCallback(rootEl, this.teardownOneComment.bind(this))
|
||||||
}
|
}
|
||||||
|
@ -298,11 +297,11 @@ class TopicCommentListing {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onPaginate(pageNo: number): Promise<void> {
|
private async onPaginate(pageNo: number): Promise<void> {
|
||||||
if (!this.urlTemplates.commentListingPageNo) {
|
if (!urlTemplates.commentListingPageNo) {
|
||||||
console.error('this.urlTemplates.commentListingPageNo is unset')
|
console.error('urlTemplates.commentListingPageNo is unset')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
document.location.href = this.urlTemplates.commentListingPageNo({
|
document.location.href = urlTemplates.commentListingPageNo({
|
||||||
topicSlug: this.options.topicSlugOriginal,
|
topicSlug: this.options.topicSlugOriginal,
|
||||||
pageId: pageNo
|
pageId: pageNo
|
||||||
})
|
})
|
||||||
|
@ -347,7 +346,6 @@ class TopicCommentListing {
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(): void {
|
initialize(): void {
|
||||||
this.initializeUrls()
|
|
||||||
this.initializeBoundFunctions()
|
this.initializeBoundFunctions()
|
||||||
this.initializeWrappers()
|
this.initializeWrappers()
|
||||||
this.initializePaginators()
|
this.initializePaginators()
|
||||||
|
@ -358,6 +356,7 @@ class TopicCommentListing {
|
||||||
|
|
||||||
export function init(options: TopicCommentListingOptionsType): void {
|
export function init(options: TopicCommentListingOptionsType): void {
|
||||||
oneCommentInit()
|
oneCommentInit()
|
||||||
|
initializeUrls(options)
|
||||||
const obj = new TopicCommentListing(options)
|
const obj = new TopicCommentListing(options)
|
||||||
obj.initialize()
|
obj.initialize()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +1,129 @@
|
||||||
|
import { getSvgIcon, loaderTemplate } from './common'
|
||||||
import {
|
import {
|
||||||
type TopicCommentListingUrltemplateType
|
type ForumPopoverEl,
|
||||||
} from '../topic-comment-listing'
|
type ForumPopoverTipEl,
|
||||||
import { loaderTemplate } from './common'
|
add as newPopoverAdd,
|
||||||
import {
|
teardown as newPopoverTeardown
|
||||||
add as popoverAdd, getFunctions, type GetFunctionsReturnType
|
} from './new-popover.ts'
|
||||||
} from './popover.ts'
|
import { urlTemplates } from '../topic-comment-listing'
|
||||||
import { add as newPopoverAdd } from './new-popover.ts'
|
import { mdiLinkOff } from '@mdi/js'
|
||||||
|
import { add as usernameAdd } from './username.ts'
|
||||||
|
|
||||||
export class VotingValue {
|
const errorIcon = getSvgIcon(mdiLinkOff)
|
||||||
private readonly rootEl: HTMLAnchorElement
|
|
||||||
private readonly commentPk: number
|
|
||||||
private readonly urlTemplates: TopicCommentListingUrltemplateType
|
|
||||||
private cachedContent?: string | Element
|
|
||||||
private popoverFunctions!: GetFunctionsReturnType
|
|
||||||
|
|
||||||
constructor (
|
interface StoredObjType {
|
||||||
rootEl: HTMLAnchorElement, commentPk: number,
|
commentPk: number
|
||||||
urlTemplates: TopicCommentListingUrltemplateType
|
cachedContent?: string | Element
|
||||||
) {
|
}
|
||||||
this.rootEl = rootEl
|
|
||||||
this.commentPk = commentPk
|
|
||||||
this.urlTemplates = urlTemplates
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(): void {
|
const storedObjs = new Map<HTMLAnchorElement, StoredObjType>()
|
||||||
// this.rootEl.setAttribute('title', loaderTemplate.outerHTML)
|
|
||||||
this.rootEl.addEventListener('click', ev => {
|
function onClickElement(e: MouseEvent | PointerEvent): void {
|
||||||
ev.preventDefault()
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickUsernameInTip(ev: MouseEvent | PointerEvent): void {
|
||||||
|
if (!(ev.target instanceof HTMLAnchorElement)) return
|
||||||
|
const elWrapper: ForumPopoverTipEl<HTMLDivElement> | null =
|
||||||
|
ev.target.closest('.voting-value-popover-wrapper')
|
||||||
|
if (!(elWrapper instanceof HTMLDivElement)) return
|
||||||
|
elWrapper.forumPopoverEl.forumPopoverConfig.popoverObj.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTipInserted(el: ForumPopoverEl<HTMLAnchorElement>): void {
|
||||||
|
const elTipBody = el.forumPopoverTip
|
||||||
|
?.getElementsByClassName('popover-body').item(0)
|
||||||
|
if (!elTipBody) return
|
||||||
|
elTipBody.querySelectorAll<HTMLAnchorElement>(
|
||||||
|
'a.forum-username'
|
||||||
|
).forEach((el) => {
|
||||||
|
usernameAdd(el)
|
||||||
|
el.addEventListener(
|
||||||
|
'click', onClickUsernameInTip, { once: true, passive: true })
|
||||||
})
|
})
|
||||||
newPopoverAdd(this.rootEl, {
|
}
|
||||||
callbacks: {
|
|
||||||
onTipInserted: (el): void => {
|
function onTipRemoved(el: ForumPopoverEl<HTMLAnchorElement>): void {
|
||||||
console.debug('onTipInserted', this, arguments)
|
const elTipBody = el.forumPopoverTip
|
||||||
},
|
?.getElementsByClassName('popover-body').item(0)
|
||||||
onTipRemoved: (el): void => {
|
if (!elTipBody) return
|
||||||
console.debug('onTipRemoved', el.forumPopoverTip)
|
elTipBody.querySelectorAll<HTMLAnchorElement>(
|
||||||
}
|
'a.forum-username'
|
||||||
},
|
).forEach((el) => {
|
||||||
popoverOpts: {
|
el.removeEventListener('click', onClickUsernameInTip)
|
||||||
content: (el): string | Element => {
|
})
|
||||||
console.debug('XXX', el)
|
}
|
||||||
if (this.cachedContent == null) {
|
|
||||||
this.cachedContent = <SVGElement>loaderTemplate.cloneNode(true)
|
function displayError(el: ForumPopoverEl<HTMLAnchorElement>): void {
|
||||||
setTimeout(() => {
|
if (!el.forumPopoverTip) return
|
||||||
this.cachedContent = 'asd'
|
|
||||||
if (el.forumPopoverTip) {
|
|
||||||
el.forumPopoverTip
|
el.forumPopoverTip
|
||||||
.getElementsByClassName('popover-body').item(0)
|
.getElementsByClassName('popover-body').item(0)
|
||||||
?.replaceChildren(this.cachedContent)
|
?.replaceChildren(errorIcon.cloneNode(true))
|
||||||
el.forumPopoverConfig.popoverObj.update()
|
el.forumPopoverConfig.popoverObj.update()
|
||||||
} else console.debug('NO TIP')
|
}
|
||||||
}, 3000)
|
|
||||||
|
async function loadVotingContent(
|
||||||
|
el: ForumPopoverEl<HTMLAnchorElement>
|
||||||
|
): Promise<void> {
|
||||||
|
const config = storedObjs.get(el)
|
||||||
|
if (!config) {
|
||||||
|
displayError(el)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return this.cachedContent
|
const url = urlTemplates.getVotingValueDetails({
|
||||||
},
|
commentPk: config.commentPk
|
||||||
|
})
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
displayError(el)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.cachedContent = await response.text()
|
||||||
|
const elTipBody = el.forumPopoverTip
|
||||||
|
?.getElementsByClassName('popover-body').item(0)
|
||||||
|
if (elTipBody instanceof HTMLElement) {
|
||||||
|
elTipBody.innerHTML = config.cachedContent
|
||||||
|
elTipBody.querySelectorAll<HTMLAnchorElement>(
|
||||||
|
'a.forum-username'
|
||||||
|
).forEach((el) => {
|
||||||
|
usernameAdd(el)
|
||||||
|
el.addEventListener(
|
||||||
|
'click', onClickUsernameInTip, { once: true, passive: true })
|
||||||
|
})
|
||||||
|
el.forumPopoverConfig.popoverObj.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContent(el: ForumPopoverEl<HTMLAnchorElement>): string | Element {
|
||||||
|
const storedObj = storedObjs.get(el)
|
||||||
|
if (!storedObj) return <SVGElement>errorIcon.cloneNode(true)
|
||||||
|
if (storedObj.cachedContent == null) {
|
||||||
|
storedObj.cachedContent = <SVGElement>loaderTemplate.cloneNode(true)
|
||||||
|
void loadVotingContent(el).then()
|
||||||
|
}
|
||||||
|
return storedObj.cachedContent
|
||||||
|
}
|
||||||
|
|
||||||
|
export function add(elRoot: HTMLAnchorElement, commentPk: number): void {
|
||||||
|
if (storedObjs.has(elRoot)) {
|
||||||
|
throw new Error(`VotingValue.add: ${elRoot.outerHTML} already initialized`)
|
||||||
|
}
|
||||||
|
storedObjs.set(elRoot, { commentPk })
|
||||||
|
newPopoverAdd(elRoot, {
|
||||||
|
callbacks: { onTipInserted, onTipRemoved },
|
||||||
|
popoverOpts: {
|
||||||
|
content: getContent,
|
||||||
html: true,
|
html: true,
|
||||||
sanitize: false,
|
sanitize: false,
|
||||||
container: <HTMLElement>this.rootEl.parentElement,
|
// title: 'cucc',
|
||||||
title: 'cucc'
|
container: <HTMLElement>elRoot.parentElement,
|
||||||
|
customClass: 'voting-value-popover-wrapper'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// popoverAdd(this.rootEl, {
|
elRoot.addEventListener('click', onClickElement)
|
||||||
// callbacks: {
|
}
|
||||||
// getContent: this.getContent.bind(this),
|
|
||||||
// // getTitle: this.getTitle.bind(this),
|
export function teardown(el: HTMLAnchorElement): void {
|
||||||
// onDomInsertedTip: this.onDomInsertedTip.bind(this),
|
el.removeEventListener('click', onClickElement)
|
||||||
// onDomRemovedTip: this.onDomRemovedTip.bind(this)
|
newPopoverTeardown(el)
|
||||||
// },
|
|
||||||
// html: true,
|
|
||||||
// delay: {
|
|
||||||
// show: 250,
|
|
||||||
// hide: 0
|
|
||||||
// },
|
|
||||||
// showOnClick: true,
|
|
||||||
// sanitize: false,
|
|
||||||
// initialContent: loaderTemplate.outerHTML
|
|
||||||
// })
|
|
||||||
// this.popoverFunctions = getFunctions(this.rootEl)
|
|
||||||
// newPopoverAdd(this.rootEl, {
|
|
||||||
// onTipInserted: (x) => {}
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown(): void { }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import Popover from 'bootstrap/js/src/popover.js'
|
import Popover from 'bootstrap/js/src/popover.js'
|
||||||
import { addOnremoveCallback } from './mutation-observer'
|
import { addOnremoveCallback } from './mutation-observer'
|
||||||
|
|
||||||
type ForumPopoverEl<ElCls extends HTMLElement> = ElCls & {
|
export type ForumPopoverEl<ElCls extends HTMLElement> = ElCls & {
|
||||||
forumPopoverConfig: StoredConfigType<ElCls>
|
forumPopoverConfig: StoredConfigType<ElCls>
|
||||||
forumPopoverTip?: ForumPopoverTipEl<ElCls>
|
forumPopoverTip?: ForumPopoverTipEl<ElCls>
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForumPopoverTipEl<ElCls extends HTMLElement> = ElCls & {
|
export type ForumPopoverTipEl<ElCls extends HTMLElement> = ElCls & {
|
||||||
forumPopoverEl: ForumPopoverEl<ElCls>
|
forumPopoverEl: ForumPopoverEl<ElCls>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,16 +97,16 @@ function onTipInserted<ElCls extends HTMLElement>(
|
||||||
this: ForumPopoverEl<ElCls>
|
this: ForumPopoverEl<ElCls>
|
||||||
): void {
|
): void {
|
||||||
const config = this.forumPopoverConfig
|
const config = this.forumPopoverConfig
|
||||||
if (config.callbacks?.onTipInserted) config.callbacks.onTipInserted(this)
|
|
||||||
// @ts-expect-error `tip` exists, it's just not in the type definition
|
// @ts-expect-error `tip` exists, it's just not in the type definition
|
||||||
const elTip = <ForumPopoverTipEl<ElCls>>config.popoverObj.tip
|
const elTip = <ForumPopoverTipEl<ElCls>>config.popoverObj.tip
|
||||||
elTip.forumPopoverEl = this
|
elTip.forumPopoverEl = this
|
||||||
this.forumPopoverTip = elTip
|
this.forumPopoverTip = elTip
|
||||||
addOnremoveCallback(elTip, onTipRemoved)
|
if (config.callbacks?.onTipInserted) config.callbacks.onTipInserted(this)
|
||||||
elTip.addEventListener('mouseenter', onMouseEnterTip)
|
elTip.addEventListener('mouseenter', onMouseEnterTip)
|
||||||
elTip.addEventListener('mouseleave', onMouseLeaveTip)
|
elTip.addEventListener('mouseleave', onMouseLeaveTip)
|
||||||
elTip.addEventListener('focusin', onFocusinTip)
|
elTip.addEventListener('focusin', onFocusinTip)
|
||||||
elTip.addEventListener('focusout', onFocusoutTip)
|
elTip.addEventListener('focusout', onFocusoutTip)
|
||||||
|
addOnremoveCallback(elTip, onTipRemoved)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFocusinElement<ElCls extends HTMLElement>(
|
function onFocusinElement<ElCls extends HTMLElement>(
|
||||||
|
@ -141,6 +141,13 @@ function onMouseleaveElement<ElCls extends HTMLElement>(
|
||||||
hidePopover(config)
|
hidePopover(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPopoverHidden<ElCls extends HTMLElement>(
|
||||||
|
this: ForumPopoverEl<ElCls>
|
||||||
|
): void {
|
||||||
|
const config = this.forumPopoverConfig
|
||||||
|
config.isShown = config.isTipFocused = config.isTipHovered = false
|
||||||
|
}
|
||||||
|
|
||||||
// The content() function is executed twice (probable bootstrap bug):
|
// The content() function is executed twice (probable bootstrap bug):
|
||||||
// use this function as an umbrella to only call it once
|
// use this function as an umbrella to only call it once
|
||||||
function onGetContent<ElCls extends HTMLElement>(
|
function onGetContent<ElCls extends HTMLElement>(
|
||||||
|
@ -266,17 +273,18 @@ export function add<ElCls extends HTMLElement>(
|
||||||
configurable: true
|
configurable: true
|
||||||
})
|
})
|
||||||
el.addEventListener('inserted.bs.popover', onTipInserted)
|
el.addEventListener('inserted.bs.popover', onTipInserted)
|
||||||
|
el.addEventListener('hidden.bs.popover', onPopoverHidden)
|
||||||
el.addEventListener('focusin', onFocusinElement)
|
el.addEventListener('focusin', onFocusinElement)
|
||||||
el.addEventListener('focusout', onFocusoutElement)
|
el.addEventListener('focusout', onFocusoutElement)
|
||||||
el.addEventListener('mouseenter', onMouseenterElement)
|
el.addEventListener('mouseenter', onMouseenterElement)
|
||||||
el.addEventListener('mouseleave', onMouseleaveElement)
|
el.addEventListener('mouseleave', onMouseleaveElement)
|
||||||
// popoverConfig.popoverObj.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardown<ElCls extends HTMLElement>(el: ElCls): void {
|
export function teardown<ElCls extends HTMLElement>(el: ElCls): void {
|
||||||
if (!isForumPopoverEl(el)) return
|
if (!isForumPopoverEl(el)) return
|
||||||
el.forumPopoverConfig.popoverObj.dispose()
|
el.forumPopoverConfig.popoverObj.dispose()
|
||||||
el.removeEventListener('inserted.bs.popover', onTipInserted)
|
el.removeEventListener('inserted.bs.popover', onTipInserted)
|
||||||
|
el.removeEventListener('hidden.bs.popover', onPopoverHidden)
|
||||||
el.removeEventListener('focusin', onFocusinElement)
|
el.removeEventListener('focusin', onFocusinElement)
|
||||||
el.removeEventListener('focusout', onFocusoutElement)
|
el.removeEventListener('focusout', onFocusoutElement)
|
||||||
el.removeEventListener('mouseenter', onMouseenterElement)
|
el.removeEventListener('mouseenter', onMouseenterElement)
|
||||||
|
|
|
@ -4,9 +4,12 @@ import { add as popoverAdd } from './popover.ts'
|
||||||
import { add as toastAdd } from './toast.ts'
|
import { add as toastAdd } from './toast.ts'
|
||||||
import { add as usernameAdd } from './username.ts'
|
import { add as usernameAdd } from './username.ts'
|
||||||
import { add as timeActualizerAdd } from './time-actualizer.ts'
|
import { add as timeActualizerAdd } from './time-actualizer.ts'
|
||||||
import { VotingValue } from './comment-voting-details.ts'
|
|
||||||
import {
|
import {
|
||||||
type TopicCommentListingUrltemplateType, type TopicBoundFunctionsType
|
add as votingValueAdd, teardown as votingValueTeardown
|
||||||
|
} from './comment-voting-details.ts'
|
||||||
|
import {
|
||||||
|
type TopicBoundFunctionsType,
|
||||||
|
urlTemplates
|
||||||
} from '../topic-comment-listing.ts'
|
} from '../topic-comment-listing.ts'
|
||||||
import {
|
import {
|
||||||
mdiArrowLeft, mdiArrowLeftRight, mdiArrowRight, mdiCogOutline
|
mdiArrowLeft, mdiArrowLeftRight, mdiArrowRight, mdiCogOutline
|
||||||
|
@ -47,16 +50,13 @@ export class OneComment {
|
||||||
private readonly strings: PassedStringsType
|
private readonly strings: PassedStringsType
|
||||||
private readonly elBody: HTMLDivElement
|
private readonly elBody: HTMLDivElement
|
||||||
private readonly elBtnCommentActions: HTMLButtonElement
|
private readonly elBtnCommentActions: HTMLButtonElement
|
||||||
private readonly urlTemplates: TopicCommentListingUrltemplateType
|
|
||||||
private actionsPopoverReferences?: ActionsPopoverReferencesType
|
private actionsPopoverReferences?: ActionsPopoverReferencesType
|
||||||
private readonly votingValue?: VotingValue
|
|
||||||
|
|
||||||
constructor ({ root, commentPk, boundFunctions, strings, urlTemplates }: {
|
constructor ({ root, commentPk, boundFunctions, strings }: {
|
||||||
root: HTMLElement
|
root: HTMLElement
|
||||||
commentPk: number
|
commentPk: number
|
||||||
boundFunctions: TopicBoundFunctionsType
|
boundFunctions: TopicBoundFunctionsType
|
||||||
strings: PassedStringsType
|
strings: PassedStringsType
|
||||||
urlTemplates: TopicCommentListingUrltemplateType
|
|
||||||
}) {
|
}) {
|
||||||
this.rootEl = root
|
this.rootEl = root
|
||||||
this.commentPk = commentPk
|
this.commentPk = commentPk
|
||||||
|
@ -65,7 +65,6 @@ export class OneComment {
|
||||||
onClickCommentNumber: this.onClickCommentNumber.bind(this)
|
onClickCommentNumber: this.onClickCommentNumber.bind(this)
|
||||||
}
|
}
|
||||||
this.strings = strings
|
this.strings = strings
|
||||||
this.urlTemplates = urlTemplates
|
|
||||||
const aEls = <HTMLCollectionOf<HTMLAnchorElement>>
|
const aEls = <HTMLCollectionOf<HTMLAnchorElement>>
|
||||||
root.getElementsByClassName('forum-username')
|
root.getElementsByClassName('forum-username')
|
||||||
this.elBody = <HTMLDivElement>root.getElementsByClassName('card-block')[0]
|
this.elBody = <HTMLDivElement>root.getElementsByClassName('card-block')[0]
|
||||||
|
@ -76,16 +75,14 @@ export class OneComment {
|
||||||
timeActualizerAdd(
|
timeActualizerAdd(
|
||||||
<HTMLCollectionOf<HTMLTimeElement>>
|
<HTMLCollectionOf<HTMLTimeElement>>
|
||||||
root.getElementsByClassName('forum-time'))
|
root.getElementsByClassName('forum-time'))
|
||||||
this.votingValue = this.initVotingValue()
|
this.initVotingValue()
|
||||||
this.initializeCallbacks()
|
this.initializeCallbacks()
|
||||||
}
|
}
|
||||||
|
|
||||||
private initVotingValue(): VotingValue | undefined {
|
private initVotingValue(): VotingValue | undefined {
|
||||||
const rootEl = this.rootEl.getElementsByClassName('voting-value')[0]
|
const elRoot = this.rootEl.getElementsByClassName('voting-value')[0]
|
||||||
if (!(rootEl instanceof HTMLAnchorElement)) return
|
if (!(elRoot instanceof HTMLAnchorElement)) return
|
||||||
const obj = new VotingValue(rootEl, this.commentPk, this.urlTemplates)
|
votingValueAdd(elRoot, this.commentPk)
|
||||||
obj.initialize()
|
|
||||||
return obj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSuccessClipboardCopy(): void {
|
private onSuccessClipboardCopy(): void {
|
||||||
|
@ -189,25 +186,25 @@ export class OneComment {
|
||||||
if (typeof topicSlug !== 'string') return
|
if (typeof topicSlug !== 'string') return
|
||||||
if (this.actionsPopoverReferences?.elExpandCommentsDown) {
|
if (this.actionsPopoverReferences?.elExpandCommentsDown) {
|
||||||
this.actionsPopoverReferences.elExpandCommentsDown.href =
|
this.actionsPopoverReferences.elExpandCommentsDown.href =
|
||||||
this.urlTemplates.expandCommentsDown({
|
urlTemplates.expandCommentsDown({
|
||||||
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.actionsPopoverReferences?.elExpandCommentsUp) {
|
if (this.actionsPopoverReferences?.elExpandCommentsUp) {
|
||||||
this.actionsPopoverReferences.elExpandCommentsUp.href =
|
this.actionsPopoverReferences.elExpandCommentsUp.href =
|
||||||
this.urlTemplates.expandCommentsUp({
|
urlTemplates.expandCommentsUp({
|
||||||
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.actionsPopoverReferences?.elExpandCommentsUpRecursive) {
|
if (this.actionsPopoverReferences?.elExpandCommentsUpRecursive) {
|
||||||
this.actionsPopoverReferences.elExpandCommentsUpRecursive.href =
|
this.actionsPopoverReferences.elExpandCommentsUpRecursive.href =
|
||||||
this.urlTemplates.expandCommentsUpRecursive({
|
urlTemplates.expandCommentsUpRecursive({
|
||||||
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.actionsPopoverReferences?.elExpandCommentsInThread) {
|
if (this.actionsPopoverReferences?.elExpandCommentsInThread) {
|
||||||
this.actionsPopoverReferences.elExpandCommentsInThread.href =
|
this.actionsPopoverReferences.elExpandCommentsInThread.href =
|
||||||
this.urlTemplates.expandCommentsEntireThread({
|
urlTemplates.expandCommentsEntireThread({
|
||||||
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
topicSlug, commentPk: this.commentPk, scrollToPk: this.commentPk
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -315,6 +312,12 @@ export class OneComment {
|
||||||
this.initializeActionsPopover(prevCommentLink)
|
this.initializeActionsPopover(prevCommentLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private teardownVotingValue(): void {
|
||||||
|
const elRoot = this.rootEl.getElementsByClassName('voting-value')[0]
|
||||||
|
if (!(elRoot instanceof HTMLAnchorElement)) return
|
||||||
|
votingValueTeardown(elRoot)
|
||||||
|
}
|
||||||
|
|
||||||
teardown(): void {
|
teardown(): void {
|
||||||
this.elAnchorCommentNumber.removeEventListener(
|
this.elAnchorCommentNumber.removeEventListener(
|
||||||
'click', this.boundFunctions.onClickCommentNumber)
|
'click', this.boundFunctions.onClickCommentNumber)
|
||||||
|
@ -332,7 +335,7 @@ export class OneComment {
|
||||||
}
|
}
|
||||||
for (const el of this.oneReplies.values()) this.teardownOneReply(el)
|
for (const el of this.oneReplies.values()) this.teardownOneReply(el)
|
||||||
this.oneReplies.clear()
|
this.oneReplies.clear()
|
||||||
this.votingValue?.teardown()
|
this.teardownVotingValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
flashHighlight(): void {
|
flashHighlight(): void {
|
||||||
|
|
|
@ -206,7 +206,7 @@ class Username {
|
||||||
html: true,
|
html: true,
|
||||||
sanitize: false,
|
sanitize: false,
|
||||||
customClass: 'forum-username-tooltip-wrapper',
|
customClass: 'forum-username-tooltip-wrapper',
|
||||||
trigger: 'hover',
|
trigger: 'hover focus',
|
||||||
delay: {
|
delay: {
|
||||||
show: 250,
|
show: 250,
|
||||||
hide: 0
|
hide: 0
|
||||||
|
|
Loading…
Reference in a new issue