Typescript for frontend code.

This commit is contained in:
John Livingston 2021-04-07 18:14:58 +02:00
parent 89a03032ff
commit 3e46552ec0
8 changed files with 302 additions and 78 deletions

View File

@ -5,19 +5,36 @@
"es6": true "es6": true
}, },
"extends": [ "extends": [
"standard" "standard-with-typescript"
], ],
"globals": {}, "globals": {},
"plugins": [], "parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"project": [
"./client/tsconfig.json"
]
},
"plugins": [
"@typescript-eslint"
],
"ignorePatterns": [], "ignorePatterns": [],
"rules": { "rules": {
"max-len": [ "@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}],
"error", "@typescript-eslint/no-floating-promises": "error",
{ "@typescript-eslint/no-misused-promises": "error",
"code": 120, "@typescript-eslint/no-var-requires": "off",
"comments": 120 "@typescript-eslint/strict-boolean-expressions": "off",
} "@typescript-eslint/return-await": [2, "in-try-catch"], // FIXME: correct?
], "@typescript-eslint/no-invalid-void-type": "off",
"no-unused-vars": [2, {"argsIgnorePattern": "^_"}] "@typescript-eslint/triple-slash-reference": "off",
} "max-len": [
"error",
{
"code": 120,
"comments": 120
}
],
"no-unused-vars": "off"
}
} }

View File

