mirror of
https://github.com/wbbaddons/Tims-Chat.git
synced 2024-12-29 22:40:09 +00:00
Run prettier on all files
This commit is contained in:
parent
cfe91be22d
commit
3a88e73ee1
@ -11,54 +11,85 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Chat/console'
|
||||
, 'Bastelstu.be/bottle'
|
||||
, 'Bastelstu.be/_Push'
|
||||
, 'WoltLabSuite/Core/Core'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/Timer/Repeating'
|
||||
, 'WoltLabSuite/Core/User'
|
||||
, './Chat/Autocompleter'
|
||||
, './Chat/CommandHandler'
|
||||
, './Chat/DataStructure/Throttle'
|
||||
, './Chat/Message'
|
||||
, './Chat/Messenger'
|
||||
, './Chat/ParseError'
|
||||
, './Chat/ProfileStore'
|
||||
, './Chat/Room'
|
||||
, './Chat/Template'
|
||||
, './Chat/Ui/Attachment/Upload'
|
||||
, './Chat/Ui/AutoAway'
|
||||
, './Chat/Ui/Chat'
|
||||
, './Chat/Ui/ConnectionWarning'
|
||||
, './Chat/Ui/ErrorDialog'
|
||||
, './Chat/Ui/Input'
|
||||
, './Chat/Ui/Input/Autocompleter'
|
||||
, './Chat/Ui/MessageStream'
|
||||
, './Chat/Ui/MessageActions/Delete'
|
||||
, './Chat/Ui/Mobile'
|
||||
, './Chat/Ui/Notification'
|
||||
, './Chat/Ui/ReadMarker'
|
||||
, './Chat/Ui/Settings'
|
||||
, './Chat/Ui/Topic'
|
||||
, './Chat/Ui/UserActionDropdownHandler'
|
||||
, './Chat/Ui/UserList'
|
||||
], function (console, Bottle, Push, Core, Language, RepeatingTimer, CoreUser, Autocompleter,
|
||||
CommandHandler, Throttle, Message, Messenger, ParseError, ProfileStore, Room, Template, UiAttachmentUpload, UiAutoAway, Ui,
|
||||
UiConnectionWarning, ErrorDialog, UiInput, UiInputAutocompleter, UiMessageStream, UiMessageActionDelete, UiMobile, UiNotification,
|
||||
UiReadMarker, UiSettings, UiTopic, UiUserActionDropdownHandler, UiUserList) {
|
||||
"use strict";
|
||||
define([
|
||||
'./Chat/console',
|
||||
'Bastelstu.be/bottle',
|
||||
'Bastelstu.be/_Push',
|
||||
'WoltLabSuite/Core/Core',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/Timer/Repeating',
|
||||
'WoltLabSuite/Core/User',
|
||||
'./Chat/Autocompleter',
|
||||
'./Chat/CommandHandler',
|
||||
'./Chat/DataStructure/Throttle',
|
||||
'./Chat/Message',
|
||||
'./Chat/Messenger',
|
||||
'./Chat/ParseError',
|
||||
'./Chat/ProfileStore',
|
||||
'./Chat/Room',
|
||||
'./Chat/Template',
|
||||
'./Chat/Ui/Attachment/Upload',
|
||||
'./Chat/Ui/AutoAway',
|
||||
'./Chat/Ui/Chat',
|
||||
'./Chat/Ui/ConnectionWarning',
|
||||
'./Chat/Ui/ErrorDialog',
|
||||
'./Chat/Ui/Input',
|
||||
'./Chat/Ui/Input/Autocompleter',
|
||||
'./Chat/Ui/MessageStream',
|
||||
'./Chat/Ui/MessageActions/Delete',
|
||||
'./Chat/Ui/Mobile',
|
||||
'./Chat/Ui/Notification',
|
||||
'./Chat/Ui/ReadMarker',
|
||||
'./Chat/Ui/Settings',
|
||||
'./Chat/Ui/Topic',
|
||||
'./Chat/Ui/UserActionDropdownHandler',
|
||||
'./Chat/Ui/UserList',
|
||||
], function (
|
||||
console,
|
||||
Bottle,
|
||||
Push,
|
||||
Core,
|
||||
Language,
|
||||
RepeatingTimer,
|
||||
CoreUser,
|
||||
Autocompleter,
|
||||
CommandHandler,
|
||||
Throttle,
|
||||
Message,
|
||||
Messenger,
|
||||
ParseError,
|
||||
ProfileStore,
|
||||
Room,
|
||||
Template,
|
||||
UiAttachmentUpload,
|
||||
UiAutoAway,
|
||||
Ui,
|
||||
UiConnectionWarning,
|
||||
ErrorDialog,
|
||||
UiInput,
|
||||
UiInputAutocompleter,
|
||||
UiMessageStream,
|
||||
UiMessageActionDelete,
|
||||
UiMobile,
|
||||
UiNotification,
|
||||
UiReadMarker,
|
||||
UiSettings,
|
||||
UiTopic,
|
||||
UiUserActionDropdownHandler,
|
||||
UiUserList
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
class Chat {
|
||||
constructor(roomID, config) {
|
||||
console.debug('Chat.constructor', 'Constructing …')
|
||||
|
||||
this.config = config
|
||||
this.config = config
|
||||
|
||||
this.sessionID = Core.getUuid()
|
||||
this.sessionID = Core.getUuid()
|
||||
|
||||
// Setup Bottle containers
|
||||
this.bottle = new Bottle()
|
||||
this.bottle = new Bottle()
|
||||
this.bottle.value('bottle', this.bottle)
|
||||
this.bottle.value('config', config)
|
||||
this.bottle.constant('sessionID', this.sessionID)
|
||||
@ -94,79 +125,105 @@ define([ './Chat/console'
|
||||
})
|
||||
|
||||
// Register Templates
|
||||
const selector = [ '[type="x-text/template"]'
|
||||
, '[data-application="be.bastelstu.chat"]'
|
||||
, '[data-template-name]'
|
||||
].join('')
|
||||
const selector = [
|
||||
'[type="x-text/template"]',
|
||||
'[data-application="be.bastelstu.chat"]',
|
||||
'[data-template-name]',
|
||||
].join('')
|
||||
|
||||
const templates = elBySelAll(selector)
|
||||
|
||||
Array.prototype.forEach.call(templates, (function (template) {
|
||||
this.bottle.factory(`Template.${template.dataset.templateName}`, function (container) {
|
||||
const includeNames = (template.dataset.templateIncludes || '').split(/ /).filter(item => item !== "")
|
||||
const includes = { }
|
||||
includeNames.forEach(item => includes[item] = container[item])
|
||||
Array.prototype.forEach.call(
|
||||
templates,
|
||||
function (template) {
|
||||
this.bottle.factory(
|
||||
`Template.${template.dataset.templateName}`,
|
||||
function (container) {
|
||||
const includeNames = (template.dataset.templateIncludes || '')
|
||||
.split(/ /)
|
||||
.filter((item) => item !== '')
|
||||
const includes = {}
|
||||
includeNames.forEach((item) => (includes[item] = container[item]))
|
||||
|
||||
return new Template(template.textContent, includes)
|
||||
})
|
||||
}).bind(this))
|
||||
return new Template(template.textContent, includes)
|
||||
}
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
// Register MessageTypes
|
||||
Object.entries(this.config.messageTypes)
|
||||
.forEach(([ objectType, messageType ]) => {
|
||||
const MessageType = require(messageType.module)
|
||||
Object.entries(this.config.messageTypes).forEach(
|
||||
([objectType, messageType]) => {
|
||||
const MessageType = require(messageType.module)
|
||||
|
||||
this.bottle.factory(`MessageType.${objectType.replace(/\./g, '-')}`, _ => {
|
||||
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
|
||||
this.bottle.factory(
|
||||
`MessageType.${objectType.replace(/\./g, '-')}`,
|
||||
(_) => {
|
||||
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
|
||||
|
||||
return new MessageType(...deps, objectType)
|
||||
})
|
||||
})
|
||||
return new MessageType(...deps, objectType)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// Register Commands
|
||||
Object.values(this.config.commands).forEach(command => {
|
||||
Object.values(this.config.commands).forEach((command) => {
|
||||
const Command = require(command.module)
|
||||
|
||||
this.bottle.factory(`Command.${command.package.replace(/\./g, '-')}:${command.identifier}`, _ => {
|
||||
const deps = this.bottle.digest(Command.DEPENDENCIES || [])
|
||||
this.bottle.factory(
|
||||
`Command.${command.package.replace(/\./g, '-')}:${
|
||||
command.identifier
|
||||
}`,
|
||||
(_) => {
|
||||
const deps = this.bottle.digest(Command.DEPENDENCIES || [])
|
||||
|
||||
return new Command(...deps, command)
|
||||
})
|
||||
return new Command(...deps, command)
|
||||
}
|
||||
)
|
||||
})
|
||||
this.bottle.constant('Trigger', new Map(Object.entries(this.config.triggers).map(([ trigger, commandID ]) => {
|
||||
const command = this.config.commands[commandID]
|
||||
const key = [ command.package, command.identifier ]
|
||||
return [ trigger, key ]
|
||||
})))
|
||||
this.bottle.constant(
|
||||
'Trigger',
|
||||
new Map(
|
||||
Object.entries(this.config.triggers).map(([trigger, commandID]) => {
|
||||
const command = this.config.commands[commandID]
|
||||
const key = [command.package, command.identifier]
|
||||
return [trigger, key]
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
// Register Settings
|
||||
Array.from(elBySelAll('#chatQuickSettingsNavigation .button[data-module]')).forEach(item => {
|
||||
Array.from(
|
||||
elBySelAll('#chatQuickSettingsNavigation .button[data-module]')
|
||||
).forEach((item) => {
|
||||
const Button = require(item.dataset.module)
|
||||
|
||||
this.bottle.instanceFactory(`UiSettingsButton.${item.dataset.module.replace(/\./g, '-')}`, (_, element) => {
|
||||
const deps = this.bottle.digest(Button.DEPENDENCIES || [])
|
||||
return new Button(element, ...deps)
|
||||
})
|
||||
this.bottle.instanceFactory(
|
||||
`UiSettingsButton.${item.dataset.module.replace(/\./g, '-')}`,
|
||||
(_, element) => {
|
||||
const deps = this.bottle.digest(Button.DEPENDENCIES || [])
|
||||
return new Button(element, ...deps)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
this.knows = { from: undefined
|
||||
, to: undefined
|
||||
}
|
||||
this.knows = { from: undefined, to: undefined }
|
||||
|
||||
this.processMessagesThrottled = Throttle(this.processMessages.bind(this))
|
||||
this.queuedMessages = [ ]
|
||||
this.queuedMessages = []
|
||||
this.messageSinks = new Set()
|
||||
|
||||
this.pullTimer = undefined
|
||||
this.pullTimer = undefined
|
||||
this.pullUserListTimer = undefined
|
||||
this.pushConnected = false
|
||||
this.pushConnected = false
|
||||
|
||||
this.firstFailure = null
|
||||
}
|
||||
|
||||
service(name, _constructor, args = [ ]) {
|
||||
this.bottle.factory(name, _ => {
|
||||
const deps = this.bottle.digest(_constructor.DEPENDENCIES || [ ])
|
||||
service(name, _constructor, args = []) {
|
||||
this.bottle.factory(name, (_) => {
|
||||
const deps = this.bottle.digest(_constructor.DEPENDENCIES || [])
|
||||
|
||||
return new _constructor(...deps, ...args)
|
||||
})
|
||||
@ -187,42 +244,58 @@ define([ './Chat/console'
|
||||
await this.bottle.container.Room.join()
|
||||
|
||||
// Bind unload event to leave the Chat
|
||||
window.addEventListener('unload', this.bottle.container.Room.leave.bind(this.bottle.container.Room, true))
|
||||
document.addEventListener('visibilitychange', _ => {
|
||||
window.addEventListener(
|
||||
'unload',
|
||||
this.bottle.container.Room.leave.bind(this.bottle.container.Room, true)
|
||||
)
|
||||
document.addEventListener('visibilitychange', (_) => {
|
||||
this.processMessagesThrottled.setDelay(document.hidden ? 10000 : 125)
|
||||
})
|
||||
|
||||
this.pullTimer = new RepeatingTimer(Throttle(this.pullMessages.bind(this)), this.config.reloadTime * 1e3)
|
||||
this.pullTimer = new RepeatingTimer(
|
||||
Throttle(this.pullMessages.bind(this)),
|
||||
this.config.reloadTime * 1e3
|
||||
)
|
||||
|
||||
Push.onConnect(_ => {
|
||||
Push.onConnect((_) => {
|
||||
console.debug('Chat.bootstrap', 'Push connected')
|
||||
this.pushConnected = true
|
||||
this.pullTimer.setDelta(30e3)
|
||||
}).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
.catch(error => { console.debug(error) })
|
||||
|
||||
Push.onDisconnect(_ => {
|
||||
Push.onDisconnect((_) => {
|
||||
console.debug('Chat.bootstrap', 'Push disconnected')
|
||||
this.pushConnected = false
|
||||
this.pullTimer.setDelta(this.config.reloadTime * 1e3)
|
||||
}).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
.catch(error => { console.debug(error) })
|
||||
|
||||
Push.onMessage('be.bastelstu.chat.message', this.pullMessages.bind(this))
|
||||
.catch(error => { console.debug(error) })
|
||||
Push.onMessage(
|
||||
'be.bastelstu.chat.message',
|
||||
this.pullMessages.bind(this)
|
||||
).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
|
||||
// Fetch user list every 60 seconds
|
||||
// This acts as a safety net: It should be kept current by messages whenever possible.
|
||||
this.pullUserListTimer = new RepeatingTimer(this.updateUsers.bind(this), 60e3)
|
||||
this.pullUserListTimer = new RepeatingTimer(
|
||||
this.updateUsers.bind(this),
|
||||
60e3
|
||||
)
|
||||
|
||||
this.registerMessageSink(this.bottle.container.UiMessageStream)
|
||||
this.registerMessageSink(this.bottle.container.UiNotification)
|
||||
this.registerMessageSink(this.bottle.container.UiAutoAway)
|
||||
|
||||
await Promise.all([ this.pullMessages()
|
||||
, this.updateUsers()
|
||||
, this.bottle.container.ProfileStore.ensureUsersByIDs([ CoreUser.userId ])
|
||||
])
|
||||
await Promise.all([
|
||||
this.pullMessages(),
|
||||
this.updateUsers(),
|
||||
this.bottle.container.ProfileStore.ensureUsersByIDs([CoreUser.userId]),
|
||||
])
|
||||
|
||||
return this
|
||||
}
|
||||
@ -240,7 +313,11 @@ define([ './Chat/console'
|
||||
}
|
||||
|
||||
hcf(err = undefined) {
|
||||
console.debug('Chat.hcf', 'Gotcha! FIRE was caught! FIRE’s data was newly added to the POKéDEX.', err)
|
||||
console.debug(
|
||||
'Chat.hcf',
|
||||
'Gotcha! FIRE was caught! FIRE’s data was newly added to the POKéDEX.',
|
||||
err
|
||||
)
|
||||
|
||||
this.pullTimer.stop()
|
||||
this.pullUserListTimer.stop()
|
||||
@ -259,47 +336,64 @@ define([ './Chat/console'
|
||||
|
||||
this.markAsBack()
|
||||
|
||||
let [ trigger, parameterString ] = this.bottle.container.CommandHandler.splitCommand(value)
|
||||
let [
|
||||
trigger,
|
||||
parameterString,
|
||||
] = this.bottle.container.CommandHandler.splitCommand(value)
|
||||
let command = null
|
||||
if (trigger === null) {
|
||||
command = this.bottle.container.CommandHandler.getCommandByIdentifier('be.bastelstu.chat', 'plain')
|
||||
}
|
||||
else {
|
||||
command = this.bottle.container.CommandHandler.getCommandByTrigger(trigger)
|
||||
command = this.bottle.container.CommandHandler.getCommandByIdentifier(
|
||||
'be.bastelstu.chat',
|
||||
'plain'
|
||||
)
|
||||
} else {
|
||||
command = this.bottle.container.CommandHandler.getCommandByTrigger(
|
||||
trigger
|
||||
)
|
||||
}
|
||||
|
||||
if (command === null) {
|
||||
this.ui.input.inputError(Language.get('chat.error.triggerNotFound', { trigger }))
|
||||
this.ui.input.inputError(
|
||||
Language.get('chat.error.triggerNotFound', { trigger })
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let parameters
|
||||
try {
|
||||
parameters = this.bottle.container.CommandHandler.applyCommand(command, parameterString)
|
||||
}
|
||||
catch (e) {
|
||||
parameters = this.bottle.container.CommandHandler.applyCommand(
|
||||
command,
|
||||
parameterString
|
||||
)
|
||||
} catch (e) {
|
||||
if (e instanceof ParseError) {
|
||||
e = new Error(Language.get('chat.error.invalidParameters', { data: e.data }))
|
||||
e = new Error(
|
||||
Language.get('chat.error.invalidParameters', { data: e.data })
|
||||
)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
const payload = { commandID: command.id
|
||||
, parameters
|
||||
}
|
||||
const payload = { commandID: command.id, parameters }
|
||||
|
||||
try {
|
||||
await this.bottle.container.Messenger.push(payload)
|
||||
this.ui.input.hideInputError()
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
let seriousError = true
|
||||
if (error.returnValues && error.returnValues.fieldName === 'message' && (error.returnValues.realErrorMessage || error.returnValues.errorType)) {
|
||||
this.ui.input.inputError(error.returnValues.realErrorMessage || error.returnValues.errorType)
|
||||
if (
|
||||
error.returnValues &&
|
||||
error.returnValues.fieldName === 'message' &&
|
||||
(error.returnValues.realErrorMessage ||
|
||||
error.returnValues.errorType)
|
||||
) {
|
||||
this.ui.input.inputError(
|
||||
error.returnValues.realErrorMessage ||
|
||||
error.returnValues.errorType
|
||||
)
|
||||
seriousError = false
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.ui.input.inputError(error.message)
|
||||
}
|
||||
|
||||
@ -314,8 +408,7 @@ define([ './Chat/console'
|
||||
}
|
||||
|
||||
console.debug('Chat.onSubmit', `Done`)
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
this.ui.input.inputError(e.message)
|
||||
}
|
||||
}
|
||||
@ -325,16 +418,23 @@ define([ './Chat/console'
|
||||
if (this.bottle.container.ProfileStore.getSelf().away == null) return
|
||||
console.debug('Chat.markAsBack', `Marking as back`)
|
||||
|
||||
const command = this.bottle.container.CommandHandler.getCommandByIdentifier('be.bastelstu.chat', 'back')
|
||||
return this.bottle.container.Messenger.push({ commandID: command.id, parameters: { } })
|
||||
}
|
||||
catch (err) {
|
||||
const command = this.bottle.container.CommandHandler.getCommandByIdentifier(
|
||||
'be.bastelstu.chat',
|
||||
'back'
|
||||
)
|
||||
return this.bottle.container.Messenger.push({
|
||||
commandID: command.id,
|
||||
parameters: {},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Chat.markAsBack', err)
|
||||
}
|
||||
}
|
||||
|
||||
async onSendAttachment(event) {
|
||||
return this.bottle.container.Messenger.pushAttachment(event.detail.tmpHash)
|
||||
return this.bottle.container.Messenger.pushAttachment(
|
||||
event.detail.tmpHash
|
||||
)
|
||||
}
|
||||
|
||||
onAutocomplete(event) {
|
||||
@ -354,18 +454,23 @@ define([ './Chat/console'
|
||||
}
|
||||
|
||||
async pullMessages() {
|
||||
console.debug('Chat.pullMessages', `Pulling new messages, starting at ${this.knows.to ? this.knows.to + 1 : ''}`)
|
||||
console.debug(
|
||||
'Chat.pullMessages',
|
||||
`Pulling new messages, starting at ${
|
||||
this.knows.to ? this.knows.to + 1 : ''
|
||||
}`
|
||||
)
|
||||
|
||||
let payload
|
||||
try {
|
||||
if (this.knows.to === undefined) {
|
||||
payload = await this.bottle.container.Messenger.pull()
|
||||
} else {
|
||||
payload = await this.bottle.container.Messenger.pull(
|
||||
this.knows.to + 1
|
||||
)
|
||||
}
|
||||
else {
|
||||
payload = await this.bottle.container.Messenger.pull(this.knows.to + 1)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
this.handleError(e)
|
||||
return
|
||||
}
|
||||
@ -386,12 +491,17 @@ define([ './Chat/console'
|
||||
|
||||
if (this.knows.from !== undefined && this.knows.to !== undefined) {
|
||||
messages = messages.filter((message) => {
|
||||
return !(this.knows.from <= message.messageID && message.messageID <= this.knows.to)
|
||||
return !(
|
||||
this.knows.from <= message.messageID &&
|
||||
message.messageID <= this.knows.to
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
this.queuedMessages.push(messages)
|
||||
const end = (performance ? performance : Date).now()
|
||||
@ -402,14 +512,17 @@ define([ './Chat/console'
|
||||
|
||||
handleError(error) {
|
||||
if (this.firstFailure === null) {
|
||||
console.error('Chat.handleError', `Request failed, 30 seconds until shutdown`)
|
||||
console.error(
|
||||
'Chat.handleError',
|
||||
`Request failed, 30 seconds until shutdown`
|
||||
)
|
||||
this.firstFailure = Date.now()
|
||||
this.ui.connectionWarning.show()
|
||||
}
|
||||
|
||||
console.debugException(error)
|
||||
|
||||
if ((Date.now() - this.firstFailure) >= 30e3) {
|
||||
if (Date.now() - this.firstFailure >= 30e3) {
|
||||
console.error('Chat.handleError', ' Failures for 30 seconds, aborting')
|
||||
|
||||
this.hcf(error)
|
||||
@ -419,16 +532,18 @@ define([ './Chat/console'
|
||||
async processMessages() {
|
||||
console.debug('Chat.processMessages', 'Processing messages')
|
||||
const start = (performance ? performance : Date).now()
|
||||
const messages = [ ].concat(...this.queuedMessages)
|
||||
const messages = [].concat(...this.queuedMessages)
|
||||
this.queuedMessages = []
|
||||
|
||||
if (messages.length === 0) return
|
||||
|
||||
await Promise.all(messages.map(async (message) => {
|
||||
this.bottle.container.ProfileStore.pushLastActivity(message.userID)
|
||||
await Promise.all(
|
||||
messages.map(async (message) => {
|
||||
this.bottle.container.ProfileStore.pushLastActivity(message.userID)
|
||||
|
||||
return message.getMessageType().preProcess(message)
|
||||
}))
|
||||
return message.getMessageType().preProcess(message)
|
||||
})
|
||||
)
|
||||
|
||||
const updateUserList = messages.some((message) => {
|
||||
return message.getMessageType().shouldUpdateUserList(message)
|
||||
@ -438,13 +553,19 @@ define([ './Chat/console'
|
||||
this.updateUsers()
|
||||
}
|
||||
|
||||
await this.bottle.container.ProfileStore.ensureUsersByIDs([ ].concat(...messages.map(message => message.getMessageType().getReferencedUsers(message))))
|
||||
await this.bottle.container.ProfileStore.ensureUsersByIDs(
|
||||
[].concat(
|
||||
...messages.map((message) =>
|
||||
message.getMessageType().getReferencedUsers(message)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
messages.forEach((message) => {
|
||||
message.getMessageType().preRender(message)
|
||||
})
|
||||
|
||||
this.messageSinks.forEach(sink => sink.ingest(messages))
|
||||
this.messageSinks.forEach((sink) => sink.ingest(messages))
|
||||
const end = (performance ? performance : Date).now()
|
||||
console.debug('Chat.processMessages', `took ${(end - start) / 1000}s`)
|
||||
}
|
||||
@ -453,10 +574,12 @@ define([ './Chat/console'
|
||||
console.debug('Chat.updateUsers')
|
||||
|
||||
const users = await this.bottle.container.Room.getUsers()
|
||||
await this.bottle.container.ProfileStore.ensureUsersByIDs(users.map(user => user.userID))
|
||||
await this.bottle.container.ProfileStore.ensureUsersByIDs(
|
||||
users.map((user) => user.userID)
|
||||
)
|
||||
this.ui.userList.render(users)
|
||||
}
|
||||
}
|
||||
|
||||
return Chat
|
||||
});
|
||||
})
|
||||
|
@ -11,35 +11,42 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './CommandHandler'
|
||||
, './Parser'
|
||||
], function (CommandHandler, Parser) {
|
||||
"use strict";
|
||||
define(['./CommandHandler', './Parser'], function (CommandHandler, Parser) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'CommandHandler' ]
|
||||
const DEPENDENCIES = ['CommandHandler']
|
||||
class Autocompleter {
|
||||
constructor(commandHandler) {
|
||||
if (!(commandHandler instanceof CommandHandler)) throw new TypeError('You must pass a CommandHandler to the Autocompleter')
|
||||
if (!(commandHandler instanceof CommandHandler))
|
||||
throw new TypeError(
|
||||
'You must pass a CommandHandler to the Autocompleter'
|
||||
)
|
||||
|
||||
this.commandHandler = commandHandler
|
||||
}
|
||||
|
||||
* autocomplete(text) {
|
||||
*autocomplete(text) {
|
||||
if (text === '/') {
|
||||
yield * this.autocompleteCommandTrigger(text, '')
|
||||
yield* this.autocompleteCommandTrigger(text, '')
|
||||
return
|
||||
}
|
||||
|
||||
const [ trigger, parameterString ] = this.commandHandler.splitCommand(text)
|
||||
const [trigger, parameterString] = this.commandHandler.splitCommand(text)
|
||||
|
||||
let command
|
||||
if (trigger === null) {
|
||||
command = this.commandHandler.getCommandByIdentifier('be.bastelstu.chat', 'plain')
|
||||
}
|
||||
else {
|
||||
const triggerDone = Parser.Slash.thenRight(Parser.AlnumTrigger.or(Parser.SymbolicTrigger).thenLeft(Parser.Whitespace)).parse(Parser.Streams.ofString(text))
|
||||
command = this.commandHandler.getCommandByIdentifier(
|
||||
'be.bastelstu.chat',
|
||||
'plain'
|
||||
)
|
||||
} else {
|
||||
const triggerDone = Parser.Slash.thenRight(
|
||||
Parser.AlnumTrigger.or(Parser.SymbolicTrigger).thenLeft(
|
||||
Parser.Whitespace
|
||||
)
|
||||
).parse(Parser.Streams.ofString(text))
|
||||
if (!triggerDone.isAccepted()) {
|
||||
yield * this.autocompleteCommandTrigger(text, trigger)
|
||||
yield* this.autocompleteCommandTrigger(text, trigger)
|
||||
return
|
||||
}
|
||||
|
||||
@ -56,13 +63,12 @@ define([ './CommandHandler'
|
||||
for (const item of values) {
|
||||
yield `/${trigger} ${item}`
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield * values
|
||||
} else {
|
||||
yield* values
|
||||
}
|
||||
}
|
||||
|
||||
* autocompleteCommandTrigger(text, prefix) {
|
||||
*autocompleteCommandTrigger(text, prefix) {
|
||||
const triggers = Array.from(this.commandHandler.getTriggers())
|
||||
|
||||
triggers.sort()
|
||||
@ -70,7 +76,8 @@ define([ './CommandHandler'
|
||||
for (const trigger of triggers) {
|
||||
if (trigger === '') continue
|
||||
if (!trigger.startsWith(prefix)) continue
|
||||
if (!this.commandHandler.getCommandByTrigger(trigger).isAvailable) continue
|
||||
if (!this.commandHandler.getCommandByTrigger(trigger).isAvailable)
|
||||
continue
|
||||
|
||||
yield `/${trigger} `
|
||||
}
|
||||
@ -79,4 +86,4 @@ define([ './CommandHandler'
|
||||
Autocompleter.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Autocompleter
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './console'
|
||||
, 'Bastelstu.be/_Push'
|
||||
, 'WoltLabSuite/Core/Dom/Util'
|
||||
, 'WoltLabSuite/Core/Timer/Repeating'
|
||||
, 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
], function (console, Push, DomUtil, RepeatingTimer, Ajax) {
|
||||
"use strict";
|
||||
define([
|
||||
'./console',
|
||||
'Bastelstu.be/_Push',
|
||||
'WoltLabSuite/Core/Dom/Util',
|
||||
'WoltLabSuite/Core/Timer/Repeating',
|
||||
'Bastelstu.be/PromiseWrap/Ajax',
|
||||
], function (console, Push, DomUtil, RepeatingTimer, Ajax) {
|
||||
'use strict'
|
||||
|
||||
let timer = undefined
|
||||
const mapping = new Map()
|
||||
@ -29,26 +30,44 @@ define([ './console'
|
||||
mapping.set(container, this)
|
||||
|
||||
if (timer == null) {
|
||||
timer = new RepeatingTimer(BoxRoomList.updateBoxes.bind(BoxRoomList), 60e3)
|
||||
timer = new RepeatingTimer(
|
||||
BoxRoomList.updateBoxes.bind(BoxRoomList),
|
||||
60e3
|
||||
)
|
||||
}
|
||||
|
||||
Push.onConnect(timer.setDelta.bind(timer, 300e3)).catch(error => { console.debug(error) })
|
||||
Push.onDisconnect(timer.setDelta.bind(timer, 60e3)).catch(error => { console.debug(error) })
|
||||
Push.onMessage('be.bastelstu.chat.join', BoxRoomList.updateBoxes.bind(BoxRoomList)).catch(error => { console.debug(error) })
|
||||
Push.onMessage('be.bastelstu.chat.leave', BoxRoomList.updateBoxes.bind(BoxRoomList)).catch(error => { console.debug(error) })
|
||||
Push.onConnect(timer.setDelta.bind(timer, 300e3)).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
Push.onDisconnect(timer.setDelta.bind(timer, 60e3)).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
Push.onMessage(
|
||||
'be.bastelstu.chat.join',
|
||||
BoxRoomList.updateBoxes.bind(BoxRoomList)
|
||||
).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
Push.onMessage(
|
||||
'be.bastelstu.chat.leave',
|
||||
BoxRoomList.updateBoxes.bind(BoxRoomList)
|
||||
).catch((error) => {
|
||||
console.debug(error)
|
||||
})
|
||||
}
|
||||
|
||||
static updateBoxes() {
|
||||
mapping.forEach(object => {
|
||||
mapping.forEach((object) => {
|
||||
object.update()
|
||||
})
|
||||
}
|
||||
|
||||
async update() {
|
||||
const payload = { className: 'chat\\data\\room\\RoomAction'
|
||||
, actionName: 'getBoxRoomList'
|
||||
, parameters: { }
|
||||
}
|
||||
const payload = {
|
||||
className: 'chat\\data\\room\\RoomAction',
|
||||
actionName: 'getBoxRoomList',
|
||||
parameters: {},
|
||||
}
|
||||
|
||||
payload.parameters.activeRoomID = this.container.dataset.activeRoomId
|
||||
payload.parameters.boxID = this.container.dataset.boxId
|
||||
@ -59,9 +78,12 @@ define([ './console'
|
||||
}
|
||||
|
||||
replace(data) {
|
||||
if (data.returnValues.template == null) throw new Error('template could not be found in returnValues')
|
||||
if (data.returnValues.template == null)
|
||||
throw new Error('template could not be found in returnValues')
|
||||
|
||||
const fragment = DomUtil.createFragmentFromHtml(data.returnValues.template)
|
||||
const fragment = DomUtil.createFragmentFromHtml(
|
||||
data.returnValues.template
|
||||
)
|
||||
const oldRoomList = this.container.querySelector('.chatBoxRoomList')
|
||||
const newRoomList = fragment.querySelector('.chatBoxRoomList')
|
||||
|
||||
@ -69,7 +91,9 @@ define([ './console'
|
||||
throw new Error('.chatBoxRoomList could not be found in container')
|
||||
}
|
||||
if (newRoomList == null) {
|
||||
throw new Error('.chatBoxRoomList could not be found in returned template')
|
||||
throw new Error(
|
||||
'.chatBoxRoomList could not be found in returned template'
|
||||
)
|
||||
}
|
||||
|
||||
if (oldRoomList.dataset.hash !== newRoomList.dataset.hash) {
|
||||
@ -78,11 +102,9 @@ define([ './console'
|
||||
}
|
||||
|
||||
_ajaxSetup() {
|
||||
return { silent: true
|
||||
, ignoreError: true
|
||||
}
|
||||
return { silent: true, ignoreError: true }
|
||||
}
|
||||
}
|
||||
|
||||
return BoxRoomList
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Parser' ], function (Parser) {
|
||||
"use strict";
|
||||
define(['./Parser'], function (Parser) {
|
||||
'use strict'
|
||||
|
||||
const data = Symbol('data')
|
||||
|
||||
@ -28,9 +28,7 @@ define([ './Parser' ], function (Parser) {
|
||||
return Parser.Rest
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
|
||||
}
|
||||
*autocomplete(parameterString) {}
|
||||
|
||||
get id() {
|
||||
return this[data].commandID
|
||||
@ -54,4 +52,4 @@ define([ './Parser' ], function (Parser) {
|
||||
}
|
||||
|
||||
return Command
|
||||
});
|
||||
})
|
||||
|
@ -11,16 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Away extends Command {
|
||||
getParameterParser() {
|
||||
return Parser.Rest.map(reason => ({ reason }))
|
||||
return Parser.Rest.map((reason) => ({ reason }))
|
||||
}
|
||||
}
|
||||
|
||||
return Away
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Back extends Command {
|
||||
getParameterParser() {
|
||||
@ -23,4 +21,4 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
return Back
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './_Suspension' ], function (Suspension) {
|
||||
"use strict";
|
||||
define(['./_Suspension'], function (Suspension) {
|
||||
'use strict'
|
||||
|
||||
class Ban extends Suspension {
|
||||
|
||||
}
|
||||
class Ban extends Suspension {}
|
||||
|
||||
return Ban
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain' ], function (Plain) {
|
||||
"use strict";
|
||||
define(['./Plain'], function (Plain) {
|
||||
'use strict'
|
||||
|
||||
class Broadcast extends Plain {
|
||||
|
||||
}
|
||||
class Broadcast extends Plain {}
|
||||
|
||||
return Broadcast
|
||||
});
|
||||
})
|
||||
|
@ -11,21 +11,22 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Color extends Command {
|
||||
getParameterParser() {
|
||||
// Either match a color in hexadecimal RGB notation or a color name (just letters)
|
||||
const color = Parser.F.try(Parser.RGBHex.map(color => ({ type: 'hex', value: color })))
|
||||
.or(new Parser.X().word().map(word => ({ type: 'word', value: word })))
|
||||
const color = Parser.F.try(
|
||||
Parser.RGBHex.map((color) => ({ type: 'hex', value: color }))
|
||||
).or(new Parser.X().word().map((word) => ({ type: 'word', value: word })))
|
||||
|
||||
// Either match a single color or two colors separated by a space
|
||||
return Parser.F.try(color.then(Parser.C.char(' ').thenRight(color))).or(color.map(item => [ item ]))
|
||||
return Parser.F.try(color.then(Parser.C.char(' ').thenRight(color))).or(
|
||||
color.map((item) => [item])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Color
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore' ]
|
||||
const DEPENDENCIES = ['ProfileStore']
|
||||
class Info extends Command {
|
||||
constructor(profileStore, id) {
|
||||
super(id)
|
||||
@ -24,10 +22,10 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
getParameterParser() {
|
||||
return Parser.Username.map(username => ({ username }))
|
||||
return Parser.Username.map((username) => ({ username }))
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
*autocomplete(parameterString) {
|
||||
for (const userID of this.profileStore.getLastActivity()) {
|
||||
const user = this.profileStore.get(userID)
|
||||
if (!user.username.startsWith(parameterString)) continue
|
||||
@ -39,4 +37,4 @@ define([ '../Command'
|
||||
Info.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Info
|
||||
});
|
||||
})
|
||||
|
@ -11,14 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain', '../Parser' ], function (Plain, Parser) {
|
||||
"use strict";
|
||||
define(['./Plain', '../Parser'], function (Plain, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Me extends Plain {
|
||||
getParameterParser() {
|
||||
return Parser.Rest1.map(text => ({ text }))
|
||||
return Parser.Rest1.map((text) => ({ text }))
|
||||
}
|
||||
}
|
||||
|
||||
return Me
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './_Suspension' ], function (Suspension) {
|
||||
"use strict";
|
||||
define(['./_Suspension'], function (Suspension) {
|
||||
'use strict'
|
||||
|
||||
class Mute extends Suspension {
|
||||
|
||||
}
|
||||
class Mute extends Suspension {}
|
||||
|
||||
return Mute
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
, 'WoltLabSuite/Core/StringUtil'
|
||||
], function (Command, Parser, StringUtil) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser', 'WoltLabSuite/Core/StringUtil'], function (
|
||||
Command,
|
||||
Parser,
|
||||
StringUtil
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore' ]
|
||||
const DEPENDENCIES = ['ProfileStore']
|
||||
class Plain extends Command {
|
||||
constructor(profileStore, id) {
|
||||
super(id)
|
||||
@ -25,12 +26,12 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
getParameterParser() {
|
||||
return Parser.Rest1
|
||||
.map(StringUtil.escapeHTML.bind(StringUtil))
|
||||
.map(text => ({ text }))
|
||||
return Parser.Rest1.map(
|
||||
StringUtil.escapeHTML.bind(StringUtil)
|
||||
).map((text) => ({ text }))
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
*autocomplete(parameterString) {
|
||||
const parts = parameterString.split(/ /)
|
||||
const lastWord = parts.pop().toLowerCase()
|
||||
|
||||
@ -41,13 +42,21 @@ define([ '../Command'
|
||||
for (const userID of this.profileStore.getLastActivity()) {
|
||||
const user = this.profileStore.get(userID)
|
||||
const username = user.username.toLowerCase()
|
||||
if (!username.startsWith(parameterString) && !username.startsWith(lastWord.replace(/^@/, ''))) continue
|
||||
if (
|
||||
!username.startsWith(parameterString) &&
|
||||
!username.startsWith(lastWord.replace(/^@/, ''))
|
||||
)
|
||||
continue
|
||||
|
||||
yield `${parts.concat([ lastWord.startsWith('@') ? `@${user.username}` : user.username ]).join(' ')} `
|
||||
yield `${parts
|
||||
.concat([
|
||||
lastWord.startsWith('@') ? `@${user.username}` : user.username,
|
||||
])
|
||||
.join(' ')} `
|
||||
}
|
||||
}
|
||||
}
|
||||
Plain.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Plain
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain' ], function (Plain) {
|
||||
"use strict";
|
||||
define(['./Plain'], function (Plain) {
|
||||
'use strict'
|
||||
|
||||
class Team extends Plain {
|
||||
|
||||
}
|
||||
class Team extends Plain {}
|
||||
|
||||
return Team
|
||||
});
|
||||
})
|
||||
|
@ -11,39 +11,44 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Temproom extends Command {
|
||||
getParameterParser() {
|
||||
const Create = Parser.C.string('create').thenReturns({ type: 'create' })
|
||||
const Invite = Parser.C.string('invite').thenLeft(Parser.Whitespace.rep()).thenRight(Parser.Username).map((username) => {
|
||||
return { type: 'invite'
|
||||
, username
|
||||
}
|
||||
})
|
||||
const Invite = Parser.C.string('invite')
|
||||
.thenLeft(Parser.Whitespace.rep())
|
||||
.thenRight(Parser.Username)
|
||||
.map((username) => {
|
||||
return { type: 'invite', username }
|
||||
})
|
||||
const Delete = Parser.C.string('delete').thenReturns({ type: 'delete' })
|
||||
|
||||
return Create.or(Invite).or(Delete)
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
*autocomplete(parameterString) {
|
||||
const Create = Parser.C.string('create')
|
||||
const Invite = Parser.C.string('invite')
|
||||
const Delete = Parser.C.string('delete')
|
||||
|
||||
const subcommandDone = Create.or(Invite).or(Delete).thenLeft(Parser.Whitespace)
|
||||
const subcommandDone = Create.or(Invite)
|
||||
.or(Delete)
|
||||
.thenLeft(Parser.Whitespace)
|
||||
|
||||
const subcommandCheck = subcommandDone.parse(Parser.Streams.ofString(parameterString))
|
||||
const subcommandCheck = subcommandDone.parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
if (subcommandCheck.isAccepted()) {
|
||||
return
|
||||
}
|
||||
|
||||
yield * [ 'create', 'invite ', 'delete' ].filter(item => item.startsWith(parameterString))
|
||||
yield* ['create', 'invite ', 'delete'].filter((item) =>
|
||||
item.startsWith(parameterString)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Temproom
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './_Unsuspension' ], function (Unsuspension) {
|
||||
"use strict";
|
||||
define(['./_Unsuspension'], function (Unsuspension) {
|
||||
'use strict'
|
||||
|
||||
class Unban extends Unsuspension {
|
||||
|
||||
}
|
||||
class Unban extends Unsuspension {}
|
||||
|
||||
return Unban
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './_Unsuspension' ], function (Unsuspension) {
|
||||
"use strict";
|
||||
define(['./_Unsuspension'], function (Unsuspension) {
|
||||
'use strict'
|
||||
|
||||
class Unmute extends Unsuspension {
|
||||
|
||||
}
|
||||
class Unmute extends Unsuspension {}
|
||||
|
||||
return Unmute
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
class Where extends Command {
|
||||
getParameterParser() {
|
||||
@ -23,4 +21,4 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
return Where
|
||||
});
|
||||
})
|
||||
|
@ -11,25 +11,27 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Parser'
|
||||
, './Plain'
|
||||
], function (Parser, Plain) {
|
||||
"use strict";
|
||||
define(['../Parser', './Plain'], function (Parser, Plain) {
|
||||
'use strict'
|
||||
|
||||
class Whisper extends Plain {
|
||||
getParameterParser() {
|
||||
return Parser.Username.thenLeft(Parser.Whitespace.rep()).then(super.getParameterParser()).map(([ username, object ]) => {
|
||||
object.username = username
|
||||
return Parser.Username.thenLeft(Parser.Whitespace.rep())
|
||||
.then(super.getParameterParser())
|
||||
.map(([username, object]) => {
|
||||
object.username = username
|
||||
|
||||
return object
|
||||
})
|
||||
return object
|
||||
})
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(Parser.Whitespace).parse(Parser.Streams.ofString(parameterString))
|
||||
*autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(Parser.Whitespace).parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
|
||||
if (usernameDone.isAccepted()) {
|
||||
yield * super.autocomplete(parameterString)
|
||||
yield* super.autocomplete(parameterString)
|
||||
return
|
||||
}
|
||||
|
||||
@ -43,4 +45,4 @@ define([ '../Parser'
|
||||
}
|
||||
|
||||
return Whisper
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore' ]
|
||||
const DEPENDENCIES = ['ProfileStore']
|
||||
class Suspension extends Command {
|
||||
constructor(profileStore, id) {
|
||||
super(id)
|
||||
@ -24,51 +22,68 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
getParameterParser() {
|
||||
const Globally = Parser.C.string('global').thenLeft(Parser.C.string('ly').opt())
|
||||
const Globally = Parser.C.string('global').thenLeft(
|
||||
Parser.C.string('ly').opt()
|
||||
)
|
||||
const Forever = Parser.C.string('forever').thenReturns(null)
|
||||
const Timespan = Parser.N.digits.then(Parser.C.charIn('dhm')).map(function ([ span, unit ]) {
|
||||
switch (unit) {
|
||||
case 'd':
|
||||
return span * 86400;
|
||||
case 'h':
|
||||
return span * 3600;
|
||||
case 'm':
|
||||
return span * 60;
|
||||
}
|
||||
throw new Error('Unreachable')
|
||||
})
|
||||
.rep()
|
||||
.map(parts => parts.array().reduce((carry, item) => carry + item, 0))
|
||||
.map(offset => Math.floor(Date.now() / 1000) + offset)
|
||||
const Timespan = Parser.N.digits
|
||||
.then(Parser.C.charIn('dhm'))
|
||||
.map(function ([span, unit]) {
|
||||
switch (unit) {
|
||||
case 'd':
|
||||
return span * 86400
|
||||
case 'h':
|
||||
return span * 3600
|
||||
case 'm':
|
||||
return span * 60
|
||||
}
|
||||
throw new Error('Unreachable')
|
||||
})
|
||||
.rep()
|
||||
.map((parts) => parts.array().reduce((carry, item) => carry + item, 0))
|
||||
.map((offset) => Math.floor(Date.now() / 1000) + offset)
|
||||
|
||||
const Duration = Forever.or(Timespan).or(Parser.ISODate.map(item => Math.floor(item.valueOf() / 1000)))
|
||||
const Duration = Forever.or(Timespan).or(
|
||||
Parser.ISODate.map((item) => Math.floor(item.valueOf() / 1000))
|
||||
)
|
||||
|
||||
return Parser.Username.thenLeft(Parser.Whitespace.rep())
|
||||
.then(Globally.thenLeft(Parser.Whitespace.rep()).thenReturns(true).or(Parser.F.returns(false)))
|
||||
.then(Duration)
|
||||
.then(Parser.Whitespace.rep().thenRight(Parser.Rest1).or(Parser.F.eos.thenReturns(null)))
|
||||
.map(([ username, globally, duration, reason ]) => {
|
||||
return { username
|
||||
, globally
|
||||
, duration
|
||||
, reason
|
||||
}
|
||||
})
|
||||
.then(
|
||||
Globally.thenLeft(Parser.Whitespace.rep())
|
||||
.thenReturns(true)
|
||||
.or(Parser.F.returns(false))
|
||||
)
|
||||
.then(Duration)
|
||||
.then(
|
||||
Parser.Whitespace.rep()
|
||||
.thenRight(Parser.Rest1)
|
||||
.or(Parser.F.eos.thenReturns(null))
|
||||
)
|
||||
.map(([username, globally, duration, reason]) => {
|
||||
return { username, globally, duration, reason }
|
||||
})
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(Parser.Whitespace.rep()).map(username => `"${username.replace(/"/g, '""')}"`)
|
||||
const globallyDone = usernameDone.then(Parser.C.string('global').thenLeft(Parser.C.string('ly').opt())).thenLeft(Parser.Whitespace.rep())
|
||||
*autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(
|
||||
Parser.Whitespace.rep()
|
||||
).map((username) => `"${username.replace(/"/g, '""')}"`)
|
||||
const globallyDone = usernameDone
|
||||
.then(Parser.C.string('global').thenLeft(Parser.C.string('ly').opt()))
|
||||
.thenLeft(Parser.Whitespace.rep())
|
||||
|
||||
const usernameCheck = usernameDone.parse(Parser.Streams.ofString(parameterString))
|
||||
const usernameCheck = usernameDone.parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
if (usernameCheck.isAccepted()) {
|
||||
const globallyCheck = globallyDone.parse(Parser.Streams.ofString(parameterString))
|
||||
const globallyCheck = globallyDone.parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
let prefix, rest
|
||||
if (globallyCheck.isAccepted()) {
|
||||
prefix = parameterString.substring(0, globallyCheck.offset)
|
||||
rest = parameterString.substring(globallyCheck.offset)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
prefix = parameterString.substring(0, usernameCheck.offset)
|
||||
rest = parameterString.substring(usernameCheck.offset)
|
||||
}
|
||||
@ -101,4 +116,4 @@ define([ '../Command'
|
||||
Suspension.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Suspension
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Command'
|
||||
, '../Parser'
|
||||
], function (Command, Parser) {
|
||||
"use strict";
|
||||
define(['../Command', '../Parser'], function (Command, Parser) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore' ]
|
||||
const DEPENDENCIES = ['ProfileStore']
|
||||
class Unsuspension extends Command {
|
||||
constructor(profileStore, id) {
|
||||
super(id)
|
||||
@ -24,30 +22,39 @@ define([ '../Command'
|
||||
}
|
||||
|
||||
getParameterParser() {
|
||||
const Globally = Parser.C.string('global').thenLeft(Parser.C.string('ly').opt())
|
||||
const Globally = Parser.C.string('global').thenLeft(
|
||||
Parser.C.string('ly').opt()
|
||||
)
|
||||
|
||||
return Parser.Username
|
||||
.then(Parser.Whitespace.rep().thenRight(Globally.thenReturns(true)).or(Parser.F.returns(false)))
|
||||
.map(([ username, globally ]) => {
|
||||
return { username
|
||||
, globally
|
||||
}
|
||||
return Parser.Username.then(
|
||||
Parser.Whitespace.rep()
|
||||
.thenRight(Globally.thenReturns(true))
|
||||
.or(Parser.F.returns(false))
|
||||
).map(([username, globally]) => {
|
||||
return { username, globally }
|
||||
})
|
||||
}
|
||||
|
||||
* autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(Parser.Whitespace.rep()).map(username => `"${username.replace(/"/g, '""')}"`)
|
||||
const globallyDone = usernameDone.then(Parser.C.string('global').thenLeft(Parser.C.string('ly').opt())).thenLeft(Parser.Whitespace.rep())
|
||||
*autocomplete(parameterString) {
|
||||
const usernameDone = Parser.Username.thenLeft(
|
||||
Parser.Whitespace.rep()
|
||||
).map((username) => `"${username.replace(/"/g, '""')}"`)
|
||||
const globallyDone = usernameDone
|
||||
.then(Parser.C.string('global').thenLeft(Parser.C.string('ly').opt()))
|
||||
.thenLeft(Parser.Whitespace.rep())
|
||||
|
||||
const usernameCheck = usernameDone.parse(Parser.Streams.ofString(parameterString))
|
||||
const usernameCheck = usernameDone.parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
if (usernameCheck.isAccepted()) {
|
||||
const globallyCheck = globallyDone.parse(Parser.Streams.ofString(parameterString))
|
||||
const globallyCheck = globallyDone.parse(
|
||||
Parser.Streams.ofString(parameterString)
|
||||
)
|
||||
let prefix, rest
|
||||
if (globallyCheck.isAccepted()) {
|
||||
prefix = parameterString.substring(0, globallyCheck.offset)
|
||||
rest = parameterString.substring(globallyCheck.offset)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
prefix = parameterString.substring(0, usernameCheck.offset)
|
||||
rest = parameterString.substring(usernameCheck.offset)
|
||||
}
|
||||
@ -68,4 +75,4 @@ define([ '../Command'
|
||||
Unsuspension.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Unsuspension
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Parser'
|
||||
, './ParseError'
|
||||
], function (Parser, ParseError) {
|
||||
"use strict";
|
||||
define(['./Parser', './ParseError'], function (Parser, ParseError) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'Trigger', 'Command' ]
|
||||
const DEPENDENCIES = ['Trigger', 'Command']
|
||||
class CommandHandler {
|
||||
constructor(triggers, commands) {
|
||||
this.triggers = triggers
|
||||
@ -28,19 +26,19 @@ define([ './Parser'
|
||||
|
||||
if (result.isAccepted()) {
|
||||
return result.value
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new ParseError('Empty trigger')
|
||||
}
|
||||
}
|
||||
|
||||
applyCommand(command, parameterString) {
|
||||
const result = command.getParameterParser().parse(Parser.Streams.ofString(parameterString))
|
||||
const result = command
|
||||
.getParameterParser()
|
||||
.parse(Parser.Streams.ofString(parameterString))
|
||||
|
||||
if (result.isAccepted()) {
|
||||
return result.value
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new ParseError('Could not parse', { result, parameterString })
|
||||
}
|
||||
}
|
||||
@ -64,4 +62,4 @@ define([ './Parser'
|
||||
CommandHandler.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return CommandHandler
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,13 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const listeners = new WeakMap()
|
||||
const EventEmitter = function (target) {
|
||||
Object.assign(target, {
|
||||
on(type, listener, options = { }) {
|
||||
on(type, listener, options = {}) {
|
||||
if (!listeners.has(this)) {
|
||||
listeners.set(this, new Map())
|
||||
}
|
||||
@ -33,22 +33,24 @@ define([ ], function () {
|
||||
listeners.get(this).get(type).delete(listener)
|
||||
},
|
||||
|
||||
emit(type, detail = { }) {
|
||||
emit(type, detail = {}) {
|
||||
if (!listeners.has(this)) return
|
||||
if (!listeners.get(this).has(type)) return
|
||||
|
||||
const set = listeners.get(this).get(type)
|
||||
|
||||
set.forEach((function ({ listener, options }) {
|
||||
if (options.once) {
|
||||
set.delete(listener)
|
||||
}
|
||||
set.forEach(
|
||||
function ({ listener, options }) {
|
||||
if (options.once) {
|
||||
set.delete(listener)
|
||||
}
|
||||
|
||||
listener({ target: this, detail })
|
||||
}).bind(this))
|
||||
}
|
||||
listener({ target: this, detail })
|
||||
}.bind(this)
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return EventEmitter
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const s = Symbol('s')
|
||||
const start = Symbol('start')
|
||||
@ -24,7 +24,7 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
add(value) {
|
||||
if (this[start] && this[start].value === value) {
|
||||
if (this[start] && this[start].value === value) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -45,14 +45,13 @@ define([ ], function () {
|
||||
this[s].set(value, obj)
|
||||
}
|
||||
|
||||
* [Symbol.iterator]() {
|
||||
*[Symbol.iterator]() {
|
||||
let current = this[start]
|
||||
do {
|
||||
yield current.value
|
||||
}
|
||||
while ((current = current.next))
|
||||
} while ((current = current.next))
|
||||
}
|
||||
}
|
||||
|
||||
return LRU
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class Node {
|
||||
constructor(value) {
|
||||
@ -93,39 +93,52 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
search(value) {
|
||||
if (value === this.value) return [ 'IS', this ]
|
||||
if (value === this.value) return ['IS', this]
|
||||
if (value < this.value) {
|
||||
if (this.left !== undefined) return this.left.search(value)
|
||||
return [ 'LEFT', this ]
|
||||
return ['LEFT', this]
|
||||
}
|
||||
if (value > this.value) {
|
||||
if (this.right !== undefined) return this.right.search(value)
|
||||
return [ 'RIGHT', this ]
|
||||
return ['RIGHT', this]
|
||||
}
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
|
||||
print(depth = 0) {
|
||||
console.log(" ".repeat(depth) + `${this.value}: ${this.color} (Parent: ${this.parent ? this.parent.value : '-'})`)
|
||||
console.log(
|
||||
' '.repeat(depth) +
|
||||
`${this.value}: ${this.color} (Parent: ${
|
||||
this.parent ? this.parent.value : '-'
|
||||
})`
|
||||
)
|
||||
if (this.left) this.left.print(depth + 1)
|
||||
else console.log(" ".repeat(depth + 1) + '-')
|
||||
else console.log(' '.repeat(depth + 1) + '-')
|
||||
if (this.right) this.right.print(depth + 1)
|
||||
else console.log(" ".repeat(depth + 1) + '-')
|
||||
else console.log(' '.repeat(depth + 1) + '-')
|
||||
}
|
||||
|
||||
check() {
|
||||
if (this.left && this.left.value >= this.value) throw new Error('Invalid' + this.value);
|
||||
if (this.right && this.right.value <= this.value) throw new Error('Invalid' + this.value);
|
||||
if (this.color === 'RED' && ((this.left && this.left.color !== 'BLACK') || (this.right && this.right.color !== 'BLACK'))) throw new Error('Invalid' + this.value);
|
||||
if (this.left && this.left.value >= this.value)
|
||||
throw new Error('Invalid' + this.value)
|
||||
if (this.right && this.right.value <= this.value)
|
||||
throw new Error('Invalid' + this.value)
|
||||
if (
|
||||
this.color === 'RED' &&
|
||||
((this.left && this.left.color !== 'BLACK') ||
|
||||
(this.right && this.right.color !== 'BLACK'))
|
||||
)
|
||||
throw new Error('Invalid' + this.value)
|
||||
|
||||
let leftBlacks = 1, rightBlacks = 1
|
||||
let leftBlacks = 1,
|
||||
rightBlacks = 1
|
||||
if (this.left) {
|
||||
leftBlacks = this.left.check()
|
||||
}
|
||||
if (this.right) {
|
||||
rightBlacks = this.right.check()
|
||||
}
|
||||
if (leftBlacks !== rightBlacks) throw new Error('Invalid' + this.value);
|
||||
if (leftBlacks !== rightBlacks) throw new Error('Invalid' + this.value)
|
||||
|
||||
if (this.color === 'BLACK') return leftBlacks + 1
|
||||
return leftBlacks
|
||||
@ -133,4 +146,4 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
return Node
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Node' ], function (Node) {
|
||||
"use strict";
|
||||
define(['./Node'], function (Node) {
|
||||
'use strict'
|
||||
|
||||
class Tree {
|
||||
constructor() {
|
||||
@ -29,22 +29,22 @@ define([ './Node' ], function (Node) {
|
||||
if (this.root === undefined) {
|
||||
this.root = node
|
||||
this.fix(node)
|
||||
return [ 'RIGHT', undefined ]
|
||||
return ['RIGHT', undefined]
|
||||
}
|
||||
|
||||
const search = this.search(value)
|
||||
const [ side, parent ] = search
|
||||
const [side, parent] = search
|
||||
|
||||
if (side === 'IS') return [ side, parent.value ]
|
||||
if (side === 'IS') return [side, parent.value]
|
||||
if (side === 'LEFT') {
|
||||
parent.left = node
|
||||
this.fix(node)
|
||||
return [ side, parent.value ]
|
||||
return [side, parent.value]
|
||||
}
|
||||
if (side === 'RIGHT') {
|
||||
parent.right = node
|
||||
this.fix(node)
|
||||
return [ side, parent.value ]
|
||||
return [side, parent.value]
|
||||
}
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
@ -74,8 +74,7 @@ define([ './Node' ], function (Node) {
|
||||
if (N.isRightChild && N.parent.isLeftChild) {
|
||||
this.rotateLeft(N.parent)
|
||||
N = N.left
|
||||
}
|
||||
else if (N.isLeftChild && N.parent.isRightChild) {
|
||||
} else if (N.isLeftChild && N.parent.isRightChild) {
|
||||
this.rotateRight(N.parent)
|
||||
N = N.right
|
||||
}
|
||||
@ -86,8 +85,7 @@ define([ './Node' ], function (Node) {
|
||||
G.color = 'RED'
|
||||
if (N.isLeftChild) {
|
||||
this.rotateRight(G)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.rotateLeft(G)
|
||||
}
|
||||
}
|
||||
@ -99,11 +97,9 @@ define([ './Node' ], function (Node) {
|
||||
N.right = right.left
|
||||
if (N.parent === undefined) {
|
||||
this.root = right
|
||||
}
|
||||
else if (N.isLeftChild) {
|
||||
} else if (N.isLeftChild) {
|
||||
N.parent.left = right
|
||||
}
|
||||
else if (N.isRightChild) {
|
||||
} else if (N.isRightChild) {
|
||||
N.parent.right = right
|
||||
}
|
||||
|
||||
@ -117,11 +113,9 @@ define([ './Node' ], function (Node) {
|
||||
N.left = left.right
|
||||
if (N.parent === undefined) {
|
||||
this.root = left
|
||||
}
|
||||
else if (N.isLeftChild) {
|
||||
} else if (N.isLeftChild) {
|
||||
N.parent.left = left
|
||||
}
|
||||
else if (N.isRightChild) {
|
||||
} else if (N.isRightChild) {
|
||||
N.parent.right = left
|
||||
}
|
||||
left.right = N
|
||||
@ -129,4 +123,4 @@ define([ './Node' ], function (Node) {
|
||||
}
|
||||
|
||||
return Tree
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class Throttler {
|
||||
constructor(callback, delay = 125) {
|
||||
@ -37,7 +37,7 @@ define([ ], function () {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
|
||||
this.timer = setTimeout(_ => {
|
||||
this.timer = setTimeout((_) => {
|
||||
this.timer = null
|
||||
this.hot = false
|
||||
|
||||
@ -60,8 +60,7 @@ define([ ], function () {
|
||||
guardedExecute() {
|
||||
if (this.hot) {
|
||||
this.awaiting = true
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.execute()
|
||||
}
|
||||
}
|
||||
@ -71,11 +70,10 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
set delay(newDelay) {
|
||||
if (this.awaiting && (Date.now() - this.last) > newDelay) {
|
||||
if (this.awaiting && Date.now() - this.last > newDelay) {
|
||||
this._delay = 0
|
||||
this.setTimer()
|
||||
}
|
||||
else if (this.timer) {
|
||||
} else if (this.timer) {
|
||||
this._delay = Math.max(0, newDelay - (Date.now() - this.last))
|
||||
this.setTimer()
|
||||
}
|
||||
@ -98,4 +96,4 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
return throttle
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,11 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Date/Util'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
], function (DateUtil, Language) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Date/Util', 'WoltLabSuite/Core/Language'], function (
|
||||
DateUtil,
|
||||
Language
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
class Helper {
|
||||
static deepFreeze(obj) {
|
||||
@ -38,11 +39,13 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
*/
|
||||
static isInput(element) {
|
||||
if (element.tagName === 'INPUT') {
|
||||
if (element.getAttribute('type') !== 'text' && element.getAttribute('type') !== 'password') {
|
||||
if (
|
||||
element.getAttribute('type') !== 'text' &&
|
||||
element.getAttribute('type') !== 'password'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
else if (element.tagName !== 'TEXTAREA') {
|
||||
} else if (element.tagName !== 'TEXTAREA') {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -53,21 +56,20 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
let last = 0
|
||||
let deferTimer = null
|
||||
|
||||
return function() {
|
||||
const now = new Date().getTime()
|
||||
const args = arguments
|
||||
return function () {
|
||||
const now = new Date().getTime()
|
||||
const args = arguments
|
||||
const context = scope || this
|
||||
|
||||
if (last && (now < (last + threshold))) {
|
||||
if (last && now < last + threshold) {
|
||||
clearTimeout(deferTimer)
|
||||
|
||||
return deferTimer = setTimeout(function() {
|
||||
return (deferTimer = setTimeout(function () {
|
||||
last = now
|
||||
|
||||
return fn.apply(context, args)
|
||||
}, threshold)
|
||||
}
|
||||
else {
|
||||
}, threshold))
|
||||
} else {
|
||||
last = now
|
||||
|
||||
return fn.apply(context, args)
|
||||
@ -108,8 +110,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
|
||||
if (element.nextSibling) {
|
||||
element.parentNode.insertBefore(wrapper, element.nextSibling)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
element.parentNode.appendChild(wrapper)
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
throw new Error(`Unsupported element type: ${textarea.tagName}`)
|
||||
}
|
||||
|
||||
const pre = document.createElement('pre')
|
||||
const pre = document.createElement('pre')
|
||||
const span = document.createElement('span')
|
||||
|
||||
const mirror = function () {
|
||||
@ -141,7 +142,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
pre.appendChild(document.createElement('br'))
|
||||
textarea.parentNode.insertBefore(pre, textarea)
|
||||
|
||||
textarea.addEventListener('input', mirror)
|
||||
textarea.addEventListener('input', mirror)
|
||||
mirror()
|
||||
}
|
||||
|
||||
@ -150,11 +151,12 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
constructor(size) {
|
||||
super()
|
||||
|
||||
Object.defineProperty(this, 'size', { enumerable: false
|
||||
, value: size
|
||||
, writable: false
|
||||
, configurable: false
|
||||
});
|
||||
Object.defineProperty(this, 'size', {
|
||||
enumerable: false,
|
||||
value: size,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
})
|
||||
}
|
||||
|
||||
push() {
|
||||
@ -164,7 +166,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
super.shift()
|
||||
}
|
||||
|
||||
return this.length;
|
||||
return this.length
|
||||
}
|
||||
|
||||
unshift() {
|
||||
@ -174,7 +176,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
super.pop()
|
||||
}
|
||||
|
||||
return this.length;
|
||||
return this.length
|
||||
}
|
||||
|
||||
first() {
|
||||
@ -190,9 +192,9 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
}
|
||||
|
||||
static intToRGBHex(integer) {
|
||||
const r = ((integer >> 16) & 0xFF).toString(16)
|
||||
const g = ((integer >> 8) & 0xFF).toString(16)
|
||||
const b = ((integer >> 0) & 0xFF).toString(16)
|
||||
const r = ((integer >> 16) & 0xff).toString(16)
|
||||
const g = ((integer >> 8) & 0xff).toString(16)
|
||||
const b = ((integer >> 0) & 0xff).toString(16)
|
||||
|
||||
const rr = r.length == 1 ? `0${r}` : r
|
||||
const gg = g.length == 1 ? `0${g}` : g
|
||||
@ -263,8 +265,7 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
if (firstTextNode) {
|
||||
nodeRange.setStart(firstTextNode, 0)
|
||||
nodeRange.setEnd(lastTextNode, lastTextNode.length)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
nodeRange.selectNodeContents(node)
|
||||
}
|
||||
|
||||
@ -282,9 +283,10 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
* @return {String}
|
||||
*/
|
||||
static getTextContent(node) {
|
||||
const acceptNode = node => {
|
||||
const acceptNode = (node) => {
|
||||
if (node instanceof Element) {
|
||||
if (node.tagName === 'SCRIPT' || node.tagName === 'STYLE') return NodeFilter.FILTER_REJECT
|
||||
if (node.tagName === 'SCRIPT' || node.tagName === 'STYLE')
|
||||
return NodeFilter.FILTER_REJECT
|
||||
}
|
||||
|
||||
return NodeFilter.FILTER_ACCEPT
|
||||
@ -295,33 +297,34 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
const flags = NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
|
||||
const treeWalker = document.createTreeWalker(node, flags, { acceptNode })
|
||||
|
||||
const ignoredLinks = [ ]
|
||||
const ignoredLinks = []
|
||||
|
||||
while (treeWalker.nextNode()) {
|
||||
const node = treeWalker.currentNode
|
||||
|
||||
if (node instanceof Text) {
|
||||
if (node.parentNode.tagName === 'A' && ignoredLinks.indexOf(node.parentNode) >= 0) {
|
||||
if (
|
||||
node.parentNode.tagName === 'A' &&
|
||||
ignoredLinks.indexOf(node.parentNode) >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
out += node.nodeValue.replace(/\n/g, '')
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
switch (node.tagName) {
|
||||
case 'IMG': {
|
||||
const alt = node.getAttribute('alt')
|
||||
|
||||
if (node.classList.contains('smiley')) {
|
||||
out += ` ${alt} `
|
||||
}
|
||||
else if (alt && alt !== '') {
|
||||
} else if (alt && alt !== '') {
|
||||
out += ` ${alt} [Image ${node.src}] `
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
out += ` [Image ${node.src}] `
|
||||
}
|
||||
break }
|
||||
break
|
||||
}
|
||||
|
||||
case 'BR':
|
||||
case 'LI':
|
||||
@ -329,16 +332,16 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
case 'DIV':
|
||||
case 'TR':
|
||||
out += '\n'
|
||||
break
|
||||
break
|
||||
|
||||
case 'TH':
|
||||
case 'TD':
|
||||
out += '\t'
|
||||
break
|
||||
break
|
||||
|
||||
case 'P':
|
||||
out += '\n\n'
|
||||
break
|
||||
break
|
||||
|
||||
case 'A': {
|
||||
let link = node.href
|
||||
@ -354,7 +357,9 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
const parts = text.split(/\u2026/)
|
||||
|
||||
if (parts.length === 2) {
|
||||
truncated = node.href.startsWith(parts[0]) && node.href.endsWith(parts[1])
|
||||
truncated =
|
||||
node.href.startsWith(parts[0]) &&
|
||||
node.href.endsWith(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,7 +369,8 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
}
|
||||
|
||||
out += link
|
||||
break }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -374,5 +380,4 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
}
|
||||
|
||||
return Helper
|
||||
});
|
||||
|
||||
})
|
||||
|
@ -11,10 +11,13 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, LocalStorageEmulator) {
|
||||
'use strict';
|
||||
define(['WoltLabSuite/Core/Core', './LocalStorageEmulator'], function (
|
||||
Core,
|
||||
LocalStorageEmulator
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ ]
|
||||
const DEPENDENCIES = []
|
||||
class LocalStorage {
|
||||
constructor(subprefix) {
|
||||
this.subprefix = subprefix
|
||||
@ -23,15 +26,17 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
}
|
||||
|
||||
static isQuotaExceeded(error) {
|
||||
return error instanceof DOMException && (
|
||||
return (
|
||||
error instanceof DOMException &&
|
||||
// everything except Firefox
|
||||
error.code === 22 ||
|
||||
// Firefox
|
||||
error.code === 1014 ||
|
||||
// everything except Firefox
|
||||
error.name === 'QuotaExceededError' ||
|
||||
// Firefox
|
||||
error.name === 'NS_ERROR_DOM_QUOTA_REACHED')
|
||||
(error.code === 22 ||
|
||||
// Firefox
|
||||
error.code === 1014 ||
|
||||
// everything except Firefox
|
||||
error.name === 'QuotaExceededError' ||
|
||||
// Firefox
|
||||
error.name === 'NS_ERROR_DOM_QUOTA_REACHED')
|
||||
)
|
||||
}
|
||||
|
||||
static isAvailable() {
|
||||
@ -40,8 +45,7 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
window.localStorage.setItem(x, x)
|
||||
window.localStorage.removeItem(x)
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
return LocalStorage.isQuotaExceeded(error)
|
||||
}
|
||||
}
|
||||
@ -50,8 +54,7 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
if (LocalStorage.isAvailable()) {
|
||||
this.storage = window.localStorage
|
||||
this.hasLocalStorage = true
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.info('Falling back to in-memory local storage emulation')
|
||||
this.storage = new LocalStorageEmulator()
|
||||
}
|
||||
@ -97,13 +100,19 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
*/
|
||||
set(key, value) {
|
||||
try {
|
||||
this.storage.setItem(`${this.storagePrefix}${key}`, JSON.stringify(value))
|
||||
}
|
||||
catch (error) {
|
||||
this.storage.setItem(
|
||||
`${this.storagePrefix}${key}`,
|
||||
JSON.stringify(value)
|
||||
)
|
||||
} catch (error) {
|
||||
if (!LocalStorage.isQuotaExceeded(error)) throw error
|
||||
|
||||
console.warn(`Your localStorage has exceeded the size quota for this domain`)
|
||||
console.warn(`We are falling back to an in-memory storage, this does not persist data!`)
|
||||
console.warn(
|
||||
`Your localStorage has exceeded the size quota for this domain`
|
||||
)
|
||||
console.warn(
|
||||
`We are falling back to an in-memory storage, this does not persist data!`
|
||||
)
|
||||
console.error(error)
|
||||
|
||||
const storage = new LocalStorageEmulator()
|
||||
@ -152,7 +161,7 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
* @returns {string} The last value of the provided setting
|
||||
*/
|
||||
remove(key) {
|
||||
const value = this.get(key)
|
||||
const value = this.get(key)
|
||||
const storageKey = `${this.storagePrefix}${key}`
|
||||
|
||||
this.storage.removeItem(storageKey)
|
||||
@ -167,13 +176,20 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
clear() {
|
||||
const _clear = (target) => {
|
||||
for (let key in target) {
|
||||
if (!key.startsWith(this.storagePrefix) || !target.hasOwnProperty(key)) continue
|
||||
if (
|
||||
!key.startsWith(this.storagePrefix) ||
|
||||
!target.hasOwnProperty(key)
|
||||
)
|
||||
continue
|
||||
|
||||
target.removeItem(key)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasLocalStorage && this.storage instanceof LocalStorageEmulator) {
|
||||
if (
|
||||
this.hasLocalStorage &&
|
||||
this.storage instanceof LocalStorageEmulator
|
||||
) {
|
||||
try {
|
||||
// Try to clear the real localStorage
|
||||
_clear(localStorage)
|
||||
@ -188,8 +204,9 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
this.storage = localStorage
|
||||
|
||||
console.log('Switched back to using the localStorage')
|
||||
} catch (error) {
|
||||
/* no we can’t */
|
||||
}
|
||||
catch (error) { /* no we can’t */ }
|
||||
}
|
||||
|
||||
_clear(this.storage)
|
||||
@ -198,4 +215,4 @@ define([ 'WoltLabSuite/Core/Core', './LocalStorageEmulator' ], function (Core, L
|
||||
LocalStorage.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return LocalStorage
|
||||
});
|
||||
})
|
||||
|
@ -11,16 +11,19 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
'use strict';
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class LocalStorageEmulator {
|
||||
constructor () {
|
||||
constructor() {
|
||||
this._data = new Map()
|
||||
return new Proxy(this, {
|
||||
get(target, property) {
|
||||
// Check if the property exists on the object or its prototype
|
||||
if (target.hasOwnProperty(property) || Object.getPrototypeOf(target)[property]) {
|
||||
if (
|
||||
target.hasOwnProperty(property) ||
|
||||
Object.getPrototypeOf(target)[property]
|
||||
) {
|
||||
return target[property]
|
||||
}
|
||||
|
||||
@ -29,18 +32,22 @@ define([ ], function () {
|
||||
},
|
||||
set(target, property, value) {
|
||||
// Check if the property exists on the object or its prototype
|
||||
if (target.hasOwnProperty(property) || Object.getPrototypeOf(target)[property]) {
|
||||
if (
|
||||
target.hasOwnProperty(property) ||
|
||||
Object.getPrototypeOf(target)[property]
|
||||
) {
|
||||
target[property] = value
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Proxy to the underlying map
|
||||
target.setItem(property, value)
|
||||
}
|
||||
},
|
||||
has(target, property) {
|
||||
return target.hasOwnProperty(property) // check the properties of the object
|
||||
|| Object.getPrototypeOf(target)[property] // check its prototype
|
||||
|| target._data.has(property) // check the underlying map
|
||||
return (
|
||||
target.hasOwnProperty(property) || // check the properties of the object
|
||||
Object.getPrototypeOf(target)[property] || // check its prototype
|
||||
target._data.has(property)
|
||||
) // check the underlying map
|
||||
},
|
||||
ownKeys(target) {
|
||||
// Proxy to the underlying map
|
||||
@ -50,9 +57,9 @@ define([ ], function () {
|
||||
// Make the properties of the map visible
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
configurable: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,10 +87,10 @@ define([ ], function () {
|
||||
this._data.clear()
|
||||
}
|
||||
|
||||
* [Symbol.iterator]() {
|
||||
yield * this._data.values()
|
||||
*[Symbol.iterator]() {
|
||||
yield* this._data.values()
|
||||
}
|
||||
}
|
||||
|
||||
return LocalStorageEmulator
|
||||
});
|
||||
})
|
||||
|
@ -11,19 +11,32 @@
|
||||
* 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";
|
||||
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')
|
||||
|
||||
@ -31,10 +44,10 @@ define([ './console'
|
||||
constructor(params, config) {
|
||||
console.debug('ChatLog.constructor', 'Constructing …')
|
||||
|
||||
this.config = config
|
||||
this.config = config
|
||||
|
||||
this.sessionID = Core.getUuid()
|
||||
this.bottle = new Bottle()
|
||||
this.bottle = new Bottle()
|
||||
this.bottle.value('bottle', this.bottle)
|
||||
this.bottle.value('config', config)
|
||||
this.bottle.constant('sessionID', this.sessionID)
|
||||
@ -56,36 +69,45 @@ define([ './console'
|
||||
})
|
||||
|
||||
// Register Templates
|
||||
const selector = [ '[type="x-text/template"]'
|
||||
, '[data-application="be.bastelstu.chat"]'
|
||||
, '[data-template-name]'
|
||||
].join('')
|
||||
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])
|
||||
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]))
|
||||
|
||||
return new Template(template.textContent, includes)
|
||||
})
|
||||
}).bind(this))
|
||||
return new Template(template.textContent, includes)
|
||||
}
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
// Register MessageTypes
|
||||
const messageTypes = Object.entries(this.config.messageTypes)
|
||||
messageTypes.forEach(([ objectType, messageType ]) => {
|
||||
messageTypes.forEach(([objectType, messageType]) => {
|
||||
const MessageType = require(messageType.module)
|
||||
|
||||
this.bottle.factory(`MessageType.${objectType.replace(/\./g, '-')}`, _ => {
|
||||
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
|
||||
this.bottle.factory(
|
||||
`MessageType.${objectType.replace(/\./g, '-')}`,
|
||||
(_) => {
|
||||
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
|
||||
|
||||
return new MessageType(...deps, objectType)
|
||||
})
|
||||
return new MessageType(...deps, objectType)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
this.knows = { from: undefined
|
||||
, to: undefined
|
||||
}
|
||||
this.knows = { from: undefined, to: undefined }
|
||||
|
||||
this.messageSinks = new Set()
|
||||
|
||||
@ -94,9 +116,11 @@ define([ './console'
|
||||
this.pulling = false
|
||||
}
|
||||
|
||||
service(name, _constructor, args = [ ]) {
|
||||
service(name, _constructor, args = []) {
|
||||
this.bottle.factory(name, function (container) {
|
||||
const deps = (_constructor.DEPENDENCIES || [ ]).map(dep => container[dep])
|
||||
const deps = (_constructor.DEPENDENCIES || []).map(
|
||||
(dep) => container[dep]
|
||||
)
|
||||
|
||||
return new _constructor(...deps, ...args)
|
||||
})
|
||||
@ -111,20 +135,34 @@ define([ './console'
|
||||
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 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))
|
||||
this.bottle.container.UiMessageStream.on('reachedBottom', this.pullNewer.bind(this))
|
||||
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)
|
||||
)
|
||||
this.bottle.container.UiMessageStream.on(
|
||||
'reachedBottom',
|
||||
this.pullNewer.bind(this)
|
||||
)
|
||||
|
||||
const element = document.querySelector(`#message-${this.params.messageID}`)
|
||||
const element = document.querySelector(
|
||||
`#message-${this.params.messageID}`
|
||||
)
|
||||
|
||||
// Force changing the hash to trigger a new lookup of the element.
|
||||
// At least Chrome won’t target an element if it is not in the DOM
|
||||
@ -152,8 +190,7 @@ define([ './console'
|
||||
async pull(from, to) {
|
||||
try {
|
||||
await this.handlePull(await this.performPull(from, to))
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
this.handleError(e)
|
||||
}
|
||||
}
|
||||
@ -180,7 +217,12 @@ define([ './console'
|
||||
}
|
||||
|
||||
async performPull(from = undefined, to = undefined) {
|
||||
console.debug('ChatLog.performPull', `Pulling new messages; from: ${from !== undefined ? from : 'undefined'}, to: ${to !== 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)
|
||||
}
|
||||
@ -199,25 +241,35 @@ define([ './console'
|
||||
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))
|
||||
messages = messages.filter(
|
||||
function (message) {
|
||||
return !(
|
||||
this.knows.from <= message.messageID &&
|
||||
message.messageID <= this.knows.to
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
}))
|
||||
await Promise.all(
|
||||
messages.map((message) => {
|
||||
return message.getMessageType().preProcess(message)
|
||||
})
|
||||
)
|
||||
|
||||
const userIDs = messages.map(message => message.userID)
|
||||
.filter(userID => userID !== null)
|
||||
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))
|
||||
this.messageSinks.forEach((sink) => sink.ingest(messages))
|
||||
}
|
||||
}
|
||||
|
||||
return Log
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Helper'
|
||||
, 'WoltLabSuite/Core/Date/Util'
|
||||
, 'WoltLabSuite/Core/User'
|
||||
], function (Helper, DateUtil, User) {
|
||||
"use strict";
|
||||
define([
|
||||
'./Helper',
|
||||
'WoltLabSuite/Core/Date/Util',
|
||||
'WoltLabSuite/Core/User',
|
||||
], function (Helper, DateUtil, User) {
|
||||
'use strict'
|
||||
|
||||
const m = Symbol('message')
|
||||
const m = Symbol('message')
|
||||
|
||||
class Message {
|
||||
constructor(MessageType, message) {
|
||||
@ -87,4 +88,4 @@ define([ './Helper'
|
||||
}
|
||||
|
||||
return Message
|
||||
});
|
||||
})
|
||||
|
@ -11,14 +11,15 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Date/Util'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/Dom/Util'
|
||||
, 'Bastelstu.be/Chat/User'
|
||||
], function (DateUtil, Language, DomUtil, User) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Date/Util',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/Dom/Util',
|
||||
'Bastelstu.be/Chat/User',
|
||||
], function (DateUtil, Language, DomUtil, User) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore', 'Template' ]
|
||||
const DEPENDENCIES = ['ProfileStore', 'Template']
|
||||
class MessageType {
|
||||
constructor(profileStore, templates, objectType) {
|
||||
this.profileStore = profileStore
|
||||
@ -32,32 +33,31 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
}
|
||||
|
||||
getReferencedUsers(message) {
|
||||
if (message.userID === null) return [ ]
|
||||
if (message.userID === null) return []
|
||||
|
||||
return [ message.userID ]
|
||||
return [message.userID]
|
||||
}
|
||||
|
||||
preProcess(message) {
|
||||
preProcess(message) {}
|
||||
|
||||
}
|
||||
|
||||
preRender(message) {
|
||||
|
||||
}
|
||||
preRender(message) {}
|
||||
|
||||
render(message) {
|
||||
const variables = { message
|
||||
, users: this.profileStore
|
||||
, author: this.profileStore.get(message.userID)
|
||||
, DateUtil
|
||||
, Language
|
||||
}
|
||||
const variables = {
|
||||
message,
|
||||
users: this.profileStore,
|
||||
author: this.profileStore.get(message.userID),
|
||||
DateUtil,
|
||||
Language,
|
||||
}
|
||||
|
||||
if (variables.author == null) {
|
||||
variables.author = User.getGuest(message.username)
|
||||
}
|
||||
|
||||
return DomUtil.createFragmentFromHtml(this.templates[message.objectType.replace(/\./g, '-')].fetch(variables))
|
||||
return DomUtil.createFragmentFromHtml(
|
||||
this.templates[message.objectType.replace(/\./g, '-')].fetch(variables)
|
||||
)
|
||||
}
|
||||
|
||||
renderPlainText(message) {
|
||||
@ -71,4 +71,4 @@ define([ 'WoltLabSuite/Core/Date/Util'
|
||||
MessageType.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return MessageType
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore', 'roomID' ].concat(MessageType.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['ProfileStore', 'roomID'].concat(
|
||||
MessageType.DEPENDENCIES || []
|
||||
)
|
||||
class Away extends MessageType {
|
||||
constructor(profileStore, roomID, ...superDeps) {
|
||||
super(...superDeps)
|
||||
@ -24,12 +26,13 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
render(message) {
|
||||
const isSilent = message.payload.rooms.find(room => room.roomID === this.roomID).isSilent
|
||||
const isSilent = message.payload.rooms.find(
|
||||
(room) => room.roomID === this.roomID
|
||||
).isSilent
|
||||
|
||||
if (!isSilent) {
|
||||
return super.render(message)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -45,4 +48,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
Away.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Away
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore', 'roomID' ].concat(MessageType.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['ProfileStore', 'roomID'].concat(
|
||||
MessageType.DEPENDENCIES || []
|
||||
)
|
||||
class Back extends MessageType {
|
||||
constructor(profileStore, roomID, ...superDeps) {
|
||||
super(...superDeps)
|
||||
@ -24,12 +26,13 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
render(message) {
|
||||
const isSilent = message.payload.rooms.find(room => room.roomID === this.roomID).isSilent
|
||||
const isSilent = message.payload.rooms.find(
|
||||
(room) => room.roomID === this.roomID
|
||||
).isSilent
|
||||
|
||||
if (!isSilent) {
|
||||
return super.render(message)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -45,4 +48,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
Back.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Back
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain' ], function (Plain) {
|
||||
"use strict";
|
||||
define(['./Plain'], function (Plain) {
|
||||
'use strict'
|
||||
|
||||
class Broadcast extends Plain {
|
||||
renderPlainText(message) {
|
||||
@ -21,4 +21,4 @@ define([ './Plain' ], function (Plain) {
|
||||
}
|
||||
|
||||
return Broadcast
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class ChatUpdate extends MessageType {
|
||||
preRender(message) {
|
||||
@ -25,4 +25,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
return ChatUpdate
|
||||
});
|
||||
})
|
||||
|
@ -11,15 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Color extends MessageType {
|
||||
render(message) {
|
||||
if (message.isOwnMessage()) {
|
||||
return super.render(message)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -34,4 +33,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
return Color
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,13 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, '../Helper'
|
||||
, '../MessageType'
|
||||
], function (DomTraverse, Language, Helper, MessageType) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Dom/Traverse',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'../Helper',
|
||||
'../MessageType',
|
||||
], function (DomTraverse, Language, Helper, MessageType) {
|
||||
'use strict'
|
||||
|
||||
const decorators = Symbol('decorators')
|
||||
|
||||
@ -36,16 +37,19 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
}
|
||||
|
||||
getReferencedUsers(message) {
|
||||
return super.getReferencedUsers(message).concat([ message.payload.user.userID ])
|
||||
return super
|
||||
.getReferencedUsers(message)
|
||||
.concat([message.payload.user.userID])
|
||||
}
|
||||
|
||||
render(message) {
|
||||
const rooms = message.payload.rooms.map(function (item) {
|
||||
const aug = { lastPull: null
|
||||
, lastPullHTML: null
|
||||
, lastPush: null
|
||||
, lastPushHTML: null
|
||||
}
|
||||
const aug = {
|
||||
lastPull: null,
|
||||
lastPullHTML: null,
|
||||
lastPush: null,
|
||||
lastPushHTML: null,
|
||||
}
|
||||
|
||||
if (item.lastPull) {
|
||||
aug.lastPull = new Date(item.lastPull * 1000)
|
||||
@ -57,28 +61,35 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
aug.lastPushHTML = Helper.getTimeElementHTML(aug.lastPush)
|
||||
}
|
||||
|
||||
return Object.assign({ }, item, aug)
|
||||
return Object.assign({}, item, aug)
|
||||
})
|
||||
|
||||
const payload = Helper.deepFreeze(
|
||||
Array.from(this[decorators]).reduce( (payload, decorator) => decorator(payload)
|
||||
, Object.assign({ }, message.payload, { rooms })
|
||||
)
|
||||
Array.from(this[decorators]).reduce(
|
||||
(payload, decorator) => decorator(payload),
|
||||
Object.assign({}, message.payload, { rooms })
|
||||
)
|
||||
)
|
||||
|
||||
const fragment = super.render(new Proxy(message, {
|
||||
get: function (target, property) {
|
||||
if (property === 'payload') return payload
|
||||
return target[property]
|
||||
}
|
||||
}))
|
||||
const fragment = super.render(
|
||||
new Proxy(message, {
|
||||
get: function (target, property) {
|
||||
if (property === 'payload') return payload
|
||||
return target[property]
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const icon = elCreate('span')
|
||||
icon.classList.add('icon', 'icon16', 'fa-times', 'jsTooltip', 'hideIcon')
|
||||
icon.setAttribute('title', Language.get('wcf.global.button.hide'))
|
||||
icon.addEventListener('click', () => elHide(DomTraverse.parentBySel(icon, '.chatMessageBoundary')))
|
||||
icon.addEventListener('click', () =>
|
||||
elHide(DomTraverse.parentBySel(icon, '.chatMessageBoundary'))
|
||||
)
|
||||
|
||||
const elem = fragment.querySelector('.chatMessage .containerList > li:first-child .containerHeadline')
|
||||
const elem = fragment.querySelector(
|
||||
'.chatMessage .containerList > li:first-child .containerHeadline'
|
||||
)
|
||||
elem.insertBefore(icon, elem.firstChild)
|
||||
|
||||
return fragment
|
||||
@ -86,4 +97,4 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
}
|
||||
|
||||
return Info
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,11 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType', 'WoltLabSuite/Core/Language' ], function (MessageType, Language) {
|
||||
"use strict";
|
||||
define(['../MessageType', 'WoltLabSuite/Core/Language'], function (
|
||||
MessageType,
|
||||
Language
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
class Join extends MessageType {
|
||||
shouldUpdateUserList(message) {
|
||||
@ -20,9 +23,15 @@ define([ '../MessageType', 'WoltLabSuite/Core/Language' ], function (MessageType
|
||||
}
|
||||
|
||||
renderPlainText(message) {
|
||||
return '[➡️] ' + Language.get('chat.messageType.be.bastelstu.chat.messageType.join.plain', { author: { username: message.username } })
|
||||
return (
|
||||
'[➡️] ' +
|
||||
Language.get(
|
||||
'chat.messageType.be.bastelstu.chat.messageType.join.plain',
|
||||
{ author: { username: message.username } }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Join
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,11 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType', 'WoltLabSuite/Core/Language' ], function (MessageType, Language) {
|
||||
"use strict";
|
||||
define(['../MessageType', 'WoltLabSuite/Core/Language'], function (
|
||||
MessageType,
|
||||
Language
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
class Leave extends MessageType {
|
||||
shouldUpdateUserList(message) {
|
||||
@ -20,9 +23,15 @@ define([ '../MessageType', 'WoltLabSuite/Core/Language' ], function (MessageType
|
||||
}
|
||||
|
||||
renderPlainText(message) {
|
||||
return '[⬅️️] ' + Language.get('chat.messageType.be.bastelstu.chat.messageType.leave.plain', { author: { username: message.username } })
|
||||
return (
|
||||
'[⬅️️] ' +
|
||||
Language.get(
|
||||
'chat.messageType.be.bastelstu.chat.messageType.leave.plain',
|
||||
{ author: { username: message.username } }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Leave
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Me extends MessageType {
|
||||
|
||||
}
|
||||
class Me extends MessageType {}
|
||||
|
||||
return Me
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Plain extends MessageType {
|
||||
joinable(a, b) {
|
||||
@ -25,4 +25,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
return Plain
|
||||
});
|
||||
})
|
||||
|
@ -11,28 +11,35 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Helper'
|
||||
, 'WoltLabSuite/Core/Date/Util'
|
||||
, '../MessageType'
|
||||
], function (Helper, DateUtil, MessageType) {
|
||||
"use strict";
|
||||
define([
|
||||
'../Helper',
|
||||
'WoltLabSuite/Core/Date/Util',
|
||||
'../MessageType',
|
||||
], function (Helper, DateUtil, MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Suspend extends MessageType {
|
||||
render(message) {
|
||||
const expires = message.payload.suspension.expires !== null ? new Date(message.payload.suspension.expires * 1000) : null
|
||||
const formattedExpires = expires !== null ? DateUtil.formatDateTime(expires) : null
|
||||
const aug = { expires
|
||||
, formattedExpires
|
||||
}
|
||||
const suspension = Object.assign({ }, message.payload.suspension, aug)
|
||||
const payload = Helper.deepFreeze(Object.assign({ }, message.payload, { suspension }))
|
||||
const expires =
|
||||
message.payload.suspension.expires !== null
|
||||
? new Date(message.payload.suspension.expires * 1000)
|
||||
: null
|
||||
const formattedExpires =
|
||||
expires !== null ? DateUtil.formatDateTime(expires) : null
|
||||
const aug = { expires, formattedExpires }
|
||||
const suspension = Object.assign({}, message.payload.suspension, aug)
|
||||
const payload = Helper.deepFreeze(
|
||||
Object.assign({}, message.payload, { suspension })
|
||||
)
|
||||
|
||||
return super.render(new Proxy(message, {
|
||||
get: function (target, property) {
|
||||
if (property === 'payload') return payload
|
||||
return target[property]
|
||||
}
|
||||
}))
|
||||
return super.render(
|
||||
new Proxy(message, {
|
||||
get: function (target, property) {
|
||||
if (property === 'payload') return payload
|
||||
return target[property]
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
shouldUpdateUserList(message) {
|
||||
@ -41,4 +48,4 @@ define([ '../Helper'
|
||||
}
|
||||
|
||||
return Suspend
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain' ], function (Plain) {
|
||||
"use strict";
|
||||
define(['./Plain'], function (Plain) {
|
||||
'use strict'
|
||||
|
||||
class Team extends Plain {
|
||||
joinable(a, b) {
|
||||
@ -25,4 +25,4 @@ define([ './Plain' ], function (Plain) {
|
||||
}
|
||||
|
||||
return Team
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class TemproomCreated extends MessageType {
|
||||
|
||||
}
|
||||
class TemproomCreated extends MessageType {}
|
||||
|
||||
return TemproomCreated
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class TemproomInvited extends MessageType {
|
||||
|
||||
}
|
||||
class TemproomInvited extends MessageType {}
|
||||
|
||||
return TemproomInvited
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiMessageStream' ].concat(MessageType.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['UiMessageStream'].concat(
|
||||
MessageType.DEPENDENCIES || []
|
||||
)
|
||||
class Tombstone extends MessageType {
|
||||
constructor(messageStream, ...superDeps) {
|
||||
super(...superDeps)
|
||||
@ -37,17 +39,21 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
if (!chatMessage) return false
|
||||
|
||||
const rendered = super.render(message)
|
||||
const oldIcon = node.querySelector('.chatMessageContent > .chatMessageIcon')
|
||||
const oldIcon = node.querySelector(
|
||||
'.chatMessageContent > .chatMessageIcon'
|
||||
)
|
||||
const newIcon = rendered.querySelector('.chatMessageIcon')
|
||||
|
||||
if (oldIcon) {
|
||||
oldIcon.parentNode.replaceChild(newIcon, oldIcon)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
chatMessage.parentNode.insertBefore(newIcon, chatMessage)
|
||||
}
|
||||
|
||||
chatMessage.parentNode.replaceChild(rendered.querySelector('.chatMessage'), chatMessage)
|
||||
chatMessage.parentNode.replaceChild(
|
||||
rendered.querySelector('.chatMessage'),
|
||||
chatMessage
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
@ -55,4 +61,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
Tombstone.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Tombstone
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../MessageType' ], function (MessageType) {
|
||||
"use strict";
|
||||
define(['../MessageType'], function (MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Unsuspend extends MessageType {
|
||||
shouldUpdateUserList(message) {
|
||||
@ -21,4 +21,4 @@ define([ '../MessageType' ], function (MessageType) {
|
||||
}
|
||||
|
||||
return Unsuspend
|
||||
});
|
||||
})
|
||||
|
@ -11,11 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, '../MessageType'
|
||||
], function (DomTraverse, Language, MessageType) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Dom/Traverse',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'../MessageType',
|
||||
], function (DomTraverse, Language, MessageType) {
|
||||
'use strict'
|
||||
|
||||
class Where extends MessageType {
|
||||
render(message) {
|
||||
@ -24,7 +25,9 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
const icon = elCreate('span')
|
||||
icon.classList.add('icon', 'icon16', 'fa-times', 'jsTooltip', 'hideIcon')
|
||||
icon.setAttribute('title', Language.get('wcf.global.button.hide'))
|
||||
icon.addEventListener('click', () => elHide(DomTraverse.parentBySel(icon, '.chatMessageBoundary')))
|
||||
icon.addEventListener('click', () =>
|
||||
elHide(DomTraverse.parentBySel(icon, '.chatMessageBoundary'))
|
||||
)
|
||||
|
||||
const elem = fragment.querySelector('.jsRoomInfo > .containerHeadline')
|
||||
elem.insertBefore(icon, elem.firstChild)
|
||||
@ -34,4 +37,4 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
}
|
||||
|
||||
return Where
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Plain' ], function (Plain) {
|
||||
"use strict";
|
||||
define(['./Plain'], function (Plain) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ].concat(Plain.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['UiInput'].concat(Plain.DEPENDENCIES || [])
|
||||
class Whisper extends Plain {
|
||||
constructor(input, ...superDeps) {
|
||||
super(...superDeps)
|
||||
@ -26,28 +26,39 @@ define([ './Plain' ], function (Plain) {
|
||||
const fragment = super.render(message)
|
||||
|
||||
if (this.input != null) {
|
||||
Array.prototype.forEach.call(fragment.querySelectorAll('[data-insert-whisper]'), (function (el) {
|
||||
el.addEventListener('click', (function () {
|
||||
const username = el.dataset.insertWhisper
|
||||
const sanitizedUsername = username.replace(/"/g, '""')
|
||||
const command = `/whisper "${sanitizedUsername}"`
|
||||
Array.prototype.forEach.call(
|
||||
fragment.querySelectorAll('[data-insert-whisper]'),
|
||||
function (el) {
|
||||
el.addEventListener(
|
||||
'click',
|
||||
function () {
|
||||
const username = el.dataset.insertWhisper
|
||||
const sanitizedUsername = username.replace(/"/g, '""')
|
||||
const command = `/whisper "${sanitizedUsername}"`
|
||||
|
||||
if (this.input.getText().indexOf(command) !== 0) {
|
||||
this.input.insertText(`${command} `, { prepend: true, append: false })
|
||||
this.input.focus()
|
||||
}
|
||||
}).bind(this))
|
||||
}).bind(this))
|
||||
if (this.input.getText().indexOf(command) !== 0) {
|
||||
this.input.insertText(`${command} `, {
|
||||
prepend: true,
|
||||
append: false,
|
||||
})
|
||||
this.input.focus()
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
joinable(a, b) {
|
||||
return a.userID === b.userID && a.payload.recipient === b.payload.recipient
|
||||
return (
|
||||
a.userID === b.userID && a.payload.recipient === b.payload.recipient
|
||||
)
|
||||
}
|
||||
}
|
||||
Whisper.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Whisper
|
||||
});
|
||||
})
|
||||
|
@ -11,16 +11,18 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './console'
|
||||
, 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
, './Room'
|
||||
], function (console, Ajax, Room) {
|
||||
"use strict";
|
||||
define(['./console', 'Bastelstu.be/PromiseWrap/Ajax', './Room'], function (
|
||||
console,
|
||||
Ajax,
|
||||
Room
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'sessionID', 'Room', 'Message' ]
|
||||
const DEPENDENCIES = ['sessionID', 'Room', 'Message']
|
||||
class Messenger {
|
||||
constructor(sessionID, room, Message) {
|
||||
if (!(room instanceof Room)) throw new TypeError('You must pass a Room to the Messenger')
|
||||
if (!(room instanceof Room))
|
||||
throw new TypeError('You must pass a Room to the Messenger')
|
||||
|
||||
this.sessionID = sessionID
|
||||
this.room = room
|
||||
@ -30,9 +32,7 @@ define([ './console'
|
||||
async pull(from = 0, to = 0, inLog = false) {
|
||||
console.debug(`Messenger.pull`, 'from', from, 'to', to, 'inLog', inLog)
|
||||
|
||||
const payload = { actionName: 'pull'
|
||||
, parameters: { inLog }
|
||||
}
|
||||
const payload = { actionName: 'pull', parameters: { inLog } }
|
||||
if (from !== 0 && to !== 0) {
|
||||
throw new Error('You must not set both from and to')
|
||||
}
|
||||
@ -40,42 +40,41 @@ define([ './console'
|
||||
if (to !== 0) payload.parameters.to = to
|
||||
|
||||
const data = await Ajax.api(this, payload)
|
||||
const messages = Object.values(data.returnValues.messages).map((item) => this.Message.instance(item))
|
||||
const messages = Object.values(data.returnValues.messages).map((item) =>
|
||||
this.Message.instance(item)
|
||||
)
|
||||
const { from: newFrom, to: newTo } = data.returnValues
|
||||
|
||||
return { messages, from: newFrom, to: newTo }
|
||||
}
|
||||
|
||||
async push({ commandID, parameters }) {
|
||||
const payload = { actionName: 'push'
|
||||
, parameters: { commandID
|
||||
, parameters: JSON.stringify(parameters)
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
actionName: 'push',
|
||||
parameters: { commandID, parameters: JSON.stringify(parameters) },
|
||||
}
|
||||
|
||||
return Ajax.api(this, payload)
|
||||
}
|
||||
|
||||
async pushAttachment(tmpHash) {
|
||||
const payload = { actionName: 'pushAttachment'
|
||||
, parameters: { tmpHash }
|
||||
}
|
||||
const payload = { actionName: 'pushAttachment', parameters: { tmpHash } }
|
||||
|
||||
return Ajax.api(this, payload)
|
||||
}
|
||||
|
||||
_ajaxSetup() {
|
||||
return { silent: true
|
||||
, ignoreError: true
|
||||
, data: { className: 'chat\\data\\message\\MessageAction'
|
||||
, parameters: { roomID: this.room.roomID
|
||||
, sessionID: this.sessionID
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
silent: true,
|
||||
ignoreError: true,
|
||||
data: {
|
||||
className: 'chat\\data\\message\\MessageAction',
|
||||
parameters: { roomID: this.room.roomID, sessionID: this.sessionID },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Messenger.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Messenger
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class ParseError extends Error {
|
||||
constructor(message, data) {
|
||||
@ -23,4 +23,4 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
return ParseError
|
||||
});
|
||||
})
|
||||
|
@ -11,88 +11,112 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'Bastelstu.be/parser-combinator'
|
||||
], function (parsec) {
|
||||
"use strict";
|
||||
define(['Bastelstu.be/parser-combinator'], function (parsec) {
|
||||
'use strict'
|
||||
|
||||
const { C, F, N, X, parser, Streams } = parsec
|
||||
const response = parsec.parsec.response
|
||||
|
||||
const peek = function (p) {
|
||||
return new parser((input, index = 0) =>
|
||||
p
|
||||
.parse(input, index)
|
||||
.fold(
|
||||
accept => response.accept(accept.value, accept.input, index, false),
|
||||
reject => response.reject(input.location(reject.offset), false)
|
||||
p.parse(input, index).fold(
|
||||
(accept) => response.accept(accept.value, accept.input, index, false),
|
||||
(reject) => response.reject(input.location(reject.offset), false)
|
||||
)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const Whitespace = F.satisfy(item => /\s/.test(item))
|
||||
const Whitespace = F.satisfy((item) => /\s/.test(item))
|
||||
|
||||
const Rest = F.any.optrep().map(item => item.join(''))
|
||||
const Rest1 = F.any.rep().map(item => item.join(''))
|
||||
const Rest = F.any.optrep().map((item) => item.join(''))
|
||||
const Rest1 = F.any.rep().map((item) => item.join(''))
|
||||
|
||||
const AlnumTrigger = C.letter.or(N.digit).rep().map(item => item.join(''))
|
||||
const SymbolicTrigger = F.not(C.letter.or(N.digit).or(Whitespace)).rep().map(item => item.join(''))
|
||||
const AlnumTrigger = C.letter
|
||||
.or(N.digit)
|
||||
.rep()
|
||||
.map((item) => item.join(''))
|
||||
const SymbolicTrigger = F.not(C.letter.or(N.digit).or(Whitespace))
|
||||
.rep()
|
||||
.map((item) => item.join(''))
|
||||
const Slash = C.char('/')
|
||||
const Trigger = Slash.thenRight(
|
||||
peek(Slash.map(item => null)).or(AlnumTrigger.thenLeft(Whitespace.rep().or(F.eos))).or(SymbolicTrigger.thenLeft(Whitespace.optrep()))
|
||||
peek(Slash.map((item) => null))
|
||||
.or(AlnumTrigger.thenLeft(Whitespace.rep().or(F.eos)))
|
||||
.or(SymbolicTrigger.thenLeft(Whitespace.optrep()))
|
||||
).or(F.returns(null))
|
||||
const Command = Trigger.then(Rest)
|
||||
|
||||
const Quote = C.char('"')
|
||||
const QuotedUsername = Quote.thenRight(
|
||||
((Quote.thenRight(Quote)).or(F.not(Quote))).rep()
|
||||
).thenLeft(Quote).map(item => item.join(''))
|
||||
Quote.thenRight(Quote).or(F.not(Quote)).rep()
|
||||
)
|
||||
.thenLeft(Quote)
|
||||
.map((item) => item.join(''))
|
||||
const Comma = C.char(',')
|
||||
const UnquotedUsername = F.not(Comma.or(Quote).or(Whitespace)).then(F.not(Comma.or(Whitespace)).optrep().map(item => item.join(''))).map(item => item.join(''))
|
||||
const UnquotedUsername = F.not(Comma.or(Quote).or(Whitespace))
|
||||
.then(
|
||||
F.not(Comma.or(Whitespace))
|
||||
.optrep()
|
||||
.map((item) => item.join(''))
|
||||
)
|
||||
.map((item) => item.join(''))
|
||||
const Username = QuotedUsername.or(UnquotedUsername)
|
||||
|
||||
const Decimal = (length) => N.digit.occurrence(length).map(item => parseInt(item.join(''), 10))
|
||||
const Decimal = (length) =>
|
||||
N.digit.occurrence(length).map((item) => parseInt(item.join(''), 10))
|
||||
|
||||
const Hexadecimal = N.digit
|
||||
.or(C.charIn('abcdefABCDEF'))
|
||||
.rep()
|
||||
.map(x => x.join(''))
|
||||
.map((x) => x.join(''))
|
||||
|
||||
const RGBHex = (C.char('#').opt())
|
||||
const RGBHex = C.char('#')
|
||||
.opt()
|
||||
.thenRight(
|
||||
Hexadecimal.filter(x => x.length === 3 || x.length === 6)
|
||||
.map(item => {
|
||||
if (item.length === 3) {
|
||||
item = `${item[0]}${item[0]}${item[1]}${item[1]}${item[2]}${item[2]}`
|
||||
}
|
||||
Hexadecimal.filter((x) => x.length === 3 || x.length === 6).map(
|
||||
(item) => {
|
||||
if (item.length === 3) {
|
||||
item = `${item[0]}${item[0]}${item[1]}${item[1]}${item[2]}${item[2]}`
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
).map(item => `#${item}`)
|
||||
return item
|
||||
}
|
||||
)
|
||||
)
|
||||
.map((item) => `#${item}`)
|
||||
|
||||
const Dash = C.char('-')
|
||||
const Datestring = Decimal(4).filter(item => 2000 <= item && item <= 2030)
|
||||
.thenLeft(Dash).then(Decimal(2).filter(item => 1 <= item && item <= 12))
|
||||
.thenLeft(Dash).then(Decimal(2).filter(item => 1 <= item))
|
||||
const Datestring = Decimal(4)
|
||||
.filter((item) => 2000 <= item && item <= 2030)
|
||||
.thenLeft(Dash)
|
||||
.then(Decimal(2).filter((item) => 1 <= item && item <= 12))
|
||||
.thenLeft(Dash)
|
||||
.then(Decimal(2).filter((item) => 1 <= item))
|
||||
|
||||
const Colon = C.char(':')
|
||||
const Timestring = Decimal(2).filter(item => 0 <= item && item <= 23)
|
||||
.thenLeft(Colon).then(Decimal(2).filter(item => 0 <= item && item <= 59))
|
||||
.thenLeft(Colon).then(Decimal(2).filter(item => 0 <= item && item <= 59))
|
||||
const Timestring = Decimal(2)
|
||||
.filter((item) => 0 <= item && item <= 23)
|
||||
.thenLeft(Colon)
|
||||
.then(Decimal(2).filter((item) => 0 <= item && item <= 59))
|
||||
.thenLeft(Colon)
|
||||
.then(Decimal(2).filter((item) => 0 <= item && item <= 59))
|
||||
|
||||
const ISODate = Datestring.then(C.char('T').thenRight(Timestring).opt()).map(function ([ year, month, day, time ]) {
|
||||
const date = new Date()
|
||||
date.setFullYear(year)
|
||||
date.setMonth(month - 1)
|
||||
date.setDate(day)
|
||||
const ISODate = Datestring.then(C.char('T').thenRight(Timestring).opt()).map(
|
||||
function ([year, month, day, time]) {
|
||||
const date = new Date()
|
||||
date.setFullYear(year)
|
||||
date.setMonth(month - 1)
|
||||
date.setDate(day)
|
||||
|
||||
time.map(function ([ hour, minute, second ]) {
|
||||
date.setHours(hour)
|
||||
date.setMinutes(minute)
|
||||
date.setSeconds(second)
|
||||
})
|
||||
time.map(function ([hour, minute, second]) {
|
||||
date.setHours(hour)
|
||||
date.setMinutes(minute)
|
||||
date.setSeconds(second)
|
||||
})
|
||||
|
||||
return date
|
||||
})
|
||||
return date
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
Streams,
|
||||
@ -122,4 +146,4 @@ define([ 'Bastelstu.be/parser-combinator'
|
||||
N,
|
||||
X,
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -11,14 +11,15 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
, './DataStructure/LRU'
|
||||
, './User'
|
||||
, 'WoltLabSuite/Core/User'
|
||||
], function (Ajax, LRU, User, CoreUser) {
|
||||
"use strict";
|
||||
define([
|
||||
'Bastelstu.be/PromiseWrap/Ajax',
|
||||
'./DataStructure/LRU',
|
||||
'./User',
|
||||
'WoltLabSuite/Core/User',
|
||||
], function (Ajax, LRU, User, CoreUser) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ ]
|
||||
const DEPENDENCIES = []
|
||||
/**
|
||||
* ProfileStore stores information about users.
|
||||
*/
|
||||
@ -40,44 +41,47 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
*/
|
||||
async ensureUsersByIDs(userIDs) {
|
||||
// Dedup
|
||||
userIDs = userIDs.filter((value, index, self) => self.indexOf(value) === index)
|
||||
.map(userID => parseInt(userID, 10))
|
||||
userIDs = userIDs
|
||||
.filter((value, index, self) => self.indexOf(value) === index)
|
||||
.map((userID) => parseInt(userID, 10))
|
||||
|
||||
const missing = [ ]
|
||||
const promises = [ ]
|
||||
userIDs.forEach((function (userID) {
|
||||
if (this.isRecent(userID)) return
|
||||
if (this.processing.has(userID)) {
|
||||
promises.push(this.processing.get(userID))
|
||||
return
|
||||
}
|
||||
missing.push(userID)
|
||||
}).bind(this))
|
||||
const missing = []
|
||||
const promises = []
|
||||
userIDs.forEach(
|
||||
function (userID) {
|
||||
if (this.isRecent(userID)) return
|
||||
if (this.processing.has(userID)) {
|
||||
promises.push(this.processing.get(userID))
|
||||
return
|
||||
}
|
||||
missing.push(userID)
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
if (missing.length > 0) {
|
||||
const payload = { actionName: 'getUsersByID'
|
||||
, parameters: { userIDs: missing }
|
||||
}
|
||||
const request = (async _ => {
|
||||
const payload = {
|
||||
actionName: 'getUsersByID',
|
||||
parameters: { userIDs: missing },
|
||||
}
|
||||
const request = (async (_) => {
|
||||
try {
|
||||
const response = await Ajax.api(this, payload)
|
||||
return Object.entries(response.returnValues).forEach(([ userID, user ]) => {
|
||||
userID = parseInt(userID, 10)
|
||||
const data = { user: new User(user)
|
||||
, date: Date.now()
|
||||
}
|
||||
this.users.set(userID, data)
|
||||
this.processing.delete(userID)
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
missing.forEach(userID => this.processing.delete(userID))
|
||||
return Object.entries(response.returnValues).forEach(
|
||||
([userID, user]) => {
|
||||
userID = parseInt(userID, 10)
|
||||
const data = { user: new User(user), date: Date.now() }
|
||||
this.users.set(userID, data)
|
||||
this.processing.delete(userID)
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
missing.forEach((userID) => this.processing.delete(userID))
|
||||
|
||||
throw err
|
||||
}
|
||||
})()
|
||||
|
||||
missing.forEach(userID => this.processing.set(userID, request))
|
||||
missing.forEach((userID) => this.processing.set(userID, request))
|
||||
promises.push(request)
|
||||
}
|
||||
|
||||
@ -93,7 +97,7 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
async getUsersByIDs(userIDs) {
|
||||
await this.ensureUsersByIDs(userIDs)
|
||||
|
||||
return new Map(userIDs.map(userID => [ userID, this.get(userID) ]))
|
||||
return new Map(userIDs.map((userID) => [userID, this.get(userID)]))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,7 +161,7 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
const user = this.users.get(userID)
|
||||
|
||||
if (user != null) {
|
||||
return user.date > (Date.now() - (5 * 60e3))
|
||||
return user.date > Date.now() - 5 * 60e3
|
||||
}
|
||||
|
||||
return false
|
||||
@ -169,7 +173,7 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
* @returns {User[]}
|
||||
*/
|
||||
values() {
|
||||
return Array.from(this.users.values()).map(item => item.user)
|
||||
return Array.from(this.users.values()).map((item) => item.user)
|
||||
}
|
||||
|
||||
pushLastActivity(userID) {
|
||||
@ -178,18 +182,19 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
this.lastActivity.add(userID)
|
||||
}
|
||||
|
||||
* getLastActivity() {
|
||||
yield * this.lastActivity
|
||||
*getLastActivity() {
|
||||
yield* this.lastActivity
|
||||
}
|
||||
|
||||
_ajaxSetup() {
|
||||
return { silent: true
|
||||
, ignoreError: true
|
||||
, data: { className: 'chat\\data\\user\\UserAction' }
|
||||
}
|
||||
return {
|
||||
silent: true,
|
||||
ignoreError: true,
|
||||
data: { className: 'chat\\data\\user\\UserAction' },
|
||||
}
|
||||
}
|
||||
}
|
||||
ProfileStore.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return ProfileStore
|
||||
});
|
||||
})
|
||||
|
@ -11,20 +11,21 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
, 'WoltLabSuite/Core/Core'
|
||||
, './User'
|
||||
], function (Ajax, Core, User) {
|
||||
"use strict";
|
||||
define([
|
||||
'Bastelstu.be/PromiseWrap/Ajax',
|
||||
'WoltLabSuite/Core/Core',
|
||||
'./User',
|
||||
], function (Ajax, Core, User) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'sessionID', 'roomID' ]
|
||||
const DEPENDENCIES = ['sessionID', 'roomID']
|
||||
/**
|
||||
* Represents a chat room.
|
||||
*/
|
||||
class Room {
|
||||
constructor(sessionID, roomID) {
|
||||
this.sessionID = sessionID
|
||||
this.roomID = roomID
|
||||
this.roomID = roomID
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,12 +34,11 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async join() {
|
||||
const payload = { className: 'chat\\data\\room\\RoomAction'
|
||||
, actionName: 'join'
|
||||
, parameters: { roomID: this.roomID
|
||||
, sessionID: this.sessionID
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
className: 'chat\\data\\room\\RoomAction',
|
||||
actionName: 'join',
|
||||
parameters: { roomID: this.roomID, sessionID: this.sessionID },
|
||||
}
|
||||
|
||||
return Ajax.api(this, payload)
|
||||
}
|
||||
@ -49,12 +49,11 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
* @param {boolean} unload Send a beacon if true'ish and a regular AJAX request otherwise.
|
||||
*/
|
||||
leave(unload = false) {
|
||||
const payload = { className: 'chat\\data\\room\\RoomAction'
|
||||
, actionName: 'leave'
|
||||
, parameters: { roomID: this.roomID
|
||||
, sessionID: this.sessionID
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
className: 'chat\\data\\room\\RoomAction',
|
||||
actionName: 'leave',
|
||||
parameters: { roomID: this.roomID, sessionID: this.sessionID },
|
||||
}
|
||||
|
||||
if (unload && FormData && (navigator.sendBeacon || window.fetch)) {
|
||||
// Ordinary AJAX requests are unreliable during unload:
|
||||
@ -65,22 +64,26 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
|
||||
const formData = new FormData()
|
||||
Core.serialize(payload)
|
||||
.split('&')
|
||||
.map((item) => item.split('='))
|
||||
.map((item) => item.map(decodeURIComponent))
|
||||
.forEach((item) => formData.append(item[0], item[1]))
|
||||
.split('&')
|
||||
.map((item) => item.split('='))
|
||||
.map((item) => item.map(decodeURIComponent))
|
||||
.forEach((item) => formData.append(item[0], item[1]))
|
||||
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(url, formData)
|
||||
}
|
||||
|
||||
if (window.fetch) {
|
||||
fetch(url, { method: 'POST', keepalive: true, redirect: 'follow', body: formData })
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
keepalive: true,
|
||||
redirect: 'follow',
|
||||
body: formData,
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return Ajax.api(this, payload)
|
||||
}
|
||||
}
|
||||
@ -91,23 +94,22 @@ define([ 'Bastelstu.be/PromiseWrap/Ajax'
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async getUsers() {
|
||||
const payload = { className: 'chat\\data\\room\\RoomAction'
|
||||
, actionName: 'getUsers'
|
||||
, objectIDs: [ this.roomID ]
|
||||
}
|
||||
const payload = {
|
||||
className: 'chat\\data\\room\\RoomAction',
|
||||
actionName: 'getUsers',
|
||||
objectIDs: [this.roomID],
|
||||
}
|
||||
|
||||
const result = await Ajax.api(this, payload)
|
||||
|
||||
return Object.values(result.returnValues).map(user => new User(user))
|
||||
return Object.values(result.returnValues).map((user) => new User(user))
|
||||
}
|
||||
|
||||
_ajaxSetup() {
|
||||
return { silent: true
|
||||
, ignoreError: true
|
||||
}
|
||||
return { silent: true, ignoreError: true }
|
||||
}
|
||||
}
|
||||
Room.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Room
|
||||
});
|
||||
})
|
||||
|
@ -11,30 +11,30 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Template' ], function (_Template) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Template'], function (_Template) {
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Template extends WoltLab Suite's Templates by passing in a list of
|
||||
* re-usable sub-templates.
|
||||
*/
|
||||
class Template extends _Template {
|
||||
constructor(string, templates = { }) {
|
||||
constructor(string, templates = {}) {
|
||||
super(string)
|
||||
|
||||
this.templates = templates
|
||||
|
||||
const oldFetch = this.fetch
|
||||
this.fetch = (function (variables) {
|
||||
variables = Object.assign({ }, variables)
|
||||
this.fetch = function (variables) {
|
||||
variables = Object.assign({}, variables)
|
||||
|
||||
const templates = Object.assign({ }, this.templates, variables.t || { })
|
||||
const templates = Object.assign({}, this.templates, variables.t || {})
|
||||
variables.t = templates
|
||||
|
||||
return oldFetch(variables)
|
||||
}).bind(this)
|
||||
}.bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
return Template
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class Ui {
|
||||
constructor() {
|
||||
}
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
return Ui
|
||||
});
|
||||
})
|
||||
|
@ -11,30 +11,42 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/Upload'
|
||||
, 'WoltLabSuite/Core/Dom/Change/Listener'
|
||||
, 'WoltLabSuite/Core/Dom/Util'
|
||||
, 'WoltLabSuite/Core/Ui/Dialog'
|
||||
, '../../DataStructure/EventEmitter'
|
||||
], function(Language, Upload, DomChangeListener, DomUtil, Dialog, EventEmitter) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/Upload',
|
||||
'WoltLabSuite/Core/Dom/Change/Listener',
|
||||
'WoltLabSuite/Core/Dom/Util',
|
||||
'WoltLabSuite/Core/Ui/Dialog',
|
||||
'../../DataStructure/EventEmitter',
|
||||
], function (
|
||||
Language,
|
||||
Upload,
|
||||
DomChangeListener,
|
||||
DomUtil,
|
||||
Dialog,
|
||||
EventEmitter
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DIALOG_BUTTON_ID = 'chatAttachmentUploadButton'
|
||||
const DIALOG_BUTTON_ID = 'chatAttachmentUploadButton'
|
||||
const DIALOG_CONTAINER_ID = 'chatAttachmentUploadDialog'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput', 'Room' ];
|
||||
const DEPENDENCIES = ['UiInput', 'Room']
|
||||
class UiAttachmentUpload extends Upload {
|
||||
constructor(input, room) {
|
||||
const buttonContainer = document.querySelector(`#${DIALOG_CONTAINER_ID} > .upload`)
|
||||
const buttonContainer = document.querySelector(
|
||||
`#${DIALOG_CONTAINER_ID} > .upload`
|
||||
)
|
||||
const buttonContainerId = DomUtil.identify(buttonContainer)
|
||||
|
||||
const previewContainer = document.querySelector(`#${DIALOG_CONTAINER_ID} > .attachmentPreview`)
|
||||
const previewContainer = document.querySelector(
|
||||
`#${DIALOG_CONTAINER_ID} > .attachmentPreview`
|
||||
)
|
||||
const previewContainerId = DomUtil.identify(previewContainer)
|
||||
|
||||
super(buttonContainerId, previewContainerId, {
|
||||
className: 'wcf\\data\\attachment\\AttachmentAction',
|
||||
acceptableFiles: [ '.png', '.gif', '.jpg', '.jpeg' ]
|
||||
acceptableFiles: ['.png', '.gif', '.jpg', '.jpeg'],
|
||||
})
|
||||
|
||||
this.input = input
|
||||
@ -44,7 +56,9 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
this.uploadDescription = document.querySelector(`#${DIALOG_CONTAINER_ID} > small`)
|
||||
this.uploadDescription = document.querySelector(
|
||||
`#${DIALOG_CONTAINER_ID} > small`
|
||||
)
|
||||
|
||||
const button = document.getElementById(DIALOG_BUTTON_ID)
|
||||
const container = document.getElementById(DIALOG_CONTAINER_ID)
|
||||
@ -59,18 +73,20 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
|
||||
Dialog.openStatic(container.id, null, {
|
||||
title: elData(container, 'title'),
|
||||
onShow: () => this.showDialog()
|
||||
onShow: () => this.showDialog(),
|
||||
})
|
||||
})
|
||||
|
||||
const deleteAction = new WCF.Action.Delete('wcf\\data\\attachment\\AttachmentAction', `#${this.previewContainer.id} > p`)
|
||||
const deleteAction = new WCF.Action.Delete(
|
||||
'wcf\\data\\attachment\\AttachmentAction',
|
||||
`#${this.previewContainer.id} > p`
|
||||
)
|
||||
deleteAction.setCallback(() => this.closeDialog())
|
||||
|
||||
this.input.on('input', (event) => {
|
||||
if (event.target.input.value.length == 0) {
|
||||
button.classList.remove('disabled')
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
button.classList.add('disabled')
|
||||
}
|
||||
})
|
||||
@ -95,16 +111,13 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
|
||||
async send(tmpHash, event) {
|
||||
event.preventDefault()
|
||||
const parameters = { promise: Promise.resolve()
|
||||
, tmpHash
|
||||
}
|
||||
const parameters = { promise: Promise.resolve(), tmpHash }
|
||||
this.emit('send', parameters)
|
||||
|
||||
try {
|
||||
await parameters.promise
|
||||
this.closeDialog()
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
// TODO: Error handling
|
||||
console.error(error)
|
||||
}
|
||||
@ -141,14 +154,15 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
* @see WoltLabSuite/Core/Upload#_getParameters
|
||||
*/
|
||||
_getParameters() {
|
||||
this.tmpHash = [ ...crypto.getRandomValues(new Uint8Array(20)) ]
|
||||
.map(m => ('0' + m.toString(16)).slice(-2))
|
||||
this.tmpHash = [...crypto.getRandomValues(new Uint8Array(20))]
|
||||
.map((m) => ('0' + m.toString(16)).slice(-2))
|
||||
.join('')
|
||||
|
||||
return { objectType: "be.bastelstu.chat.message"
|
||||
, parentObjectID: this.room.roomID
|
||||
, tmpHash: this.tmpHash
|
||||
}
|
||||
return {
|
||||
objectType: 'be.bastelstu.chat.message',
|
||||
parentObjectID: this.room.roomID,
|
||||
tmpHash: this.tmpHash,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,22 +172,28 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
if (data.returnValues.errors && data.returnValues.errors[0]) {
|
||||
const error = data.returnValues.errors[0]
|
||||
|
||||
elInnerError(this._button, Language.get(`wcf.attachment.upload.error.${error.errorType}`, {
|
||||
filename: error.filename
|
||||
}))
|
||||
elInnerError(
|
||||
this._button,
|
||||
Language.get(`wcf.attachment.upload.error.${error.errorType}`, {
|
||||
filename: error.filename,
|
||||
})
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
elInnerError(this._button, '')
|
||||
}
|
||||
|
||||
if (data.returnValues.attachments && data.returnValues.attachments[uploadId]) {
|
||||
if (
|
||||
data.returnValues.attachments &&
|
||||
data.returnValues.attachments[uploadId]
|
||||
) {
|
||||
this._removeButton()
|
||||
elHide(this.uploadDescription)
|
||||
|
||||
const attachment = data.returnValues.attachments[uploadId]
|
||||
const url = attachment.thumbnailURL || attachment.tinyURL || attachment.url
|
||||
const url =
|
||||
attachment.thumbnailURL || attachment.tinyURL || attachment.url
|
||||
|
||||
if (!url) {
|
||||
throw new Error('Missing image URL')
|
||||
@ -188,8 +208,7 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
|
||||
if (url === attachment.thumbnailURL) {
|
||||
img.classList.add('attachmentThumbnail')
|
||||
}
|
||||
else if (url === attachment.tinyURL) {
|
||||
} else if (url === attachment.tinyURL) {
|
||||
img.classList.add('attachmentTinyThumbnail')
|
||||
}
|
||||
|
||||
@ -199,9 +218,8 @@ define([ 'WoltLabSuite/Core/Language'
|
||||
DomUtil.replaceElement(progress, img)
|
||||
|
||||
this.createButtonGroup(uploadId, attachment.attachmentID, this.tmpHash)
|
||||
}
|
||||
else {
|
||||
console.error("Received neither an error nor an attachment response")
|
||||
} else {
|
||||
console.error('Received neither an error nor an attachment response')
|
||||
console.error(data.returnValues)
|
||||
}
|
||||
}
|
||||
|
@ -11,25 +11,46 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../console'
|
||||
, '../CommandHandler'
|
||||
, '../LocalStorage'
|
||||
, '../Messenger'
|
||||
, '../ProfileStore'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/Timer/Repeating'
|
||||
], function (console, CommandHandler, LocalStorage, Messenger, ProfileStore, Language, RepeatingTimer) {
|
||||
"use strict";
|
||||
define([
|
||||
'../console',
|
||||
'../CommandHandler',
|
||||
'../LocalStorage',
|
||||
'../Messenger',
|
||||
'../ProfileStore',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/Timer/Repeating',
|
||||
], function (
|
||||
console,
|
||||
CommandHandler,
|
||||
LocalStorage,
|
||||
Messenger,
|
||||
ProfileStore,
|
||||
Language,
|
||||
RepeatingTimer
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'config', 'CommandHandler', 'Messenger', 'ProfileStore', 'UiInput' ]
|
||||
const DEPENDENCIES = [
|
||||
'config',
|
||||
'CommandHandler',
|
||||
'Messenger',
|
||||
'ProfileStore',
|
||||
'UiInput',
|
||||
]
|
||||
class AutoAway {
|
||||
constructor(config, commandHandler, messenger, profileStore, input) {
|
||||
if (!(commandHandler instanceof CommandHandler)) throw new TypeError('You must pass a CommandHandler to the AutoAway')
|
||||
if (!(messenger instanceof Messenger)) throw new TypeError('You must pass a Messenger to the AutoAway')
|
||||
if (!(profileStore instanceof ProfileStore)) throw new TypeError('You must pass a ProfileStore to the AutoAway')
|
||||
if (!(commandHandler instanceof CommandHandler))
|
||||
throw new TypeError('You must pass a CommandHandler to the AutoAway')
|
||||
if (!(messenger instanceof Messenger))
|
||||
throw new TypeError('You must pass a Messenger to the AutoAway')
|
||||
if (!(profileStore instanceof ProfileStore))
|
||||
throw new TypeError('You must pass a ProfileStore to the AutoAway')
|
||||
|
||||
this.storage = new LocalStorage('AutoAway.')
|
||||
this.awayCommand = commandHandler.getCommandByIdentifier('be.bastelstu.chat', 'away')
|
||||
this.awayCommand = commandHandler.getCommandByIdentifier(
|
||||
'be.bastelstu.chat',
|
||||
'away'
|
||||
)
|
||||
if (this.awayCommand == null) {
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
@ -47,16 +68,22 @@ define([ '../console'
|
||||
return
|
||||
}
|
||||
|
||||
this.timer = new RepeatingTimer(this.setAway.bind(this), this.config.autoAwayTime * 60e3)
|
||||
this.input.on('input', this.inputListener = (event) => {
|
||||
this.storage.set('channel', Date.now())
|
||||
this.reset()
|
||||
})
|
||||
this.timer = new RepeatingTimer(
|
||||
this.setAway.bind(this),
|
||||
this.config.autoAwayTime * 60e3
|
||||
)
|
||||
this.input.on(
|
||||
'input',
|
||||
(this.inputListener = (event) => {
|
||||
this.storage.set('channel', Date.now())
|
||||
this.reset()
|
||||
})
|
||||
)
|
||||
this.storage.observe('channel', this.reset.bind(this))
|
||||
}
|
||||
|
||||
ingest(messages) {
|
||||
if (messages.some(message => message.isOwnMessage())) this.reset()
|
||||
if (messages.some((message) => message.isOwnMessage())) this.reset()
|
||||
}
|
||||
|
||||
reset() {
|
||||
@ -70,8 +97,11 @@ define([ '../console'
|
||||
async setAway() {
|
||||
console.debug('AutoAway.setAway', `Attempting to set as away`)
|
||||
|
||||
if (this.storage.get('setAway') >= (Date.now() - 10e3)) {
|
||||
console.debug('AutoAway.setAway', `setAway called within the last 10 seconds in another Tab`)
|
||||
if (this.storage.get('setAway') >= Date.now() - 10e3) {
|
||||
console.debug(
|
||||
'AutoAway.setAway',
|
||||
`setAway called within the last 10 seconds in another Tab`
|
||||
)
|
||||
return
|
||||
}
|
||||
this.storage.set('setAway', Date.now())
|
||||
@ -81,10 +111,13 @@ define([ '../console'
|
||||
return
|
||||
}
|
||||
|
||||
this.messenger.push({ commandID: this.awayCommand.id, parameters: { reason: Language.get('chat.user.autoAway') } })
|
||||
this.messenger.push({
|
||||
commandID: this.awayCommand.id,
|
||||
parameters: { reason: Language.get('chat.user.autoAway') },
|
||||
})
|
||||
}
|
||||
}
|
||||
AutoAway.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return AutoAway
|
||||
});
|
||||
})
|
||||
|
@ -11,42 +11,58 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Ui' ], function (Ui) {
|
||||
"use strict";
|
||||
define(['../Ui'], function (Ui) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiAttachmentUpload'
|
||||
, 'UiAutoAway'
|
||||
, 'UiConnectionWarning'
|
||||
, 'UiInput'
|
||||
, 'UiInputAutocompleter'
|
||||
, 'UiMessageActionDelete'
|
||||
, 'UiMessageStream'
|
||||
, 'UiMobile'
|
||||
, 'UiNotification'
|
||||
, 'UiReadMarker'
|
||||
, 'UiSettings'
|
||||
, 'UiTopic'
|
||||
, 'UiUserActionDropdownHandler'
|
||||
, 'UiUserList'
|
||||
]
|
||||
const DEPENDENCIES = [
|
||||
'UiAttachmentUpload',
|
||||
'UiAutoAway',
|
||||
'UiConnectionWarning',
|
||||
'UiInput',
|
||||
'UiInputAutocompleter',
|
||||
'UiMessageActionDelete',
|
||||
'UiMessageStream',
|
||||
'UiMobile',
|
||||
'UiNotification',
|
||||
'UiReadMarker',
|
||||
'UiSettings',
|
||||
'UiTopic',
|
||||
'UiUserActionDropdownHandler',
|
||||
'UiUserList',
|
||||
]
|
||||
class Chat extends Ui {
|
||||
constructor(attachmentUpload, autoAway, connectionWarning, input, autocompleter, messageActionDelete, messageStream, mobile, notification, readMarker, settings, topic, userActionDropdownHandler, userList) {
|
||||
constructor(
|
||||
attachmentUpload,
|
||||
autoAway,
|
||||
connectionWarning,
|
||||
input,
|
||||
autocompleter,
|
||||
messageActionDelete,
|
||||
messageStream,
|
||||
mobile,
|
||||
notification,
|
||||
readMarker,
|
||||
settings,
|
||||
topic,
|
||||
userActionDropdownHandler,
|
||||
userList
|
||||
) {
|
||||
super()
|
||||
|
||||
this.actionDropdownHandler = userActionDropdownHandler
|
||||
this.attachmentUpload = attachmentUpload
|
||||
this.autoAway = autoAway
|
||||
this.autocompleter = autocompleter
|
||||
this.connectionWarning = connectionWarning
|
||||
this.input = input
|
||||
this.messageActionDelete = messageActionDelete
|
||||
this.messageStream = messageStream
|
||||
this.mobile = mobile
|
||||
this.notification = notification
|
||||
this.readMarker = readMarker
|
||||
this.settings = settings
|
||||
this.topic = topic
|
||||
this.userList = userList
|
||||
this.attachmentUpload = attachmentUpload
|
||||
this.autoAway = autoAway
|
||||
this.autocompleter = autocompleter
|
||||
this.connectionWarning = connectionWarning
|
||||
this.input = input
|
||||
this.messageActionDelete = messageActionDelete
|
||||
this.messageStream = messageStream
|
||||
this.mobile = mobile
|
||||
this.notification = notification
|
||||
this.readMarker = readMarker
|
||||
this.settings = settings
|
||||
this.topic = topic
|
||||
this.userList = userList
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
@ -69,4 +85,4 @@ define([ '../Ui' ], function (Ui) {
|
||||
Chat.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Chat
|
||||
});
|
||||
})
|
||||
|
@ -11,25 +11,22 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../console'
|
||||
], function (console) {
|
||||
"use strict";
|
||||
define(['../console'], function (console) {
|
||||
'use strict'
|
||||
|
||||
class ConnectionWarning {
|
||||
constructor() {
|
||||
this.warning = elById('chatConnectionWarning')
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
|
||||
}
|
||||
bootstrap() {}
|
||||
|
||||
show() {
|
||||
elShow(this.warning)
|
||||
if (this.timeout) return
|
||||
|
||||
console.debug('ConnectionWarning.show', 'Setting timeout')
|
||||
this.timeout = setTimeout(_ => {
|
||||
this.timeout = setTimeout((_) => {
|
||||
console.debug('ConnectionWarning.show', 'Timeout has passed')
|
||||
this.timeout = undefined
|
||||
|
||||
@ -44,9 +41,11 @@ define([ '../console'
|
||||
if (!this.timeout || force) {
|
||||
elHide(this.warning)
|
||||
window.clearTimeout(this.timeout)
|
||||
}
|
||||
else {
|
||||
console.debug('ConnectionWarning.hide', 'Automatically hiding after timeout has passed')
|
||||
} else {
|
||||
console.debug(
|
||||
'ConnectionWarning.hide',
|
||||
'Automatically hiding after timeout has passed'
|
||||
)
|
||||
this.autoHide = true
|
||||
}
|
||||
}
|
||||
@ -57,4 +56,4 @@ define([ '../console'
|
||||
}
|
||||
|
||||
return ConnectionWarning
|
||||
});
|
||||
})
|
||||
|
@ -11,28 +11,31 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/Template'
|
||||
, 'WoltLabSuite/Core/Ui/Dialog'
|
||||
], function (Language, Template, UiDialog) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/Template',
|
||||
'WoltLabSuite/Core/Ui/Dialog',
|
||||
], function (Language, Template, UiDialog) {
|
||||
'use strict'
|
||||
|
||||
const html = [ '[type="x-text/template"]'
|
||||
, '[data-application="be.bastelstu.chat"]'
|
||||
, '[data-template-name="be-bastelstu-chat-errorDialog"]'
|
||||
].join('')
|
||||
const html = [
|
||||
'[type="x-text/template"]',
|
||||
'[data-application="be.bastelstu.chat"]',
|
||||
'[data-template-name="be-bastelstu-chat-errorDialog"]',
|
||||
].join('')
|
||||
|
||||
const wrapper = new Template(elBySel(html).textContent)
|
||||
|
||||
class ErrorDialog {
|
||||
constructor(message) {
|
||||
const options = { title: Language.get('wcf.global.error.title')
|
||||
, closable: false
|
||||
}
|
||||
const options = {
|
||||
title: Language.get('wcf.global.error.title'),
|
||||
closable: false,
|
||||
}
|
||||
|
||||
UiDialog.openStatic('chatError', wrapper.fetch({ message }), options)
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorDialog
|
||||
});
|
||||
})
|
||||
|
@ -11,21 +11,22 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../console'
|
||||
, '../Helper'
|
||||
, 'WoltLabSuite/Core/Core'
|
||||
, 'WoltLabSuite/Core/Event/Key'
|
||||
, '../DataStructure/EventEmitter'
|
||||
, '../DataStructure/Throttle'
|
||||
], function (console, Helper, Core, EventKey, EventEmitter, Throttle) {
|
||||
"use strict";
|
||||
define([
|
||||
'../console',
|
||||
'../Helper',
|
||||
'WoltLabSuite/Core/Core',
|
||||
'WoltLabSuite/Core/Event/Key',
|
||||
'../DataStructure/EventEmitter',
|
||||
'../DataStructure/Throttle',
|
||||
], function (console, Helper, Core, EventKey, EventEmitter, Throttle) {
|
||||
'use strict'
|
||||
|
||||
class Input {
|
||||
constructor() {
|
||||
this.inputContainer = elById('chatInputContainer')
|
||||
this.input = elBySel('textarea', this.inputContainer)
|
||||
this.charCounter = elBySel('.charCounter', this.inputContainer)
|
||||
this.errorElement = elBySel('.innerError', this.inputContainer)
|
||||
this.input = elBySel('textarea', this.inputContainer)
|
||||
this.charCounter = elBySel('.charCounter', this.inputContainer)
|
||||
this.errorElement = elBySel('.innerError', this.inputContainer)
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
@ -34,21 +35,29 @@ define([ '../console'
|
||||
}
|
||||
|
||||
this.input.addEventListener('keydown', this.handleInputKeyDown.bind(this))
|
||||
this.input.addEventListener('input', Throttle(this.handleInput.bind(this)))
|
||||
this.input.addEventListener(
|
||||
'input',
|
||||
Throttle(this.handleInput.bind(this))
|
||||
)
|
||||
|
||||
Helper.makeFlexible(this.input)
|
||||
this.handleInput()
|
||||
}
|
||||
|
||||
handleInput(event) {
|
||||
this.charCounter.textContent = `${this.input.value.length} / ${this.input.getAttribute('maxlength')}`
|
||||
this.charCounter.textContent = `${
|
||||
this.input.value.length
|
||||
} / ${this.input.getAttribute('maxlength')}`
|
||||
this.emit('input')
|
||||
}
|
||||
|
||||
handleInputKeyDown(event) {
|
||||
if (EventKey.Enter(event) && !event.shiftKey) {
|
||||
if (event.isComposing) {
|
||||
console.debug('Ui/Input.handleInputKeyDown', 'Ignored Enter key while composing characters.')
|
||||
console.debug(
|
||||
'Ui/Input.handleInputKeyDown',
|
||||
'Ignored Enter key while composing characters.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@ -62,8 +71,7 @@ define([ '../console'
|
||||
if (!parameters.cancel) {
|
||||
this.emit('submit')
|
||||
}
|
||||
}
|
||||
else if (EventKey.Tab(event)) {
|
||||
} else if (EventKey.Tab(event)) {
|
||||
// prevent leaving the input
|
||||
event.preventDefault()
|
||||
|
||||
@ -92,9 +100,7 @@ define([ '../console'
|
||||
insertText(text, options) {
|
||||
this.focus()
|
||||
|
||||
options = Object.assign({ append: true
|
||||
, prepend: false
|
||||
}, options)
|
||||
options = Object.assign({ append: true, prepend: false }, options)
|
||||
|
||||
if (!(options.append || options.prepend)) {
|
||||
// replace
|
||||
@ -102,11 +108,11 @@ define([ '../console'
|
||||
}
|
||||
|
||||
if (options.append) {
|
||||
this.input.value += text;
|
||||
this.input.value += text
|
||||
}
|
||||
|
||||
if (options.prepend) {
|
||||
this.input.value = text + this.input.value;
|
||||
this.input.value = text + this.input.value
|
||||
}
|
||||
|
||||
// always position caret at the end
|
||||
@ -119,8 +125,7 @@ define([ '../console'
|
||||
inputError(message) {
|
||||
if (typeof window.elInnerError === 'function') {
|
||||
elInnerError(this.inputContainer.firstElementChild, message)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.inputContainer.classList.add('formError')
|
||||
this.errorElement.textContent = message
|
||||
elShow(this.errorElement)
|
||||
@ -130,8 +135,7 @@ define([ '../console'
|
||||
hideInputError() {
|
||||
if (typeof window.elInnerError === 'function') {
|
||||
elInnerError(this.inputContainer.firstElementChild, false)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.inputContainer.classList.remove('formError')
|
||||
this.errorElement.textContent = ''
|
||||
elHide(this.errorElement)
|
||||
@ -141,4 +145,4 @@ define([ '../console'
|
||||
EventEmitter(Input.prototype)
|
||||
|
||||
return Input
|
||||
});
|
||||
})
|
||||
|
@ -11,17 +11,21 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Util'
|
||||
, 'WoltLabSuite/Core/Event/Key'
|
||||
, 'WoltLabSuite/Core/Ui/Suggestion'
|
||||
], function (DomUtil, EventKey, Suggestion) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Dom/Util',
|
||||
'WoltLabSuite/Core/Event/Key',
|
||||
'WoltLabSuite/Core/Ui/Suggestion',
|
||||
], function (DomUtil, EventKey, Suggestion) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ]
|
||||
const DEPENDENCIES = ['UiInput']
|
||||
class Autocompleter extends Suggestion {
|
||||
constructor(input) {
|
||||
const elementId = DomUtil.identify(input.input)
|
||||
const options = { callbackSelect: (_elementId, selection) => this.insertSelection(selection) }
|
||||
const options = {
|
||||
callbackSelect: (_elementId, selection) =>
|
||||
this.insertSelection(selection),
|
||||
}
|
||||
|
||||
super(elementId, options)
|
||||
|
||||
@ -76,11 +80,9 @@ define([ 'WoltLabSuite/Core/Dom/Util'
|
||||
sendCompletions(completions) {
|
||||
this.completions = new Map()
|
||||
|
||||
const returnValues = completions.map(completion => {
|
||||
const returnValues = completions.map((completion) => {
|
||||
this.completions.set(++this.completionId, completion)
|
||||
return { label: completion
|
||||
, objectID: this.completionId
|
||||
}
|
||||
return { label: completion, objectID: this.completionId }
|
||||
})
|
||||
|
||||
this._ajaxSuccess({ returnValues })
|
||||
@ -89,4 +91,4 @@ define([ 'WoltLabSuite/Core/Dom/Util'
|
||||
Autocompleter.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Autocompleter
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Ui' ], function (Ui) {
|
||||
"use strict";
|
||||
define(['../Ui'], function (Ui) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiMessageStream', 'UiMessageActionDelete' ]
|
||||
const DEPENDENCIES = ['UiMessageStream', 'UiMessageActionDelete']
|
||||
class Log extends Ui {
|
||||
constructor(messageStream, messageActionDelete) {
|
||||
super()
|
||||
@ -32,4 +32,4 @@ define([ '../Ui' ], function (Ui) {
|
||||
Log.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Log
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'Bastelstu.be/Chat/Message', 'Bastelstu.be/PromiseWrap/Ajax', 'Bastelstu.be/PromiseWrap/Ui/Confirmation' ], function (Message, Ajax, Confirmation) {
|
||||
"use strict";
|
||||
define([
|
||||
'Bastelstu.be/Chat/Message',
|
||||
'Bastelstu.be/PromiseWrap/Ajax',
|
||||
'Bastelstu.be/PromiseWrap/Ui/Confirmation',
|
||||
], function (Message, Ajax, Confirmation) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiMessageStream', 'Message' ]
|
||||
const DEPENDENCIES = ['UiMessageStream', 'Message']
|
||||
class Delete {
|
||||
constructor(messageStream, message) {
|
||||
this.messageStream = messageStream
|
||||
@ -26,19 +30,19 @@ define([ 'Bastelstu.be/Chat/Message', 'Bastelstu.be/PromiseWrap/Ajax', 'Bastelst
|
||||
}
|
||||
|
||||
bindListener({ detail }) {
|
||||
detail.forEach(item => {
|
||||
detail.forEach((item) => {
|
||||
if (!item) return
|
||||
|
||||
const { node, message } = item
|
||||
const button = node.querySelector('.jsDeleteButton')
|
||||
if (!button) return
|
||||
|
||||
button.addEventListener('click', async event => {
|
||||
button.addEventListener('click', async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
await Confirmation.show({
|
||||
message: button.dataset.confirmMessageHtml,
|
||||
messageIsHtml: true
|
||||
messageIsHtml: true,
|
||||
})
|
||||
|
||||
await this.delete(message.messageID)
|
||||
@ -50,31 +54,31 @@ define([ 'Bastelstu.be/Chat/Message', 'Bastelstu.be/PromiseWrap/Ajax', 'Bastelst
|
||||
|
||||
async delete(messageID) {
|
||||
{
|
||||
const payload = { objectIDs: [ messageID ] }
|
||||
const payload = { objectIDs: [messageID] }
|
||||
|
||||
await Ajax.api(this, payload)
|
||||
}
|
||||
|
||||
{
|
||||
const objectType = 'be.bastelstu.chat.messageType.tombstone'
|
||||
const payload = { messageID
|
||||
, userID: null
|
||||
}
|
||||
const payload = { messageID, userID: null }
|
||||
const message = this.Message.instance({ objectType, payload })
|
||||
message.getMessageType().render(message)
|
||||
}
|
||||
}
|
||||
|
||||
_ajaxSetup() {
|
||||
return { silent: true
|
||||
, ignoreError: true
|
||||
, data: { className: 'chat\\data\\message\\MessageAction'
|
||||
, actionName: 'trash'
|
||||
}
|
||||
}
|
||||
return {
|
||||
silent: true,
|
||||
ignoreError: true,
|
||||
data: {
|
||||
className: 'chat\\data\\message\\MessageAction',
|
||||
actionName: 'trash',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Delete.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Delete
|
||||
});
|
||||
})
|
||||
|
@ -11,29 +11,39 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../Helper'
|
||||
, 'WoltLabSuite/Core/Date/Util'
|
||||
, 'WoltLabSuite/Core/Dom/Change/Listener'
|
||||
, 'WoltLabSuite/Core/Language'
|
||||
, 'WoltLabSuite/Core/User'
|
||||
, 'WoltLabSuite/Core/Dom/Traverse'
|
||||
, '../DataStructure/EventEmitter'
|
||||
, '../DataStructure/RedBlackTree/Tree'
|
||||
], function (Helper, DateUtil, DomChangeListener, Language, User, DOMTraverse, EventEmitter, Tree) {
|
||||
"use strict";
|
||||
define([
|
||||
'../Helper',
|
||||
'WoltLabSuite/Core/Date/Util',
|
||||
'WoltLabSuite/Core/Dom/Change/Listener',
|
||||
'WoltLabSuite/Core/Language',
|
||||
'WoltLabSuite/Core/User',
|
||||
'WoltLabSuite/Core/Dom/Traverse',
|
||||
'../DataStructure/EventEmitter',
|
||||
'../DataStructure/RedBlackTree/Tree',
|
||||
], function (
|
||||
Helper,
|
||||
DateUtil,
|
||||
DomChangeListener,
|
||||
Language,
|
||||
User,
|
||||
DOMTraverse,
|
||||
EventEmitter,
|
||||
Tree
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const enableAutoscroll = Symbol('enableAutoscroll')
|
||||
|
||||
const DEPENDENCIES = [ ]
|
||||
const DEPENDENCIES = []
|
||||
class MessageStream {
|
||||
constructor() {
|
||||
this.stream = elById('chatMessageStream')
|
||||
this.scrollContainer = elBySel('.scrollContainer', this.stream)
|
||||
|
||||
this[enableAutoscroll] = true
|
||||
this[enableAutoscroll] = true
|
||||
this.lastScrollPosition = undefined
|
||||
this.nodeMap = new WeakMap()
|
||||
this.positions = new Tree()
|
||||
this.nodeMap = new WeakMap()
|
||||
this.positions = new Tree()
|
||||
}
|
||||
|
||||
get enableAutoscroll() {
|
||||
@ -50,7 +60,11 @@ define([ '../Helper'
|
||||
|
||||
bootstrap() {
|
||||
this.scrollContainer.addEventListener('copy', this.onCopy.bind(this))
|
||||
this.scrollContainer.addEventListener('scroll', Helper.throttle(this.onScroll, 100, this), { passive: true })
|
||||
this.scrollContainer.addEventListener(
|
||||
'scroll',
|
||||
Helper.throttle(this.onScroll, 100, this),
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
|
||||
getDateMarker(date) {
|
||||
@ -69,123 +83,131 @@ define([ '../Helper'
|
||||
}
|
||||
|
||||
ingest(messages) {
|
||||
let scrollTopBefore = this.enableAutoscroll ? 0 : this.scrollContainer.scrollTop
|
||||
let scrollTopBefore = this.enableAutoscroll
|
||||
? 0
|
||||
: this.scrollContainer.scrollTop
|
||||
let prependedHeight = 0
|
||||
|
||||
const ul = elBySel('ul', this.scrollContainer)
|
||||
const ul = elBySel('ul', this.scrollContainer)
|
||||
const first = ul.firstElementChild
|
||||
|
||||
const ingested = messages.map((function (item) {
|
||||
let currentScrollHeight = 0
|
||||
const ingested = messages.map(
|
||||
function (item) {
|
||||
let currentScrollHeight = 0
|
||||
|
||||
const li = elCreate('li')
|
||||
const li = elCreate('li')
|
||||
|
||||
// Allow messages types to not render a messages
|
||||
// This can be used for status messages like ChatUpdate
|
||||
let fragment
|
||||
if ((fragment = item.getMessageType().render(item)) === false) return
|
||||
// Allow messages types to not render a messages
|
||||
// This can be used for status messages like ChatUpdate
|
||||
let fragment
|
||||
if ((fragment = item.getMessageType().render(item)) === false) return
|
||||
|
||||
if (fragment.querySelector(`.userMention[data-user-id="${User.userId}"]`)) li.classList.add('mentioned')
|
||||
if (
|
||||
fragment.querySelector(
|
||||
`.userMention[data-user-id="${User.userId}"]`
|
||||
)
|
||||
)
|
||||
li.classList.add('mentioned')
|
||||
|
||||
li.appendChild(fragment)
|
||||
li.appendChild(fragment)
|
||||
|
||||
li.classList.add('chatMessageBoundary')
|
||||
li.setAttribute('id', `message-${item.messageID}`)
|
||||
li.dataset.objectType = item.objectType
|
||||
li.dataset.userId = item.userID
|
||||
if (item.isOwnMessage()) li.classList.add('own')
|
||||
if (item.isDeleted) li.classList.add('tombstone')
|
||||
li.classList.add('chatMessageBoundary')
|
||||
li.setAttribute('id', `message-${item.messageID}`)
|
||||
li.dataset.objectType = item.objectType
|
||||
li.dataset.userId = item.userID
|
||||
if (item.isOwnMessage()) li.classList.add('own')
|
||||
if (item.isDeleted) li.classList.add('tombstone')
|
||||
|
||||
const position = this.positions.insert(item.messageID)
|
||||
if (position[1] !== undefined) {
|
||||
const sibling = elById(`message-${position[1]}`)
|
||||
if (!sibling) throw new Error('Unreachable')
|
||||
const position = this.positions.insert(item.messageID)
|
||||
if (position[1] !== undefined) {
|
||||
const sibling = elById(`message-${position[1]}`)
|
||||
if (!sibling) throw new Error('Unreachable')
|
||||
|
||||
let nodeBefore, nodeAfter
|
||||
let dateMarkerBetween = false
|
||||
if (position[0] === 'LEFT') {
|
||||
nodeAfter = sibling
|
||||
nodeBefore = sibling.previousElementSibling
|
||||
|
||||
if (nodeBefore && nodeBefore.classList.contains('dateMarker')) {
|
||||
elRemove(nodeBefore)
|
||||
let nodeBefore, nodeAfter
|
||||
let dateMarkerBetween = false
|
||||
if (position[0] === 'LEFT') {
|
||||
nodeAfter = sibling
|
||||
nodeBefore = sibling.previousElementSibling
|
||||
}
|
||||
}
|
||||
else if (position[0] === 'RIGHT') {
|
||||
nodeBefore = sibling
|
||||
nodeAfter = sibling.nextElementSibling
|
||||
|
||||
if (nodeAfter && nodeAfter.classList.contains('dateMarker')) {
|
||||
elRemove(nodeAfter)
|
||||
if (nodeBefore && nodeBefore.classList.contains('dateMarker')) {
|
||||
elRemove(nodeBefore)
|
||||
nodeBefore = sibling.previousElementSibling
|
||||
}
|
||||
} else if (position[0] === 'RIGHT') {
|
||||
nodeBefore = sibling
|
||||
nodeAfter = sibling.nextElementSibling
|
||||
|
||||
if (nodeAfter && nodeAfter.classList.contains('dateMarker')) {
|
||||
elRemove(nodeAfter)
|
||||
nodeAfter = sibling.nextElementSibling
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
|
||||
const messageBefore = this.nodeMap.get(nodeBefore)
|
||||
if (nodeBefore && !messageBefore) throw new Error('Unreachable')
|
||||
const messageAfter = this.nodeMap.get(nodeAfter)
|
||||
if (nodeAfter && !messageAfter) throw new Error('Unreachable')
|
||||
const messageBefore = this.nodeMap.get(nodeBefore)
|
||||
if (nodeBefore && !messageBefore) throw new Error('Unreachable')
|
||||
const messageAfter = this.nodeMap.get(nodeAfter)
|
||||
if (nodeAfter && !messageAfter) throw new Error('Unreachable')
|
||||
|
||||
if (!this.enableAutoscroll && nodeAfter) currentScrollHeight = this.scrollContainer.scrollHeight
|
||||
if (!this.enableAutoscroll && nodeAfter)
|
||||
currentScrollHeight = this.scrollContainer.scrollHeight
|
||||
|
||||
let context = nodeAfter
|
||||
if (nodeAfter) nodeAfter.classList.remove('first')
|
||||
if (messageBefore) {
|
||||
if (this.onDifferentDays(messageBefore.date, item.date)) {
|
||||
const dateMarker = this.getDateMarker(item.date)
|
||||
ul.insertBefore(dateMarker, nodeAfter)
|
||||
let context = nodeAfter
|
||||
if (nodeAfter) nodeAfter.classList.remove('first')
|
||||
if (messageBefore) {
|
||||
if (this.onDifferentDays(messageBefore.date, item.date)) {
|
||||
const dateMarker = this.getDateMarker(item.date)
|
||||
ul.insertBefore(dateMarker, nodeAfter)
|
||||
li.classList.add('first')
|
||||
} else {
|
||||
if (
|
||||
messageBefore.objectType !== item.objectType ||
|
||||
!item.getMessageType().joinable(messageBefore, item)
|
||||
) {
|
||||
li.classList.add('first')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
li.classList.add('first')
|
||||
}
|
||||
else {
|
||||
if (messageBefore.objectType !== item.objectType || !item.getMessageType().joinable(messageBefore, item)) {
|
||||
li.classList.add('first')
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
li.classList.add('first')
|
||||
}
|
||||
if (messageAfter) {
|
||||
if (this.onDifferentDays(messageAfter.date, item.date)) {
|
||||
const dateMarker = this.getDateMarker(messageAfter.date)
|
||||
ul.insertBefore(dateMarker, nodeAfter)
|
||||
context = dateMarker
|
||||
nodeAfter.classList.add('first')
|
||||
}
|
||||
else {
|
||||
if (messageAfter.objectType !== item.objectType || !item.getMessageType().joinable(item, messageAfter)) {
|
||||
if (messageAfter) {
|
||||
if (this.onDifferentDays(messageAfter.date, item.date)) {
|
||||
const dateMarker = this.getDateMarker(messageAfter.date)
|
||||
ul.insertBefore(dateMarker, nodeAfter)
|
||||
context = dateMarker
|
||||
nodeAfter.classList.add('first')
|
||||
} else {
|
||||
if (
|
||||
messageAfter.objectType !== item.objectType ||
|
||||
!item.getMessageType().joinable(item, messageAfter)
|
||||
) {
|
||||
nodeAfter.classList.add('first')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.insertBefore(li, context)
|
||||
|
||||
if (!this.enableAutoscroll && nodeAfter) {
|
||||
prependedHeight +=
|
||||
this.scrollContainer.scrollHeight - currentScrollHeight
|
||||
}
|
||||
} else {
|
||||
li.classList.add('first')
|
||||
ul.insertBefore(li, null)
|
||||
}
|
||||
|
||||
ul.insertBefore(li, context);
|
||||
this.nodeMap.set(li, item)
|
||||
|
||||
if (!this.enableAutoscroll && nodeAfter) {
|
||||
prependedHeight += this.scrollContainer.scrollHeight - currentScrollHeight
|
||||
}
|
||||
}
|
||||
else {
|
||||
li.classList.add('first')
|
||||
ul.insertBefore(li, null)
|
||||
}
|
||||
return { node: li, message: item }
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
this.nodeMap.set(li, item)
|
||||
|
||||
return { node: li
|
||||
, message: item
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
if (ingested.some(item => item != null)) {
|
||||
if (ingested.some((item) => item != null)) {
|
||||
if (this.enableAutoscroll) {
|
||||
this.scrollToBottom()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.stream.classList.add('activity')
|
||||
this.scrollContainer.scrollTop = scrollTopBefore + prependedHeight
|
||||
}
|
||||
@ -208,7 +230,10 @@ define([ '../Helper'
|
||||
|
||||
let direction = 'down'
|
||||
|
||||
if (this.lastScrollPosition != null && scrollTop < this.lastScrollPosition) {
|
||||
if (
|
||||
this.lastScrollPosition != null &&
|
||||
scrollTop < this.lastScrollPosition
|
||||
) {
|
||||
direction = 'up'
|
||||
}
|
||||
|
||||
@ -219,12 +244,10 @@ define([ '../Helper'
|
||||
|
||||
if (distanceFromTop <= 7) {
|
||||
this.emit('reachedTop')
|
||||
}
|
||||
else if (distanceFromTop <= 300) {
|
||||
} else if (distanceFromTop <= 300) {
|
||||
this.emit('nearTop')
|
||||
}
|
||||
}
|
||||
else if (direction === 'down') {
|
||||
} else if (direction === 'down') {
|
||||
if (distanceFromTop > 7) {
|
||||
this.emit('scrollDown')
|
||||
}
|
||||
@ -232,8 +255,7 @@ define([ '../Helper'
|
||||
if (distanceFromBottom <= 7) {
|
||||
this.scrollToBottom()
|
||||
this.emit('reachedBottom')
|
||||
}
|
||||
else if (distanceFromBottom <= 300) {
|
||||
} else if (distanceFromBottom <= 300) {
|
||||
this.emit('nearBottom')
|
||||
}
|
||||
}
|
||||
@ -250,16 +272,19 @@ define([ '../Helper'
|
||||
// Get the first and last node in the selection
|
||||
let originalStart, start, end, originalEnd
|
||||
start = originalStart = selection.getRangeAt(0).startContainer
|
||||
end = originalEnd = selection.getRangeAt(selection.rangeCount - 1).endContainer
|
||||
end = originalEnd = selection.getRangeAt(selection.rangeCount - 1)
|
||||
.endContainer
|
||||
|
||||
const startOffset = selection.getRangeAt(0).startOffset
|
||||
const endOffset = selection.getRangeAt(selection.rangeCount - 1).endOffset
|
||||
const endOffset = selection.getRangeAt(selection.rangeCount - 1).endOffset
|
||||
|
||||
// The Traverse module needs nodes of the Element type, the selected elements could be of type Text
|
||||
while (!(start instanceof Element) && start.parentNode) start = start.parentNode
|
||||
while (!(start instanceof Element) && start.parentNode)
|
||||
start = start.parentNode
|
||||
while (!(end instanceof Element) && end.parentNode) end = end.parentNode
|
||||
|
||||
if (!start || !end) throw new Error('Unexpected error, no element nodes in selection')
|
||||
if (!start || !end)
|
||||
throw new Error('Unexpected error, no element nodes in selection')
|
||||
|
||||
// Try to find the starting li element in the selection
|
||||
if (!start.id || start.id.indexOf('message-') !== 0) {
|
||||
@ -272,7 +297,10 @@ define([ '../Helper'
|
||||
}
|
||||
|
||||
// Do not select a message if we selected only a new line
|
||||
if (originalStart instanceof Text && originalStart.textContent.substring(startOffset) === "") {
|
||||
if (
|
||||
originalStart instanceof Text &&
|
||||
originalStart.textContent.substring(startOffset) === ''
|
||||
) {
|
||||
start = DOMTraverse.next(start)
|
||||
}
|
||||
|
||||
@ -289,15 +317,14 @@ define([ '../Helper'
|
||||
end = DOMTraverse.prev(end)
|
||||
}
|
||||
|
||||
const elements = [ ]
|
||||
let next = start
|
||||
const elements = []
|
||||
let next = start
|
||||
|
||||
do {
|
||||
elements.push(next)
|
||||
|
||||
if (next === end) break
|
||||
}
|
||||
while (next = DOMTraverse.next(next))
|
||||
} while ((next = DOMTraverse.next(next)))
|
||||
|
||||
// Only apply our custom formatting when selecting multiple or whole messages
|
||||
if (elements.length === 1) {
|
||||
@ -305,37 +332,59 @@ define([ '../Helper'
|
||||
range.setStart(originalStart, startOffset)
|
||||
range.setEnd(originalEnd, endOffset)
|
||||
|
||||
if (!Helper.rangeSpansTextContent(range, start.querySelector('.chatMessage'))) return
|
||||
if (
|
||||
!Helper.rangeSpansTextContent(
|
||||
range,
|
||||
start.querySelector('.chatMessage')
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
event.clipboardData.setData('text/plain', elements.map((el, index, arr) => {
|
||||
const message = this.nodeMap.get(el)
|
||||
event.clipboardData.setData(
|
||||
'text/plain',
|
||||
elements
|
||||
.map((el, index, arr) => {
|
||||
const message = this.nodeMap.get(el)
|
||||
|
||||
if (el.classList.contains('dateMarker')) return `== ${el.textContent.trim()} ==`
|
||||
if (el.classList.contains('dateMarker'))
|
||||
return `== ${el.textContent.trim()} ==`
|
||||
|
||||
if (!message) return
|
||||
if (!message) return
|
||||
|
||||
if (el.classList.contains('tombstone')) {
|
||||
return `[${message.formattedTime}] ${Language.get('chat.messageType.be.bastelstu.chat.messageType.tombstone.message')}`
|
||||
}
|
||||
if (el.classList.contains('tombstone')) {
|
||||
return `[${message.formattedTime}] ${Language.get(
|
||||
'chat.messageType.be.bastelstu.chat.messageType.tombstone.message'
|
||||
)}`
|
||||
}
|
||||
|
||||
const elem = elBySel('.chatMessage', el)
|
||||
const elem = elBySel('.chatMessage', el)
|
||||
|
||||
let body
|
||||
if (typeof (body = message.getMessageType().renderPlainText(message)) === 'undefined' || body === false) {
|
||||
body = Helper.getTextContent(elem).replace(/\t+/g, '\t') // collapse multiple tabs
|
||||
.replace(/ +/g, ' ') // collapse multiple spaces
|
||||
.replace(/([\t ]*\n){2,}/g, '\n') // collapse line consisting of tabs, spaces and newlines
|
||||
.replace(/^[\t ]+|[\t ]+$/gm, '') // remove leading and trailing whitespace per line
|
||||
}
|
||||
let body
|
||||
if (
|
||||
typeof (body = message
|
||||
.getMessageType()
|
||||
.renderPlainText(message)) === 'undefined' ||
|
||||
body === false
|
||||
) {
|
||||
body = Helper.getTextContent(elem)
|
||||
.replace(/\t+/g, '\t') // collapse multiple tabs
|
||||
.replace(/ +/g, ' ') // collapse multiple spaces
|
||||
.replace(/([\t ]*\n){2,}/g, '\n') // collapse line consisting of tabs, spaces and newlines
|
||||
.replace(/^[\t ]+|[\t ]+$/gm, '') // remove leading and trailing whitespace per line
|
||||
}
|
||||
|
||||
return `[${message.formattedTime}] <${message.username}> ${body.trim()}`
|
||||
}).filter(x => x).join('\n'))
|
||||
return `[${message.formattedTime}] <${
|
||||
message.username
|
||||
}> ${body.trim()}`
|
||||
})
|
||||
.filter((x) => x)
|
||||
.join('\n')
|
||||
)
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.error('Unable to use the clipboard API')
|
||||
console.error(e)
|
||||
}
|
||||
@ -345,4 +394,4 @@ define([ '../Helper'
|
||||
MessageStream.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return MessageStream
|
||||
});
|
||||
})
|
||||
|
@ -11,8 +11,8 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Ui/Screen' ], function (UiScreen) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Ui/Screen'], function (UiScreen) {
|
||||
'use strict'
|
||||
|
||||
const initialized = Symbol('initialized')
|
||||
|
||||
@ -22,10 +22,11 @@ define([ 'WoltLabSuite/Core/Ui/Screen' ], function (UiScreen) {
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
UiScreen.on('screen-md-down', { match: this.enable.bind(this)
|
||||
, unmatch: this.disable.bind(this)
|
||||
, setup: this.init.bind(this)
|
||||
})
|
||||
UiScreen.on('screen-md-down', {
|
||||
match: this.enable.bind(this),
|
||||
unmatch: this.disable.bind(this),
|
||||
setup: this.init.bind(this),
|
||||
})
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -36,19 +37,15 @@ define([ 'WoltLabSuite/Core/Ui/Screen' ], function (UiScreen) {
|
||||
this.initQuickSettings()
|
||||
}
|
||||
|
||||
enable() {
|
||||
enable() {}
|
||||
|
||||
}
|
||||
|
||||
disable() {
|
||||
|
||||
}
|
||||
disable() {}
|
||||
|
||||
initQuickSettings() {
|
||||
const navigation = elBySel('#chatQuickSettingsNavigation > ul')
|
||||
const navigation = elBySel('#chatQuickSettingsNavigation > ul')
|
||||
const quickSettings = elById('chatQuickSettings')
|
||||
|
||||
navigation.addEventListener(WCF_CLICK_EVENT, event => {
|
||||
navigation.addEventListener(WCF_CLICK_EVENT, (event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
// mimic dropdown behavior
|
||||
@ -57,7 +54,7 @@ define([ 'WoltLabSuite/Core/Ui/Screen' ], function (UiScreen) {
|
||||
}, 10)
|
||||
})
|
||||
|
||||
quickSettings.addEventListener(WCF_CLICK_EVENT, event => {
|
||||
quickSettings.addEventListener(WCF_CLICK_EVENT, (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@ -67,4 +64,4 @@ define([ 'WoltLabSuite/Core/Ui/Screen' ], function (UiScreen) {
|
||||
}
|
||||
|
||||
return Mobile
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,10 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Language'], function (Language) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore' ]
|
||||
const DEPENDENCIES = ['ProfileStore']
|
||||
class Notification {
|
||||
constructor(profileStore) {
|
||||
this.profileStore = profileStore
|
||||
@ -27,11 +27,14 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
document.addEventListener('visibilitychange', this.onVisibilitychange.bind(this))
|
||||
document.addEventListener(
|
||||
'visibilitychange',
|
||||
this.onVisibilitychange.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
get systemSupported() {
|
||||
return "Notification" in window
|
||||
return 'Notification' in window
|
||||
}
|
||||
|
||||
get systemDenied() {
|
||||
@ -40,7 +43,10 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
|
||||
get systemGranted() {
|
||||
if (this.systemDenied) {
|
||||
console.warn('[Notification]', 'System Notifications: permission denied')
|
||||
console.warn(
|
||||
'[Notification]',
|
||||
'System Notifications: permission denied'
|
||||
)
|
||||
}
|
||||
|
||||
return window.Notification.permission === 'granted'
|
||||
@ -57,7 +63,7 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
|
||||
ingest(messages) {
|
||||
if (!this.active) {
|
||||
messages.forEach(message => {
|
||||
messages.forEach((message) => {
|
||||
const body = message.getMessageType().renderPlainText(message)
|
||||
|
||||
if (body === false) return
|
||||
@ -70,10 +76,7 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
// The user information is guaranteed to be cached at this point
|
||||
const user = this.profileStore.get(message.userID)
|
||||
const title = Language.get('chat.notification.title', { message })
|
||||
const options = { body
|
||||
, icon: user.imageUrl
|
||||
, badge: user.imageUrl
|
||||
}
|
||||
const options = { body, icon: user.imageUrl, badge: user.imageUrl }
|
||||
|
||||
const notification = new window.Notification(title, options)
|
||||
|
||||
@ -88,14 +91,14 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
updateBrowserTitle() {
|
||||
if (this.unread > 0) {
|
||||
document.title = `(${this.unread}) ${this.browserTitle}`
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
document.title = this.browserTitle
|
||||
}
|
||||
}
|
||||
|
||||
enableSystemNotifications() {
|
||||
if (!this.systemSupported) return Promise.reject(new Error('Notifications are not supported'))
|
||||
if (!this.systemSupported)
|
||||
return Promise.reject(new Error('Notifications are not supported'))
|
||||
|
||||
if (this.systemGranted) {
|
||||
this.systemEnabled = true
|
||||
@ -104,13 +107,12 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.Notification.requestPermission(permission => {
|
||||
window.Notification.requestPermission((permission) => {
|
||||
this.systemEnabled = permission === 'granted'
|
||||
|
||||
if (this.systemEnabled) {
|
||||
resolve()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
reject(new Error(permission))
|
||||
}
|
||||
})
|
||||
@ -124,4 +126,4 @@ define([ 'WoltLabSuite/Core/Language' ], function (Language) {
|
||||
Notification.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Notification
|
||||
});
|
||||
})
|
||||
|
@ -11,28 +11,34 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiMessageStream' ]
|
||||
const DEPENDENCIES = ['UiMessageStream']
|
||||
class ReadMarker {
|
||||
constructor(messageStream) {
|
||||
this.messageStream = messageStream
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
document.addEventListener('visibilitychange', this.onVisibilitychange.bind(this))
|
||||
document.addEventListener(
|
||||
'visibilitychange',
|
||||
this.onVisibilitychange.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
onVisibilitychange() {
|
||||
if (document.hidden) {
|
||||
const ul = elBySel('ul', this.messageStream.stream)
|
||||
let lc = ul.lastElementChild
|
||||
let lc = ul.lastElementChild
|
||||
|
||||
// delete previous markers
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.readMarker'), marker => {
|
||||
marker.classList.remove('readMarker')
|
||||
})
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll('.readMarker'),
|
||||
(marker) => {
|
||||
marker.classList.remove('readMarker')
|
||||
}
|
||||
)
|
||||
|
||||
if (lc) {
|
||||
lc.classList.add('readMarker')
|
||||
@ -43,4 +49,4 @@ define([ ], function () {
|
||||
ReadMarker.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return ReadMarker
|
||||
});
|
||||
})
|
||||
|
@ -11,23 +11,27 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
'use strict';
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiSettingsButton' ]
|
||||
const DEPENDENCIES = ['UiSettingsButton']
|
||||
class Settings {
|
||||
constructor(modules) {
|
||||
this.modules = modules
|
||||
this.buttons = Array.from(elBySelAll('#chatQuickSettingsNavigation .button[data-module]'))
|
||||
this.buttons = Array.from(
|
||||
elBySelAll('#chatQuickSettingsNavigation .button[data-module]')
|
||||
)
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
this.buttons.forEach(element => {
|
||||
this.modules[element.dataset.module.replace(/\./g, '-')].instance(element).bootstrap()
|
||||
this.buttons.forEach((element) => {
|
||||
this.modules[element.dataset.module.replace(/\./g, '-')]
|
||||
.instance(element)
|
||||
.bootstrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
Settings.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Settings
|
||||
});
|
||||
})
|
||||
|
@ -11,10 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './ToggleButton' ], function (ToggleButton) {
|
||||
'use strict';
|
||||
define(['./ToggleButton'], function (ToggleButton) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiMessageStream' ].concat(ToggleButton.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['UiMessageStream'].concat(
|
||||
ToggleButton.DEPENDENCIES || []
|
||||
)
|
||||
class AutoscrollButton extends ToggleButton {
|
||||
constructor(element, messageStream, ...superDeps) {
|
||||
super(element, true, undefined, ...superDeps)
|
||||
@ -40,4 +42,4 @@ define([ './ToggleButton' ], function (ToggleButton) {
|
||||
AutoscrollButton.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return AutoscrollButton
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
'use strict';
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ ]
|
||||
const DEPENDENCIES = []
|
||||
class Button {
|
||||
constructor(element) {
|
||||
if (!element || !element instanceof Element) throw new Error('No DOM element provided')
|
||||
if (!element || !element instanceof Element)
|
||||
throw new Error('No DOM element provided')
|
||||
|
||||
this.element = element
|
||||
}
|
||||
@ -33,4 +34,4 @@ define([ ], function () {
|
||||
Button.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return Button
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,17 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './ToggleButton' ], function (ToggleButton) {
|
||||
'use strict';
|
||||
define(['./ToggleButton'], function (ToggleButton) {
|
||||
'use strict'
|
||||
|
||||
class FullscreenButton extends ToggleButton {
|
||||
constructor(element, ...superDeps) {
|
||||
super(element, false, 'Bastelstu.be/Chat/Ui/Settings/FullscreenButton', ...superDeps)
|
||||
super(
|
||||
element,
|
||||
false,
|
||||
'Bastelstu.be/Chat/Ui/Settings/FullscreenButton',
|
||||
...superDeps
|
||||
)
|
||||
}
|
||||
|
||||
enable() {
|
||||
@ -31,4 +36,4 @@ define([ './ToggleButton' ], function (ToggleButton) {
|
||||
}
|
||||
|
||||
return FullscreenButton
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,20 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './ToggleButton' ], function (ToggleButton) {
|
||||
'use strict';
|
||||
define(['./ToggleButton'], function (ToggleButton) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiNotification' ].concat(ToggleButton.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['UiNotification'].concat(
|
||||
ToggleButton.DEPENDENCIES || []
|
||||
)
|
||||
class NotificationsButton extends ToggleButton {
|
||||
constructor(element, notification, ...superDeps) {
|
||||
super(element, false, 'Bastelstu.be/Chat/Ui/Settings/NotificationsButton', ...superDeps)
|
||||
super(
|
||||
element,
|
||||
false,
|
||||
'Bastelstu.be/Chat/Ui/Settings/NotificationsButton',
|
||||
...superDeps
|
||||
)
|
||||
|
||||
this.notification = notification
|
||||
}
|
||||
@ -26,14 +33,17 @@ define([ './ToggleButton' ], function (ToggleButton) {
|
||||
super.bootstrap()
|
||||
|
||||
// Hide the button if notifications are not supported or the permission has been denied
|
||||
if (!this.notification.systemSupported || this.notification.systemDenied) {
|
||||
if (
|
||||
!this.notification.systemSupported ||
|
||||
this.notification.systemDenied
|
||||
) {
|
||||
elRemove(this.element.closest('li'))
|
||||
}
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable()
|
||||
this.notification.enableSystemNotifications().catch(error => {
|
||||
this.notification.enableSystemNotifications().catch((error) => {
|
||||
this.disable()
|
||||
|
||||
if (this.notification.systemDenied) elRemove(this.element)
|
||||
@ -48,4 +58,4 @@ define([ './ToggleButton' ], function (ToggleButton) {
|
||||
NotificationsButton.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return NotificationsButton
|
||||
});
|
||||
})
|
||||
|
@ -11,12 +11,13 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './ToggleButton'
|
||||
, 'WoltLabSuite/Core/Ui/Screen'
|
||||
], function (ToggleButton, UiScreen) {
|
||||
'use strict';
|
||||
define(['./ToggleButton', 'WoltLabSuite/Core/Ui/Screen'], function (
|
||||
ToggleButton,
|
||||
UiScreen
|
||||
) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ].concat(ToggleButton.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = ['UiInput'].concat(ToggleButton.DEPENDENCIES || [])
|
||||
class SmiliesButton extends ToggleButton {
|
||||
constructor(element, input, ...superDeps) {
|
||||
super(element, false, undefined, ...superDeps)
|
||||
@ -25,7 +26,7 @@ define([ './ToggleButton'
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
this.container = elById('smileyPickerContainer')
|
||||
this.container = elById('smileyPickerContainer')
|
||||
|
||||
// Remove this button if smileys are disabled
|
||||
if (!this.container) {
|
||||
@ -37,7 +38,11 @@ define([ './ToggleButton'
|
||||
// Initialize the smiley picker tab menu
|
||||
$('.messageTabMenu').messageTabMenu()
|
||||
|
||||
$('#smilies-text').on('mousedown', '.jsSmiley', this.insertSmiley.bind(this))
|
||||
$('#smilies-text').on(
|
||||
'mousedown',
|
||||
'.jsSmiley',
|
||||
this.insertSmiley.bind(this)
|
||||
)
|
||||
this.closeButton.addEventListener('mousedown', this.disable.bind(this))
|
||||
|
||||
// Start in desktop mode
|
||||
@ -48,9 +53,9 @@ define([ './ToggleButton'
|
||||
|
||||
// Setup media queries
|
||||
UiScreen.on('screen-md-down', {
|
||||
match: this.enableMobile.bind(this),
|
||||
match: this.enableMobile.bind(this),
|
||||
unmatch: this.disableMobile.bind(this),
|
||||
setup: this.setupMobile.bind(this)
|
||||
setup: this.setupMobile.bind(this),
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,12 +68,23 @@ define([ './ToggleButton'
|
||||
*/
|
||||
setupMobile() {
|
||||
this.shadowToggleButton = document.createElement('span')
|
||||
this.shadowToggleButton.classList.add('smiliesToggleMobileButton', 'button', 'small')
|
||||
this.shadowToggleButton.innerHTML = '<span class="icon icon24 fa-smile-o"></span>'
|
||||
this.shadowToggleButton.addEventListener('mousedown', this.onClick.bind(this))
|
||||
this.shadowToggleButton.classList.add(
|
||||
'smiliesToggleMobileButton',
|
||||
'button',
|
||||
'small'
|
||||
)
|
||||
this.shadowToggleButton.innerHTML =
|
||||
'<span class="icon icon24 fa-smile-o"></span>'
|
||||
this.shadowToggleButton.addEventListener(
|
||||
'mousedown',
|
||||
this.onClick.bind(this)
|
||||
)
|
||||
|
||||
const shadowContainer = elBySel('#chatInputContainer > div')
|
||||
shadowContainer.insertBefore(this.shadowToggleButton, shadowContainer.firstChild)
|
||||
shadowContainer.insertBefore(
|
||||
this.shadowToggleButton,
|
||||
shadowContainer.firstChild
|
||||
)
|
||||
|
||||
this.enableMobile()
|
||||
}
|
||||
@ -156,4 +172,4 @@ define([ './ToggleButton'
|
||||
SmiliesButton.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return SmiliesButton
|
||||
});
|
||||
})
|
||||
|
@ -11,13 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Button'
|
||||
, '../../LocalStorage'
|
||||
, '../../DataStructure/EventEmitter'
|
||||
], function (Button, LocalStorage, EventEmitter) {
|
||||
'use strict';
|
||||
define([
|
||||
'./Button',
|
||||
'../../LocalStorage',
|
||||
'../../DataStructure/EventEmitter',
|
||||
], function (Button, LocalStorage, EventEmitter) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ ].concat(Button.DEPENDENCIES || [ ])
|
||||
const DEPENDENCIES = [].concat(Button.DEPENDENCIES || [])
|
||||
class ToggleButton extends Button {
|
||||
constructor(element, defaultState, storageKey, ...superDeps) {
|
||||
super(element, ...superDeps)
|
||||
@ -38,8 +39,7 @@ define([ './Button'
|
||||
|
||||
if (this.defaultState) {
|
||||
this.enable()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.disable()
|
||||
}
|
||||
}
|
||||
@ -69,8 +69,7 @@ define([ './Button'
|
||||
|
||||
if (this.enabled) {
|
||||
this.disable()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.enable()
|
||||
}
|
||||
}
|
||||
@ -79,4 +78,4 @@ define([ './Button'
|
||||
ToggleButton.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return ToggleButton
|
||||
});
|
||||
})
|
||||
|
@ -11,18 +11,21 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Traverse' ], function (Traverse) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Dom/Traverse'], function (Traverse) {
|
||||
'use strict'
|
||||
|
||||
class Topic {
|
||||
bootstrap() {
|
||||
elBySelAll('.chatRoomTopic', document, function (element) {
|
||||
elBySel('.jsDismissRoomTopicButton', element).addEventListener('click', function (event) {
|
||||
elRemove(element)
|
||||
})
|
||||
elBySel('.jsDismissRoomTopicButton', element).addEventListener(
|
||||
'click',
|
||||
function (event) {
|
||||
elRemove(element)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return Topic
|
||||
});
|
||||
})
|
||||
|
@ -11,18 +11,23 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
, 'WoltLabSuite/Core/Dom/Util'
|
||||
, 'WoltLabSuite/Core/Ui/Dropdown/Simple'
|
||||
], function (DomTraverse, DomUtil, SimpleDropdown) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/Dom/Traverse',
|
||||
'WoltLabSuite/Core/Dom/Util',
|
||||
'WoltLabSuite/Core/Ui/Dropdown/Simple',
|
||||
], function (DomTraverse, DomUtil, SimpleDropdown) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'ProfileStore', 'Template.UserListDropdownMenuItems', 'bottle' ]
|
||||
const DEPENDENCIES = [
|
||||
'ProfileStore',
|
||||
'Template.UserListDropdownMenuItems',
|
||||
'bottle',
|
||||
]
|
||||
class UserActionDropdownHandler {
|
||||
constructor(profiles, dropdownTemplate, bottle) {
|
||||
this.profiles = profiles
|
||||
this.dropdownTemplate = dropdownTemplate
|
||||
this.bottle = bottle
|
||||
this.profiles = profiles
|
||||
this.dropdownTemplate = dropdownTemplate
|
||||
this.bottle = bottle
|
||||
|
||||
this.container = elById('main')
|
||||
}
|
||||
@ -32,7 +37,15 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
const userElement = event.target.classList.contains('jsUserActionDropdown') ? event.target : DomTraverse.parentByClass(event.target, 'jsUserActionDropdown', this.container)
|
||||
const userElement = event.target.classList.contains(
|
||||
'jsUserActionDropdown'
|
||||
)
|
||||
? event.target
|
||||
: DomTraverse.parentByClass(
|
||||
event.target,
|
||||
'jsUserActionDropdown',
|
||||
this.container
|
||||
)
|
||||
|
||||
if (!userElement) return
|
||||
|
||||
@ -46,23 +59,37 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
|
||||
// Note: We would usually use firstElementChild here, but this
|
||||
// is not supported in Safari and Edge
|
||||
const dropdown = DomUtil.createFragmentFromHtml(this.dropdownTemplate.fetch({ user })).querySelector('*')
|
||||
const dropdown = DomUtil.createFragmentFromHtml(
|
||||
this.dropdownTemplate.fetch({ user })
|
||||
).querySelector('*')
|
||||
|
||||
Array.from(elBySelAll('[data-module]', dropdown)).forEach(element => {
|
||||
Array.from(elBySelAll('[data-module]', dropdown)).forEach((element) => {
|
||||
const moduleName = element.dataset.module
|
||||
let userAction
|
||||
if (!this.bottle.container.UserAction || (userAction = this.bottle.container.UserAction[`${moduleName.replace(/\./g, '-')}`]) == null) {
|
||||
this.bottle.factory(`UserAction.${moduleName.replace(/\./g, '-')}`, _ => {
|
||||
const UserAction = require(moduleName)
|
||||
const deps = this.bottle.digest(UserAction.DEPENDENCIES || [])
|
||||
if (
|
||||
!this.bottle.container.UserAction ||
|
||||
(userAction = this.bottle.container.UserAction[
|
||||
`${moduleName.replace(/\./g, '-')}`
|
||||
]) == null
|
||||
) {
|
||||
this.bottle.factory(
|
||||
`UserAction.${moduleName.replace(/\./g, '-')}`,
|
||||
(_) => {
|
||||
const UserAction = require(moduleName)
|
||||
const deps = this.bottle.digest(UserAction.DEPENDENCIES || [])
|
||||
|
||||
return new UserAction(...deps)
|
||||
})
|
||||
return new UserAction(...deps)
|
||||
}
|
||||
)
|
||||
|
||||
userAction = this.bottle.container.UserAction[`${moduleName.replace(/\./g, '-')}`]
|
||||
userAction = this.bottle.container.UserAction[
|
||||
`${moduleName.replace(/\./g, '-')}`
|
||||
]
|
||||
}
|
||||
|
||||
element.addEventListener(WCF_CLICK_EVENT, (event) => userAction.onClick(user, event))
|
||||
element.addEventListener(WCF_CLICK_EVENT, (event) =>
|
||||
userAction.onClick(user, event)
|
||||
)
|
||||
})
|
||||
|
||||
SimpleDropdown.initFragment(userElement, dropdown)
|
||||
@ -77,4 +104,4 @@ define([ 'WoltLabSuite/Core/Dom/Traverse'
|
||||
UserActionDropdownHandler.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return UserActionDropdownHandler
|
||||
});
|
||||
})
|
||||
|
@ -11,14 +11,14 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
class Action {
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
onClick(userID, event) { }
|
||||
onClick(userID, event) {}
|
||||
}
|
||||
|
||||
return Action
|
||||
});
|
||||
})
|
||||
|
@ -11,17 +11,15 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../../console'
|
||||
, './Action'
|
||||
], function (console, Action) {
|
||||
"use strict";
|
||||
define(['../../console', './Action'], function (console, Action) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ]
|
||||
const DEPENDENCIES = ['UiInput']
|
||||
class BanAction extends Action {
|
||||
constructor(input) {
|
||||
super()
|
||||
|
||||
this.input = input
|
||||
this.input = input
|
||||
}
|
||||
|
||||
onClick(user, event) {
|
||||
@ -35,7 +33,7 @@ define([ '../../console'
|
||||
|
||||
this.input.insertText(command, { append: false, prepend: true })
|
||||
this.input.focus()
|
||||
setTimeout(_ => {
|
||||
setTimeout((_) => {
|
||||
this.input.emit('autocomplete')
|
||||
}, 1)
|
||||
}
|
||||
@ -43,4 +41,4 @@ define([ '../../console'
|
||||
BanAction.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return BanAction
|
||||
});
|
||||
})
|
||||
|
@ -11,17 +11,15 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ '../../console'
|
||||
, './Action'
|
||||
], function (console, Action) {
|
||||
"use strict";
|
||||
define(['../../console', './Action'], function (console, Action) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ]
|
||||
const DEPENDENCIES = ['UiInput']
|
||||
class MuteAction extends Action {
|
||||
constructor(input) {
|
||||
super()
|
||||
|
||||
this.input = input
|
||||
this.input = input
|
||||
}
|
||||
|
||||
onClick(user, event) {
|
||||
@ -35,7 +33,7 @@ define([ '../../console'
|
||||
|
||||
this.input.insertText(command, { append: false, prepend: true })
|
||||
this.input.focus()
|
||||
setTimeout(_ => {
|
||||
setTimeout((_) => {
|
||||
this.input.emit('autocomplete')
|
||||
}, 1)
|
||||
}
|
||||
@ -43,4 +41,4 @@ define([ '../../console'
|
||||
MuteAction.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return MuteAction
|
||||
});
|
||||
})
|
||||
|
@ -11,15 +11,15 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ './Action', '../../console' ], function (Action, console) {
|
||||
"use strict";
|
||||
define(['./Action', '../../console'], function (Action, console) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'UiInput' ]
|
||||
const DEPENDENCIES = ['UiInput']
|
||||
class WhisperAction extends Action {
|
||||
constructor(input) {
|
||||
super()
|
||||
|
||||
this.input = input
|
||||
this.input = input
|
||||
}
|
||||
|
||||
onClick(user, event) {
|
||||
@ -38,4 +38,4 @@ define([ './Action', '../../console' ], function (Action, console) {
|
||||
WhisperAction.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return WhisperAction
|
||||
});
|
||||
})
|
||||
|
@ -11,28 +11,26 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/Dom/Util' ], function (DomUtil) {
|
||||
"use strict";
|
||||
define(['WoltLabSuite/Core/Dom/Util'], function (DomUtil) {
|
||||
'use strict'
|
||||
|
||||
const DEPENDENCIES = [ 'Template.UserList' ]
|
||||
const DEPENDENCIES = ['Template.UserList']
|
||||
class UserList {
|
||||
constructor(userListTemplate) {
|
||||
this.userListTemplate = userListTemplate
|
||||
this.chatUserList = elById('chatUserList')
|
||||
this.userListTemplate = userListTemplate
|
||||
this.chatUserList = elById('chatUserList')
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
|
||||
}
|
||||
bootstrap() {}
|
||||
|
||||
render(users) {
|
||||
users.sort((a, b) => a.username.localeCompare(b.username))
|
||||
const html = this.userListTemplate.fetch({ users })
|
||||
const html = this.userListTemplate.fetch({ users })
|
||||
const fragment = DomUtil.createFragmentFromHtml(html)
|
||||
|
||||
// Replace the current user list with the new one
|
||||
const currentList = elBySel('#chatUserList > .boxContent > ul')
|
||||
const parentNode = currentList.parentNode
|
||||
const parentNode = currentList.parentNode
|
||||
parentNode.removeChild(currentList)
|
||||
parentNode.appendChild(fragment)
|
||||
}
|
||||
@ -40,4 +38,4 @@ define([ 'WoltLabSuite/Core/Dom/Util' ], function (DomUtil) {
|
||||
UserList.DEPENDENCIES = DEPENDENCIES
|
||||
|
||||
return UserList
|
||||
});
|
||||
})
|
||||
|
@ -11,11 +11,12 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ 'WoltLabSuite/Core/User'
|
||||
, 'WoltLabSuite/Core/StringUtil'
|
||||
, './Helper'
|
||||
], function (CoreUser, StringUtil, Helper) {
|
||||
"use strict";
|
||||
define([
|
||||
'WoltLabSuite/Core/User',
|
||||
'WoltLabSuite/Core/StringUtil',
|
||||
'./Helper',
|
||||
], function (CoreUser, StringUtil, Helper) {
|
||||
'use strict'
|
||||
|
||||
const u = Symbol('user')
|
||||
|
||||
@ -26,14 +27,15 @@ define([ 'WoltLabSuite/Core/User'
|
||||
constructor(user) {
|
||||
this[u] = Helper.deepFreeze(user)
|
||||
|
||||
Object.getOwnPropertyNames(this[u]).forEach(key => {
|
||||
Object.getOwnPropertyNames(this[u]).forEach((key) => {
|
||||
if (this[key]) {
|
||||
throw new Error('Attempting to override existing property')
|
||||
}
|
||||
|
||||
Object.defineProperty(this, key, { value: this[u][key]
|
||||
, enumerable: true
|
||||
})
|
||||
Object.defineProperty(this, key, {
|
||||
value: this[u][key],
|
||||
enumerable: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,28 +44,36 @@ define([ 'WoltLabSuite/Core/User'
|
||||
if (this.color1 === null && this.color2 === null) return this.username
|
||||
|
||||
// Single color
|
||||
if (this.color1 === this.color2) return `<span style="color: ${Helper.intToRGBHex(this.color1)};">${StringUtil.escapeHTML(this.username)}</span>`
|
||||
if (this.color1 === this.color2)
|
||||
return `<span style="color: ${Helper.intToRGBHex(
|
||||
this.color1
|
||||
)};">${StringUtil.escapeHTML(this.username)}</span>`
|
||||
|
||||
// Gradient
|
||||
const r1 = (this.color1 >> 16) & 0xFF
|
||||
const r2 = (this.color2 >> 16) & 0xFF
|
||||
const g1 = (this.color1 >> 8) & 0xFF
|
||||
const g2 = (this.color2 >> 8) & 0xFF
|
||||
const b1 = this.color1 & 0xFF
|
||||
const b2 = this.color2 & 0xFF
|
||||
const r1 = (this.color1 >> 16) & 0xff
|
||||
const r2 = (this.color2 >> 16) & 0xff
|
||||
const g1 = (this.color1 >> 8) & 0xff
|
||||
const g2 = (this.color2 >> 8) & 0xff
|
||||
const b1 = this.color1 & 0xff
|
||||
const b2 = this.color2 & 0xff
|
||||
|
||||
const steps = this.username.length - 1
|
||||
const r = (r1 - r2) / steps
|
||||
const g = (g1 - g2) / steps
|
||||
const b = (b1 - b2) / steps
|
||||
|
||||
return this[u].username.split('').map((letter, index) => {
|
||||
const R = Math.round(r1 - index * r)
|
||||
const G = Math.round(g1 - index * g)
|
||||
const B = Math.round(b1 - index * b)
|
||||
return this[u].username
|
||||
.split('')
|
||||
.map((letter, index) => {
|
||||
const R = Math.round(r1 - index * r)
|
||||
const G = Math.round(g1 - index * g)
|
||||
const B = Math.round(b1 - index * b)
|
||||
|
||||
return `<span style="color: rgb(${R}, ${G}, ${B})">${StringUtil.escapeHTML(letter)}</span>`
|
||||
}).join('')
|
||||
return `<span style="color: rgb(${R}, ${G}, ${B})">${StringUtil.escapeHTML(
|
||||
letter
|
||||
)}</span>`
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
get self() {
|
||||
@ -71,11 +81,7 @@ define([ 'WoltLabSuite/Core/User'
|
||||
}
|
||||
|
||||
static getGuest(username) {
|
||||
const payload = { username
|
||||
, userID: null
|
||||
, color1: null
|
||||
, color2: null
|
||||
}
|
||||
const payload = { username, userID: null, color1: null, color2: null }
|
||||
|
||||
return new User(payload)
|
||||
}
|
||||
@ -90,4 +96,4 @@ define([ 'WoltLabSuite/Core/User'
|
||||
}
|
||||
|
||||
return User
|
||||
});
|
||||
})
|
||||
|
@ -11,11 +11,11 @@
|
||||
* or later of the General Public License.
|
||||
*/
|
||||
|
||||
define([ ], function () {
|
||||
"use strict";
|
||||
define([], function () {
|
||||
'use strict'
|
||||
|
||||
const start = Date.now()
|
||||
let last = start
|
||||
let last = start
|
||||
|
||||
const group = function () {
|
||||
if (window.console.group) window.console.group()
|
||||
@ -46,13 +46,12 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
const debug = function (handler, ...args) {
|
||||
const now = Date.now()
|
||||
const time = [ (now - start), `\t+${(now - last)}ms\t` ]
|
||||
const now = Date.now()
|
||||
const time = [now - start, `\t+${now - last}ms\t`]
|
||||
|
||||
if (args.length) {
|
||||
println('debug', ...time, `[${handler}]\t`, ...args)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
println('debug', ...time, handler)
|
||||
}
|
||||
|
||||
@ -61,7 +60,7 @@ define([ ], function () {
|
||||
|
||||
const debugException = function (error) {
|
||||
if (error instanceof Error) {
|
||||
let message = `[${error.name}] „${error.message}“ in ${error.fileName} on line ${error.lineNumber}\n`
|
||||
let message = `[${error.name}] „${error.message}“ in ${error.fileName} on line ${error.lineNumber}\n`
|
||||
|
||||
if (error.stack) {
|
||||
message += 'Stacktrace:\n'
|
||||
@ -69,8 +68,7 @@ define([ ], function () {
|
||||
}
|
||||
|
||||
println('error', message)
|
||||
}
|
||||
else if (error.code && error.message) {
|
||||
} else if (error.code && error.message) {
|
||||
debugAjaxException(error)
|
||||
}
|
||||
}
|
||||
@ -79,20 +77,21 @@ define([ ], function () {
|
||||
groupCollapsed()
|
||||
let details = `[${error.code}] ${error.message}`
|
||||
|
||||
const br2nl = (string) => string.split('\n')
|
||||
.map(line => line.replace(/<br\s*\/?>$/i, ''))
|
||||
.join('\n')
|
||||
const br2nl = (string) =>
|
||||
string
|
||||
.split('\n')
|
||||
.map((line) => line.replace(/<br\s*\/?>$/i, ''))
|
||||
.join('\n')
|
||||
|
||||
if (error.stacktrace) {
|
||||
details += `\nStacktrace:\n${br2nl(error.stacktrace)}`
|
||||
}
|
||||
else if (error.exceptionID) {
|
||||
} else if (error.exceptionID) {
|
||||
details += `\nException ID: ${error.exceptionID}`
|
||||
}
|
||||
|
||||
println('debug', details)
|
||||
|
||||
error.previous.forEach(previous => {
|
||||
error.previous.forEach((previous) => {
|
||||
let details = ''
|
||||
|
||||
group()
|
||||
@ -103,17 +102,18 @@ define([ ], function () {
|
||||
println('debug', details)
|
||||
})
|
||||
|
||||
error.previous.forEach(_ => groupEnd())
|
||||
error.previous.forEach((_) => groupEnd())
|
||||
groupEnd()
|
||||
}
|
||||
|
||||
return { log
|
||||
, warn
|
||||
, error
|
||||
, debug
|
||||
, debugException
|
||||
, group
|
||||
, groupCollapsed
|
||||
, groupEnd
|
||||
}
|
||||
});
|
||||
return {
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
debug,
|
||||
debugException,
|
||||
group,
|
||||
groupCollapsed,
|
||||
groupEnd,
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user