Popover displaying clickable usernames in voting value

This commit is contained in:
László Károlyi 2023-11-14 16:35:53 +01:00
parent 75e0ec90dd
commit 37b8d96a78
Signed by: karolyi
GPG key ID: 2DCAF25E55735BFE
7 changed files with 227 additions and 172 deletions

View file

@ -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 -%}

View file

@ -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)

View file

@ -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()
} }

View file

@ -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 { }
} }

View file

@ -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)

View file

@ -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 {

View file

@ -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