@ -1,7 +1,7 @@
'use strict' 'use strict'
function register ({ registerHook, _peertubeHelpers }) { function register ({ registerHook }: RegisterOptions): void {
registerHook({ registerHook({
target: 'action:router.navigation-end', target: 'action:router.navigation-end',
handler: () => { handler: () => {

11
client/peertube.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
interface RegisterOptions {
registerHook: any
peertubeHelpers: any
}
interface Video {
isLive: boolean
isLocal: boolean
originInstanceUrl: string
uuid: string
}

20
client/tsconfig.json Normal file
View File

@ -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": []
}

View File

@ -1,18 +1,20 @@
'use strict' 'use strict'
function register ({ registerHook, peertubeHelpers }) { interface VideoCache {[key: string]: Video}
function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
const logger = { const logger = {
log: (s) => console.log('[peertube-plugin-livechat] ' + s), log: (s: string) => console.log('[peertube-plugin-livechat] ' + s),
info: (s) => console.info('[peertube-plugin-livechat] ' + s), info: (s: string) => console.info('[peertube-plugin-livechat] ' + s),
error: (s) => console.error('[peertube-plugin-livechat] ' + s), error: (s: string) => console.error('[peertube-plugin-livechat] ' + s),
warn: (s) => console.warn('[peertube-plugin-livechat] ' + s) warn: (s: string) => console.warn('[peertube-plugin-livechat] ' + s)
} }
const videoCache = {} const videoCache: VideoCache = {}
let lastUUID = null let lastUUID: string | null = null
let settings = {} let settings: any = {}
function parseUUIDs (s) { function parseUUIDs (s: string): string[] {
if (!s) { if (!s) {
return [] return []
} }
@ -25,7 +27,7 @@ function register ({ registerHook, peertubeHelpers }) {
return a.filter(line => line !== '') return a.filter(line => line !== '')
} }
function getBaseRoute () { function getBaseRoute (): string {
// FIXME: should be provided by PeertubeHelpers (does not exists for now) // FIXME: should be provided by PeertubeHelpers (does not exists for now)
// We are guessing the route with the correct plugin version with this trick: // We are guessing the route with the correct plugin version with this trick:
const staticBase = peertubeHelpers.getBaseStaticRoute() const staticBase = peertubeHelpers.getBaseStaticRoute()
@ -33,7 +35,7 @@ function register ({ registerHook, peertubeHelpers }) {
return staticBase.replace(/\/static.*$/, '/router') return staticBase.replace(/\/static.*$/, '/router')
} }
function getIframeUri (uuid) { function getIframeUri (uuid: string): string | null {
if (!settings) { if (!settings) {
logger.error('Settings are not initialized, too soon to compute the iframeUri') logger.error('Settings are not initialized, too soon to compute the iframeUri')
return null return null
@ -52,7 +54,7 @@ function register ({ registerHook, peertubeHelpers }) {
// we need to pass the complete url. // we need to pass the complete url.
const video = videoCache[uuid] const video = videoCache[uuid]
if (video) { if (video) {
const url = video.originInstanceUrl + '/videos/watch/' + uuid const url: string = video.originInstanceUrl + '/videos/watch/' + uuid
iframeUri = getBaseRoute() + '/webchat?url=' + encodeURIComponent(url) iframeUri = getBaseRoute() + '/webchat?url=' + encodeURIComponent(url)
} }
} }
@ -63,7 +65,13 @@ function register ({ registerHook, peertubeHelpers }) {
return iframeUri 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') const button = document.createElement('button')
button.classList.add( button.classList.add(
'peertube-plugin-livechat-button', 'peertube-plugin-livechat-button',
@ -71,7 +79,8 @@ function register ({ registerHook, peertubeHelpers }) {
) )
button.onclick = callback button.onclick = callback
if (icon) { 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') const iconEl = document.createElement('span')
iconEl.classList.add('peertube-plugin-livechat-button-icon') iconEl.classList.add('peertube-plugin-livechat-button-icon')
iconEl.setAttribute('style', iconEl.setAttribute('style',
@ -85,9 +94,10 @@ function register ({ registerHook, peertubeHelpers }) {
buttonContainer.append(button) buttonContainer.append(button)
} }
function insertChatDom (container, peertubeHelpers, uuid, showOpenBlank) { async function insertChatDom (container: HTMLElement, uuid: string, showOpenBlank: boolean): Promise<void> {
logger.log('Adding livechat in the DOM...') logger.log('Adding livechat in the DOM...')
const p = new Promise((resolve, reject) => { const p = new Promise<void>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Promise.all([ Promise.all([
peertubeHelpers.translate('Open chat'), peertubeHelpers.translate('Open chat'),
peertubeHelpers.translate('Open chat in a new window'), peertubeHelpers.translate('Open chat in a new window'),
@ -121,51 +131,46 @@ function register ({ registerHook, peertubeHelpers }) {
return p return p
} }
function openChat () { function openChat (): void | boolean {
const p = new Promise((resolve, reject) => { const uuid = lastUUID
const uuid = lastUUID if (!uuid) {
if (!uuid) { logger.log('No current uuid.')
logger.log('No current uuid.') return false
return reject(new Error('No current uuid.')) }
}
logger.info('Trying to load the chat for video ' + uuid + '.') logger.info('Trying to load the chat for video ' + uuid + '.')
const iframeUri = getIframeUri(uuid) const iframeUri = getIframeUri(uuid)
if (!iframeUri) { if (!iframeUri) {
logger.error('Incorrect iframe uri') logger.error('Incorrect iframe uri')
return reject(new Error('Incorrect iframe uri')) return false
} }
const additionalStyles = settings['chat-style'] || '' const additionalStyles = settings['chat-style'] || ''
logger.info('Opening the chat...') logger.info('Opening the chat...')
const container = document.getElementById('peertube-plugin-livechat-container') const container = document.getElementById('peertube-plugin-livechat-container')
if (!container) { if (!container) {
logger.error('Cant found the livechat container.') logger.error('Cant found the livechat container.')
return reject(new Error('Cant found the livechat container')) return false
} }
if (container.querySelector('iframe')) { if (container.querySelector('iframe')) {
logger.error('Seems that there is already an iframe in the container.') 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.')) return false
} }
// Creating the iframe... // Creating the iframe...
const iframe = document.createElement('iframe') const iframe = document.createElement('iframe')
iframe.setAttribute('src', iframeUri) iframe.setAttribute('src', iframeUri)
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms') iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
iframe.setAttribute('frameborder', '0') iframe.setAttribute('frameborder', '0')
if (additionalStyles) { if (additionalStyles) {
iframe.setAttribute('style', additionalStyles) iframe.setAttribute('style', additionalStyles)
} }
container.append(iframe) container.append(iframe)
container.setAttribute('peertube-plugin-livechat-state', 'open') container.setAttribute('peertube-plugin-livechat-state', 'open')
resolve()
})
return p
} }
function closeChat () { function closeChat (): void {
const container = document.getElementById('peertube-plugin-livechat-container') const container = document.getElementById('peertube-plugin-livechat-container')
if (!container) { if (!container) {
logger.error('Cant close livechat, container not found.') logger.error('Cant close livechat, container not found.')
@ -177,7 +182,7 @@ function register ({ registerHook, peertubeHelpers }) {
container.setAttribute('peertube-plugin-livechat-state', 'closed') container.setAttribute('peertube-plugin-livechat-state', 'closed')
} }
function initChat () { function initChat (): void {
const videoWrapper = document.querySelector('#video-wrapper') const videoWrapper = document.querySelector('#video-wrapper')
if (!videoWrapper) { if (!videoWrapper) {
logger.error('The required div is not present in the DOM.') 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') container.setAttribute('peertube-plugin-livechat-state', 'initializing')
videoWrapper.append(container) videoWrapper.append(container)
peertubeHelpers.getSettings().then(s => { peertubeHelpers.getSettings().then((s: any) => {
settings = s settings = s
const liveOn = !!settings['chat-all-lives'] const liveOn = !!settings['chat-all-lives']
const nonLiveOn = !!settings['chat-all-non-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...') logger.log('Checking if this video should have a chat...')
const uuid = lastUUID const uuid = lastUUID
if (!uuid) {
logger.error('There is no lastUUID.')
return
}
const video = videoCache[uuid] const video = videoCache[uuid]
if (!video) { if (!video) {
logger.error('Can\'t find the video ' + uuid + ' in the videoCache') logger.error('Can\'t find the video ' + uuid + ' in the videoCache')
return 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.') logger.log('This video is not local, and we dont want chats on non local videos.')
return return
} }
if (uuids.indexOf(uuid) >= 0) { if (uuids.includes(uuid)) {
logger.log('This video is in the list for chats.') logger.log('This video is in the list for chats.')
} else if (video.isLive && liveOn) { } else if (video.isLive && liveOn) {
logger.log('This video is live and we want all lives.') logger.log('This video is live and we want all lives.')
@ -226,10 +235,11 @@ function register ({ registerHook, peertubeHelpers }) {
return 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']) { if (settings['chat-auto-display']) {
openChat() openChat()
} else { } else if (container) {
container.setAttribute('peertube-plugin-livechat-state', 'closed') container.setAttribute('peertube-plugin-livechat-state', 'closed')
} }
}) })
@ -238,7 +248,7 @@ function register ({ registerHook, peertubeHelpers }) {
registerHook({ registerHook({
target: 'filter:api.video-watch.video.get.result', 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 // For now, hooks for action:video-watch... did not receive the video object
// So we store video objects in videoCache // So we store video objects in videoCache
videoCache[video.uuid] = video videoCache[video.uuid] = video

157
package-lock.json generated
View File

@ -5284,6 +5284,163 @@
"repeat-string": "^1.6.1" "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": { "tsconfig-paths": {
"version": "3.9.0", "version": "3.9.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",

View File

@ -39,6 +39,7 @@
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^5.0.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"svgo": "^2.2.1", "svgo": "^2.2.1",
"ts-loader": "^8.1.0",
"typescript": "^4.2.3", "typescript": "^4.2.3",
"webpack": "^4.41.2", "webpack": "^4.41.2",
"webpack-cli": "^3.3.10" "webpack-cli": "^3.3.10"

View File

@ -3,15 +3,23 @@ const path = require("path")
const EsmWebpackPlugin = require("@purtuga/esm-webpack-plugin") const EsmWebpackPlugin = require("@purtuga/esm-webpack-plugin")
const clientFiles = [ const clientFiles = [
'common-client-plugin.js', 'common-client-plugin',
'videowatch-client-plugin.js' 'videowatch-client-plugin'
] ]
let config = clientFiles.map(f => ({ let config = clientFiles.map(f => ({
entry: "./client/" + f, entry: "./client/" + f + ".ts",
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader'
}
]
},
output: { output: {
path: path.resolve(__dirname, "./dist/client"), path: path.resolve(__dirname, "./dist/client"),
filename: "./" + f, filename: "./" + f + ".js",
library: "script", library: "script",
libraryTarget: "var" libraryTarget: "var"
}, },