Tims-Chat/files_wcf/js/Bastelstu.be/Chat/Log.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

276 lines
6.5 KiB
JavaScript
Raw Normal View History

2018-08-16 22:30:59 +00:00
/*
2021-02-04 22:04:35 +00:00
* Copyright (c) 2010-2021 Tim Düsterhus.
2018-08-16 22:30:59 +00:00
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
2022-03-04 20:21:52 +00:00
* Change Date: 2026-03-04
2018-08-16 22:30:59 +00:00
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
define([
'./console',
'Bastelstu.be/bottle',
'WoltLabSuite/Core/Core',
'./Message',
'./Messenger',
'./ProfileStore',
'./Room',
'./Template',
'./Ui/Log',
'./Ui/MessageStream',
'./Ui/MessageActions/Delete',
], function (
console,
Bottle,
Core,
Message,
Messenger,
ProfileStore,
Room,
Template,
Ui,
UiMessageStream,
UiMessageActionDelete
) {
'use strict'
const loader = Symbol('loader')
class Log {
constructor(params, config) {
console.debug('ChatLog.constructor', 'Constructing …')
this.config = config
this.sessionID = Core.getUuid()
this.bottle = new Bottle()
this.bottle.value('bottle', this.bottle)
this.bottle.value('config', config)
this.bottle.constant('sessionID', this.sessionID)
this.bottle.constant('roomID', params.roomID)
// Register chat components
this.service('Messenger', Messenger)
this.service('ProfileStore', ProfileStore)
this.service('Room', Room)
// Register UI components
this.service('Ui', Ui)
this.service('UiMessageActionDelete', UiMessageActionDelete)
this.service('UiMessageStream', UiMessageStream)
// Register Models
this.bottle.instanceFactory('Message', (container, m) => {
return new Message(container.MessageType, m)
})
// Register Templates
const selector = [
'[type="x-text/template"]',
'[data-application="be.bastelstu.chat"]',
'[data-template-name]',
].join('')
const templates = elBySelAll(selector)
templates.forEach(
function (template) {
this.bottle.factory(
`Template.${elData(template, 'template-name')}`,
function (container) {
const includeNames = (elData(template, 'template-includes') || '')
.split(/ /)
.filter((item) => item !== '')
const includes = {}
includeNames.forEach((item) => (includes[item] = container[item]))
2020-11-01 16:41:19 +00:00
2018-08-16 22:30:59 +00:00
return new Template(template.textContent, includes)
}
)
}.bind(this)
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
// Register MessageTypes
const messageTypes = Object.entries(this.config.messageTypes)
messageTypes.forEach(([objectType, messageType]) => {
const MessageType = require(messageType.module)
this.bottle.factory(
`MessageType.${objectType.replace(/\./g, '-')}`,
(_) => {
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
return new MessageType(...deps, objectType)
}
)
})
this.knows = { from: undefined, to: undefined }
this.messageSinks = new Set()
this.params = params
this.pulling = false
}
service(name, _constructor, args = []) {
this.bottle.factory(name, function (container) {
const deps = (_constructor.DEPENDENCIES || []).map(
(dep) => container[dep]
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
return new _constructor(...deps, ...args)
})
}
async bootstrap() {
console.debug('ChatLog.bootstrap', 'Initializing …')
this.ui = this.bottle.container.Ui
this.ui.bootstrap()
this.registerMessageSink(this.bottle.container.UiMessageStream)
if (this.params.messageID > 0) {
await Promise.all([
this.pull(undefined, this.params.messageID),
this.pull(this.params.messageID + 1),
])
} else {
await this.pull()
}
this.bottle.container.UiMessageStream.on(
'nearTop',
this.pullOlder.bind(this)
)
this.bottle.container.UiMessageStream.on(
'reachedTop',
this.pullOlder.bind(this)
)
this.bottle.container.UiMessageStream.on(
'nearBottom',
this.pullNewer.bind(this)
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
this.bottle.container.UiMessageStream.on(
'reachedBottom',
this.pullNewer.bind(this)
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
const element = document.querySelector(
`#message-${this.params.messageID}`
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
// Force changing the hash to trigger a new lookup of the element.
// At least Chrome wont target an element if it is not in the DOM
// on the initial page load with an hash set.
window.location.hash = ''
window.location.hash = `message-${this.params.messageID}`
if (element && element.scrollIntoView) element.scrollIntoView()
return this
}
registerMessageSink(sink) {
if (typeof sink.ingest !== 'function') {
throw new Error('The given sink does not provide a .ingest function.')
}
this.messageSinks.add(sink)
}
unregisterMessageSink(sink) {
this.messageSinks.delete(sink)
}
async pull(from, to) {
try {
await this.handlePull(await this.performPull(from, to))
} catch (e) {
this.handleError(e)
}
}
async pullOlder() {
if (this.pulling) return
if (this.knows.from <= 1) return
this.pulling = true
await this.pull(undefined, this.knows.from - 1)
this.pulling = false
}
async pullNewer() {
if (this.pulling) return
this.pulling = true
await this.pull(this.knows.to + 1)
this.pulling = false
}
async performPull(from = undefined, to = undefined) {
console.debug(
'ChatLog.performPull',
`Pulling new messages; from: ${
from !== undefined ? from : 'undefined'
}, to: ${to !== undefined ? to : 'undefined'}`
)
return this.bottle.container.Messenger.pull(from, to, true)
}
handleError(error) {
console.debug('ChatLog.handleError', `Request failed`)
console.debugException(error)
}
async handlePull(payload) {
console.debug('ChatLog.handlePull', payload)
// Null range: No messages satisfy the constraints
if (payload.from > payload.to) return
let messages = payload.messages
if (this.knows.from !== undefined && this.knows.to !== undefined) {
messages = messages.filter(
function (message) {
return !(
this.knows.from <= message.messageID &&
message.messageID <= this.knows.to
)
}.bind(this)
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
}
if (this.knows.from === undefined || payload.from < this.knows.from)
this.knows.from = payload.from
if (this.knows.to === undefined || payload.to > this.knows.to)
this.knows.to = payload.to
await Promise.all(
messages.map((message) => {
return message.getMessageType().preProcess(message)
})
2020-11-01 16:41:19 +00:00
)
2018-08-16 22:30:59 +00:00
const userIDs = messages
.map((message) => message.userID)
.filter((userID) => userID !== null)
await this.bottle.container.ProfileStore.ensureUsersByIDs(userIDs)
this.messageSinks.forEach((sink) => sink.ingest(messages))
}
}
return Log
})