{"version":3,"file":"app-b9e83459.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-input/chat-input.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-link-search/chat-link-search-result-entry/chat-link-search-result-entry.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/chat-message.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/download-file-chat-message-content/download-file-chat-message-content.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/incognito-chat-message-content/incognito-chat-message-content.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/regular-chat-message-content/regular-chat-message-content.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-view/chat-view.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-input/chat-input.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-input/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-link-search/chat-link-search-result-entry/chat-link-search-result-entry.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-link-search/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/chat-message.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/download-file-chat-message-content/download-file-chat-message-content.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/incognito-chat-message-content/incognito-chat-message-content.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-message/regular-chat-message-content/regular-chat-message-content.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-view/chat-view.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/chat-view/index.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"chat-input ${ canUploadFiles ? `chat-input--can-upload-files` : `` } ${ incognitoOn ? `chat-input--incognito` : `` }\\\"> <span if.to-view=\\\"showIncognito && incognitoOn\\\" class=\\\"chat-input__incognito-text\\\" t=\\\"common.chat-input.incognitoMessage\\\"></span> <div class=\\\"chat-input__input-wrapper\\\"> <input show.to-view=\\\"messagingAllowed\\\" ref=\\\"inputDomElement\\\" class=\\\"chat-input__input\\\" value.two-way=\\\"inputValue\\\" type=\\\"text\\\" t=\\\"[placeholder]common.chat-input.inputPlaceholder;[aria-label]common.chat-input.inputPlaceholder\\\"> <div if.to-view=\\\"messagingAllowed\\\" class=\\\"chat-input__actions\\\"> <div if.to-view=\\\"showIncognito\\\" class=\\\"chat-input__action-icon-wrapper mdc-ripple-surface ${ incognitoOn ? `` : `chat-input__action-icon-wrapper--inactive` }\\\" click.delegate=\\\"toggleIncognito()\\\" t=\\\"[aria-label]common.chat-input.incognitoAnonymous\\\" use-as-button> <safe-icon class=\\\"chat-input__action-icon\\\" icon-name=\\\"System/Incognito\\\"></safe-icon> </div> <div repeat.for=\\\"action of actions\\\" class=\\\"chat-input__action-icon-wrapper mdc-ripple-surface\\\" click.trigger=\\\"action.callback()\\\" use-as-button t=\\\"[aria-label]common.chat-input.${action.ariaLabelTk}\\\"> <safe-icon class=\\\"chat-input__action-icon\\\" icon-name.to-view=\\\"action.icon\\\"></safe-icon> </div> </div> </div> <p if.to-view=\\\"!messagingAllowed\\\" class=\\\"chat-input__not-allowed-text\\\" t=\\\"common.chat-input.messagingDisallowed\\\"></p> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"chat-link-result-entry mdc-button\\\"> <div class=\\\"mdc-button__ripple\\\"></div> <div class=\\\"chat-link-result-entry__content\\\"> <x-icon class=\\\"chat-link-result-entry__icon\\\" icon-type.to-view=\\\"iconType\\\" icon-name.to-view=\\\"iconName\\\"></x-icon> <div class=\\\"chat-link-result-entry__details\\\"> <div class=\\\"chat-link-result-entry__breadcrumb\\\">${entryBreadcrumb}</div> <div class=\\\"chat-link-result-entry__title\\\">${entryTitle}</div> </div> <div class=\\\"chat-link-result-entry__hr\\\"></div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lp-chat-message lp-chat-message--${message.type} ${!isGroupChat ? 'lp-chat-message--no-avatar' : ''}\\\"> <avatar-icon if.to-view=\\\"showAvatar\\\" class=\\\"lp-chat-message__avatar\\\" avatar-svg-download-link.to-view=\\\"avatarUrl\\\" points.to-view=\\\"avatarPoints\\\" level-mode=\\\"primary-color-indicator\\\" fallback-avatar.to-view=\\\"fallbackAvatar\\\" online.to-view=\\\"isAuthorOnline\\\"></avatar-icon> <div if.to-view=\\\"message.userId !== currentUserId && message.type === ChatMessageType.Incognito\\\" class=\\\"lp-chat-message__incognito-avatar-wrapper\\\"> <x-icon class=\\\"lp-chat-message__incognito-avatar\\\" icon-type=\\\"custom\\\" size=\\\"normal\\\" icon-name=\\\"System/Incognito\\\"></x-icon> </div> <div class=\\\"lp-chat-message__content ${isFirst ? 'lp-chat-message__content--first': '' } ${canUploadFiles ? 'lp-chat-message__content--with-file-drop-zone' : ''} ${message.isSystem ? 'lp-chat-message__content--system' : ''} ${message.userId === currentUserId ? 'lp-chat-message__content--own' : ''}\\\"> <p if.to-view=\\\"showAuthorName\\\" class=\\\"lp-chat-message__author-name\\\">${authorName}</p> <p if.to-view=\\\"message.isSystem\\\" class=\\\"lp-chat-message__author-name\\\" t=\\\"globals.systemMessage\\\"></p> <regular-chat-message-content if.to-view=\\\"message.type === ChatMessageType.Regular\\\" current-course-id.to-view=\\\"currentCourseId\\\" raw-text.to-view=\\\"message.text\\\"></regular-chat-message-content> <h6 if.to-view=\\\"message.type === ChatMessageType.Duel\\\" class=\\\"lp-chat-message__title\\\" t=\\\"common.chat-message.duelInvitationTitle\\\"></h6> <p if.to-view=\\\"message.type === ChatMessageType.Duel\\\" class=\\\"lp-chat-message__text\\\" t=\\\"common.chat-message.duelInvitationText\\\"></p> <incognito-chat-message-content if.to-view=\\\"message.type === ChatMessageType.Incognito\\\" current-user-id.to-view=\\\"currentUserId\\\" message.to-view=\\\"message\\\" can-toggle-anonymous-message.to-view=\\\"canToggleAnonymousMessage\\\"></incognito-chat-message-content> <download-file-chat-message-content if.to-view=\\\"message.type === ChatMessageType.File\\\" current-user-id.to-view=\\\"currentUserId\\\" message.to-view=\\\"message\\\"></download-file-chat-message-content> <p class=\\\"lp-chat-message__small-print\\\">${formatDate(message.createdAt)}</p> </div> <div class=\\\"lp-chat-message__caption ${message.userId === currentUserId ? 'lp-chat-message__caption--own' : ''}\\\"> <p if.to-view=\\\"message.duelStatus === DuelStatus.InvitationPending && message.userId === currentUserId\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationOpenCaption\\\" t-params.to-view=\\\"{user: otherName}\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.Declined && message.userId !== currentUserId\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationDeclinedByYouCaption\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.Declined && message.userId === currentUserId\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationDeclinedByOtherCaption\\\" t-params.to-view=\\\"{user: otherName}\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.InvitationExpired && message.userId !== currentUserId\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationExpiredByYouCaption\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.InvitationExpired && message.userId === currentUserId\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationExpiredByOtherCaption\\\" t-params.to-view=\\\"{user: otherName}\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.Accepted\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationAcceptedCaption\\\"></p> <p if.to-view=\\\"message.duelStatus === DuelStatus.Finished\\\" class=\\\"lp-chat-message__caption-text\\\" t=\\\"common.chat-message.duelInvitationFinishedCaption\\\"></p> </div> <div class=\\\"lp-chat-message__btn-box ${message.userId === currentUserId ? 'lp-chat-message__btn-box--own' : ''}\\\"> <button-standard if.to-view=\\\"message.duelStatus === DuelStatus.InvitationPending && message.userId !== currentUserId\\\" disabled.to-view=\\\"isGroupChat\\\" class=\\\"lp-chat-message__btn\\\" click.trigger=\\\"acceptInvitation(message)\\\" t=\\\"[btn-text]common.chat-message.duelInvitationAcceptBtn\\\" btn-type=\\\"outlined\\\"></button-standard> <button-standard if.to-view=\\\"message.duelStatus === DuelStatus.Accepted && message.userId !== currentUserId\\\" disabled.to-view=\\\"isGroupChat\\\" class=\\\"lp-chat-message__btn\\\" click.trigger=\\\"acceptInvitation(message)\\\" t=\\\"[btn-text]common.chat-message.duelInvitationContinueBtn\\\" btn-type=\\\"outlined\\\"></button-standard> <button-standard if.to-view=\\\"message.duelStatus === DuelStatus.InvitationPending && message.userId !== currentUserId\\\" disabled.to-view=\\\"isGroupChat\\\" class=\\\"lp-chat-message__btn\\\" click.trigger=\\\"declineInvitation(message)\\\" t=\\\"[btn-text]common.chat-message.duelInvitationDeclineBtn\\\" btn-type=\\\"outlined\\\"></button-standard> <button-standard if.to-view=\\\"shouldShowResultButton(message, currentUserId)\\\" disabled.to-view=\\\"isGroupChat\\\" class=\\\"lp-chat-message__btn lp-chat-message__btn--bigger\\\" t=\\\"[btn-text]common.chat-message.duelInvitationShowResults\\\" click.trigger=\\\"showDuelResult(message)\\\" btn-type=\\\"outlined\\\"></button-standard> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"download-file-chat-message-content ${message.userId === currentUserId ? 'download-file-chat-message-content--own' : ''}\\\"> <p class=\\\"lp-chat-message__text\\\">${message.text}</p> <button-smaller class=\\\"download-file-chat-message-content__btn\\\" btn-type.to-view=\\\"`icon`\\\" btn-style.to-view=\\\"`inverted`\\\" icon-name.to-view=\\\"`System/Download`\\\" click.trigger=\\\"handleDownloadFileClick(message)\\\" icon-type.to-view=\\\"`custom`\\\"></button-smaller> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"incognito-chat-message-content\\\"> <h6 if.to-view=\\\"message.userId !== currentUserId\\\" class=\\\"lp-chat-message__title\\\" t=\\\"${showUser ? `common.chat-message.incognitoUserTitle` : `common.chat-message.incognitoAnonymousTitle`}\\\" t-params.to-view=\\\"{ name: message.nickname }\\\"></h6> <x-icon if.to-view=\\\"message.userId === currentUserId\\\" class=\\\"incognito-chat-message-content__icon\\\" icon-type.to-view=\\\"`custom`\\\" size.to-view=\\\"`normal`\\\" icon-name.to-view=\\\"`System/Incognito`\\\"></x-icon> <p class=\\\"lp-chat-message__text\\\">${message.text}</p> <button if.to-view=\\\"canToggleAnonymousMessage\\\" class=\\\"incognito-chat-message-content__toggle-anonymous-btn\\\" t=\\\"${showUser ? `common.chat-message.hideUser` : `common.chat-message.showUser`}\\\" click.trigger=\\\"toggleShowUser()\\\"></button> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"regular-chat-message-content\\\"> <p class=\\\"lp-chat-message__text\\\"> <span repeat.for=\\\"part of augmentedMessage\\\"> ${!part.title ? part.raw : ''} <a if.to-view=\\\"part.title\\\" class=\\\"lp-chat-message__link ${isContentFromDifferentCourses(part) ? 'lp-chat-message__link--disabled' : ''}\\\" href.to-view=\\\"part.link ? part.link : ''\\\" click.delegate=\\\"handlePartCallback(part)\\\"> <span class=\\\"lp-chat-message__link-text\\\"> ${part.title} </span> <x-icon if.to-view=\\\"!isContentFromDifferentCourses(part)\\\" class=\\\"lp-chat-message__link-icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Jump\\\" size=\\\"unspecified\\\"></x-icon> </a> </span> <span if.to-view=\\\"hasContentFromDifferentCourses\\\" class=\\\"lp-chat-message__small-print\\\" t=\\\"common.chat-message.contentFromDifferentCourse\\\"></span> </p> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"chat-view ${messages.length === 0 ? `chat-view--no-messages` : ``} ${containerModeOff ? `chat-view--no-container` : ``}\\\"> <div class=\\\"chat-view__messages-and-scroll-button-container\\\"> <file-drop-zone if.to-view=\\\"canUploadFiles && chatRoom\\\" dragging-in-progress.from-view=\\\"drag\\\" wheel.trigger=\\\"handleScrollRequest()\\\" class=\\\"chat-view__file-drop-zone ${scrollingEnabled ? 'chat-view__file-drop-zone--no-scroll' : ''} ${drag ? 'chat-view__file-drop-zone--drag' : ''}\\\" url.to-view=\\\"getFileUploadUrl()\\\" success-callback.to-view=\\\"fileUploadSuccessCallback\\\" accepted-files.to-view=\\\"undefined\\\" upload-query.to-view=\\\"FILE_UPLOAD_QUERY\\\" drop-zone-style=\\\"background\\\"></file-drop-zone> <div class=\\\"chat-view__messages-container ${canUploadFiles ? 'chat-view__messages-container--with-file-drop-zone' : ''}\\\" scroll.trigger=\\\"handleScrollRequest()\\\" ref=\\\"chatWindowDomElement\\\"> <div if.to-view=\\\"showChatEmptyPlaceholder\\\" class=\\\"chat-view__empty lp-site-content-small\\\"> <h3 class=\\\"chat-view__empty-title\\\" t=\\\"common.chat-view.chatEmptyPageDescriptionTitle\\\"></h3> <p class=\\\"chat-view__empty-text\\\" t=\\\"common.chat-view.chatEmptyPageDescriptionTextMessages\\\"></p> <p class=\\\"chat-view__empty-text\\\" t=\\\"common.chat-view.chatEmptyPageDescriptionTextNotifications\\\"></p> <p class=\\\"chat-view__empty-text\\\" t=\\\"common.chat-view.chatEmptyPageDescriptionTextChallenges\\\"></p> </div> <div else class=\\\"chat-view__messages lp-site-content-small\\\"> <button-smaller show.bind=\\\"loadPreviousButtonVisible\\\" btn-style=\\\"inverted\\\" class=\\\"chat-view__has-previous-link\\\" t=\\\"[btnText]common.chat-view.loadPrevious\\\" click.delegate=\\\"loadPrevious()\\\"></button-smaller> <chat-message repeat.for=\\\"message of messages\\\" author-name.to-view=\\\"message.nickname\\\" other-name.to-view=\\\"otherUserName\\\" can-upload-files.to-view=\\\"canUploadFiles\\\" avatar-url.to-view=\\\"message.avatarUrl\\\" avatar-points.to-view=\\\"message.avatarPoints\\\" is-author-online.to-view=\\\"message.isAuthorOnline\\\" last-message-from-id.to-view=\\\"messages[$index-1].userId\\\" last-message-type.to-view=\\\"messages[$index-1].type\\\" is-latest.to-view=\\\"messages[messages.length-1].id === message.id\\\" current-user-id.to-view=\\\"currentUserId\\\" current-course-id.to-view=\\\"currentCourseId\\\" message.to-view=\\\"message\\\" is-group-chat.to-view=\\\"isGroupChat\\\" can-toggle-anonymous-message.to-view=\\\"canToggleAnonymousMessage\\\"></chat-message> </div> </div> <div class=\\\"chat-view__scroll-down-button-container\\\"> <div class=\\\"lp-site-content-small\\\"> <button-smaller if.to-view=\\\"scrollToBottomButtonVisible\\\" btn-type=\\\"icon\\\" btn-style=\\\"inverted\\\" icon-name=\\\"System/Arrow - Down\\\" click.trigger=\\\"scrollToBottom()\\\" icon-type=\\\"custom\\\"></button-smaller> </div> </div> </div> <div class=\\\"chat-view__input-box\\\"> <div class=\\\"chat-view__input-box-content lp-site-content-small\\\"> <chat-input send-message.delegate=\\\"handleNewMessage($event)\\\" messaging-allowed.to-view=\\\"messagingAllowed\\\" custom-actions.to-view=\\\"inputActions\\\" input-value.two-way=\\\"inputValue\\\" can-upload-files.to-view=\\\"canUploadFiles\\\" show-incognito.to-view=\\\"showIncognito\\\"></chat-input> </div> </div> </template>\";\n// Exports\nexport default code;","import { autoinject, bindable, computedFrom, PLATFORM, useView } from 'aurelia-framework';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { SendChatMessageType } from 'common/enum/chat.message.enum';\nimport { KeyboardKey } from 'common/enum/keyboard.key.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\n\n/**\n * @event {SendMessageEvent} send-message\n */\n@autoinject()\n@useView(PLATFORM.moduleName('./chat-input.html'))\nexport class ChatInput {\n  @bindable() public messagingAllowed = false;\n  @bindable() public inputValue = '';\n  @bindable() public customActions: Array<IChatInputAction> = [];\n  @bindable() public showIncognito = false;\n  @bindable() public canUploadFiles = false;\n\n  private readonly element: HTMLElement;\n  private readonly inputDomElement: HTMLInputElement | null = null;\n  private incognitoOn = false;\n  private subscriptionManager: SubscriptionManager;\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.inputDomElement, 'inputDomElement cannot be null or undefined');\n    this.subscriptionManager.subscribeToKeyboardEvent(this.inputDomElement, KeyboardKey.Enter, () => {\n      this.sendMessage();\n    });\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  @computedFrom('customActions')\n  private get actions(): Array<IChatInputAction> {\n    return [...this.customActions, {\n      callback: () => this.sendMessage(),\n      icon: 'System/Send',\n      ariaLabelTk: 'sendMessage',\n    }];\n  }\n\n  private sendMessage(): void {\n    if(this.inputValue === '') return;\n    DomUtils.fireEvent<SendMessageEvent['detail']>(this.element, {\n      bubbles: true,\n      detail: {\n        message: this.inputValue,\n        type: this.incognitoOn ? SendChatMessageType.Incognito : SendChatMessageType.Regular,\n        anonymous: this.incognitoOn,\n      },\n      name: 'send-message',\n    });\n    this.inputValue = '';\n  }\n\n  private toggleIncognito(): void {\n    this.incognitoOn = !this.incognitoOn;\n  }\n\n  private linkPage(): void {\n    DomUtils.fireEvent(this.element, {\n      bubbles: true,\n      name: 'link-page',\n    });\n  }\n}\n\nexport interface IChatInputAction {\n  callback: () => void;\n  icon: string;\n  ariaLabelTk: string;\n}\n\nexport type SendMessageEvent = CustomEvent<{\n  message: string;\n  type: SendChatMessageType;\n  anonymous: boolean;\n}>;\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-input/chat-input'));\n}\n","import { MDCRipple } from '@material/ripple';\nimport { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { IconType } from 'common/aurelia-components/x-icon/x-icon';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\n\n@autoinject()\n@useView(PLATFORM.moduleName('./chat-link-search-result-entry.html'))\nexport class ChatLinkSearchResultEntry {\n  @bindable() public entryBreadcrumb: string | null = null;\n  @bindable() public entryTitle: string | null = null;\n  @bindable() public iconName: string | null = null;\n  @bindable() public iconType: IconType = IconType.fal;\n  private element: HTMLElement | null = null;\n  private ripple: MDCRipple | null = null;\n\n  constructor(element: Element) {\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.element, 'element ref not set');\n    this.ripple = new MDCRipple(this.element);\n  }\n\n  protected detached(): void {\n    this.ripple?.destroy();\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-link-search/chat-link-search-result-entry/chat-link-search-result-entry'));\n}\n","import { autoinject, bindable, PLATFORM, useView, computedFrom } from 'aurelia-framework';\nimport { IChatMessageDto } from 'common/dto/user/chat/chat.interface';\nimport { ChatMessageType } from 'common/enum/chat.message.enum';\nimport { DuelStatus } from 'common/enum/quiz.attempt.status.enum';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport Moment from 'moment';\nimport { SpecialAvatarTypes } from 'common/aurelia-components/avatar-icon/avatar-icon';\n\nexport type MessageAugmentationChangeUnsubscriber = () => void;\n\nexport abstract class CommonChatMessageService {\n  public abstract getAugmentedMessage(text: string): Array<IChatMessagePart>;\n  public abstract subscribeToMessageAugmentationChange(cb: () => void): MessageAugmentationChangeUnsubscriber;\n}\n\n/**\n * @event {ChatMessageEvent} invitation-declined\n * @event {ChatMessageEvent} invitation-accepted\n * @event {ChatMessageEvent} show-duel-result\n */\n@autoinject()\n@useView(PLATFORM.moduleName('./chat-message.html'))\nexport class ChatMessage {\n  @bindable() public message: IChatMessageDto<string> | null = null;\n  @bindable() public isLatest = false;\n  @bindable() public avatarUrl: string | null = null;\n  @bindable() public avatarPoints: number | null = null;\n  @bindable() public isAuthorOnline = false;\n  @bindable() public authorName: string | null = null;\n  @bindable() public otherName: string | null = null;\n  @bindable() public currentUserId: number | null = null;\n  @bindable() public lastMessageFromId: number | null = null;\n  @bindable() public lastMessageType: ChatMessageType | null = null;\n  @bindable() public isGroupChat = false;\n  @bindable() public canToggleAnonymousMessage = false;\n  @bindable() public canUploadFiles = false;\n  @bindable() public currentCourseId: number | null = null;\n\n  protected DuelStatus = DuelStatus;\n  protected ChatMessageType = ChatMessageType;\n\n  constructor(\n    private readonly element: Element,\n    private readonly commonChatMessageService: CommonChatMessageService,\n  ) { }\n\n  @computedFrom('message')\n  private get fallbackAvatar(): SpecialAvatarTypes {\n    return this.message?.isSystem ? SpecialAvatarTypes.System : SpecialAvatarTypes.Default;\n  }\n\n  @computedFrom(\n    'message',\n    'currentUserId',\n    'lastMessageFromId',\n    'lastMessageType',\n    'isGroupChat',\n  )\n  private get showAvatar(): boolean {\n    return this.message?.userId !== this.currentUserId\n      && (this.message?.userId !== this.lastMessageFromId || this.lastMessageType === ChatMessageType.Incognito)\n      && this.message?.type !== ChatMessageType.Incognito\n      && this.isGroupChat;\n  }\n\n  @computedFrom('message.userId', 'message.type', 'lastMessageFromId', 'lastMessageType', 'currentUserId')\n  private get isFirst(): boolean {\n    return this.message?.userId !== this.lastMessageFromId\n      || (\n        (this.message.type === ChatMessageType.Incognito || this.lastMessageType === ChatMessageType.Incognito)\n        && this.message.userId !== this.currentUserId\n      );\n  }\n\n  @computedFrom('message.type', 'isFirst', 'message.userId', 'currentUserId')\n  private get showAuthorName(): boolean {\n    return this.message?.type !== ChatMessageType.Incognito\n      && !this.message?.isSystem\n      && this.isFirst\n      && this.message?.userId !== this.currentUserId;\n  }\n\n  private async declineInvitation(message: IChatMessageDto<string>): Promise<void> {\n    DomUtils.fireEvent<ChatMessageEvent['detail']>(this.element, {\n      bubbles: true,\n      detail: { message },\n      name: 'invitation-declined',\n    });\n  }\n\n  private async acceptInvitation(message: IChatMessageDto<string>): Promise<void> {\n    DomUtils.fireEvent<ChatMessageEvent['detail']>(this.element, {\n      bubbles: true,\n      detail: { message },\n      name: 'invitation-accepted',\n    });\n  }\n\n  private async showDuelResult(message: IChatMessageDto<string>): Promise<void> {\n    DomUtils.fireEvent<ChatMessageEvent['detail']>(this.element, {\n      bubbles: true,\n      detail: { message },\n      name: 'show-duel-result',\n    });\n  }\n\n  private formatDate(date: Date): string {\n    return Moment(date).format('lll');\n  }\n\n  private shouldShowResultButton(message?: IChatMessageDto<string>, currentUserId?: number): boolean {\n    return !!message && (\n      message.duelStatus === DuelStatus.Finished || (\n        (message.duelStatus === DuelStatus.InvitationExpired || message.duelStatus === DuelStatus.Declined)\n        && message.userId === currentUserId\n      )\n    );\n  }\n}\n\nexport interface IChatMessagePart {\n  courseId?: number;\n  raw: string;\n  title?: string;\n  callback?: () => Promise<void>;\n}\n\nexport type ChatMessageEvent = Event & { detail: { message: IChatMessageDto<string> } };\n","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { ChatApiService } from 'common/aurelia-services/ChatApiService';\nimport { IChatMessageDto } from 'common/dto/user/chat/chat.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\n\n@autoinject()\n@useView(PLATFORM.moduleName('./download-file-chat-message-content.html'))\nexport class DownloadFileChatMessageContent {\n  @bindable() public message: IChatMessageDto<string> | null = null;\n  @bindable() public currentUserId: number | null = null;\n  private apiService: ChatApiService;\n\n  constructor(apiService: ChatApiService) {\n    this.apiService = apiService;\n  }\n\n  private async handleDownloadFileClick(message: IChatMessageDto<string>): Promise<void> {\n    assertNotNullOrUndefined(message.fileId, 'can`t download file without fileId');\n    assertNotNullOrUndefined(message.text, 'can`t download file without text');\n    this.apiService.downloadFile(message.chatRoomId, message.fileId, message.text);\n  }\n}\n","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { IChatMessageDto } from 'common/dto/user/chat/chat.interface';\n\n@autoinject()\n@useView(PLATFORM.moduleName('./incognito-chat-message-content.html'))\nexport class IncognitoChatMessageContent {\n  @bindable() public message: IChatMessageDto<string> | null = null;\n  @bindable() public currentUserId: number | null = null;\n  @bindable() public canToggleAnonymousMessage = false;\n\n  private showUser = false;\n\n  private toggleShowUser(): void {\n    this.showUser = !this.showUser;\n  }\n}\n","import { PLATFORM, FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-message/chat-message'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-message/download-file-chat-message-content/download-file-chat-message-content'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-message/incognito-chat-message-content/incognito-chat-message-content'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-message/regular-chat-message-content/regular-chat-message-content'));\n}\n","import { autoinject, bindable, computedFrom, observable, PLATFORM, useView } from 'aurelia-framework';\nimport { CommonChatMessageService, IChatMessagePart, MessageAugmentationChangeUnsubscriber } from '../chat-message';\n\n@autoinject()\n@useView(PLATFORM.moduleName('./regular-chat-message-content.html'))\nexport class RegularChatMessageContent {\n  @bindable() public rawText: string | null = null;\n  @bindable() public currentCourseId: number | null = null;\n\n  @observable() private augmentationRevision = 0;\n  private unsubscribeAugmentationSubscription?: MessageAugmentationChangeUnsubscriber;\n\n  constructor(\n    private commonChatMessageService: CommonChatMessageService,\n  ) { }\n\n  protected attached(): void {\n    this.unsubscribeAugmentationSubscription = this.commonChatMessageService.subscribeToMessageAugmentationChange(() => {\n      this.augmentationRevision += 1;\n    });\n  }\n\n  protected detached(): void {\n    this.unsubscribeAugmentationSubscription?.();\n  }\n\n  private isContentFromDifferentCourses(part: IChatMessagePart): boolean {\n    if(!this.currentCourseId) return false;\n    return part.courseId !== this.currentCourseId;\n  }\n\n  @computedFrom('rawText', 'augmentationRevision')\n  private get augmentedMessage(): Array<IChatMessagePart> {\n    if(this.rawText == null) return [];\n    return this.commonChatMessageService.getAugmentedMessage(this.rawText);\n  }\n\n  @computedFrom('augmentedMessage', 'currentCourseId')\n  private get hasContentFromDifferentCourses(): boolean {\n    const hasLinks = this.augmentedMessage.some(part => !!part.courseId);\n    return hasLinks ? this.augmentedMessage.some(part => this.isContentFromDifferentCourses(part)) : false;\n  }\n\n  private handlePartCallback(part: IChatMessagePart): boolean {\n    if(part.callback) {\n      part.callback();\n      return false;\n    }\n    return true;\n  }\n}\n","import { autoinject, bindable, computedFrom, PLATFORM, useView } from 'aurelia-framework';\nimport { IChatInputAction, SendMessageEvent } from 'common/aurelia-components/chat-input/chat-input';\nimport { ChatApiService } from 'common/aurelia-services/ChatApiService';\nimport { ChatCacheService } from 'common/aurelia-services/ChatCacheService';\nimport { ChatHelperService } from 'common/aurelia-services/ChatHelperService';\nimport { DistinarChatService } from 'common/aurelia-services/DistinarChatService';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport {\n  ICachedConversation,\n  IChatMessageDto,\n  IChatRoomConversationUserDto,\n  IChatRoomIdWithUsersDto,\n  ISendMessageDto,\n} from 'common/dto/user/chat/chat.interface';\nimport { SendChatMessageType } from 'common/enum/chat.message.enum';\nimport { ICommonState } from 'common/store/store.interface';\nimport { IUploadQuery } from 'common/utils/ApiQueryStringUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport Moment from 'moment';\n\n@autoinject()\n@useView(PLATFORM.moduleName('./chat-view.html'))\nexport class ChatView {\n  @bindable() public chatRoom: IChatRoomIdWithUsersDto<string> | null = null;\n  @bindable() public currentUserId: number | null = null;\n  @bindable() public currentCourseId: number | null = null;\n  @bindable() public messagingAllowed = false;\n  @bindable() public inputActions: Array<IChatInputAction> = [];\n  @bindable() public inputValue = '';\n  @bindable() public showIncognito = false;\n  @bindable() public canToggleAnonymousMessage = false;\n  @bindable() public canUploadFiles = false;\n  @bindable() public isGroupChat = true;\n  @bindable() public containerModeOff = false;\n\n  private element: HTMLElement;\n  private subscriptionManager: SubscriptionManager;\n  \n  private conversation: ICachedConversation<string> | null = null;\n  \n  // scrolling\n  private readonly MIN_SCROLL_DISTANCE = 30;\n  private chatWindowDomElement?: HTMLElement;\n  private scrollToBottomSubscriptionManager: SubscriptionManager;\n  private scrollToBottomWhileContentGrowsMaxTimeout?: number;\n  private scrollToBottomTimeout?: number;\n  private scrollToBottomButtonVisible = false;\n  private loadPreviousButtonVisible = false;\n  private scrollingEnabled = false;\n  private debouncedResetScrollingEnabled = FunctionUtils.debounce(() => { this.scrollingEnabled = false; }, 100);\n\n  // file upload\n  private drag = false;\n  private readonly FILE_UPLOAD_QUERY: IUploadQuery<File>[] = [\n    { queryKey: 'title', value: 'name' },\n  ];\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n    private storeService: StoreService<ICommonState>,\n    private chatCacheService: ChatCacheService,\n    private distinarChatService: DistinarChatService,\n    private chatApiService: ChatApiService,\n    private chatHelperService: ChatHelperService,\n  ) {\n    this.element = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n    this.scrollToBottomSubscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.chatWindowDomElement, 'forgot to ref chatWindowDomElement');\n\n    this.refreshSubscription();\n    this.scrollToBottomWhileContentGrows();\n\n    const updateScrollRelatedButtonVisibilities = (): void => {\n      this.updateLoadPreviousButtonVisible();\n      this.updateScrollToBottomButtonVisible();\n    };\n    this.subscriptionManager.subscribeToDomEvent(document.body, 'scroll', updateScrollRelatedButtonVisibilities);\n    this.subscriptionManager.subscribeToElementResize(document.body, updateScrollRelatedButtonVisibilities);\n    this.subscriptionManager.subscribeToDomEvent(this.chatWindowDomElement, 'scroll', updateScrollRelatedButtonVisibilities);\n    this.subscriptionManager.subscribeToElementResize(this.chatWindowDomElement, updateScrollRelatedButtonVisibilities);\n    updateScrollRelatedButtonVisibilities();\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n    this.storeService.disposeAllSubscriptions(this);\n    this.debouncedResetScrollingEnabled.cancel();\n  }\n\n  /**\n   * scrolls to bottom of chat\n   * setTimeout to avoid weird behaviour,\n   * where scrolling stops a few pixels from bottom up\n   * maybe needs a polyfill or library or something\n   */\n  public scrollToBottom(): void {\n    clearTimeout(this.scrollToBottomTimeout);\n    this.scrollToBottomTimeout = window.setTimeout(() => {\n      if(this.chatWindowDomElement && !this.containerModeOff) {\n        this.chatWindowDomElement.scrollTo({\n          top: this.chatWindowDomElement.scrollHeight,\n          behavior: 'auto',\n        });\n      } else {\n        document.body.scrollTo({\n          top: document.body.scrollHeight,\n          behavior: 'auto',\n        });\n      }\n    }, 80);\n    this.scrollingEnabled = false;\n  }\n\n  /**\n   * Scrolls to the bottom while the chat content grows to account for delayed content rendering\n   */\n  private scrollToBottomWhileContentGrows(): void {\n    clearTimeout(this.scrollToBottomWhileContentGrowsMaxTimeout);\n    this.scrollToBottomSubscriptionManager.disposeSubscriptions();\n\n    if(this.chatWindowDomElement) {\n      let timeout: number | undefined;\n\n      this.scrollToBottomWhileContentGrowsMaxTimeout = window.setTimeout(() => {\n        clearTimeout(timeout);\n        this.scrollToBottomSubscriptionManager.disposeSubscriptions();\n      }, 1000);\n\n      this.scrollToBottomSubscriptionManager.subscribeToElementResize(this.chatWindowDomElement, () => {\n        this.scrollToBottom();\n\n        clearTimeout(timeout);\n        timeout = window.setTimeout(() => {\n          this.scrollToBottomSubscriptionManager.disposeSubscriptions();\n          clearTimeout(this.scrollToBottomWhileContentGrowsMaxTimeout);\n        }, 100);\n      });\n    }\n\n    this.scrollToBottom();\n  }\n\n  private containerModeOffChanged(): void {\n    this.updateLoadPreviousButtonVisible();\n    this.updateScrollToBottomButtonVisible();\n  }\n\n  private chatRoomChanged(): void {\n    this.refreshSubscription();\n  }\n\n  private refreshSubscription(): void {\n    this.ensureLatestConversationInCache();\n    this.storeService.disposeAllSubscriptions(this);\n    if(this.chatRoom) {\n      let lastMessageDate: string | undefined;\n      this.storeService.subscribeToDetailedChanges(this, [\n        state => this.chatRoom && state.cache.conversationsByChatRoomId[this.chatRoom.id],\n      ], state => {\n        assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n        const conversation = state.cache.conversationsByChatRoomId[this.chatRoom.id];\n        if(conversation) {\n          this.conversation = conversation;\n          this.updateLoadPreviousButtonVisible();\n          const newLastMessageDate = this.conversation.partialConversations[0]?.rows[0]?.createdAt;\n          if(newLastMessageDate !== lastMessageDate && Moment(newLastMessageDate).isAfter(lastMessageDate ?? 0)) {\n            this.scrollToBottomWhileContentGrows();\n          }\n          lastMessageDate = newLastMessageDate;\n        }\n      });\n    }\n  }\n\n  @computedFrom('messages.length', 'canUploadFiles', 'inputValue')\n  private get showChatEmptyPlaceholder(): boolean {\n    return this.messages.length === 0\n      && !this.canUploadFiles;\n  }\n\n  @computedFrom('conversation', 'chatRoom')\n  private get messages(): Array<IChatMessageDto<string> & { avatarUrl: string | null; isAuthorOnline: boolean; avatarPoints: number | null }> {\n    if(!this.conversation) return [];\n    return Object.values(this.conversation.partialConversations)\n      .reduce((acc, val) => [...acc, ...val?.rows ?? []], [] as Array<IChatMessageDto<string>>)\n      .map(message => {\n        const participant = this.getChatRoomParticipant(message.userId);\n        return {\n          ...message,\n          avatarUrl: participant?.avatarSvgDownloadLink ?? null,\n          isAuthorOnline: participant?.online ?? false,\n          avatarPoints: participant?.points ?? null,\n        };\n      })\n      .reverse();\n  }\n\n  @computedFrom('conversation', 'chatRoom')\n  private get otherUserName(): string {\n    const otherUser = this.chatRoom?.users.find(user => user.id !== this.currentUserId) ?? null;\n    return otherUser?.nickname ?? '-';\n  }\n\n  /**\n  * constructs and sends new message\n  */\n  private async sendMessage(text: string, type: SendChatMessageType, anonymous: boolean): Promise<void> {\n    assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n    const message: ISendMessageDto = {\n      text,\n      type,\n      anonymous,\n    };\n    const chatRoomId = this.chatRoom.id;\n\n    try {\n      await this.chatApiService.sendMessage(chatRoomId, message);\n    } catch(error) {\n      if(error.message === 'ERR_RECEIVE_MESSAGES_DISABLED') {\n        DomUtils.fireEvent<{ chatRoomId: number }>(this.element, {\n          bubbles: true,\n          detail: {\n            chatRoomId,\n          },\n          name: ReceiveMessagesDisabledEventName,\n        });\n      }\n    }\n    const state = await this.storeService.getState();\n    const isDistinarChat = state.cache.conversationsByChatRoomId[chatRoomId]?.event != null;\n    if(isDistinarChat) {\n      await this.distinarChatService.notifyMessageSent(message);\n    }\n    await this.chatCacheService.ensureLatestConversationInCache(chatRoomId);\n  }\n\n  /**\n   * when a new message is submitted by the user,\n   * it is displayed and the page scrolls down to accommodate the new message\n   */\n  private handleNewMessage(event: SendMessageEvent): void {\n    this.sendMessage(event.detail.message, event.detail.type, event.detail.anonymous);\n  }\n\n  private async ensureLatestConversationInCache(): Promise<void> {\n    if(!this.chatRoom) return;\n    await this.chatCacheService.ensureLatestConversationInCache(this.chatRoom.id);\n  }\n  private async loadPrevious(): Promise<void> {\n    assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n    this.chatCacheService.updatePreviousMessagesInCache(this.chatRoom.id);\n  }\n\n  private getChatRoomParticipant(userId: number): IChatRoomConversationUserDto | null {\n    if(this.chatRoom) {\n      return this.chatHelperService.getChatRoomConversationParticipant(this.chatRoom, userId);\n    }\n    return null;\n  }\n\n  private getFileUploadUrl(): string {\n    assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n    return `/api/chat/${this.chatRoom.id}/files`;\n  }\n\n  // callback for dropzone\n  private fileUploadSuccessCallback = async (file: File): Promise<void> => {\n    assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n    const message: ISendMessageDto = {\n      text: file.name,\n      type: SendChatMessageType.File,\n      anonymous: false,\n    };\n\n    const state = await this.storeService.getState();\n    const isDistinarChat = state.cache.conversationsByChatRoomId[this.chatRoom.id]?.event != null;\n    if(isDistinarChat) {\n      await this.distinarChatService.notifyMessageSent(message);\n    }\n    await this.chatCacheService.ensureLatestConversationInCache(this.chatRoom.id);\n  };\n\n  private async handleScrollRequest(): Promise<void> {\n    if(!this.canUploadFiles) return;\n    this.scrollingEnabled = true;\n    this.debouncedResetScrollingEnabled();\n  }\n\n  private updateLoadPreviousButtonVisible(): void {\n    this.loadPreviousButtonVisible = !!this.conversation?.hasPrevious\n      && (!this.containerModeOff || document.body.scrollTop < this.MIN_SCROLL_DISTANCE);\n  }\n\n  private updateScrollToBottomButtonVisible(): void {\n    const element = this.containerModeOff ? document.body : this.chatWindowDomElement;\n\n    this.scrollToBottomButtonVisible = !!element\n      && (element.scrollHeight - element.offsetHeight) > (element.scrollTop + this.MIN_SCROLL_DISTANCE);\n  }\n}\n\nexport const ReceiveMessagesDisabledEventName = 'receive-messages-disabled';\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/chat-view/chat-view'));\n}\n"],"names":[],"sourceRoot":""}