diff --git a/.eslintrc.json b/.eslintrc.json index bfbd79a082e..48758d5db6a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,9 +13,7 @@ "plugin:import/typescript" ], "globals": { - "$": true, "amplify": true, - "jQuery": true, "ko": true, "sinon": true, "wire": true, diff --git a/package.json b/package.json index c310de14f7f..732af50b08e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "highlight.js": "11.6.0", "http-status-codes": "2.2.0", "jquery": "3.6.1", - "jquery-mousewheel": "3.1.13", "js-cookie": "3.0.1", "jszip": "3.10.1", "keyboardjs": "2.7.0", @@ -39,7 +38,6 @@ "redux": "4.2.0", "redux-logdown": "1.0.4", "redux-thunk": "2.4.1", - "simplebar": "5.3.9", "speakingurl": "14.0.1", "switch-path": "1.2.0", "tsyringe": "4.7.0", @@ -68,6 +66,7 @@ "@types/generate-changelog": "1.8.1", "@types/highlight.js": "10.1.0", "@types/jest": "29.2.0", + "@types/jquery": "^3", "@types/js-cookie": "3.0.2", "@types/jsdom": "20.0.0", "@types/keyboardjs": "2.5.0", @@ -82,7 +81,6 @@ "@types/react-redux": "7.1.24", "@types/react-transition-group": "4.4.5", "@types/redux-mock-store": "1.0.3", - "@types/simplebar": "5.3.3", "@types/sinon": "10.0.13", "@types/speakingurl": "13.0.3", "@types/uint32": "0.2.0", diff --git a/src/script/components/Conversation/Conversation.tsx b/src/script/components/Conversation/Conversation.tsx index 0427fd73026..2653e2c8945 100644 --- a/src/script/components/Conversation/Conversation.tsx +++ b/src/script/components/Conversation/Conversation.tsx @@ -41,6 +41,7 @@ import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {getLogger} from 'Util/Logger'; import {safeMailOpen, safeWindowOpen} from 'Util/SanitizationUtil'; +import {incomingCssClass, removeAnimationsClass} from 'Util/util'; import {ConversationState} from '../../conversation/ConversationState'; import {Conversation as ConversationEntity} from '../../entity/Conversation'; @@ -361,7 +362,11 @@ const ConversationList: FC = ({ }; return ( -
+
{activeConversation && ( <> = ({ userId, - onClose = noop, + onClose = () => {}, userRepository, actionsViewModel, core = container.resolve(Core), diff --git a/src/script/main/app.ts b/src/script/main/app.ts index d836ee2735e..cb57fbba8a0 100644 --- a/src/script/main/app.ts +++ b/src/script/main/app.ts @@ -666,9 +666,7 @@ class App { } private _registerSingleInstanceCleaning() { - $(window).on('beforeunload', () => { - this.singleInstanceHandler.deregisterInstance(); - }); + window.addEventListener('beforeunload', () => this.singleInstanceHandler.deregisterInstance()); } /** @@ -736,7 +734,7 @@ class App { * Subscribe to 'beforeunload' to stop calls and disconnect the WebSocket. */ private _subscribeToUnloadEvents(): void { - $(window).on('unload', () => { + window.addEventListener('unload', () => { this.logger.info("'window.onunload' was triggered, so we will disconnect from the backend."); this.repository.event.disconnectWebSocket(); this.repository.calling.destroy(); @@ -854,7 +852,7 @@ class App { } this.logger.warn('No internet access. Continuing when internet connectivity regained.'); - $(window).on('online', () => _logoutOnBackend()); + window.addEventListener('online', () => _logoutOnBackend()); }; /** @@ -914,7 +912,7 @@ class App { // Setting up the App //############################################################################## -$(async () => { +(async () => { const config = Config.getConfig(); const apiClient = container.resolve(APIClient); await apiClient.useVersion(config.SUPPORTED_API_VERSIONS, config.ENABLE_DEV_BACKEND_API); @@ -938,6 +936,6 @@ $(async () => { app.initApp(shouldPersist ? ClientType.PERMANENT : ClientType.TEMPORARY); } } -}); +})(); export {App}; diff --git a/src/script/main/globals.ts b/src/script/main/globals.ts index 6d94742a0f6..d6f538505d1 100644 --- a/src/script/main/globals.ts +++ b/src/script/main/globals.ts @@ -29,10 +29,6 @@ import '../page/AppMain'; import 'Util/LocalizerUtil'; import '../localization/Localizer'; -import '../view_model/bindings/CommonBindings'; -import '../view_model/bindings/ConversationListBindings'; -import '../view_model/bindings/MessageListBindings'; -import '../view_model/bindings/VideoCallingBindings'; import '../view_model/MainViewModel'; window.amplify = amplify; diff --git a/src/script/page/MainContent/MainContent.tsx b/src/script/page/MainContent/MainContent.tsx index 79178a80161..4d675fa6d02 100644 --- a/src/script/page/MainContent/MainContent.tsx +++ b/src/script/page/MainContent/MainContent.tsx @@ -19,6 +19,7 @@ import {FC, ReactNode, useContext, useState} from 'react'; +import cx from 'classnames'; import {CSSTransition, SwitchTransition} from 'react-transition-group'; import {container} from 'tsyringe'; @@ -29,6 +30,7 @@ import {HistoryImport} from 'Components/HistoryImport'; import {Icon} from 'Components/Icon'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; +import {incomingCssClass, removeAnimationsClass} from 'Util/util'; import {Collection} from './panels/Collection'; import {AboutPreferences} from './panels/preferences/AboutPreferences'; @@ -118,13 +120,21 @@ const MainContent: FC = ({ )} {state === ContentState.PREFERENCES_ABOUT && ( -
+
)} {state === ContentState.PREFERENCES_ACCOUNT && ( -
+
= ({ )} {state === ContentState.PREFERENCES_AV && ( -
+
= ({ )} {state === ContentState.PREFERENCES_DEVICES && ( - - repositories.message.resetSession(userId, device.id, conversation) - } - userState={container.resolve(UserState)} - verifyDevice={(userId, device, verified) => repositories.client.verifyClient(userId, device, verified)} - /> +
+ + repositories.message.resetSession(userId, device.id, conversation) + } + userState={container.resolve(UserState)} + verifyDevice={(userId, device, verified) => + repositories.client.verifyClient(userId, device, verified) + } + /> +
)} {state === ContentState.PREFERENCES_OPTIONS && ( -
+
)} diff --git a/src/script/ui/WindowHandler.ts b/src/script/ui/WindowHandler.ts index b23e2cfe31f..eccf993f8f3 100644 --- a/src/script/ui/WindowHandler.ts +++ b/src/script/ui/WindowHandler.ts @@ -31,8 +31,7 @@ export class WindowHandler { } private _listenToUnhandledPromiseRejection(): void { - $(window).on('unhandledrejection', (event: any): void | false => { - const promiseRejectionEvent = event.originalEvent; + window.addEventListener('unhandledrejection', (promiseRejectionEvent: any): void | false => { const error = promiseRejectionEvent.reason || {}; const isDegraded = error.type === ConversationError.TYPE.DEGRADED_CONVERSATION_CANCELLATION; diff --git a/src/script/util/DebugUtil.ts b/src/script/util/DebugUtil.ts index b7ff8d5828a..5b3b5b80269 100644 --- a/src/script/util/DebugUtil.ts +++ b/src/script/util/DebugUtil.ts @@ -52,8 +52,7 @@ import {EventRepository} from '../event/EventRepository'; import {checkVersion} from '../lifecycle/newVersionHandler'; import {MessageCategory} from '../message/MessageCategory'; import {Core} from '../service/CoreSingleton'; -import {EventRecord, StorageRepository} from '../storage'; -import {StorageSchemata} from '../storage/StorageSchemata'; +import {EventRecord, StorageRepository, StorageSchemata} from '../storage'; import {UserRepository} from '../user/UserRepository'; import {UserState} from '../user/UserState'; import {ViewModelRepositories} from '../view_model/MainViewModel'; @@ -74,11 +73,10 @@ export class DebugUtil { private readonly eventRepository: EventRepository; private readonly storageRepository: StorageRepository; private readonly messageRepository: MessageRepository; + public readonly $: JQueryStatic; /** Used by QA test automation. */ public readonly userRepository: UserRepository; /** Used by QA test automation. */ - public readonly $: JQueryStatic; - /** Used by QA test automation. */ public readonly Dexie: typeof Dexie; constructor( diff --git a/src/script/util/util.ts b/src/script/util/util.ts index a98b840afcf..b8ec4ffb8e9 100644 --- a/src/script/util/util.ts +++ b/src/script/util/util.ts @@ -404,3 +404,15 @@ export const getSelectionPosition = (element: HTMLTextAreaElement, currentMentio // temporary hack that disables mls for old 'broken' desktop clients, see https://github.com/wireapp/wire-desktop/pull/6094 export const supportsMLS = () => Config.getConfig().FEATURE.ENABLE_MLS && (!Runtime.isDesktopApp() || window.systemCrypto); + +export const incomingCssClass = 'content-animation-incoming-horizontal-left'; + +export const removeAnimationsClass = (element: HTMLElement | null) => { + if (element) { + element.addEventListener('animationend', () => { + if (element.classList.contains(incomingCssClass)) { + element.classList.remove(incomingCssClass); + } + }); + } +}; diff --git a/src/script/view_model/ContentViewModel.ts b/src/script/view_model/ContentViewModel.ts index bb38f3cdb5d..dda2cf54a53 100644 --- a/src/script/view_model/ContentViewModel.ts +++ b/src/script/view_model/ContentViewModel.ts @@ -28,7 +28,6 @@ import {t} from 'Util/LocalizerUtil'; import {getLogger, Logger} from 'Util/Logger'; import {matchQualifiedIds} from 'Util/QualifiedId'; import {isConversationEntity} from 'Util/TypePredicateUtil'; -import {alias} from 'Util/util'; import type {MainViewModel, ViewModelRepositories} from './MainViewModel'; @@ -153,22 +152,15 @@ export class ContentViewModel { amplify.subscribe(WebAppEvents.CONVERSATION.SHOW, this.showConversation); } - private _shiftContent(contentSelector: string, hideSidebar: boolean = false): void { - const incomingCssClass = 'content-animation-incoming-horizontal-left'; + private _shiftContent(hideSidebar: boolean = false): void { + const sidebar = document.querySelector(`#${this.sidebarId}`) as HTMLElement | null; - $(contentSelector) - .removeClass(incomingCssClass) - .off(alias.animationend) - .addClass(incomingCssClass) - .one(alias.animationend, function () { - $(this).removeClass(incomingCssClass).off(alias.animationend); - }); - - const sidebar = $(`#${this.sidebarId}`); if (hideSidebar) { - sidebar.css('visibility', 'hidden'); - } else { - sidebar.removeAttr('style'); + if (sidebar) { + sidebar.style.visibility = 'hidden'; + } + } else if (sidebar) { + sidebar.style.visibility = ''; } } @@ -316,31 +308,6 @@ export class ContentViewModel { return state; }; - private readonly getElementOfContent = (state: string) => { - switch (state) { - case ContentState.COLLECTION: - return '.collection'; - case ContentState.CONVERSATION: - return '.conversation'; - case ContentState.CONNECTION_REQUESTS: - return '.connect-requests'; - case ContentState.PREFERENCES_ABOUT: - return '.preferences-about'; - case ContentState.PREFERENCES_ACCOUNT: - return '.preferences-account'; - case ContentState.PREFERENCES_AV: - return '.preferences-av'; - case ContentState.PREFERENCES_DEVICE_DETAILS: - return '.preferences-device-details'; - case ContentState.PREFERENCES_DEVICES: - return '.preferences-devices'; - case ContentState.PREFERENCES_OPTIONS: - return '.preferences-options'; - default: - return '.watermark'; - } - }; - private readonly releaseContent = (newContentState: ContentState) => { this.previousState = this.state(); @@ -360,7 +327,6 @@ export class ContentViewModel { this.state(newContentState); return this._shiftContent( - this.getElementOfContent(newContentState), newContentState === ContentState.HISTORY_EXPORT || newContentState === ContentState.HISTORY_IMPORT, ); }; diff --git a/src/script/view_model/bindings/CommonBindings.ts b/src/script/view_model/bindings/CommonBindings.ts index d7eeb03850a..c8d8bab2476 100644 --- a/src/script/view_model/bindings/CommonBindings.ts +++ b/src/script/view_model/bindings/CommonBindings.ts @@ -17,147 +17,7 @@ * */ -import {Runtime} from '@wireapp/commons'; -import $ from 'jquery'; import ko from 'knockout'; -import SimpleBar from 'simplebar'; -import {debounce, throttle} from 'underscore'; - -import {isEnterKey} from 'Util/KeyboardUtil'; -import {TIME_IN_MILLIS} from 'Util/TimeUtil'; -import {stripUrlWrapper} from 'Util/util'; - -import {overlayedObserver} from '../../ui/overlayedObserver'; -import {viewportObserver} from '../../ui/viewportObserver'; - -type KOEvent = JQuery.Event & {currentTarget: Element; originalEvent: T}; - -/** - * Indicate that the current binding loop should not try to bind this element's children. - * @see http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html - */ -ko.bindingHandlers.stopBinding = { - init() { - return {controlsDescendantBindings: true}; - }, -}; - -ko.virtualElements.allowedBindings.stopBinding = true; - -/** - * Resize textarea according to the containing text. - */ -ko.bindingHandlers.resize = { - init(element, valueAccessor, _allBindings, _data, context) { - const params = ko.unwrap(valueAccessor()) || {}; - - let lastHeight = element.scrollHeight; - - const resizeTextarea = ((textareaElement: HTMLTextAreaElement) => { - textareaElement.style.height = '0'; - const newStyleHeight = `${textareaElement.scrollHeight}px`; - textareaElement.style.height = newStyleHeight; - - const currentHeight = textareaElement.clientHeight; - - if (lastHeight !== currentHeight) { - lastHeight = currentHeight; - const maxHeight = window.parseInt(getComputedStyle(textareaElement).maxHeight, 10); - - const isMaximumHeight = currentHeight >= maxHeight; - const newStyleOverflowY = isMaximumHeight ? 'scroll' : 'hidden'; - textareaElement.style.overflowY = newStyleOverflowY; - } - }).bind(null, element); - const throttledResizeTextarea = throttle(resizeTextarea, 100, {leading: !params.delayedResize}); - - resizeTextarea(); - return ko.applyBindingsToNode( - element, - { - event: { - focus: throttledResizeTextarea, - input: throttledResizeTextarea, - }, - }, - context, - ); - }, -}; - -/** - * Register on enter key pressed. - */ -ko.bindingHandlers.enter = { - init(element, valueAccessor, _allBindings, data, context) { - function wrapper(_data: unknown, event: KOEvent) { - const keyboardEvent = event.originalEvent || (event as unknown as KeyboardEvent); - - if (isEnterKey(keyboardEvent) && !keyboardEvent.shiftKey && !keyboardEvent.altKey) { - const callback = valueAccessor(); - if (typeof callback === 'function') { - callback.call(this, data, keyboardEvent); - return false; - } - } - return true; - } - - return ko.applyBindingsToNode( - element, - { - event: { - keypress: wrapper, - }, - }, - context, - ); - }, -}; - -/** - * Binding for . - */ -ko.bindingHandlers.file_select = { - init(element, valueAccessor, _allBindings, _data, context) { - function wrapper(_: unknown, event: KOEvent) { - if ((event.target as HTMLInputElement).files.length > 0) { - valueAccessor().call(this, (event.target as HTMLInputElement).files); - - // http://stackoverflow.com/a/12102992/4453133 - // wait before clearing to fix autotests - window.setTimeout(() => { - $(event.target).val(null); - }, TIME_IN_MILLIS.SECOND); - } - } - - return ko.applyBindingsToNode( - element, - { - event: { - change: wrapper, - focus(_data: unknown, event: KOEvent) { - return $(event.target).blur(); - }, - }, - }, - context, - ); - }, -}; - -/** - * Wait for image to be loaded before applying as background image. - */ -ko.bindingHandlers.loadImage = { - init(element, valueAccessor) { - const image_src = stripUrlWrapper(ko.unwrap(valueAccessor())); - const image = new Image(); - image.onload = () => (element.style.backgroundImage = `url(${image_src})`); - image.src = image_src; - }, -}; /** * Will only fire once when the value has changed. @@ -176,279 +36,3 @@ ko.bindingHandlers.loadImage = { eventName, ); }; - -/** - * Subscribe to changes and receive the new and the old value - * https://github.com/knockout/knockout/issues/914#issuecomment-66697321 - */ -(ko.subscribable.fn as any).subscribeChanged = function (handler: (latestValue: unknown, oldValue: unknown) => void) { - let savedValue = this.peek(); - return this.subscribe((latestValue: unknown) => { - const oldValue = savedValue; - savedValue = latestValue; - handler(latestValue, oldValue); - }); -}; - -ko.bindingHandlers.fadingscrollbar = { - init(element) { - const animationSpeed = 12; - function parseColor(color: string) { - const ctx = document.createElement('canvas').getContext('2d'); - ctx.fillStyle = color; - ctx.fillRect(0, 0, 1, 1); - return ctx.getImageData(0, 0, 1, 1).data; - } - - const initialColor = parseColor(window.getComputedStyle(element).getPropertyValue('--scrollbar-color')); - const currentColor = initialColor.slice(); - let state = 'idle'; - let animating = false; - - function setAnimationState(newState: string) { - state = newState; - if (!animating) { - animate(); - } - } - - function animate() { - switch (state) { - case 'fadein': - fadeStep(animationSpeed); - break; - case 'fadeout': - fadeStep(-animationSpeed); - break; - - default: - animating = false; - return; - } - animating = true; - window.requestAnimationFrame(animate); - } - - const fadeStep = (delta: number) => { - const initialAlpha = initialColor[3]; - const currentAlpha = currentColor[3]; - const hasAppeared = delta > 0 && currentAlpha >= initialAlpha; - const hasDisappeared = delta < 0 && currentAlpha <= 0; - if (hasAppeared || hasDisappeared) { - return setAnimationState('idle'); - } - currentColor[3] += delta; - const [r, g, b, a] = currentColor; - element.style.setProperty('--scrollbar-color', ` rgba(${r}, ${g}, ${b}, ${a / 255})`); - }; - const fadeIn = () => setAnimationState('fadein'); - const fadeOut = () => setAnimationState('fadeout'); - const debouncedFadeOut = debounce(fadeOut, 1000); - - element.addEventListener('mouseenter', fadeIn); - element.addEventListener('mouseleave', fadeOut); - element.addEventListener('mousemove', fadeIn); - element.addEventListener('mousemove', debouncedFadeOut); - element.addEventListener('scroll', fadeIn); - element.addEventListener('scroll', debouncedFadeOut); - - ko.utils.domNodeDisposal.addDisposeCallback(element, () => { - element.removeEventListener('mouseenter', fadeIn); - element.removeEventListener('mouseleave', fadeOut); - element.removeEventListener('mousemove', fadeIn); - element.removeEventListener('mousemove', debouncedFadeOut); - element.removeEventListener('scroll', fadeIn); - element.removeEventListener('scroll', debouncedFadeOut); - }); - }, -}; - -ko.bindingHandlers.simplebar = { - init(element, valueAccessor) { - const {trigger = valueAccessor(), onInit} = valueAccessor(); - const simpleBar = new SimpleBar(element, {autoHide: false}); - if (ko.isObservable(trigger)) { - const triggerSubscription = trigger.subscribe(() => simpleBar.recalculate()); - ko.utils.domNodeDisposal.addDisposeCallback(element, () => triggerSubscription.dispose()); - } - if (onInit) { - onInit(simpleBar); - } - }, -}; - -ko.bindingHandlers.electron_remove = { - init(element) { - if (Runtime.isDesktopApp()) { - $(element).remove(); - } - }, -}; - -ko.bindingHandlers.visibility = (function () { - const setVisibility = (element: Element, valueAccessor: () => boolean) => { - const hidden = ko.unwrap(valueAccessor()); - $(element).css('visibility', hidden ? 'visible' : 'hidden'); - }; - return { - init: setVisibility, - update: setVisibility, - }; -})(); - -/** - * Add 'hide-controls' when the mouse leave the element or stops moving. - */ -ko.bindingHandlers.hide_controls = { - init(element, valueAccessor) { - const {timeout = valueAccessor(), skipClass} = valueAccessor(); - let hide_timeout: number = undefined; - const startTimer = () => { - hide_timeout = window.setTimeout(() => { - element.classList.add('hide-controls'); - }, timeout); - }; - - element.onmouseenter = function () { - element.classList.remove('hide-controls'); - }; - - element.onmouseleave = function () { - if (document.hasFocus()) { - return element.classList.add('hide-controls'); - } - }; - - element.onmousemove = function ({target}: MouseEvent) { - window.clearTimeout(hide_timeout); - - element.classList.remove('hide-controls'); - - let node = target as Element; - while (node && node !== element) { - if (node.classList.contains(skipClass)) { - return; - } - node = node.parentNode as Element; - } - startTimer(); - }; - - startTimer(); - }, -}; - -/** - * Element is added to view. - */ -ko.bindingHandlers.added_to_view = { - init(_element, valueAccessor) { - const callback = valueAccessor(); - callback(); - }, -}; - -/** - * Element is removed from view - */ -ko.bindingHandlers.removed_from_view = { - init(element, valueAccessor) { - const callback = valueAccessor(); - ko.utils.domNodeDisposal.addDisposeCallback(element, () => callback()); - }, -}; - -/** - * Adds a callback called whenever an element is in viewport and not overlayed by another element. - */ -ko.bindingHandlers.in_viewport = { - init(element, valueAccessor) { - const {onVisible = valueAccessor(), container} = valueAccessor(); - if (!onVisible) { - return; - } - - const releaseTrackers = () => { - overlayedObserver.removeElement(element); - viewportObserver.removeElement(element); - }; - - let inViewport = false; - let visible = false; - const triggerCallbackIfVisible = () => { - if (inViewport && visible) { - onVisible(); - releaseTrackers(); - } - }; - - viewportObserver.trackElement( - element, - (isInViewport: boolean) => { - inViewport = isInViewport; - triggerCallbackIfVisible(); - }, - container, - true, - ); - overlayedObserver.trackElement(element, isVisible => { - visible = isVisible; - triggerCallbackIfVisible(); - }); - - ko.utils.domNodeDisposal.addDisposeCallback(element, releaseTrackers); - }, -}; - -ko.bindingHandlers.tooltip = { - update(element, valueAccessor) { - const {text = valueAccessor(), position, disabled} = valueAccessor(); - if (!disabled) { - element.classList.add('with-tooltip', `with-tooltip--${position === 'bottom' ? 'bottom' : 'top'}`); - element.setAttribute('data-tooltip', text); - } - }, -}; - -/** - * Suppresses the click event if we are in the macOs wrapper and are dragging the window - */ -ko.bindingHandlers.clickOrDrag = { - init(element, valueAccessor, allBindings, viewModel, bindingContext) { - const isMacDesktop = Runtime.isDesktopApp() && Runtime.isMacOS(); - const context = bindingContext.$data; - const callback = valueAccessor().bind(context, context); - if (!isMacDesktop) { - return element.addEventListener('click', callback); - } - - let isMoved = false; - let isDragging = false; - let startX = 0; - let startY = 0; - - element.addEventListener('mousedown', ({screenX, screenY}: MouseEvent) => { - isDragging = true; - isMoved = false; - startX = screenX; - startY = screenY; - }); - - element.addEventListener('mousemove', ({screenX, screenY}: MouseEvent) => { - if (isDragging && !isMoved) { - const diffX = Math.abs(startX - screenX); - const diffY = Math.abs(startY - screenY); - if (diffX > 1 || diffY > 1) { - isMoved = true; - } - } - }); - - element.addEventListener('mouseup', (event: MouseEvent) => { - if (!isMoved) { - callback(event); - } - isDragging = false; - }); - }, -}; diff --git a/src/script/view_model/bindings/ConversationListBindings.ts b/src/script/view_model/bindings/ConversationListBindings.ts deleted file mode 100644 index 6168c4c3391..00000000000 --- a/src/script/view_model/bindings/ConversationListBindings.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Wire - * Copyright (C) 2018 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import {WebAppEvents} from '@wireapp/webapp-events'; -import {amplify} from 'amplify'; -import ko from 'knockout'; -import {throttle} from 'underscore'; - -import {isScrollable, isScrolledBottom, isScrolledTop} from 'Util/scroll-helpers'; - -import type {Conversation} from '../../entity/Conversation'; - -// show scroll borders -ko.bindingHandlers.bordered_list = (function () { - const calculateBorders = throttle((element: HTMLElement) => { - if (element) { - window.requestAnimationFrame(() => { - const list_column = $(element).parent(); - if (element.offsetHeight <= 0 || !isScrollable(element)) { - list_column.removeClass('left-list-center-border-bottom conversations-center-border-top'); - return; - } - - list_column.toggleClass('left-list-center-border-top', !isScrolledTop(element)); - list_column.toggleClass('left-list-center-border-bottom', !isScrolledBottom(element)); - }); - } - }, 100); - - return { - init(element: HTMLElement) { - element.addEventListener('scroll', () => calculateBorders(element)); - $('.left').on('click', () => calculateBorders(element)); - $(window).on('resize', () => calculateBorders(element)); - amplify.subscribe(WebAppEvents.LIFECYCLE.LOADED, () => calculateBorders(element)); - }, - - update(element: HTMLElement, valueAccessor: ko.PureComputed | ko.ObservableArray) { - ko.unwrap(valueAccessor()); - calculateBorders($(element) as any); - }, - }; -})(); diff --git a/src/script/view_model/bindings/MessageListBindings.ts b/src/script/view_model/bindings/MessageListBindings.ts deleted file mode 100644 index e54a146088f..00000000000 --- a/src/script/view_model/bindings/MessageListBindings.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Wire - * Copyright (C) 2018 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import 'jquery-mousewheel'; -import ko from 'knockout'; - -ko.bindingHandlers.infinite_scroll = { - init(scrollingElement: HTMLElement, params: () => {onHitBottom: () => void; onHitTop: () => void}) { - const {onHitTop, onHitBottom} = params(); - - const onScroll = ({target: element}: Event & {target: HTMLElement}) => { - // On some HiDPI screens scrollTop returns a floating point number instead of an integer - // https://github.com/jquery/api.jquery.com/issues/608 - const scrollPosition = Math.ceil(element.scrollTop); - const scrollEnd = element.offsetHeight + scrollPosition; - const hitTop = scrollPosition <= 0; - const hitBottom = scrollEnd >= element.scrollHeight; - - if (hitTop) { - onHitTop(); - } else if (hitBottom) { - onHitBottom(); - } - }; - - const onMouseWheel = ({currentTarget, deltaY}: WheelEvent) => { - const element = currentTarget as HTMLElement; - const isScrollable = element.scrollHeight > element.clientHeight; - if (isScrollable) { - // if the element is scrollable, the scroll event will take the relay - return true; - } - const isScrollingUp = deltaY > 0; - if (isScrollingUp) { - return onHitTop(); - } - return onHitBottom(); - }; - - scrollingElement.addEventListener('scroll', onScroll); - scrollingElement.addEventListener('wheel', onMouseWheel); - - ko.utils.domNodeDisposal.addDisposeCallback(scrollingElement, () => { - scrollingElement.removeEventListener('scroll', onScroll); - scrollingElement.removeEventListener('wheel', onMouseWheel); - }); - }, -}; diff --git a/src/script/view_model/bindings/VideoCallingBindings.ts b/src/script/view_model/bindings/VideoCallingBindings.ts deleted file mode 100644 index 8fb06423cb2..00000000000 --- a/src/script/view_model/bindings/VideoCallingBindings.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Wire - * Copyright (C) 2018 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import ko from 'knockout'; - -// http://stackoverflow.com/questions/28762211/unable-to-mute-html5-video-tag-in-firefox -ko.bindingHandlers.muteMediaElement = { - update(element: HTMLMediaElement, valueAccessor: ko.Observable) { - if (valueAccessor()) { - element.muted = true; - } - }, -}; - -ko.bindingHandlers.sourceStream = { - update(element: HTMLMediaElement, valueAccessor: ko.Observable) { - const stream = valueAccessor(); - if (stream) { - element.srcObject = stream; - } - }, -}; diff --git a/src/style/foundation/header.less b/src/style/foundation/header.less index de6c2227381..8bb338d483a 100644 --- a/src/style/foundation/header.less +++ b/src/style/foundation/header.less @@ -17,6 +17,5 @@ * */ -@import (less) '../../node_modules/simplebar/dist/simplebar.css'; @import (less) '../fonts/redacted.css'; @import (less) '../fonts/zeta-neue.css'; diff --git a/src/types/window.d.ts b/src/types/window.d.ts index 8ca4ad5981d..51bdd75f2fc 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -21,9 +21,9 @@ import {amplify} from 'amplify'; import jQuery from 'jquery'; import ko from 'knockout'; -import {WireModule} from './Wire.types'; +import {t} from 'Util/LocalizerUtil'; -import {t} from '../script/util/LocalizerUtil'; +import {WireModule} from './Wire.types'; declare global { interface Window { diff --git a/test/unit_tests/view_model/bindings/MessageListBindingsSpec.js b/test/unit_tests/view_model/bindings/MessageListBindingsSpec.js deleted file mode 100644 index b3b2b9efafe..00000000000 --- a/test/unit_tests/view_model/bindings/MessageListBindingsSpec.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Wire - * Copyright (C) 2022 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import 'src/script/view_model/bindings/MessageListBindings'; - -import {bindHtml} from '../../../helper/knockoutHelpers'; - -describe('messageListBindings', () => { - describe('ko.bindingHandlers.infinite_scroll', () => { - const contentHeight = 1000; - const scrollingTests = [ - {expectedCalls: ['onHitBottom'], initialScroll: 0, scrollTop: contentHeight}, - {expectedCalls: ['onHitTop'], initialScroll: contentHeight, scrollTop: 0}, - // TODO: [Jest] Jest doesn't do the rendering calculations that are necessary in this test - // {expectedCalls: [], initialScroll: 0, scrollTop: contentHeight / 2}, - ]; - - scrollingTests.forEach(({expectedCalls, initialScroll, scrollTop}) => { - it('calls params functions when scroll hits top and bottom', () => { - const context = { - onHitBottom: jest.fn(), - onHitTop: jest.fn(), - }; - - const boundElement = `
-
-
`; - - return bindHtml(boundElement, context).then(domContainer => { - const scrollingElement = domContainer.querySelector('.scroll'); - scrollingElement.scrollTop = initialScroll; - - const isScrollingUp = initialScroll > scrollTop; - const isScrollingDown = initialScroll < scrollTop; - const getDeltaY = () => { - if (isScrollingUp) { - return 1; - } else if (isScrollingDown) { - return -1; - } - return 0; - }; - - scrollingElement.dispatchEvent(new WheelEvent('wheel', {deltaY: getDeltaY()})); - - scrollingElement.scrollTop = scrollTop; - return new Promise(resolve => { - setTimeout(() => { - ['onHitBottom', 'onHitTop'].forEach(callback => { - if (expectedCalls.includes(callback)) { - // eslint-disable-next-line jest/no-conditional-expect - expect(context[callback]).toHaveBeenCalled(); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect(context[callback]).not.toHaveBeenCalled(); - } - }); - resolve(); - // eslint-disable-next-line no-magic-numbers - }, 50); - }); - }); - }); - }); - }); -}); diff --git a/test/unit_tests/view_model/bindingsSpec.js b/test/unit_tests/view_model/bindingsSpec.js index 6dc03bf6200..baf064fea5b 100644 --- a/test/unit_tests/view_model/bindingsSpec.js +++ b/test/unit_tests/view_model/bindingsSpec.js @@ -18,48 +18,11 @@ */ // eslint-disable-next-line id-length -import $ from 'jquery'; import ko from 'knockout'; import '../../../src/script/view_model/bindings/CommonBindings'; describe('ko.bindingHandlers', () => { - describe('ko.bindingHandlers.enter', () => { - const binding = ko.bindingHandlers.enter; - let element = null; - let handler = null; - - beforeEach(() => { - element = document.createElement('div'); - - handler = {on_enter: () => 'yay'}; - - // we need the callFake since the spyOn will overwrite the on_enter property - spyOn(handler, 'on_enter').and.callFake(() => () => 'yay'); - - binding.init(element, handler.on_enter); - }); - - it('can execute callback when enter is pressed', () => { - const keyboardEvent = new KeyboardEvent('keypress', {key: 'Enter'}); - element.dispatchEvent(keyboardEvent); - - expect(handler.on_enter).toHaveBeenCalled(); - }); - - it('can not execute callback when another key is pressed', () => { - $(element).trigger($.Event('keypress', {key: 'Esc'})); - - expect(handler.on_enter).not.toHaveBeenCalled(); - }); - - it('can not execute callback when another event is triggered', () => { - $(element).trigger($.Event('keyup', {key: 'Enter'})); - - expect(handler.on_enter).not.toHaveBeenCalled(); - }); - }); - describe('ko.subscribable.fn.subscribe_once', () => { let observable = null; let handler = null; diff --git a/yarn.lock b/yarn.lock index ea6a5b14394..6dbd071735c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2629,13 +2629,6 @@ __metadata: languageName: node linkType: hard -"@juggle/resize-observer@npm:^3.3.1": - version: 3.3.1 - resolution: "@juggle/resize-observer@npm:3.3.1" - checksum: ddabc4044276a2cb57d469c4917206c7e39f2463aa8e3430e33e4eda554412afe29c22afa40e6708b49dad5d56768dc83acd68a704b1dcd49a0906bb96b991b2 - languageName: node - linkType: hard - "@koush/wrtc@npm:0.5.3": version: 0.5.3 resolution: "@koush/wrtc@npm:0.5.3" @@ -3256,6 +3249,15 @@ __metadata: languageName: node linkType: hard +"@types/jquery@npm:^3": + version: 3.5.14 + resolution: "@types/jquery@npm:3.5.14" + dependencies: + "@types/sizzle": "*" + checksum: 159d6f804ed1a204b3f79f2d591a271d82e866bd45bd49fb6ef40561a25dbe0f47ec7815681b44cc2db5598425f72811e7e80ab0e983d980470998ac56feb375 + languageName: node + linkType: hard + "@types/js-cookie@npm:3.0.2": version: 3.0.2 resolution: "@types/js-cookie@npm:3.0.2" @@ -3501,15 +3503,6 @@ __metadata: languageName: node linkType: hard -"@types/simplebar@npm:5.3.3": - version: 5.3.3 - resolution: "@types/simplebar@npm:5.3.3" - dependencies: - simplebar: "*" - checksum: db0dfe8e8dbbe9a98ffa840aeb9e14ee8a8d5d5cc3042f5348be470c5864d40278459636124ef631bd700e749aa9cdafc6844e43f805b28edf090ce24eb982f4 - languageName: node - linkType: hard - "@types/sinon@npm:10.0.13": version: 10.0.13 resolution: "@types/sinon@npm:10.0.13" @@ -5418,13 +5411,6 @@ __metadata: languageName: node linkType: hard -"can-use-dom@npm:^0.1.0": - version: 0.1.0 - resolution: "can-use-dom@npm:0.1.0" - checksum: 488fc94c40f2fcce46ebd41abf17ef0449acf0d6b145116036cd592a8e977e5729918d4b3b7c642ce7b1f5b83d330ade39a172cf6b6ef91093785991a868b308 - languageName: node - linkType: hard - "caniuse-api@npm:^3.0.0": version: 3.0.0 resolution: "caniuse-api@npm:3.0.0" @@ -6014,7 +6000,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:3.25.5, core-js@npm:^3.0.1": +"core-js@npm:3.25.5": version: 3.25.5 resolution: "core-js@npm:3.25.5" checksum: 208b308c49bc022f90d4349d4c99802a73c9d55053976b3c529f10014c1e37845926defad8c519f2c7f71ea0acf18d2b323ab6aaee34dc85b4c4b3ced0623f3f @@ -10469,13 +10455,6 @@ __metadata: languageName: node linkType: hard -"jquery-mousewheel@npm:3.1.13": - version: 3.1.13 - resolution: "jquery-mousewheel@npm:3.1.13" - checksum: 3be0b9320a8846d64bfc705c6b23eb6489dc0d5ba73800a94c7aafbc82f54d103a0909f0d619756adc124b48b6158ff8e601619dd4aaef213e0c68818cb99b21 - languageName: node - linkType: hard - "jquery@npm:3.6.1": version: 3.6.1 resolution: "jquery@npm:3.6.1" @@ -11210,13 +11189,6 @@ __metadata: languageName: node linkType: hard -"lodash.throttle@npm:^4.1.1": - version: 4.1.1 - resolution: "lodash.throttle@npm:4.1.1" - checksum: 129c0a28cee48b348aef146f638ef8a8b197944d4e9ec26c1890c19d9bf5a5690fe11b655c77a4551268819b32d27f4206343e30c78961f60b561b8608c8c805 - languageName: node - linkType: hard - "lodash.truncate@npm:^4.4.2": version: 4.4.2 resolution: "lodash.truncate@npm:4.4.2" @@ -14884,20 +14856,6 @@ __metadata: languageName: node linkType: hard -"simplebar@npm:*, simplebar@npm:5.3.9": - version: 5.3.9 - resolution: "simplebar@npm:5.3.9" - dependencies: - "@juggle/resize-observer": ^3.3.1 - can-use-dom: ^0.1.0 - core-js: ^3.0.1 - lodash.debounce: ^4.0.8 - lodash.memoize: ^4.1.2 - lodash.throttle: ^4.1.1 - checksum: 11918d02c946b4925068852b4fb2dcc5008e2fb91b7eeba6c27aed810f23b985de043b12fef674fbffbbfa6201904e09d8fbc29e9959562fa6a5cfb9935751d1 - languageName: node - linkType: hard - "sinon@npm:14.0.1": version: 14.0.1 resolution: "sinon@npm:14.0.1" @@ -16982,6 +16940,7 @@ __metadata: "@types/generate-changelog": 1.8.1 "@types/highlight.js": 10.1.0 "@types/jest": 29.2.0 + "@types/jquery": ^3 "@types/js-cookie": 3.0.2 "@types/jsdom": 20.0.0 "@types/keyboardjs": 2.5.0 @@ -16996,7 +16955,6 @@ __metadata: "@types/react-redux": 7.1.24 "@types/react-transition-group": 4.4.5 "@types/redux-mock-store": 1.0.3 - "@types/simplebar": 5.3.3 "@types/sinon": 10.0.13 "@types/speakingurl": 13.0.3 "@types/uint32": 0.2.0 @@ -17071,7 +17029,6 @@ __metadata: jest-environment-jsdom: 29.2.1 jest-jasmine2: 29.2.1 jquery: 3.6.1 - jquery-mousewheel: 3.1.13 js-cookie: 3.0.1 jsdom-worker: 0.3.0 jszip: 3.10.1 @@ -17108,7 +17065,6 @@ __metadata: redux-mock-store: 1.5.4 redux-thunk: 2.4.1 simple-git: 3.14.1 - simplebar: 5.3.9 sinon: 14.0.1 snabbdom: 3.5.1 speakingurl: 14.0.1