From 3e46552ec0ba20d89f7c6d3c93b15266276c4bbe Mon Sep 17 00:00:00 2001 From: John Livingston Date: Wed, 7 Apr 2021 18:14:58 +0200 Subject: [PATCH] Typescript for frontend code. --- client/.eslintrc.json | 39 +++-- ...ient-plugin.js => common-client-plugin.ts} | 2 +- client/peertube.d.ts | 11 ++ client/tsconfig.json | 20 +++ ...-plugin.js => videowatch-client-plugin.ts} | 134 ++++++++------- package-lock.json | 157 ++++++++++++++++++ package.json | 1 + webpack.config.js | 16 +- 8 files changed, 302 insertions(+), 78 deletions(-) rename client/{common-client-plugin.js => common-client-plugin.ts} (81%) create mode 100644 client/peertube.d.ts create mode 100644 client/tsconfig.json rename client/{videowatch-client-plugin.js => videowatch-client-plugin.ts} (67%) diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 4ad2d1a6..7132d963 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -5,19 +5,36 @@ "es6": true }, "extends": [ - "standard" + "standard-with-typescript" ], "globals": {}, - "plugins": [], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "project": [ + "./client/tsconfig.json" + ] + }, + "plugins": [ + "@typescript-eslint" + ], "ignorePatterns": [], "rules": { - "max-len": [ - "error", - { - "code": 120, - "comments": 120 - } - ], - "no-unused-vars": [2, {"argsIgnorePattern": "^_"}] - } + "@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/return-await": [2, "in-try-catch"], // FIXME: correct? + "@typescript-eslint/no-invalid-void-type": "off", + "@typescript-eslint/triple-slash-reference": "off", + "max-len": [ + "error", + { + "code": 120, + "comments": 120 + } + ], + "no-unused-vars": "off" + } } diff --git a/client/common-client-plugin.js b/client/common-client-plugin.ts similarity index 81% rename from client/common-client-plugin.js rename to client/common-client-plugin.ts index 3e233faf..6babffee 100644 --- a/client/common-client-plugin.js +++ b/client/common-client-plugin.ts @@ -1,7 +1,7 @@ 'use strict' -function register ({ registerHook, _peertubeHelpers }) { +function register ({ registerHook }: RegisterOptions): void { registerHook({ target: 'action:router.navigation-end', handler: () => { diff --git a/client/peertube.d.ts b/client/peertube.d.ts new file mode 100644 index 00000000..683e3a37 --- /dev/null +++ b/client/peertube.d.ts @@ -0,0 +1,11 @@ +interface RegisterOptions { + registerHook: any + peertubeHelpers: any +} + +interface Video { + isLive: boolean + isLocal: boolean + originInstanceUrl: string + uuid: string +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 00000000..89e45dbc --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "es6", + "target": "es5", + "allowJs": true, + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "alwaysStrict": true, // should already be true because of strict:true + "noImplicitAny": true, // should already be true because of strict:true + "noImplicitThis": true, // should already be true because of strict:true + "noImplicitReturns": true, + "strictBindCallApply": true, // should already be true because of strict:true + "noUnusedLocals": true, + "outDir": "../dist/client", + "paths": {} + }, + "include": ["./**/*"], + "exclude": [] +} diff --git a/client/videowatch-client-plugin.js b/client/videowatch-client-plugin.ts similarity index 67% rename from client/videowatch-client-plugin.js rename to client/videowatch-client-plugin.ts index d58611bc..5b6ffa98 100644 --- a/client/videowatch-client-plugin.js +++ b/client/videowatch-client-plugin.ts @@ -1,18 +1,20 @@ 'use strict' -function register ({ registerHook, peertubeHelpers }) { +interface VideoCache {[key: string]: Video} + +function register ({ registerHook, peertubeHelpers }: RegisterOptions): void { const logger = { - log: (s) => console.log('[peertube-plugin-livechat] ' + s), - info: (s) => console.info('[peertube-plugin-livechat] ' + s), - error: (s) => console.error('[peertube-plugin-livechat] ' + s), - warn: (s) => console.warn('[peertube-plugin-livechat] ' + s) + log: (s: string) => console.log('[peertube-plugin-livechat] ' + s), + info: (s: string) => console.info('[peertube-plugin-livechat] ' + s), + error: (s: string) => console.error('[peertube-plugin-livechat] ' + s), + warn: (s: string) => console.warn('[peertube-plugin-livechat] ' + s) } - const videoCache = {} - let lastUUID = null - let settings = {} + const videoCache: VideoCache = {} + let lastUUID: string | null = null + let settings: any = {} - function parseUUIDs (s) { + function parseUUIDs (s: string): string[] { if (!s) { return [] } @@ -25,7 +27,7 @@ function register ({ registerHook, peertubeHelpers }) { return a.filter(line => line !== '') } - function getBaseRoute () { + function getBaseRoute (): string { // FIXME: should be provided by PeertubeHelpers (does not exists for now) // We are guessing the route with the correct plugin version with this trick: const staticBase = peertubeHelpers.getBaseStaticRoute() @@ -33,7 +35,7 @@ function register ({ registerHook, peertubeHelpers }) { return staticBase.replace(/\/static.*$/, '/router') } - function getIframeUri (uuid) { + function getIframeUri (uuid: string): string | null { if (!settings) { logger.error('Settings are not initialized, too soon to compute the iframeUri') return null @@ -52,7 +54,7 @@ function register ({ registerHook, peertubeHelpers }) { // we need to pass the complete url. const video = videoCache[uuid] if (video) { - const url = video.originInstanceUrl + '/videos/watch/' + uuid + const url: string = video.originInstanceUrl + '/videos/watch/' + uuid iframeUri = getBaseRoute() + '/webchat?url=' + encodeURIComponent(url) } } @@ -63,7 +65,13 @@ function register ({ registerHook, peertubeHelpers }) { return iframeUri } - function displayButton (buttonContainer, name, label, callback, icon) { + function displayButton ( + buttonContainer: HTMLElement, + name: string, + label: string, + callback: () => void | boolean, + icon: string | null + ): void { const button = document.createElement('button') button.classList.add( 'peertube-plugin-livechat-button', @@ -71,7 +79,8 @@ function register ({ registerHook, peertubeHelpers }) { ) button.onclick = callback if (icon) { - const iconUrl = peertubeHelpers.getBaseStaticRoute() + '/images/' + icon + // FIXME: remove «as string» when peertube types will be available + const iconUrl = (peertubeHelpers.getBaseStaticRoute() as string) + '/images/' + icon const iconEl = document.createElement('span') iconEl.classList.add('peertube-plugin-livechat-button-icon') iconEl.setAttribute('style', @@ -85,9 +94,10 @@ function register ({ registerHook, peertubeHelpers }) { buttonContainer.append(button) } - function insertChatDom (container, peertubeHelpers, uuid, showOpenBlank) { + async function insertChatDom (container: HTMLElement, uuid: string, showOpenBlank: boolean): Promise { logger.log('Adding livechat in the DOM...') - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.all([ peertubeHelpers.translate('Open chat'), peertubeHelpers.translate('Open chat in a new window'), @@ -121,51 +131,46 @@ function register ({ registerHook, peertubeHelpers }) { return p } - function openChat () { - const p = new Promise((resolve, reject) => { - const uuid = lastUUID - if (!uuid) { - logger.log('No current uuid.') - return reject(new Error('No current uuid.')) - } + function openChat (): void | boolean { + const uuid = lastUUID + if (!uuid) { + logger.log('No current uuid.') + return false + } - logger.info('Trying to load the chat for video ' + uuid + '.') - const iframeUri = getIframeUri(uuid) - if (!iframeUri) { - logger.error('Incorrect iframe uri') - return reject(new Error('Incorrect iframe uri')) - } - const additionalStyles = settings['chat-style'] || '' + logger.info('Trying to load the chat for video ' + uuid + '.') + const iframeUri = getIframeUri(uuid) + if (!iframeUri) { + logger.error('Incorrect iframe uri') + return false + } + const additionalStyles = settings['chat-style'] || '' - logger.info('Opening the chat...') - const container = document.getElementById('peertube-plugin-livechat-container') - if (!container) { - logger.error('Cant found the livechat container.') - return reject(new Error('Cant found the livechat container')) - } + logger.info('Opening the chat...') + const container = document.getElementById('peertube-plugin-livechat-container') + if (!container) { + logger.error('Cant found the livechat container.') + return false + } - if (container.querySelector('iframe')) { - logger.error('Seems that there is already an iframe in the container.') - return reject(new Error('Seems that there is already an iframe in the container.')) - } + if (container.querySelector('iframe')) { + logger.error('Seems that there is already an iframe in the container.') + return false + } - // Creating the iframe... - const iframe = document.createElement('iframe') - iframe.setAttribute('src', iframeUri) - iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms') - iframe.setAttribute('frameborder', '0') - if (additionalStyles) { - iframe.setAttribute('style', additionalStyles) - } - container.append(iframe) - container.setAttribute('peertube-plugin-livechat-state', 'open') - - resolve() - }) - return p + // Creating the iframe... + const iframe = document.createElement('iframe') + iframe.setAttribute('src', iframeUri) + iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms') + iframe.setAttribute('frameborder', '0') + if (additionalStyles) { + iframe.setAttribute('style', additionalStyles) + } + container.append(iframe) + container.setAttribute('peertube-plugin-livechat-state', 'open') } - function closeChat () { + function closeChat (): void { const container = document.getElementById('peertube-plugin-livechat-container') if (!container) { logger.error('Cant close livechat, container not found.') @@ -177,7 +182,7 @@ function register ({ registerHook, peertubeHelpers }) { container.setAttribute('peertube-plugin-livechat-state', 'closed') } - function initChat () { + function initChat (): void { const videoWrapper = document.querySelector('#video-wrapper') if (!videoWrapper) { logger.error('The required div is not present in the DOM.') @@ -193,7 +198,7 @@ function register ({ registerHook, peertubeHelpers }) { container.setAttribute('peertube-plugin-livechat-state', 'initializing') videoWrapper.append(container) - peertubeHelpers.getSettings().then(s => { + peertubeHelpers.getSettings().then((s: any) => { settings = s const liveOn = !!settings['chat-all-lives'] const nonLiveOn = !!settings['chat-all-non-lives'] @@ -205,17 +210,21 @@ function register ({ registerHook, peertubeHelpers }) { logger.log('Checking if this video should have a chat...') const uuid = lastUUID + if (!uuid) { + logger.error('There is no lastUUID.') + return + } const video = videoCache[uuid] if (!video) { logger.error('Can\'t find the video ' + uuid + ' in the videoCache') return } - if (settings['chat-only-locals' && !video.isLocal]) { + if (settings['chat-only-locals'] && !video.isLocal) { logger.log('This video is not local, and we dont want chats on non local videos.') return } - if (uuids.indexOf(uuid) >= 0) { + if (uuids.includes(uuid)) { logger.log('This video is in the list for chats.') } else if (video.isLive && liveOn) { logger.log('This video is live and we want all lives.') @@ -226,10 +235,11 @@ function register ({ registerHook, peertubeHelpers }) { return } - insertChatDom(container, peertubeHelpers, uuid, !!settings['chat-open-blank']).then(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + insertChatDom(container as HTMLElement, uuid, !!settings['chat-open-blank']).then(() => { if (settings['chat-auto-display']) { openChat() - } else { + } else if (container) { container.setAttribute('peertube-plugin-livechat-state', 'closed') } }) @@ -238,7 +248,7 @@ function register ({ registerHook, peertubeHelpers }) { registerHook({ target: 'filter:api.video-watch.video.get.result', - handler: (video) => { + handler: (video: Video) => { // For now, hooks for action:video-watch... did not receive the video object // So we store video objects in videoCache videoCache[video.uuid] = video diff --git a/package-lock.json b/package-lock.json index a04ef4c6..96b88385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5284,6 +5284,163 @@ "repeat-string": "^1.6.1" } }, + "ts-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz", + "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", diff --git a/package.json b/package.json index 546970f8..af7193ee 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-standard": "^5.0.0", "npm-run-all": "^4.1.5", "svgo": "^2.2.1", + "ts-loader": "^8.1.0", "typescript": "^4.2.3", "webpack": "^4.41.2", "webpack-cli": "^3.3.10" diff --git a/webpack.config.js b/webpack.config.js index e370961b..b2b82040 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,15 +3,23 @@ const path = require("path") const EsmWebpackPlugin = require("@purtuga/esm-webpack-plugin") const clientFiles = [ - 'common-client-plugin.js', - 'videowatch-client-plugin.js' + 'common-client-plugin', + 'videowatch-client-plugin' ] let config = clientFiles.map(f => ({ - entry: "./client/" + f, + entry: "./client/" + f + ".ts", + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader' + } + ] + }, output: { path: path.resolve(__dirname, "./dist/client"), - filename: "./" + f, + filename: "./" + f + ".js", library: "script", libraryTarget: "var" },