More removal of bound functions
This commit is contained in:
parent
95cd6d378e
commit
e9df2ae400
7 changed files with 499 additions and 445 deletions
|
@ -39,14 +39,14 @@
|
|||
</div>
|
||||
{% endif -%}
|
||||
</section>
|
||||
<section class="pagination-comments">
|
||||
{{ paginator_generic(page=page_comments, adjacent_pages=django_settings.PAGINATOR_DEFAULT_ADJACENT_PAGES) }}
|
||||
<section class="pagination-comments-wrapper">
|
||||
{{ paginator_generic(page=page_comments, adjacent_pages=django_settings.PAGINATOR_DEFAULT_ADJACENT_PAGES, extra_classnames='pagination-comments') }}
|
||||
</section>
|
||||
{% for comment in page_comments %}
|
||||
{{ topic_comment_template(comment=comment, topic=topic, is_template=False) }}
|
||||
{% endfor %}
|
||||
<section class="pagination-comments">
|
||||
{{ paginator_generic(page=page_comments, adjacent_pages=django_settings.PAGINATOR_DEFAULT_ADJACENT_PAGES) }}
|
||||
<section class="pagination-comments-wrapper">
|
||||
{{ paginator_generic(page=page_comments, adjacent_pages=django_settings.PAGINATOR_DEFAULT_ADJACENT_PAGES, extra_classnames='pagination-comments') }}
|
||||
</section>
|
||||
</article>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% macro paginator_generic(page, adjacent_pages) -%}
|
||||
{% macro paginator_generic(page, adjacent_pages, extra_classnames='') -%}
|
||||
{% set page_number_list = paginator_generic_get_list(current_no=page.number, num_pages=page.paginator.num_pages, adjacent_pages=adjacent_pages) %}
|
||||
{% if page_number_list %}
|
||||
<ul class="pagination">
|
||||
<ul class="pagination{% if extra_classnames %} {{ extra_classnames }}{% endif%}">
|
||||
{%- for dict_page in page_number_list %}
|
||||
{% if dict_page.type == 'number' -%}
|
||||
<li class="page-item page-numbered
|
||||
|
|
|
@ -245,7 +245,7 @@
|
|||
|
||||
}
|
||||
|
||||
.pagination-comments {
|
||||
.pagination-comments-wrapper {
|
||||
.pagination {
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@ import { type TemplateExecutor } from 'lodash'
|
|||
import template from 'lodash/template.js'
|
||||
import { addOnremoveCallback } from './utils/mutation-observer.ts'
|
||||
import { getNavbarHeight, promiseWindowLoad } from './utils/common.ts'
|
||||
import { Paginator } from './utils/paginator.ts'
|
||||
import { OneComment, init as oneCommentInit } from './utils/one-comment.ts'
|
||||
import { Paginator, type PaginatorRootElType } from './utils/paginator.ts'
|
||||
import {
|
||||
init as oneCommentInit, initElonecomment, type ElOnecommentType,
|
||||
teardownOnecomment, isInViewport, deselectAsFocused, selectAsFocused,
|
||||
jumpToSelf, flashHighlight, highlightOn, highlightOff
|
||||
} from './utils/one-comment.ts'
|
||||
|
||||
export interface PassedStringsType {
|
||||
commentUrlCopied: string
|
||||
|
@ -72,7 +76,7 @@ interface TopicCommentListingOptionsType {
|
|||
strings: PassedStringsType
|
||||
}
|
||||
|
||||
export interface TopicCommentListingUrltemplateType {
|
||||
export let urlTemplates: {
|
||||
commentListing: TemplateExecutor
|
||||
expandCommentsDown: TemplateExecutor
|
||||
expandCommentsUp: TemplateExecutor
|
||||
|
@ -83,14 +87,6 @@ export interface TopicCommentListingUrltemplateType {
|
|||
getVotingValueDetails?: TemplateExecutor
|
||||
}
|
||||
|
||||
export interface TopicBoundFunctionsType {
|
||||
commentHighlightOn: (event: Event) => void
|
||||
commentHighlightOff: (event: Event) => void
|
||||
onClickJumpToComment: (event: Event) => void
|
||||
getALink: (scrollToPk: number) => string
|
||||
}
|
||||
|
||||
export let urlTemplates: TopicCommentListingUrltemplateType
|
||||
export let stringsPassed: PassedStringsType
|
||||
|
||||
function initializeUrls(options: TopicCommentListingOptionsType): void {
|
||||
|
@ -145,220 +141,221 @@ function initializeUrls(options: TopicCommentListingOptionsType): void {
|
|||
}
|
||||
}
|
||||
|
||||
type ElTopicCommentListingType = Document & {
|
||||
forumTopicCommentListing: TopicCommentListing
|
||||
}
|
||||
|
||||
function getElRoot(): ElTopicCommentListingType {
|
||||
return <ElTopicCommentListingType>document
|
||||
}
|
||||
|
||||
function onClickJumpToComment(event: MouseEvent): void {
|
||||
const commentLink = event.currentTarget
|
||||
if (!(commentLink instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(commentLink.dataset.forumLinkTo ?? '-1')
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
const elOneComment = obj.comments.get(commentPk)
|
||||
if (!elOneComment) return
|
||||
event.preventDefault()
|
||||
// This is an intentional click so add it to navigation history
|
||||
history.pushState(null, '', getALink(obj, commentPk))
|
||||
jumpToSelf(elOneComment).catch((e) => {
|
||||
console.error(`jumpToSelf failed with ${e}`)
|
||||
})
|
||||
}
|
||||
|
||||
function getFirstOrLastCommentPk(obj: TopicCommentListing): number {
|
||||
const keys = [...obj.comments.keys()]
|
||||
if (keys.length === 1) return keys[0]
|
||||
const firstPk = keys[0]
|
||||
const lastPk = keys[keys.length - 1]
|
||||
const elFirstComment: ElOnecommentType =
|
||||
obj.comments.values().next().value
|
||||
const firstPos = elFirstComment.getBoundingClientRect().top
|
||||
return firstPos > 0 ? firstPk : lastPk
|
||||
}
|
||||
|
||||
function onScrollFirst(): void {
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
obj.isPageScrolled = true
|
||||
}
|
||||
|
||||
function onScrollEnd(): void {
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
const navbarHeight = getNavbarHeight()
|
||||
const viewportHeight = document.documentElement.clientHeight
|
||||
let selectedCommentPk = 0
|
||||
for (const [thisCommentPk, elOneComment] of obj.comments.entries()) {
|
||||
if (isInViewport(elOneComment, navbarHeight, viewportHeight)) {
|
||||
selectedCommentPk = thisCommentPk
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!selectedCommentPk) selectedCommentPk = getFirstOrLastCommentPk(obj)
|
||||
if (obj.lastSelectedCommentPk === selectedCommentPk) return
|
||||
const elOneCommentOld = obj.comments.get(obj.lastSelectedCommentPk)
|
||||
if (elOneCommentOld) deselectAsFocused(elOneCommentOld.forumOneComment)
|
||||
const elOneCommentNew = obj.comments.get(selectedCommentPk)
|
||||
if (elOneCommentNew) selectAsFocused(elOneCommentNew.forumOneComment)
|
||||
updateUrl(obj, selectedCommentPk)
|
||||
obj.lastSelectedCommentPk = selectedCommentPk
|
||||
}
|
||||
|
||||
async function onPaginate(
|
||||
this: PaginatorRootElType, pageNo: number, elClicked: HTMLAnchorElement
|
||||
): Promise<void> {
|
||||
if (!urlTemplates.commentListingPageNo) {
|
||||
console.error('urlTemplates.commentListingPageNo is unset')
|
||||
return
|
||||
}
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
obj.paginators.forEach(paginator => {
|
||||
paginator.setLoading(elClicked)
|
||||
})
|
||||
document.location.href = urlTemplates.commentListingPageNo({
|
||||
topicSlug: obj.options.topicSlugOriginal,
|
||||
pageId: pageNo
|
||||
})
|
||||
}
|
||||
|
||||
function onTeardownOnecomment(el: ElOnecommentType): void {
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
obj.comments.delete(el.forumOneComment.commentPk)
|
||||
teardownOnecomment(
|
||||
el, onClickJumpToComment, commentHighlightOn, commentHighlightOff)
|
||||
}
|
||||
|
||||
function getALink(obj: TopicCommentListing, scrollToPk: number): string {
|
||||
switch (obj.options.listingMode) {
|
||||
case 'commentListing':
|
||||
return urlTemplates.commentListing({
|
||||
topicSlug: obj.options.topicSlugOriginal, commentPk: scrollToPk
|
||||
})
|
||||
case 'expandCommentsDown':
|
||||
return urlTemplates.expandCommentsDown({
|
||||
topicSlug: obj.options.topicSlugOriginal,
|
||||
commentPk: obj.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsUp':
|
||||
return urlTemplates.expandCommentsUp({
|
||||
topicSlug: obj.options.topicSlugOriginal,
|
||||
commentPk: obj.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsUpRecursive':
|
||||
return urlTemplates.expandCommentsUpRecursive({
|
||||
topicSlug: obj.options.topicSlugOriginal,
|
||||
commentPk: obj.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsEntireThread':
|
||||
return urlTemplates.expandCommentsEntireThread({
|
||||
topicSlug: obj.options.topicSlugOriginal,
|
||||
commentPk: obj.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function commentHighlightOn(event: Event): void {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(target.dataset.forumLinkTo ?? '-1')
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
const elOnecomment = obj.comments.get(commentPk)
|
||||
if (!elOnecomment) return
|
||||
highlightOn(elOnecomment)
|
||||
}
|
||||
|
||||
function commentHighlightOff(event: Event): void {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(target.dataset.forumLinkTo ?? '-1')
|
||||
const obj = getElRoot().forumTopicCommentListing
|
||||
const elOnecomment = obj.comments.get(commentPk)
|
||||
if (!elOnecomment) return
|
||||
highlightOff(elOnecomment)
|
||||
}
|
||||
|
||||
function updateUrl(obj: TopicCommentListing, commentPk: number): void {
|
||||
const path = getALink(obj, commentPk)
|
||||
if (!path || path === location.pathname) return
|
||||
history.replaceState(null, '', path)
|
||||
}
|
||||
|
||||
async function initialJumpToComment(obj: TopicCommentListing): Promise<void> {
|
||||
let scrollToPk: number
|
||||
if (obj.options.listingMode === 'commentListing') {
|
||||
if (obj.options.commentPk == null) return
|
||||
scrollToPk = obj.options.commentPk
|
||||
} else {
|
||||
if (obj.options.scrollToPk == null) return
|
||||
scrollToPk = obj.options.scrollToPk
|
||||
}
|
||||
const elOnecomment = obj.comments.get(scrollToPk)
|
||||
if (!elOnecomment) return
|
||||
await promiseWindowLoad
|
||||
// Don't jump to comment when the user scrolled the page before
|
||||
// the onload event firing
|
||||
if (obj.isPageScrolled) return
|
||||
await jumpToSelf(elOnecomment)
|
||||
await flashHighlight(elOnecomment)
|
||||
}
|
||||
|
||||
class TopicCommentListing {
|
||||
private readonly options: TopicCommentListingOptionsType
|
||||
private boundFunctions!: TopicBoundFunctionsType
|
||||
private root!: HTMLElement
|
||||
private readonly comments = new Map<number, OneComment>()
|
||||
private isPageScrolled = false
|
||||
private lastSelectedCommentPk = 0
|
||||
private readonly paginators = new Array<Paginator>()
|
||||
readonly options: TopicCommentListingOptionsType
|
||||
readonly comments = new Map<number, ElOnecommentType>()
|
||||
isPageScrolled = false
|
||||
lastSelectedCommentPk = 0
|
||||
readonly paginators = new Array<Paginator>()
|
||||
|
||||
constructor (options: TopicCommentListingOptionsType) {
|
||||
constructor (
|
||||
root: ElTopicCommentListingType,
|
||||
options: TopicCommentListingOptionsType
|
||||
) {
|
||||
this.options = options
|
||||
this.initializeElOnecomments(root)
|
||||
this.initializePaginators(root)
|
||||
addEventListener('scrollend', onScrollEnd)
|
||||
}
|
||||
|
||||
private commentHighlightOn(event: Event): void {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(target.dataset.forumLinkTo ?? '-1')
|
||||
const oneComment = this.comments.get(commentPk)
|
||||
if (!oneComment) return
|
||||
oneComment.highlightOn()
|
||||
}
|
||||
|
||||
private commentHighlightOff(event: Event): void {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(target.dataset.forumLinkTo ?? '-1')
|
||||
const oneComment = this.comments.get(commentPk)
|
||||
if (!oneComment) return
|
||||
oneComment.highlightOff()
|
||||
}
|
||||
|
||||
private onClickJumpToComment(event: Event): void {
|
||||
const commentLink = event.currentTarget
|
||||
if (!(commentLink instanceof HTMLAnchorElement)) return
|
||||
const commentPk = parseInt(commentLink.dataset.forumLinkTo ?? '-1')
|
||||
const oneComment = this.comments.get(commentPk)
|
||||
if (!oneComment) return
|
||||
event.preventDefault()
|
||||
// This is an intentional click so add it to navigation history
|
||||
history.pushState(null, '', this.getALink(commentPk))
|
||||
oneComment.jumpToSelf()
|
||||
}
|
||||
|
||||
private onScrollFirst(): void {
|
||||
this.isPageScrolled = true
|
||||
}
|
||||
|
||||
private onScrollEnd(): void {
|
||||
const navbarHeight = getNavbarHeight()
|
||||
const viewportHeight = document.documentElement.clientHeight
|
||||
let selectedCommentPk: number = 0
|
||||
for (const [thisCommentPk, oneComment] of this.comments.entries()) {
|
||||
if (oneComment.isInViewport(navbarHeight, viewportHeight)) {
|
||||
selectedCommentPk = thisCommentPk
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!selectedCommentPk) selectedCommentPk = this.getFirstOrLastCommentPk()
|
||||
if (this.lastSelectedCommentPk === selectedCommentPk) return
|
||||
this.comments.get(this.lastSelectedCommentPk)?.deselectAsFocused()
|
||||
this.updateUrl(selectedCommentPk)
|
||||
this.comments.get(selectedCommentPk)?.selectAsFocused()
|
||||
this.lastSelectedCommentPk = selectedCommentPk
|
||||
}
|
||||
|
||||
private getFirstOrLastCommentPk(): number {
|
||||
const keys = [...this.comments.keys()]
|
||||
if (keys.length === 1) return keys[0]
|
||||
const firstPk = keys[0]
|
||||
const lastPk = keys[keys.length - 1]
|
||||
const firstComment: OneComment = this.comments.values().next().value
|
||||
const firstPos = firstComment.rootEl.getBoundingClientRect().top
|
||||
return firstPos > 0 ? firstPk : lastPk
|
||||
}
|
||||
|
||||
private getALink(scrollToPk: number): string {
|
||||
switch (this.options.listingMode) {
|
||||
case 'commentListing':
|
||||
return urlTemplates.commentListing({
|
||||
topicSlug: this.options.topicSlugOriginal, commentPk: scrollToPk
|
||||
})
|
||||
case 'expandCommentsDown':
|
||||
return urlTemplates.expandCommentsDown({
|
||||
topicSlug: this.options.topicSlugOriginal,
|
||||
commentPk: this.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsUp':
|
||||
return urlTemplates.expandCommentsUp({
|
||||
topicSlug: this.options.topicSlugOriginal,
|
||||
commentPk: this.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsUpRecursive':
|
||||
return urlTemplates.expandCommentsUpRecursive({
|
||||
topicSlug: this.options.topicSlugOriginal,
|
||||
commentPk: this.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
case 'expandCommentsEntireThread':
|
||||
return urlTemplates.expandCommentsEntireThread({
|
||||
topicSlug: this.options.topicSlugOriginal,
|
||||
commentPk: this.options.commentPk,
|
||||
scrollToPk
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private updateUrl(commentPk: number): void {
|
||||
const path = this.getALink(commentPk)
|
||||
if (!path || path === location.pathname) return
|
||||
history.replaceState(null, '', path)
|
||||
}
|
||||
|
||||
private initializeOneComment(rootEl: HTMLElement): void {
|
||||
const commentPk = parseInt(rootEl.dataset.forumCommentPk ?? '-1')
|
||||
if (commentPk === -1) throw new Error('forumLinkTo not found')
|
||||
this.comments.set(commentPk, new OneComment({
|
||||
root: rootEl, commentPk, boundFunctions: this.boundFunctions
|
||||
}))
|
||||
addOnremoveCallback(rootEl, this.teardownOneComment.bind(this))
|
||||
}
|
||||
|
||||
private teardownOneComment(rootEl: HTMLElement): void {
|
||||
const strCommentId = rootEl.dataset.forumCommentPk
|
||||
if (typeof strCommentId !== 'string') return
|
||||
const commentPk = parseInt(strCommentId)
|
||||
this.comments.get(commentPk)?.teardown()
|
||||
this.comments.delete(commentPk)
|
||||
}
|
||||
|
||||
private initializeBoundFunctions(): void {
|
||||
addEventListener('scroll', this.onScrollFirst.bind(this), { once: true })
|
||||
this.boundFunctions = {
|
||||
commentHighlightOn: this.commentHighlightOn.bind(this),
|
||||
commentHighlightOff: this.commentHighlightOff.bind(this),
|
||||
onClickJumpToComment: this.onClickJumpToComment.bind(this),
|
||||
getALink: this.getALink.bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
private initializeWrappers(): void {
|
||||
const root = document.getElementById(this.options.selectors.root)
|
||||
if (!root) return
|
||||
this.root = root
|
||||
private initializeElOnecomments(root: ElTopicCommentListingType): void {
|
||||
const commentWrappers = <HTMLCollectionOf<HTMLElement>>
|
||||
this.root.getElementsByClassName(this.options.selectors.commentWrapper)
|
||||
root.getElementsByClassName(this.options.selectors.commentWrapper)
|
||||
for (const el of commentWrappers) {
|
||||
this.initializeOneComment(el)
|
||||
const commentPk = parseInt(el.dataset.forumCommentPk ?? '-1')
|
||||
if (commentPk === -1) throw new Error('forumLinkTo not found')
|
||||
const aLink = getALink(this, commentPk)
|
||||
const elOneComment = initElonecomment(
|
||||
el, commentPk, aLink, onClickJumpToComment, commentHighlightOn,
|
||||
commentHighlightOff)
|
||||
addOnremoveCallback(elOneComment, onTeardownOnecomment)
|
||||
this.comments.set(commentPk, elOneComment)
|
||||
}
|
||||
}
|
||||
|
||||
private async onPaginate(pageNo: number): Promise<void> {
|
||||
if (!urlTemplates.commentListingPageNo) {
|
||||
console.error('urlTemplates.commentListingPageNo is unset')
|
||||
return
|
||||
}
|
||||
document.location.href = urlTemplates.commentListingPageNo({
|
||||
topicSlug: this.options.topicSlugOriginal,
|
||||
pageId: pageNo
|
||||
})
|
||||
}
|
||||
|
||||
private initializePaginators(): void {
|
||||
private initializePaginators(root: ElTopicCommentListingType): void {
|
||||
if (this.options.listingMode !== 'commentListing') return
|
||||
const wrappers =
|
||||
<HTMLCollectionOf<HTMLElement>>
|
||||
this.root.getElementsByClassName('pagination-comments')
|
||||
<HTMLCollectionOf<HTMLUListElement>>
|
||||
root.getElementsByClassName('pagination-comments')
|
||||
for (const elRoot of wrappers) {
|
||||
this.paginators.push(new Paginator({
|
||||
root: elRoot,
|
||||
callbacks: {
|
||||
load: this.onPaginate.bind(this)
|
||||
}
|
||||
this.paginators.push(Paginator.add({
|
||||
root: elRoot, callbacks: { load: onPaginate }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private initialJumpToComment(): void {
|
||||
let scrollToPk: number
|
||||
if (this.options.listingMode === 'commentListing') {
|
||||
if (this.options.commentPk == null) return
|
||||
scrollToPk = this.options.commentPk
|
||||
} else {
|
||||
if (this.options.scrollToPk == null) return
|
||||
scrollToPk = this.options.scrollToPk
|
||||
}
|
||||
const oneComment = this.comments.get(scrollToPk)
|
||||
if (!oneComment) return
|
||||
promiseWindowLoad.then(() => {
|
||||
// Don't jump to comment when the user scrolled the page before
|
||||
// the onload event firing
|
||||
if (this.isPageScrolled) return
|
||||
oneComment.jumpToSelf(() => {
|
||||
oneComment.flashHighlight()
|
||||
})
|
||||
}, () => {
|
||||
console.error('initial scroll resulted in failure')
|
||||
})
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this.initializeBoundFunctions()
|
||||
this.initializeWrappers()
|
||||
this.initializePaginators()
|
||||
this.initialJumpToComment()
|
||||
addEventListener('scrollend', this.onScrollEnd.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
export function init(options: TopicCommentListingOptionsType): void {
|
||||
stringsPassed = options.strings
|
||||
oneCommentInit()
|
||||
initializeUrls(options)
|
||||
stringsPassed = options.strings
|
||||
const obj = new TopicCommentListing(options)
|
||||
obj.initialize()
|
||||
const root = getElRoot()
|
||||
root.forumTopicCommentListing = new TopicCommentListing(root, options)
|
||||
addEventListener('scroll', onScrollFirst, { once: true })
|
||||
initialJumpToComment(root.forumTopicCommentListing).catch((e) => {
|
||||
console.error('initial scroll resulted in failure:', e)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -300,6 +300,10 @@ export function add(elRoot: HTMLAnchorElement, commentPk: number): void {
|
|||
throw new Error(`VotingValue.add: ${elRoot.outerHTML} already initialized`)
|
||||
}
|
||||
storedObjs.set(elRoot, { commentPk })
|
||||
const container = elRoot.parentElement
|
||||
if (!container) {
|
||||
throw new Error(`container of ${elRoot.outerHTML} is not a HTMLElement`)
|
||||
}
|
||||
popoverAdd(elRoot, {
|
||||
callbacks: { onTipInserted, onTipRemoved },
|
||||
popoverOpts: {
|
||||
|
@ -311,7 +315,7 @@ export function add(elRoot: HTMLAnchorElement, commentPk: number): void {
|
|||
hide: 250
|
||||
},
|
||||
title: stringsPassed.castedVotes,
|
||||
container: <HTMLElement>elRoot.parentElement,
|
||||
container,
|
||||
// template: popoverTemplate,
|
||||
customClass: 'voting-value-popover-wrapper'
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ import { add as timeActualizerAdd } from './time-actualizer.ts'
|
|||
import {
|
||||
add as votingValueAdd, teardown as votingValueTeardown
|
||||
} from './comment-voting-details.ts'
|
||||
import {
|
||||
type TopicBoundFunctionsType, urlTemplates, stringsPassed
|
||||
} from '../topic-comment-listing.ts'
|
||||
import { urlTemplates, stringsPassed } from '../topic-comment-listing.ts'
|
||||
import {
|
||||
mdiArrowLeft, mdiArrowLeftRight, mdiArrowRight, mdiCogOutline
|
||||
} from '@mdi/js'
|
||||
|
@ -32,61 +30,171 @@ interface ActionsPopoverReferencesType {
|
|||
elExpandCommentsInThread?: HTMLAnchorElement
|
||||
}
|
||||
|
||||
interface OneCommentBoundFunctionsType extends TopicBoundFunctionsType {
|
||||
onClickCommentNumber: (event: MouseEvent) => void
|
||||
export type ElOnecommentType = HTMLElement & {
|
||||
forumOneComment: OneComment
|
||||
}
|
||||
|
||||
function getElRoot(el: HTMLElement): ElOnecommentType {
|
||||
const elRoot = el.closest<ElOnecommentType>('section.topic-comment-wrapper')
|
||||
if (!elRoot) {
|
||||
throw new Error('Closest section.topic-comment-wrapper not found')
|
||||
}
|
||||
return elRoot
|
||||
}
|
||||
|
||||
function getContentCommentActions(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): HTMLElement {
|
||||
const obj = getElRoot(el).forumOneComment
|
||||
return obj.getContentCommentActions()
|
||||
}
|
||||
|
||||
function onPopoverTipInsertedCommentAction(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): void {
|
||||
const obj = getElRoot(el).forumOneComment
|
||||
obj.onPopoverTipInsertedCommentAction(el)
|
||||
}
|
||||
|
||||
function onPopoverTipRemovedCommentAction(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): void {
|
||||
const obj = getElRoot(el).forumOneComment
|
||||
obj.onPopoverTipRemovedCommentAction(el)
|
||||
}
|
||||
|
||||
function onSuccessClipboardCopy(): void {
|
||||
toastAdd({ message: stringsPassed.commentUrlCopied })
|
||||
}
|
||||
|
||||
function onErrorClipboardCopy(): void {
|
||||
toastAdd({ message: stringsPassed.noClipboardError })
|
||||
}
|
||||
|
||||
function onClickCommentNumber(this: HTMLAnchorElement, ev: MouseEvent): void {
|
||||
const commentLink = ev.currentTarget
|
||||
if (!(commentLink instanceof HTMLAnchorElement)) return
|
||||
ev.preventDefault()
|
||||
if (navigator.clipboard == null) {
|
||||
onErrorClipboardCopy()
|
||||
return
|
||||
}
|
||||
navigator.clipboard.writeText(commentLink.href).then(
|
||||
onSuccessClipboardCopy, onErrorClipboardCopy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return `true` if at least 1/3 of the comment body is visible.
|
||||
*/
|
||||
export function isInViewport(
|
||||
el: ElOnecommentType, zeroPoint: number, viewportHeight: number
|
||||
): boolean {
|
||||
const bodyInfo = el.forumOneComment.elBody.getBoundingClientRect()
|
||||
const relBodyBottomY = bodyInfo.top + bodyInfo.height
|
||||
// Comment is fully outside of the viewport
|
||||
if (
|
||||
relBodyBottomY <= zeroPoint || bodyInfo.top >= viewportHeight
|
||||
) return false
|
||||
// Top inside
|
||||
if (bodyInfo.top >= zeroPoint) return true
|
||||
// Top outside, bottom inside and above 60% height
|
||||
if (relBodyBottomY >= viewportHeight * 0.6) return true
|
||||
// Bottom inside but below 60% height
|
||||
const relThirdBottomY = bodyInfo.top + bodyInfo.height / 3
|
||||
// True if the top 1/3 of the comment body is inside
|
||||
return relThirdBottomY > zeroPoint
|
||||
}
|
||||
|
||||
export function selectAsFocused(obj: OneComment): void {
|
||||
if (obj.elRoot.classList.contains('is-currently-focused')) return
|
||||
obj.elRoot.classList.add('is-currently-focused')
|
||||
}
|
||||
|
||||
export function deselectAsFocused(obj: OneComment): void {
|
||||
if (!obj.elRoot.classList.contains('is-currently-focused')) return
|
||||
obj.elRoot.classList.remove('is-currently-focused')
|
||||
}
|
||||
|
||||
export async function jumpToSelf(el: ElOnecommentType): Promise<void> {
|
||||
document.addEventListener('scrollend', () => {
|
||||
if (document.activeElement instanceof HTMLAnchorElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
}, { once: true })
|
||||
const finishPromise = new Promise<void>((resolve) => {
|
||||
document.addEventListener('scrollend', () => {
|
||||
resolve()
|
||||
}, { once: true })
|
||||
})
|
||||
scroll({ top: getScrollTop(el), behavior: 'auto' })
|
||||
await finishPromise
|
||||
}
|
||||
|
||||
export async function flashHighlight(el: ElOnecommentType): Promise<void> {
|
||||
const finishPromise = new Promise<void>((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
highlightOn(el)
|
||||
requestAnimationFrame(() => {
|
||||
highlightOff(el)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
await finishPromise
|
||||
}
|
||||
|
||||
export function highlightOn(el: ElOnecommentType): void {
|
||||
el.classList.add(highlightedClass)
|
||||
}
|
||||
|
||||
export function highlightOff(el: ElOnecommentType): void {
|
||||
el.classList.remove(highlightedClass)
|
||||
}
|
||||
|
||||
export class OneComment {
|
||||
readonly rootEl: HTMLElement
|
||||
private readonly commentPk: number
|
||||
private readonly boundFunctions: OneCommentBoundFunctionsType
|
||||
private readonly oneReplies = new Map<number, HTMLDivElement>()
|
||||
private elPrevCommentLink?: HTMLAnchorElement
|
||||
private elAnchorCommentNumber!: HTMLAnchorElement
|
||||
private readonly elBody: HTMLDivElement
|
||||
private readonly elBtnCommentActions: HTMLButtonElement
|
||||
readonly elRoot: HTMLElement
|
||||
readonly commentPk: number
|
||||
elAnchorCommentNumber!: HTMLAnchorElement
|
||||
elPrevCommentLink?: HTMLAnchorElement
|
||||
readonly oneReplies = new Map<number, HTMLDivElement>()
|
||||
readonly elBtnCommentActions: HTMLButtonElement
|
||||
readonly elBody: HTMLDivElement
|
||||
private actionsPopoverReferences?: ActionsPopoverReferencesType
|
||||
|
||||
constructor ({ root, commentPk, boundFunctions }: {
|
||||
root: HTMLElement
|
||||
commentPk: number
|
||||
boundFunctions: TopicBoundFunctionsType
|
||||
}) {
|
||||
this.rootEl = root
|
||||
constructor (
|
||||
elRoot: HTMLElement, commentPk: number, aLink: string,
|
||||
onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
) {
|
||||
this.elRoot = elRoot
|
||||
this.commentPk = commentPk
|
||||
this.boundFunctions = {
|
||||
...boundFunctions,
|
||||
onClickCommentNumber: this.onClickCommentNumber.bind(this)
|
||||
}
|
||||
const aEls = <HTMLCollectionOf<HTMLAnchorElement>>
|
||||
root.getElementsByClassName('forum-username')
|
||||
this.elBody = <HTMLDivElement>root.getElementsByClassName('card-block')[0]
|
||||
this.elBody =
|
||||
<HTMLDivElement>
|
||||
elRoot.getElementsByClassName('card-block').item(0)
|
||||
this.elBtnCommentActions =
|
||||
<HTMLButtonElement>
|
||||
root.getElementsByClassName('forum-comment-actions')[0]
|
||||
elRoot.getElementsByClassName('forum-comment-actions').item(0)
|
||||
const aEls = <HTMLCollectionOf<HTMLAnchorElement>>
|
||||
elRoot.getElementsByClassName('forum-username')
|
||||
for (const aEl of aEls) usernameAdd(aEl)
|
||||
timeActualizerAdd(
|
||||
<HTMLCollectionOf<HTMLTimeElement>>
|
||||
root.getElementsByClassName('forum-time'))
|
||||
elRoot.getElementsByClassName('forum-time'))
|
||||
this.initVotingValue()
|
||||
this.initializeCallbacks()
|
||||
this.initializeCallbacks(
|
||||
aLink, onClickJumpToComment, commentHighlightOn, commentHighlightOff)
|
||||
}
|
||||
|
||||
private initVotingValue(): void {
|
||||
const elRoot = this.rootEl.getElementsByClassName('voting-value')[0]
|
||||
if (!(elRoot instanceof HTMLAnchorElement)) return
|
||||
const elRoot = this.elRoot.getElementsByClassName('voting-value').item(0)
|
||||
if (!(elRoot instanceof HTMLAnchorElement)) {
|
||||
throw new Error('voting value on not found on comment')
|
||||
}
|
||||
votingValueAdd(elRoot, this.commentPk)
|
||||
}
|
||||
|
||||
private onSuccessClipboardCopy(): void {
|
||||
toastAdd({ message: stringsPassed.commentUrlCopied })
|
||||
}
|
||||
|
||||
private onErrorClipboardCopy(): void {
|
||||
toastAdd({ message: stringsPassed.noClipboardError })
|
||||
}
|
||||
|
||||
private onPopoverTipInsertedCommentAction(
|
||||
onPopoverTipInsertedCommentAction(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): void {
|
||||
if (!this.actionsPopoverReferences) return
|
||||
|
@ -109,7 +217,9 @@ export class OneComment {
|
|||
delete this.actionsPopoverReferences
|
||||
}
|
||||
|
||||
private onPopoverTipRemovedCommentAction(el: HTMLButtonElement): void {
|
||||
onPopoverTipRemovedCommentAction(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): void {
|
||||
if (this.actionsPopoverReferences?.elExpandCommentsDown) {
|
||||
Tooltip.getInstance(
|
||||
this.actionsPopoverReferences.elExpandCommentsDown)?.dispose()
|
||||
|
@ -130,69 +240,27 @@ export class OneComment {
|
|||
delete this.actionsPopoverReferences
|
||||
}
|
||||
|
||||
private onClickCommentNumber(event: MouseEvent): void {
|
||||
const commentLink = event.currentTarget
|
||||
if (!(commentLink instanceof HTMLAnchorElement)) return
|
||||
event.preventDefault()
|
||||
if (navigator.clipboard == null) {
|
||||
this.onErrorClipboardCopy()
|
||||
return
|
||||
}
|
||||
navigator.clipboard.writeText(commentLink.href).then(
|
||||
this.onSuccessClipboardCopy.bind(this),
|
||||
this.onErrorClipboardCopy.bind(this))
|
||||
}
|
||||
|
||||
private initializeOneReply(el: HTMLDivElement): number {
|
||||
const replyLink = <HTMLAnchorElement>
|
||||
el.getElementsByClassName('forum-to-reply-comment')[0]
|
||||
const commentPk = parseInt(replyLink.dataset.forumLinkTo ?? '-1')
|
||||
if (commentPk === -1) throw new Error('forumLinkTo not found')
|
||||
this.oneReplies.set(commentPk, el)
|
||||
replyLink.addEventListener(
|
||||
'mouseenter', this.boundFunctions.commentHighlightOn)
|
||||
replyLink.addEventListener(
|
||||
'mouseleave', this.boundFunctions.commentHighlightOff)
|
||||
replyLink.addEventListener(
|
||||
'focusin', this.boundFunctions.commentHighlightOn)
|
||||
replyLink.addEventListener(
|
||||
'focusout', this.boundFunctions.commentHighlightOff)
|
||||
replyLink.addEventListener(
|
||||
'click', this.boundFunctions.onClickJumpToComment)
|
||||
private initializeOneReply(
|
||||
elOneReply: HTMLDivElement, onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): number {
|
||||
const el =
|
||||
<HTMLAnchorElement>
|
||||
elOneReply.getElementsByClassName('forum-to-reply-comment')[0]
|
||||
const commentPk = parseInt(el.dataset.forumLinkTo ?? '-1')
|
||||
if (commentPk === -1) throw new Error('forumLinkTo on reply not found')
|
||||
this.oneReplies.set(commentPk, elOneReply)
|
||||
el.addEventListener('mouseenter', commentHighlightOn)
|
||||
el.addEventListener('mouseleave', commentHighlightOff)
|
||||
el.addEventListener('focusin', commentHighlightOn)
|
||||
el.addEventListener('focusout', commentHighlightOff)
|
||||
el.addEventListener('click', onClickJumpToComment)
|
||||
return commentPk
|
||||
}
|
||||
|
||||
private teardownOneReply(el: HTMLDivElement): void {
|
||||
const replyLink = <HTMLAnchorElement>
|
||||
el.getElementsByClassName('forum-to-reply-comment')[0]
|
||||
replyLink.removeEventListener(
|
||||
'mouseenter', this.boundFunctions.commentHighlightOn)
|
||||
replyLink.removeEventListener(
|
||||
'mouseleave', this.boundFunctions.commentHighlightOff)
|
||||
replyLink.removeEventListener(
|
||||
'focusin', this.boundFunctions.commentHighlightOn)
|
||||
replyLink.removeEventListener(
|
||||
'focusout', this.boundFunctions.commentHighlightOff)
|
||||
replyLink.removeEventListener(
|
||||
'click', this.boundFunctions.onClickJumpToComment)
|
||||
}
|
||||
|
||||
private initializePrevComment(prevCommentLink: HTMLAnchorElement): void {
|
||||
this.elPrevCommentLink = prevCommentLink
|
||||
this.elPrevCommentLink.addEventListener(
|
||||
'mouseenter', this.boundFunctions.commentHighlightOn)
|
||||
this.elPrevCommentLink.addEventListener(
|
||||
'mouseleave', this.boundFunctions.commentHighlightOff)
|
||||
this.elPrevCommentLink.addEventListener(
|
||||
'focusin', this.boundFunctions.commentHighlightOn)
|
||||
this.elPrevCommentLink.addEventListener(
|
||||
'focusout', this.boundFunctions.commentHighlightOff)
|
||||
this.elPrevCommentLink.addEventListener(
|
||||
'click', this.boundFunctions.onClickJumpToComment)
|
||||
}
|
||||
|
||||
private initPopoverLinks(): void {
|
||||
const topicSlug = this.rootEl.dataset.forumTopicSlug
|
||||
const topicSlug = this.elRoot.dataset.forumTopicSlug
|
||||
if (typeof topicSlug !== 'string') return
|
||||
if (this.actionsPopoverReferences?.elExpandCommentsDown) {
|
||||
this.actionsPopoverReferences.elExpandCommentsDown.href =
|
||||
|
@ -220,32 +288,28 @@ export class OneComment {
|
|||
}
|
||||
}
|
||||
|
||||
private getContentCommentActions(
|
||||
el: ForumPopoverEl<HTMLButtonElement>
|
||||
): HTMLElement {
|
||||
getContentCommentActions(): HTMLElement {
|
||||
const elRoot = <HTMLDivElement>elCommentActionTemplate.cloneNode(true)
|
||||
this.actionsPopoverReferences = {
|
||||
elContentRoot: elRoot
|
||||
}
|
||||
this.actionsPopoverReferences = { elContentRoot: elRoot }
|
||||
const elAnchorEcd =
|
||||
<HTMLAnchorElement>
|
||||
elRoot.getElementsByClassName('expand-comments-down')[0]
|
||||
elRoot.getElementsByClassName('expand-comments-down').item(0)
|
||||
if (this.elPrevCommentLink) {
|
||||
elAnchorEcd.getElementsByClassName('icon')[0]
|
||||
.replaceWith(getSvgIcon(mdiArrowLeft))
|
||||
elAnchorEcd.getElementsByClassName('icon').item(0)
|
||||
?.replaceWith(getSvgIcon(mdiArrowLeft))
|
||||
this.actionsPopoverReferences.elExpandCommentsDown = elAnchorEcd
|
||||
} else elAnchorEcd.remove()
|
||||
const elAnchorEcu =
|
||||
<HTMLAnchorElement>
|
||||
elRoot.getElementsByClassName('expand-comments-up')[0]
|
||||
elRoot.getElementsByClassName('expand-comments-up').item(0)
|
||||
const elAnchorEcur =
|
||||
<HTMLAnchorElement>
|
||||
elRoot.getElementsByClassName('expand-comments-up-recursive')[0]
|
||||
elRoot.getElementsByClassName('expand-comments-up-recursive').item(0)
|
||||
if (this.oneReplies.size) {
|
||||
elAnchorEcu.getElementsByClassName('icon')[0]
|
||||
.replaceWith(getSvgIcon(mdiArrowRight))
|
||||
elAnchorEcur.getElementsByClassName('icon')[0]
|
||||
.replaceWith(getSvgIcon(myArrowRightDouble))
|
||||
elAnchorEcu.getElementsByClassName('icon').item(0)
|
||||
?.replaceWith(getSvgIcon(mdiArrowRight))
|
||||
elAnchorEcur.getElementsByClassName('icon').item(0)
|
||||
?.replaceWith(getSvgIcon(myArrowRightDouble))
|
||||
this.actionsPopoverReferences.elExpandCommentsUp = elAnchorEcu
|
||||
this.actionsPopoverReferences.elExpandCommentsUpRecursive = elAnchorEcur
|
||||
} else {
|
||||
|
@ -254,27 +318,25 @@ export class OneComment {
|
|||
}
|
||||
const elAnchorEcit =
|
||||
<HTMLAnchorElement>
|
||||
elRoot.getElementsByClassName('expand-comments-in-thread')[0]
|
||||
elRoot.getElementsByClassName('expand-comments-in-thread').item(0)
|
||||
if (this.oneReplies.size || this.elPrevCommentLink) {
|
||||
elAnchorEcit.getElementsByClassName('icon')[0]
|
||||
.replaceWith(getSvgIcon(mdiArrowLeftRight))
|
||||
elAnchorEcit.getElementsByClassName('icon').item(0)
|
||||
?.replaceWith(getSvgIcon(mdiArrowLeftRight))
|
||||
this.actionsPopoverReferences.elExpandCommentsInThread = elAnchorEcit
|
||||
} else elAnchorEcit.remove()
|
||||
this.initPopoverLinks()
|
||||
return elRoot
|
||||
}
|
||||
|
||||
private initializeActionsPopover(prevCommentLink?: HTMLAnchorElement): void {
|
||||
if (this.oneReplies.size === 0 && !prevCommentLink) {
|
||||
this.elBtnCommentActions.remove()
|
||||
private initializeActionsPopover(): void {
|
||||
if (this.oneReplies.size === 0 && !this.elPrevCommentLink) {
|
||||
this.elBtnCommentActions.hidden = true
|
||||
return
|
||||
}
|
||||
this.elBtnCommentActions.replaceChildren(elSvgCog.cloneNode(true))
|
||||
popoverAdd(this.elBtnCommentActions, {
|
||||
popoverOpts: {
|
||||
content: el => {
|
||||
return this.getContentCommentActions(el)
|
||||
},
|
||||
content: getContentCommentActions,
|
||||
html: true,
|
||||
delay: { show: 500, hide: 500 },
|
||||
sanitize: false,
|
||||
|
@ -282,124 +344,51 @@ export class OneComment {
|
|||
container: <HTMLDivElement>this.elBtnCommentActions.parentElement
|
||||
},
|
||||
callbacks: {
|
||||
onTipInserted: el => {
|
||||
this.onPopoverTipInsertedCommentAction(el)
|
||||
},
|
||||
onTipRemoved: el => {
|
||||
this.onPopoverTipRemovedCommentAction(el)
|
||||
}
|
||||
onTipInserted: onPopoverTipInsertedCommentAction,
|
||||
onTipRemoved: onPopoverTipRemovedCommentAction
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private initializeCallbacks(): void {
|
||||
private initializePrevComment(
|
||||
el: HTMLAnchorElement, onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): void {
|
||||
el.addEventListener('mouseenter', commentHighlightOn)
|
||||
el.addEventListener('mouseleave', commentHighlightOff)
|
||||
el.addEventListener('focusin', commentHighlightOn)
|
||||
el.addEventListener('focusout', commentHighlightOff)
|
||||
el.addEventListener('click', onClickJumpToComment)
|
||||
this.elPrevCommentLink = el
|
||||
}
|
||||
|
||||
private initializeCallbacks(
|
||||
aLink: string, onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): void {
|
||||
this.elAnchorCommentNumber =
|
||||
<HTMLAnchorElement>
|
||||
this.rootEl.getElementsByClassName('comment-number')[0]
|
||||
this.elAnchorCommentNumber.href =
|
||||
this.boundFunctions.getALink(this.commentPk)
|
||||
this.elAnchorCommentNumber.addEventListener(
|
||||
'click', this.boundFunctions.onClickCommentNumber)
|
||||
this.elRoot.getElementsByClassName('comment-number').item(0)
|
||||
this.elAnchorCommentNumber.href = aLink
|
||||
this.elAnchorCommentNumber.addEventListener('click', onClickCommentNumber)
|
||||
const prevCommentLink = <HTMLAnchorElement | undefined>
|
||||
this.rootEl.getElementsByClassName('forum-comment-prevcomment')[0]
|
||||
this.elRoot.getElementsByClassName('forum-comment-prevcomment').item(0)
|
||||
if (prevCommentLink instanceof HTMLAnchorElement) {
|
||||
this.initializePrevComment(prevCommentLink)
|
||||
this.initializePrevComment(
|
||||
prevCommentLink, onClickJumpToComment, commentHighlightOn,
|
||||
commentHighlightOff)
|
||||
}
|
||||
const replies = <HTMLCollectionOf<HTMLDivElement>>
|
||||
this.rootEl.getElementsByClassName('forum-topic-comment-onereply')
|
||||
this.elRoot.getElementsByClassName('forum-topic-comment-onereply')
|
||||
for (const oneReplyEl of replies) {
|
||||
const commentPk = this.initializeOneReply(oneReplyEl)
|
||||
const commentPk = this.initializeOneReply(
|
||||
oneReplyEl, onClickJumpToComment, commentHighlightOn,
|
||||
commentHighlightOff)
|
||||
this.oneReplies.set(commentPk, oneReplyEl)
|
||||
}
|
||||
this.initializeActionsPopover(prevCommentLink)
|
||||
}
|
||||
|
||||
private teardownVotingValue(): void {
|
||||
const elRoot = this.rootEl.getElementsByClassName('voting-value')[0]
|
||||
if (!(elRoot instanceof HTMLAnchorElement)) return
|
||||
votingValueTeardown(elRoot)
|
||||
}
|
||||
|
||||
teardown(): void {
|
||||
this.elAnchorCommentNumber.removeEventListener(
|
||||
'click', this.boundFunctions.onClickCommentNumber)
|
||||
if (this.elPrevCommentLink) {
|
||||
this.elPrevCommentLink.removeEventListener(
|
||||
'mouseenter', this.boundFunctions.commentHighlightOn)
|
||||
this.elPrevCommentLink.removeEventListener(
|
||||
'mouseleave', this.boundFunctions.commentHighlightOff)
|
||||
this.elPrevCommentLink.removeEventListener(
|
||||
'focusin', this.boundFunctions.commentHighlightOn)
|
||||
this.elPrevCommentLink.removeEventListener(
|
||||
'focusout', this.boundFunctions.commentHighlightOff)
|
||||
this.elPrevCommentLink.removeEventListener(
|
||||
'click', this.boundFunctions.onClickJumpToComment)
|
||||
}
|
||||
for (const el of this.oneReplies.values()) this.teardownOneReply(el)
|
||||
this.oneReplies.clear()
|
||||
this.teardownVotingValue()
|
||||
if (this.elBtnCommentActions.parentElement) {
|
||||
popoverTeardown(this.elBtnCommentActions)
|
||||
}
|
||||
}
|
||||
|
||||
flashHighlight(): void {
|
||||
requestAnimationFrame(() => {
|
||||
this.highlightOn()
|
||||
requestAnimationFrame(() => {
|
||||
this.highlightOff()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
highlightOn(): void {
|
||||
this.rootEl.classList.add(highlightedClass)
|
||||
}
|
||||
|
||||
highlightOff(): void {
|
||||
this.rootEl.classList.remove(highlightedClass)
|
||||
}
|
||||
|
||||
jumpToSelf(onFinishCallback?: () => void): void {
|
||||
document.addEventListener('scrollend', () => {
|
||||
if (document.activeElement instanceof HTMLAnchorElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
}, { once: true })
|
||||
if (onFinishCallback) {
|
||||
document.addEventListener('scrollend', onFinishCallback, { once: true })
|
||||
}
|
||||
scroll({ top: getScrollTop(this.rootEl), behavior: 'auto' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return `true` if at least 1/3 of the comment body is visible.
|
||||
*/
|
||||
isInViewport(zeroPoint: number, viewportHeight: number): boolean {
|
||||
const bodyInfo = this.elBody.getBoundingClientRect()
|
||||
const relBodyBottomY = bodyInfo.top + bodyInfo.height
|
||||
// Comment is fully outside of the viewport
|
||||
if (
|
||||
relBodyBottomY <= zeroPoint || bodyInfo.top >= viewportHeight
|
||||
) return false
|
||||
// Top inside
|
||||
if (bodyInfo.top >= zeroPoint) return true
|
||||
// Top outside, bottom inside and above 60% height
|
||||
if (relBodyBottomY >= viewportHeight * 0.6) return true
|
||||
// Bottom inside but below 60% height
|
||||
const relThirdBottomY = bodyInfo.top + bodyInfo.height / 3
|
||||
// True if the top 1/3 of the comment body is inside
|
||||
return relThirdBottomY > zeroPoint
|
||||
}
|
||||
|
||||
selectAsFocused(): void {
|
||||
if (this.rootEl.classList.contains('is-currently-focused')) return
|
||||
this.rootEl.classList.add('is-currently-focused')
|
||||
}
|
||||
|
||||
deselectAsFocused(): void {
|
||||
if (!this.rootEl.classList.contains('is-currently-focused')) return
|
||||
this.rootEl.classList.remove('is-currently-focused')
|
||||
this.initializeActionsPopover()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,6 +401,66 @@ function initCommentActionTemplate(): void {
|
|||
extractTemplateContent(elCommentActionTemplateStr)
|
||||
}
|
||||
|
||||
function teardownOneReply(
|
||||
el: HTMLDivElement, onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): void {
|
||||
const replyLink =
|
||||
<HTMLAnchorElement>
|
||||
el.getElementsByClassName('forum-to-reply-comment').item(0)
|
||||
replyLink.removeEventListener('mouseenter', commentHighlightOn)
|
||||
replyLink.removeEventListener('mouseleave', commentHighlightOff)
|
||||
replyLink.removeEventListener('focusin', commentHighlightOn)
|
||||
replyLink.removeEventListener('focusout', commentHighlightOff)
|
||||
replyLink.removeEventListener('click', onClickJumpToComment)
|
||||
}
|
||||
|
||||
function teardownVotingValue(obj: OneComment): void {
|
||||
const elRoot = obj.elRoot.getElementsByClassName('voting-value')[0]
|
||||
if (!(elRoot instanceof HTMLAnchorElement)) return
|
||||
votingValueTeardown(elRoot)
|
||||
}
|
||||
|
||||
export function teardownOnecomment(
|
||||
elRoot: ElOnecommentType, onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): void {
|
||||
const obj = elRoot.forumOneComment
|
||||
obj.elAnchorCommentNumber.removeEventListener('click', onClickCommentNumber)
|
||||
const elPrevComment = obj.elPrevCommentLink
|
||||
if (elPrevComment) {
|
||||
elPrevComment.removeEventListener('mouseenter', commentHighlightOn)
|
||||
elPrevComment.removeEventListener('mouseleave', commentHighlightOff)
|
||||
elPrevComment.removeEventListener('focusin', commentHighlightOn)
|
||||
elPrevComment.removeEventListener('focusout', commentHighlightOff)
|
||||
elPrevComment.removeEventListener('click', onClickJumpToComment)
|
||||
}
|
||||
for (const el of obj.oneReplies.values()) {
|
||||
teardownOneReply(
|
||||
el, onClickJumpToComment, commentHighlightOn, commentHighlightOff)
|
||||
}
|
||||
obj.oneReplies.clear()
|
||||
teardownVotingValue(obj)
|
||||
if (!obj.elBtnCommentActions.hidden) {
|
||||
popoverTeardown(obj.elBtnCommentActions)
|
||||
}
|
||||
}
|
||||
|
||||
export function initElonecomment(
|
||||
elRoot: HTMLElement, commentPk: number, aLink: string,
|
||||
onClickJumpToComment: (ev: MouseEvent) => void,
|
||||
commentHighlightOn: (ev: Event) => void,
|
||||
commentHighlightOff: (ev: Event) => void
|
||||
): ElOnecommentType {
|
||||
const elRootAdded = <ElOnecommentType>elRoot
|
||||
elRootAdded.forumOneComment = new OneComment(
|
||||
elRoot, commentPk, aLink, onClickJumpToComment, commentHighlightOn,
|
||||
commentHighlightOff)
|
||||
return elRootAdded
|
||||
}
|
||||
|
||||
export function init(): void {
|
||||
elSvgCog = getSvgIcon(mdiCogOutline)
|
||||
initCommentActionTemplate()
|
||||
|
|
|
@ -74,8 +74,12 @@ export class Paginator {
|
|||
if (this.isLoading) return
|
||||
this.isLoading = true
|
||||
const liEls = this.options.root.getElementsByClassName('page-numbered')
|
||||
for (const liEl of liEls) liEl.classList.add('disabled')
|
||||
elClicked?.replaceChildren(loaderTemplate.cloneNode(true))
|
||||
for (const liEl of liEls) {
|
||||
liEl.classList.add('disabled')
|
||||
if (liEl.firstElementChild === elClicked) {
|
||||
elClicked.replaceChildren(loaderTemplate.cloneNode(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsetLoading(elClicked: HTMLAnchorElement): void {
|
||||
|
|
Loading…
Reference in a new issue