Upgrade everything to Angular 12 and more + build changes
This is a very big commit that does an initial job of upgrading everything to the latest version. TSLint gets replaced by ESLint. Instead of plain node, now ts-node is being used. Old modules also get replaced with new ones (mostly ng2 to ngx). Also obsolete configs have been replaced with how it's used today with Angular. This includes: * Upgrade to: ** Angular 12 ** Typescript 4 ** ESLint 7 and replace TSLint ** Bootstrap 5 ** Eerything connected to these * Run with ts-node * Convert wepack config to angular config * Remove typescript-ioc * Update tsconfigs * Run a git command instead of using a library for sshort hash * Move assets to a new location align with default Angular settings * Database migration for new avatarUrl locations * Simplify Model extension align with newest sequelize version * Remove breadcrumb hack * Fix homeserver typo * A few general fixes that are necessary with newest Typescript rules * Define Express.User interface
6
.eslintignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# don't ever lint node_modules
|
||||||
|
node_modules
|
||||||
|
# don't lint build output (make sure it's set to your correct build folder name)
|
||||||
|
build
|
||||||
|
# don't lint nyc coverage output
|
||||||
|
coverage
|
50
.eslintrc.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"ignorePatterns": [
|
||||||
|
"projects/**/*"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.ts"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": [
|
||||||
|
"tsconfig.json"
|
||||||
|
],
|
||||||
|
"createDefaultProgram": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:@angular-eslint/recommended",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.html"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"plugin:@angular-eslint/template/recommended"
|
||||||
|
],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
119
angular.json
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"dimension": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:application": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "web",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "build/web",
|
||||||
|
"index": "web/index.html",
|
||||||
|
"main": "web/main.ts",
|
||||||
|
"polyfills": "web/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
"web/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"web/style/app.scss"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"progress": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "4mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "32kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "web/environments/environment.ts",
|
||||||
|
"with": "web/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "dimension:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "dimension:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"options": {
|
||||||
|
"proxyConfig": "proxy.conf.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "dimension:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "web/test.ts",
|
||||||
|
"polyfills": "web/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"karmaConfig": "karma.conf.js",
|
||||||
|
"assets": [
|
||||||
|
"web/favicon.ico",
|
||||||
|
"web/assets"
|
||||||
|
],
|
||||||
|
"styles": [],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "dimension",
|
||||||
|
"cli": {
|
||||||
|
"defaultCollection": "@angular-eslint/schematics"
|
||||||
|
}
|
||||||
|
}
|
45
karma.conf.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, './coverage/angular-starter'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
16428
package-lock.json
generated
183
package.json
@ -5,18 +5,17 @@
|
|||||||
"main": "build/app/index.js",
|
"main": "build/app/index.js",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start:web": "webpack-dev-server --inline --mode=development --progress --port 8082 --host 0.0.0.0",
|
"ng": "ng",
|
||||||
|
"start:web": "ng serve --configuration development --port 8082",
|
||||||
"start:app": "npm run-script build && node build/app/index.js",
|
"start:app": "npm run-script build && node build/app/index.js",
|
||||||
"start:apponly": "npm run-script build:app && node build/app/index.js",
|
"start:apponly": "ts-node-dev --respawn --transpile-only --project tsconfig.backend.json ./src/index.ts",
|
||||||
|
"node:start:apponly": "npm run-script build:app && node build/app/index.js",
|
||||||
"build": "npm run-script build:web && npm run-script build:app",
|
"build": "npm run-script build:web && npm run-script build:app",
|
||||||
"build:web": "rimraf build/web && webpack --mode production --progress --profile --bail",
|
"build:web": "rimraf build/web && ng build --configuration production",
|
||||||
"build:app": "rimraf build/app && tsc -p tsconfig-app.json",
|
"build:app": "rimraf build/app && tsc -p tsconfig.backend.json",
|
||||||
"lint": "npm run-script lint:app && npm run-script lint:web",
|
"lint": "npm run-script lint:app && npm run-script lint:web",
|
||||||
"lint:app": "tslint --project ./tsconfig-app.json -t stylish",
|
"lint:app": "tslint --project ./tsconfig.app.json",
|
||||||
"lint:web": "tslint --project ./tsconfig.json -t stylish",
|
"lint:web": "tslint --project ./tsconfig.json"
|
||||||
"i18n": "npm run-script i18n:init && npm run-script i18n:extract",
|
|
||||||
"i18n:init": "ngx-translate-extract --input ./web --output ./web/public/assets/i18n/template.json --key-as-default-value --replace --format json",
|
|
||||||
"i18n:extract": "ngx-translate-extract --input ./web --output ./web/public/assets/i18n/en.json --clean --format json"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -24,113 +23,121 @@
|
|||||||
},
|
},
|
||||||
"author": "Travis Ralston",
|
"author": "Travis Ralston",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ngx-translate/core": "^12.1.2",
|
"@angular/localize": "^12.1.1",
|
||||||
"@ngx-translate/http-loader": "^6.0.0",
|
"@fortawesome/angular-fontawesome": "^0.9.0",
|
||||||
"@types/bluebird": "^3.5.27",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@types/body-parser": "^1.17.0",
|
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||||
"@types/node": "^12.0.10",
|
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||||
"@types/validator": "^10.11.1",
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
|
"@popperjs/core": "^2.9.2",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"config": "^3.1.0",
|
"config": "^3.3.6",
|
||||||
"dns-then": "^0.1.0",
|
"dns-then": "^0.1.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"git-rev-sync": "^1.12.0",
|
|
||||||
"isipaddress": "0.0.2",
|
"isipaddress": "0.0.2",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.21",
|
||||||
"matrix-bot-sdk": "^0.3.8",
|
"matrix-bot-sdk": "^0.3.8",
|
||||||
"matrix-js-snippets": "^0.2.8",
|
"matrix-js-snippets": "^0.2.8",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"mime": "^2.4.2",
|
"mime": "^2.5.2",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.29.1",
|
||||||
"netmask": "^1.0.6",
|
"netmask": "^2.0.2",
|
||||||
"pg": "^8.5.1",
|
"ngx-ui-switch": "^12.0.1",
|
||||||
|
"postcss": "^8.3.5",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.2",
|
||||||
"request-promise": "^4.2.4",
|
"request-promise": "^4.2.6",
|
||||||
"require-dir-all": "^0.4.15",
|
"require-dir-all": "^0.4.15",
|
||||||
"semver": "^6.0.0",
|
"semver": "^7.3.5",
|
||||||
"sequelize": "^5.18.4",
|
"sequelize": "6.6.2",
|
||||||
"sequelize-typescript": "^1.0.0",
|
"sequelize-typescript": "^2.1.0",
|
||||||
"sharp": "^0.27.2",
|
"sharp": "^0.28.3",
|
||||||
"split-host": "^0.1.1",
|
"split-host": "^0.1.1",
|
||||||
"spotify-uri": "^1.0.0",
|
"spotify-uri": "^2.2.0",
|
||||||
"sqlite3": "^4.2.0",
|
"sqlite3": "^5.0.2",
|
||||||
"telegraf": "^3.30.1",
|
"telegraf": "^3.30.1",
|
||||||
"typescript": "^3.5.2",
|
"typescript": "^4.3.5",
|
||||||
"typescript-ioc": "^1.2.5",
|
"typescript-rest": "^3.0.4",
|
||||||
"typescript-rest": "^2.2.0",
|
"umzug": "^3.0.0-beta.16",
|
||||||
"umzug": "^2.2.0",
|
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"xml2js": "^0.4.23"
|
"xng-breadcrumb": "^6.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/core": "^11.2.4",
|
"@angular-devkit/build-angular": "^12.1.1",
|
||||||
"@angular/animations": "^11.2.5",
|
"@angular-devkit/core": "^12.1.1",
|
||||||
"@angular/common": "^11.2.5",
|
"@angular-eslint/builder": "12.3.0",
|
||||||
"@angular/compiler": "^11.2.5",
|
"@angular-eslint/eslint-plugin": "12.3.0",
|
||||||
"@angular/core": "^11.2.5",
|
"@angular-eslint/eslint-plugin-template": "12.3.0",
|
||||||
"@angular/forms": "^11.2.5",
|
"@angular-eslint/schematics": "12.3.0",
|
||||||
"@angular/platform-browser": "^11.2.5",
|
"@angular-eslint/template-parser": "12.3.0",
|
||||||
"@angular/platform-browser-dynamic": "^11.2.5",
|
"@angular/animations": "^12.1.1",
|
||||||
"@angular/router": "^11.2.5",
|
"@angular/cli": "^12.1.1",
|
||||||
"@angularclass/hmr": "^2.1.3",
|
"@angular/common": "^12.1.1",
|
||||||
|
"@angular/compiler": "^12.1.1",
|
||||||
|
"@angular/compiler-cli": "^12.1.1",
|
||||||
|
"@angular/core": "^12.1.1",
|
||||||
|
"@angular/forms": "^12.1.1",
|
||||||
|
"@angular/platform-browser": "^12.1.1",
|
||||||
|
"@angular/platform-browser-dynamic": "^12.1.1",
|
||||||
|
"@angular/router": "^12.1.1",
|
||||||
|
"@angularclass/hmr": "^3.0.0",
|
||||||
"@angularclass/hmr-loader": "^3.0.4",
|
"@angularclass/hmr-loader": "^3.0.4",
|
||||||
"@babel/core": "^7.4.5",
|
"@babel/core": "^7.14.6",
|
||||||
"@babel/preset-env": "^7.4.5",
|
"@babel/preset-env": "^7.14.7",
|
||||||
"@biesbjerg/ngx-translate-extract": "^7.0.3",
|
"@ckeditor/ckeditor5-angular": "^2.0.2",
|
||||||
"@ckeditor/ckeditor5-angular": "^1.1.0",
|
"@ckeditor/ckeditor5-build-classic": "^29.0.0",
|
||||||
"@ckeditor/ckeditor5-build-classic": "^12.2.0",
|
|
||||||
"@fortawesome/fontawesome": "^1.1.8",
|
"@fortawesome/fontawesome": "^1.1.8",
|
||||||
"@fortawesome/fontawesome-free-brands": "^5.0.13",
|
"@fortawesome/fontawesome-free-brands": "^5.0.13",
|
||||||
"@fortawesome/fontawesome-free-regular": "^5.0.13",
|
"@fortawesome/fontawesome-free-regular": "^5.0.13",
|
||||||
"@fortawesome/fontawesome-free-solid": "^5.0.13",
|
"@fortawesome/fontawesome-free-solid": "^5.0.13",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^4.2.1",
|
"@ng-bootstrap/ng-bootstrap": "^11.0.0-beta.2",
|
||||||
"@types/jquery": "^3.3.30",
|
"@types/bluebird": "^3.5.36",
|
||||||
|
"@types/body-parser": "^1.19.1",
|
||||||
|
"@types/jquery": "^3.5.6",
|
||||||
|
"@types/node": "^14.17.4",
|
||||||
|
"@types/validator": "^13.6.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.28.2",
|
||||||
|
"@typescript-eslint/parser": "4.28.2",
|
||||||
"angular2-template-loader": "^0.6.2",
|
"angular2-template-loader": "^0.6.2",
|
||||||
"angular2-toaster": "^7.0.0",
|
"angular2-toaster": "^11.0.1",
|
||||||
"angular2-ui-switch": "^1.2.0",
|
"bootstrap": "^5.0.2",
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
"bootstrap": "^4.3.1",
|
"core-js": "^3.15.2",
|
||||||
"codelyzer": "^5.1.0",
|
"css-loader": "^5.2.7",
|
||||||
"copy-webpack-plugin": "^5.1.2",
|
"cssnano": "^5.0.6",
|
||||||
"core-js": "^3.1.4",
|
|
||||||
"css-loader": "^3.0.0",
|
|
||||||
"cssnano": "^4.1.10",
|
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
"embed-video": "^2.0.4",
|
"embed-video": "^2.0.4",
|
||||||
"file-loader": "^4.0.0",
|
"eslint": "^7.26.0",
|
||||||
|
"eslint-webpack-plugin": "^2.5.4",
|
||||||
"goby": "^1.1.2",
|
"goby": "^1.1.2",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^2.1.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^5.3.2",
|
||||||
"iso-639-1": "^2.0.5",
|
"iso-639-1": "^2.1.9",
|
||||||
"jquery": "^3.5.0",
|
"jquery": "^3.6.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"mini-css-extract-plugin": "^0.7.0",
|
"mini-css-extract-plugin": "^2.1.0",
|
||||||
"ng2-breadcrumbs": "^0.1.281",
|
|
||||||
"ngx-modialog": "^5.0.1",
|
"ngx-modialog": "^5.0.1",
|
||||||
"node-sass": "^4.14.1",
|
|
||||||
"postcss-cssnext": "^3.1.0",
|
"postcss-cssnext": "^3.1.0",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^14.0.2",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^6.1.1",
|
||||||
"postcss-scss": "^2.0.0",
|
"postcss-scss": "^4.0.0",
|
||||||
"raw-loader": "1.0.0",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^6.6.6",
|
"rxjs": "^6.6.7",
|
||||||
"rxjs-compat": "^6.5.2",
|
"rxjs-compat": "^6.6.7",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
"screenfull": "^4.2.0",
|
"screenfull": "^5.1.0",
|
||||||
"shelljs": "^0.8.3",
|
"shelljs": "^0.8.4",
|
||||||
"spinkit": "^1.2.5",
|
"spinkit": "^2.0.1",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^3.1.0",
|
||||||
"ts-helpers": "^1.1.2",
|
"ts-helpers": "^1.1.2",
|
||||||
"tslint": "^5.18.0",
|
"ts-loader": "^9.2.3",
|
||||||
"tslint-loader": "^3.5.4",
|
"ts-node": "^10.1.0",
|
||||||
"url-loader": "^2.0.1",
|
"ts-node-dev": "^1.1.8",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^5.44.0",
|
||||||
"webpack-cli": "^3.3.5",
|
"webpack-cli": "^4.7.2",
|
||||||
"webpack-dev-server": "^3.7.2",
|
"webpack-dev-server": "^3.11.2",
|
||||||
"zone.js": "^0.11.4"
|
"zone.js": "^0.11.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
14
proxy.conf.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:8184",
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
|
"/_matrix": {
|
||||||
|
"target": "http://localhost:8184",
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
|
"/.well-known": {
|
||||||
|
"target": "http://localhost:8184",
|
||||||
|
"secure": false
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,8 @@ export default class Webserver {
|
|||||||
private loadRoutes() {
|
private loadRoutes() {
|
||||||
// TODO: Rename services to controllers, and controllers to services. They're backwards.
|
// TODO: Rename services to controllers, and controllers to services. They're backwards.
|
||||||
|
|
||||||
const apis = ["scalar", "dimension", "admin", "matrix"].map(a => path.join(__dirname, a, "*.js"));
|
const apis = ["scalar", "dimension", "admin", "matrix"].map(a => path.join(__dirname, a, "*"));
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
Server.useIoC();
|
|
||||||
Server.registerAuthenticator(new MatrixSecurity());
|
Server.registerAuthenticator(new MatrixSecurity());
|
||||||
apis.forEach(a => Server.loadServices(router, [a]));
|
apis.forEach(a => Server.loadServices(router, [a]));
|
||||||
const routes = _.uniq(router.stack.map(r => r.route.path));
|
const routes = _.uniq(router.stack.map(r => r.route.path));
|
||||||
|
@ -89,7 +89,7 @@ export class AdminStickerService {
|
|||||||
authorName: "Telegram",
|
authorName: "Telegram",
|
||||||
authorReference: request.packUrl,
|
authorReference: request.packUrl,
|
||||||
license: "Telegram",
|
license: "Telegram",
|
||||||
licensePath: "/licenses/telegram-imported.txt",
|
licensePath: "/assets/licenses/telegram-imported.txt",
|
||||||
});
|
});
|
||||||
|
|
||||||
const stickers = [];
|
const stickers = [];
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { GET, Path, PathParam, POST, PUT, Security } from "typescript-rest";
|
import { GET, Path, PathParam, POST, PUT, Security } from "typescript-rest";
|
||||||
import TermsController, { ITerms } from "../controllers/TermsController";
|
import TermsController, { ITerms } from "../controllers/TermsController";
|
||||||
import { AutoWired, Inject } from "typescript-ioc/es6";
|
|
||||||
import { ROLE_ADMIN, ROLE_USER } from "../security/MatrixSecurity";
|
import { ROLE_ADMIN, ROLE_USER } from "../security/MatrixSecurity";
|
||||||
|
|
||||||
interface CreatePolicyObject {
|
interface CreatePolicyObject {
|
||||||
@ -13,44 +12,40 @@ interface CreatePolicyObject {
|
|||||||
* Administrative API for configuring terms of service.
|
* Administrative API for configuring terms of service.
|
||||||
*/
|
*/
|
||||||
@Path("/api/v1/dimension/admin/terms")
|
@Path("/api/v1/dimension/admin/terms")
|
||||||
@AutoWired
|
|
||||||
export class AdminTermsService {
|
export class AdminTermsService {
|
||||||
|
|
||||||
@Inject
|
|
||||||
private termsController: TermsController;
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("all")
|
@Path("all")
|
||||||
@Security([ROLE_USER, ROLE_ADMIN])
|
@Security([ROLE_USER, ROLE_ADMIN])
|
||||||
public async getPolicies(): Promise<ITerms[]> {
|
public async getPolicies(): Promise<ITerms[]> {
|
||||||
return this.termsController.getPoliciesForAdmin();
|
return new TermsController().getPoliciesForAdmin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path(":shortcode/:version")
|
@Path(":shortcode/:version")
|
||||||
@Security([ROLE_USER, ROLE_ADMIN])
|
@Security([ROLE_USER, ROLE_ADMIN])
|
||||||
public async getPolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string): Promise<ITerms> {
|
public async getPolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string): Promise<ITerms> {
|
||||||
return this.termsController.getPolicyForAdmin(shortcode, version);
|
return new TermsController().getPolicyForAdmin(shortcode, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path(":shortcode/draft")
|
@Path(":shortcode/draft")
|
||||||
@Security([ROLE_USER, ROLE_ADMIN])
|
@Security([ROLE_USER, ROLE_ADMIN])
|
||||||
public async createDraftPolicy(@PathParam("shortcode") shortcode: string, request: CreatePolicyObject): Promise<ITerms> {
|
public async createDraftPolicy(@PathParam("shortcode") shortcode: string, request: CreatePolicyObject): Promise<ITerms> {
|
||||||
return this.termsController.createDraftPolicy(request.name, shortcode, request.text, request.url);
|
return new TermsController().createDraftPolicy(request.name, shortcode, request.text, request.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path(":shortcode/publish/:version")
|
@Path(":shortcode/publish/:version")
|
||||||
@Security([ROLE_USER, ROLE_ADMIN])
|
@Security([ROLE_USER, ROLE_ADMIN])
|
||||||
public async publishDraftPolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string): Promise<ITerms> {
|
public async publishDraftPolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string): Promise<ITerms> {
|
||||||
return this.termsController.publishPolicy(shortcode, version);
|
return new TermsController().publishPolicy(shortcode, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Path(":shortcode/:version")
|
@Path(":shortcode/:version")
|
||||||
@Security([ROLE_USER, ROLE_ADMIN])
|
@Security([ROLE_USER, ROLE_ADMIN])
|
||||||
public async updatePolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string, request: CreatePolicyObject): Promise<ITerms> {
|
public async updatePolicy(@PathParam("shortcode") shortcode: string, @PathParam("version") version: string, request: CreatePolicyObject): Promise<ITerms> {
|
||||||
return this.termsController.updatePolicy(request.name, shortcode, version, request.text, request.url);
|
return new TermsController().updatePolicy(request.name, shortcode, version, request.text, request.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,6 @@ import { ScalarStore } from "../../db/ScalarStore";
|
|||||||
import UserScalarToken from "../../db/models/UserScalarToken";
|
import UserScalarToken from "../../db/models/UserScalarToken";
|
||||||
import { ScalarClient } from "../../scalar/ScalarClient";
|
import { ScalarClient } from "../../scalar/ScalarClient";
|
||||||
import * as randomString from "random-string";
|
import * as randomString from "random-string";
|
||||||
import { AutoWired } from "typescript-ioc/es6";
|
|
||||||
import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache";
|
import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache";
|
||||||
import { ILoggedInUser } from "../security/MatrixSecurity";
|
import { ILoggedInUser } from "../security/MatrixSecurity";
|
||||||
|
|
||||||
@ -23,7 +22,6 @@ export interface IAccountInfoResponse {
|
|||||||
/**
|
/**
|
||||||
* API controller for account management
|
* API controller for account management
|
||||||
*/
|
*/
|
||||||
@AutoWired
|
|
||||||
export default class AccountController {
|
export default class AccountController {
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { AutoWired } from "typescript-ioc/es6";
|
|
||||||
import { ILoggedInUser } from "../security/MatrixSecurity";
|
import { ILoggedInUser } from "../security/MatrixSecurity";
|
||||||
import TermsRecord from "../../db/models/TermsRecord";
|
import TermsRecord from "../../db/models/TermsRecord";
|
||||||
import TermsTextRecord from "../../db/models/TermsTextRecord";
|
import TermsTextRecord from "../../db/models/TermsTextRecord";
|
||||||
@ -50,11 +49,7 @@ export const VERSION_DRAFT = "draft";
|
|||||||
/**
|
/**
|
||||||
* API controller for terms of service management
|
* API controller for terms of service management
|
||||||
*/
|
*/
|
||||||
@AutoWired
|
|
||||||
export default class TermsController {
|
export default class TermsController {
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getPublishedTerms(): Promise<ICachedTerms[]> {
|
private async getPublishedTerms(): Promise<ICachedTerms[]> {
|
||||||
const cache = Cache.for(CACHE_TERMS);
|
const cache = Cache.for(CACHE_TERMS);
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export class DimensionWebhooksService {
|
|||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/travisci/:webhookId")
|
@Path("/travisci/:webhookId")
|
||||||
public async postTravisCiWebhook(@PathParam("webhookId") webhookId: string, @FormParam("payload") payload: string, @HeaderParam("Signature") signature: string): Promise<any> {
|
public async postTravisCiWebhook(@PathParam("webhookId") webhookId: string, @FormParam("payload") payload: string, @HeaderParam("Signature") signature: string): Promise<void> {
|
||||||
const webhook = await Webhook.findByPk(webhookId).catch(() => null);
|
const webhook = await Webhook.findByPk(webhookId).catch(() => null);
|
||||||
if (!webhook) throw new ApiError(404, "Webhook not found");
|
if (!webhook) throw new ApiError(404, "Webhook not found");
|
||||||
if (!webhook.targetUrl) throw new ApiError(400, "Webhook not configured");
|
if (!webhook.targetUrl) throw new ApiError(400, "Webhook not configured");
|
||||||
|
@ -76,7 +76,7 @@ export class DimensionWidgetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now we need to verify we can actually make the request
|
// Now we need to verify we can actually make the request
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise<ApiError | void>((resolve, reject) => {
|
||||||
request(checkUrl, (err, response) => {
|
request(checkUrl, (err, response) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
LogService.error("DimensionWidgetService", err);
|
LogService.error("DimensionWidgetService", err);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
|
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
|
||||||
import { OpenId } from "../../models/OpenId";
|
import { OpenId } from "../../models/OpenId";
|
||||||
import AccountController, { IAccountInfoResponse, IAccountRegisteredResponse } from "../controllers/AccountController";
|
import AccountController, { IAccountInfoResponse, IAccountRegisteredResponse } from "../controllers/AccountController";
|
||||||
import { AutoWired, Inject } from "typescript-ioc/es6";
|
|
||||||
import { ILoggedInUser, ROLE_USER } from "../security/MatrixSecurity";
|
import { ILoggedInUser, ROLE_USER } from "../security/MatrixSecurity";
|
||||||
import { ScalarClient } from "../../scalar/ScalarClient";
|
import { ScalarClient } from "../../scalar/ScalarClient";
|
||||||
|
|
||||||
@ -9,19 +8,15 @@ import { ScalarClient } from "../../scalar/ScalarClient";
|
|||||||
* API for account management
|
* API for account management
|
||||||
*/
|
*/
|
||||||
@Path("/_matrix/integrations/v1/account")
|
@Path("/_matrix/integrations/v1/account")
|
||||||
@AutoWired
|
|
||||||
export class MatrixAccountService {
|
export class MatrixAccountService {
|
||||||
|
|
||||||
@Inject
|
|
||||||
private accountController: AccountController;
|
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private context: ServiceContext;
|
private context: ServiceContext;
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("register")
|
@Path("register")
|
||||||
public async register(request: OpenId): Promise<IAccountRegisteredResponse> {
|
public async register(request: OpenId): Promise<IAccountRegisteredResponse> {
|
||||||
return this.accountController.registerAccount(request, ScalarClient.KIND_MATRIX_V1);
|
return new AccountController().registerAccount(request, ScalarClient.KIND_MATRIX_V1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@ -36,7 +31,7 @@ export class MatrixAccountService {
|
|||||||
@Path("logout")
|
@Path("logout")
|
||||||
@Security(ROLE_USER)
|
@Security(ROLE_USER)
|
||||||
public async logout(): Promise<any> {
|
public async logout(): Promise<any> {
|
||||||
await this.accountController.logout(this.context.request.user);
|
await new AccountController().logout(this.context.request.user);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
|
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
|
||||||
import { AutoWired, Inject } from "typescript-ioc/es6";
|
|
||||||
import { ROLE_USER } from "../security/MatrixSecurity";
|
import { ROLE_USER } from "../security/MatrixSecurity";
|
||||||
import TermsController, { ITermsResponse } from "../controllers/TermsController";
|
import TermsController, { ITermsResponse } from "../controllers/TermsController";
|
||||||
|
|
||||||
@ -11,26 +10,22 @@ export interface SignTermsRequest {
|
|||||||
* API for account management
|
* API for account management
|
||||||
*/
|
*/
|
||||||
@Path("/_matrix/integrations/v1/terms")
|
@Path("/_matrix/integrations/v1/terms")
|
||||||
@AutoWired
|
|
||||||
export class MatrixTermsService {
|
export class MatrixTermsService {
|
||||||
|
|
||||||
@Inject
|
|
||||||
private termsController: TermsController;
|
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private context: ServiceContext;
|
private context: ServiceContext;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
public async getAllTerms(): Promise<ITermsResponse> {
|
public async getAllTerms(): Promise<ITermsResponse> {
|
||||||
return this.termsController.getAvailableTerms();
|
return new TermsController().getAvailableTerms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("")
|
@Path("")
|
||||||
@Security(ROLE_USER)
|
@Security(ROLE_USER)
|
||||||
public async signTerms(request: SignTermsRequest): Promise<any> {
|
public async signTerms(request: SignTermsRequest): Promise<any> {
|
||||||
await this.termsController.signTermsMatching(this.context.request.user, request.user_accepts);
|
await new TermsController().signTermsMatching(this.context.request.user, request.user_accepts);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { GET, Path } from "typescript-rest";
|
import { GET, Path } from "typescript-rest";
|
||||||
import { AutoWired } from "typescript-ioc/es6";
|
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ interface WellknownResponse {
|
|||||||
* Serving of the .well-known file
|
* Serving of the .well-known file
|
||||||
*/
|
*/
|
||||||
@Path("/.well-known/matrix")
|
@Path("/.well-known/matrix")
|
||||||
@AutoWired
|
|
||||||
export class MatrixWellknownService {
|
export class MatrixWellknownService {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -2,7 +2,6 @@ import { Context, GET, Path, POST, QueryParam, Security, ServiceContext } from "
|
|||||||
import { ApiError } from "../ApiError";
|
import { ApiError } from "../ApiError";
|
||||||
import { OpenId } from "../../models/OpenId";
|
import { OpenId } from "../../models/OpenId";
|
||||||
import { ScalarAccountResponse, ScalarRegisterResponse } from "../../models/ScalarResponses";
|
import { ScalarAccountResponse, ScalarRegisterResponse } from "../../models/ScalarResponses";
|
||||||
import { AutoWired, Inject } from "typescript-ioc/es6";
|
|
||||||
import AccountController from "../controllers/AccountController";
|
import AccountController from "../controllers/AccountController";
|
||||||
import { ROLE_USER } from "../security/MatrixSecurity";
|
import { ROLE_USER } from "../security/MatrixSecurity";
|
||||||
import TermsController, { ITermsResponse } from "../controllers/TermsController";
|
import TermsController, { ITermsResponse } from "../controllers/TermsController";
|
||||||
@ -14,15 +13,8 @@ import { ScalarClient } from "../../scalar/ScalarClient";
|
|||||||
* and general account management.
|
* and general account management.
|
||||||
*/
|
*/
|
||||||
@Path("/api/v1/scalar")
|
@Path("/api/v1/scalar")
|
||||||
@AutoWired
|
|
||||||
export class ScalarService {
|
export class ScalarService {
|
||||||
|
|
||||||
@Inject
|
|
||||||
private accountController: AccountController;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private termsController: TermsController;
|
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private context: ServiceContext;
|
private context: ServiceContext;
|
||||||
|
|
||||||
@ -33,7 +25,7 @@ export class ScalarService {
|
|||||||
throw new ApiError(401, "Invalid API version.");
|
throw new ApiError(401, "Invalid API version.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.accountController.registerAccount(request, ScalarClient.KIND_LEGACY);
|
const response = await new AccountController().registerAccount(request, ScalarClient.KIND_LEGACY);
|
||||||
return {scalar_token: response.token};
|
return {scalar_token: response.token};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,14 +43,14 @@ export class ScalarService {
|
|||||||
@GET
|
@GET
|
||||||
@Path("terms")
|
@Path("terms")
|
||||||
public async getTerms(): Promise<ITermsResponse> {
|
public async getTerms(): Promise<ITermsResponse> {
|
||||||
return this.termsController.getAvailableTerms();
|
return new TermsController().getAvailableTerms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("terms")
|
@Path("terms")
|
||||||
@Security(ROLE_USER)
|
@Security(ROLE_USER)
|
||||||
public async signTerms(request: SignTermsRequest): Promise<any> {
|
public async signTerms(request: SignTermsRequest): Promise<any> {
|
||||||
await this.termsController.signTermsMatching(this.context.request.user, request.user_accepts);
|
await new TermsController().signTermsMatching(this.context.request.user, request.user_accepts);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { Cache, CACHE_WIDGET_TITLES } from "../../MemoryCache";
|
|||||||
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
|
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import { ROLE_USER } from "../security/MatrixSecurity";
|
import { ROLE_USER } from "../security/MatrixSecurity";
|
||||||
import moment = require("moment");
|
import moment from 'moment';
|
||||||
|
|
||||||
interface UrlPreviewResponse {
|
interface UrlPreviewResponse {
|
||||||
cached_response: boolean;
|
cached_response: boolean;
|
||||||
|
@ -6,7 +6,7 @@ import UserScalarToken from "./models/UserScalarToken";
|
|||||||
import Upstream from "./models/Upstream";
|
import Upstream from "./models/Upstream";
|
||||||
import WidgetRecord from "./models/WidgetRecord";
|
import WidgetRecord from "./models/WidgetRecord";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as Umzug from "umzug";
|
import { SequelizeStorage, Umzug } from "umzug";
|
||||||
import AppService from "./models/AppService";
|
import AppService from "./models/AppService";
|
||||||
import AppServiceUser from "./models/AppServiceUser";
|
import AppServiceUser from "./models/AppServiceUser";
|
||||||
import NebConfiguration from "./models/NebConfiguration";
|
import NebConfiguration from "./models/NebConfiguration";
|
||||||
@ -79,15 +79,20 @@ class _DimensionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updateSchema(): Promise<any> {
|
public updateSchema(): Promise<any> {
|
||||||
LogService.info("DimensionStore", "Updating schema...");
|
LogService.info("DimensionStore", "Updating schema...",);
|
||||||
|
|
||||||
const migrator = new Umzug({
|
const migrator = new Umzug({
|
||||||
storage: "sequelize",
|
|
||||||
storageOptions: {sequelize: this.sequelize},
|
|
||||||
migrations: {
|
migrations: {
|
||||||
params: [this.sequelize.getQueryInterface()],
|
glob: path.join(__dirname, "migrations/*"),
|
||||||
path: path.join(__dirname, "migrations"),
|
resolve: ({name, path, context}) => {
|
||||||
}
|
// Adjust the migration from the new signature to the v2 signature, making easier to upgrade to v3
|
||||||
|
const migration = require(path)
|
||||||
|
return { name, up: async () => migration.default.up(context), down: async () => migration.default.down(context) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: this.sequelize.getQueryInterface(),
|
||||||
|
storage: new SequelizeStorage({ sequelize: this.sequelize }),
|
||||||
|
logger: console
|
||||||
});
|
});
|
||||||
|
|
||||||
return migrator.up();
|
return migrator.up();
|
||||||
|
@ -28,69 +28,69 @@ export class NebStore {
|
|||||||
// TODO: Support Circle CI
|
// TODO: Support Circle CI
|
||||||
// "circleci": {
|
// "circleci": {
|
||||||
// name: "Circle CI",
|
// name: "Circle CI",
|
||||||
// avatarUrl: "/img/avatars/circleci.png",
|
// avatarUrl: "/assets/img/avatars/circleci.png",
|
||||||
// description: "Announces build results from Circle CI to the room.",
|
// description: "Announces build results from Circle CI to the room.",
|
||||||
// simple: false,
|
// simple: false,
|
||||||
// },
|
// },
|
||||||
"echo": {
|
"echo": {
|
||||||
name: "Echo",
|
name: "Echo",
|
||||||
avatarUrl: "/img/avatars/echo.png", // TODO: Make this image
|
avatarUrl: "/assets/img/avatars/echo.png", // TODO: Make this image
|
||||||
description: "Repeats text given to it from !echo",
|
description: "Repeats text given to it from !echo",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
"giphy": {
|
"giphy": {
|
||||||
name: "Giphy",
|
name: "Giphy",
|
||||||
avatarUrl: "/img/avatars/giphy.png",
|
avatarUrl: "/assets/img/avatars/giphy.png",
|
||||||
description: "Posts a GIF from Giphy using !giphy <query>",
|
description: "Posts a GIF from Giphy using !giphy <query>",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
"guggy": {
|
"guggy": {
|
||||||
name: "Guggy",
|
name: "Guggy",
|
||||||
avatarUrl: "/img/avatars/guggy.png",
|
avatarUrl: "/assets/img/avatars/guggy.png",
|
||||||
description: "Send a reaction GIF using !guggy <query>",
|
description: "Send a reaction GIF using !guggy <query>",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
// TODO: Support Github
|
// TODO: Support Github
|
||||||
// "github": {
|
// "github": {
|
||||||
// name: "Github",
|
// name: "Github",
|
||||||
// avatarUrl: "/img/avatars/github.png",
|
// avatarUrl: "/assets/img/avatars/github.png",
|
||||||
// description: "Github issue management and announcements for a repository",
|
// description: "Github issue management and announcements for a repository",
|
||||||
// simple: false,
|
// simple: false,
|
||||||
// },
|
// },
|
||||||
"google": {
|
"google": {
|
||||||
name: "Google",
|
name: "Google",
|
||||||
avatarUrl: "/img/avatars/google.png",
|
avatarUrl: "/assets/img/avatars/google.png",
|
||||||
description: "Searches Google Images using !google image <query>",
|
description: "Searches Google Images using !google image <query>",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
"imgur": {
|
"imgur": {
|
||||||
name: "Imgur",
|
name: "Imgur",
|
||||||
avatarUrl: "/img/avatars/imgur.png",
|
avatarUrl: "/assets/img/avatars/imgur.png",
|
||||||
description: "Searches and posts images from Imgur using !imgur <query>",
|
description: "Searches and posts images from Imgur using !imgur <query>",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
// TODO: Support JIRA
|
// TODO: Support JIRA
|
||||||
// "jira": {
|
// "jira": {
|
||||||
// name: "Jira",
|
// name: "Jira",
|
||||||
// avatarUrl: "/img/avatars/jira.png",
|
// avatarUrl: "/assets/img/avatars/jira.png",
|
||||||
// description: "Jira issue management and announcements for a project",
|
// description: "Jira issue management and announcements for a project",
|
||||||
// simple: false,
|
// simple: false,
|
||||||
// },
|
// },
|
||||||
"rss": {
|
"rss": {
|
||||||
name: "RSS",
|
name: "RSS",
|
||||||
avatarUrl: "/img/avatars/rssbot.png",
|
avatarUrl: "/assets/img/avatars/rssbot.png",
|
||||||
description: "Announces changes to RSS feeds in the room",
|
description: "Announces changes to RSS feeds in the room",
|
||||||
simple: false,
|
simple: false,
|
||||||
},
|
},
|
||||||
"travisci": {
|
"travisci": {
|
||||||
name: "Travis CI",
|
name: "Travis CI",
|
||||||
avatarUrl: "/img/avatars/travisci.png",
|
avatarUrl: "/assets/img/avatars/travisci.png",
|
||||||
description: "Announces build results from Travis CI to the room",
|
description: "Announces build results from Travis CI to the room",
|
||||||
simple: false,
|
simple: false,
|
||||||
},
|
},
|
||||||
"wikipedia": {
|
"wikipedia": {
|
||||||
name: "Wikipedia",
|
name: "Wikipedia",
|
||||||
avatarUrl: "/img/avatars/wikipedia.png",
|
avatarUrl: "/assets/img/avatars/wikipedia.png",
|
||||||
description: "Searches wikipedia using !wikipedia <query>",
|
description: "Searches wikipedia using !wikipedia <query>",
|
||||||
simple: true,
|
simple: true,
|
||||||
},
|
},
|
||||||
|
30
src/db/migrations/20210809223100-ChangeAvatarUrl.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
queryInterface.sequelize.query(
|
||||||
|
"UPDATE dimension_widgets SET avatarUrl = REPLACE(avatarUrl, '/img/', '/assets/img/')"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
queryInterface.sequelize.query(
|
||||||
|
"UPDATE dimension_bridges SET avatarUrl = REPLACE(avatarUrl, '/img/', '/assets/img/')"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
queryInterface.sequelize.query(
|
||||||
|
"UPDATE dimension_widgets SET avatarUrl = REPLACE(avatarUrl, '/assets/img/', '/img/')"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
queryInterface.sequelize.query(
|
||||||
|
"UPDATE dimension_bridges SET avatarUrl = REPLACE(avatarUrl, '/assets/img/', '/img/')"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
@ -5,7 +5,7 @@ import { Column, Model, PrimaryKey, Table } from "sequelize-typescript";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class AppService extends Model<AppService> {
|
export default class AppService extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@Column
|
@Column
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -6,7 +6,7 @@ import AppService from "./AppService";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class AppServiceUser extends Model<AppServiceUser> {
|
export default class AppServiceUser extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@Column
|
@Column
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -6,7 +6,7 @@ import { IntegrationRecord } from "./IntegrationRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class CustomSimpleBotRecord extends Model<CustomSimpleBotRecord> implements IntegrationRecord {
|
export default class CustomSimpleBotRecord extends Model implements IntegrationRecord {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import IrcBridgeRecord from "./IrcBridgeRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class IrcBridgeNetwork extends Model<IrcBridgeNetwork> {
|
export default class IrcBridgeNetwork extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class IrcBridgeRecord extends Model<IrcBridgeRecord> {
|
export default class IrcBridgeRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -7,7 +7,7 @@ import NebIntegration from "./NebIntegration";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class NebBotUser extends Model<NebBotUser> {
|
export default class NebBotUser extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -7,7 +7,7 @@ import AppService from "./AppService";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class NebConfiguration extends Model<NebConfiguration> {
|
export default class NebConfiguration extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -7,7 +7,7 @@ import NebConfiguration from "./NebConfiguration";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class NebIntegration extends Model<NebIntegration> implements IntegrationRecord {
|
export default class NebIntegration extends Model implements IntegrationRecord {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import NebIntegration from "./NebIntegration";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class NebIntegrationConfig extends Model<NebIntegrationConfig> {
|
export default class NebIntegrationConfig extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -8,7 +8,7 @@ import User from "./User";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class NebNotificationUser extends Model<NebNotificationUser> {
|
export default class NebNotificationUser extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class SlackBridgeRecord extends Model<SlackBridgeRecord> {
|
export default class SlackBridgeRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import StickerPack from "./StickerPack";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class Sticker extends Model<Sticker> {
|
export default class Sticker extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import { IntegrationRecord } from "./IntegrationRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class StickerPack extends Model<StickerPack> implements IntegrationRecord {
|
export default class StickerPack extends Model implements IntegrationRecord {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class TelegramBridgeRecord extends Model<TelegramBridgeRecord> {
|
export default class TelegramBridgeRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import TermsTextRecord from "./TermsTextRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class TermsRecord extends Model<TermsRecord> {
|
export default class TermsRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -7,7 +7,7 @@ import TermsRecord from "./TermsRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class TermsSignedRecord extends Model<TermsSignedRecord> {
|
export default class TermsSignedRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -15,7 +15,7 @@ import TermsRecord from "./TermsRecord";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class TermsTextRecord extends Model<TermsTextRecord> {
|
export default class TermsTextRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -15,7 +15,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class TermsUpstreamRecord extends Model<TermsUpstreamRecord> {
|
export default class TermsUpstreamRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -5,7 +5,7 @@ import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-types
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class Upstream extends Model<Upstream> {
|
export default class Upstream extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -5,7 +5,7 @@ import { Column, Model, PrimaryKey, Table } from "sequelize-typescript";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class User extends Model<User> {
|
export default class User extends Model {
|
||||||
// This is really just a holding class to keep foreign keys alive
|
// This is really just a holding class to keep foreign keys alive
|
||||||
|
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
|
@ -16,7 +16,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class UserScalarToken extends Model<UserScalarToken> {
|
export default class UserScalarToken extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -7,7 +7,7 @@ import User from "./User";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class UserStickerPack extends Model<UserStickerPack> {
|
export default class UserStickerPack extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
@ -6,7 +6,7 @@ import User from "./User";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class Webhook extends Model<Webhook> {
|
export default class Webhook extends Model {
|
||||||
// This is really just a holding class to keep foreign keys alive
|
// This is really just a holding class to keep foreign keys alive
|
||||||
|
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
|
@ -6,7 +6,7 @@ import Upstream from "./Upstream";
|
|||||||
underscored: false,
|
underscored: false,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
})
|
})
|
||||||
export default class WebhookBridgeRecord extends Model<WebhookBridgeRecord> {
|
export default class WebhookBridgeRecord extends Model {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@AutoIncrement
|
@AutoIncrement
|
||||||
@Column
|
@Column
|
||||||
|
13
src/index.ts
@ -6,6 +6,16 @@ import { CURRENT_VERSION } from "./version";
|
|||||||
import { MatrixStickerBot } from "./matrix/MatrixStickerBot";
|
import { MatrixStickerBot } from "./matrix/MatrixStickerBot";
|
||||||
import * as BotSdk from "matrix-bot-sdk";
|
import * as BotSdk from "matrix-bot-sdk";
|
||||||
import User from "./db/models/User";
|
import User from "./db/models/User";
|
||||||
|
import { ILoggedInUser } from "./api/security/MatrixSecurity";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface User extends ILoggedInUser {
|
||||||
|
userId: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LogService.configure(config.logging);
|
LogService.configure(config.logging);
|
||||||
LogService.info("index", "Starting dimension " + CURRENT_VERSION);
|
LogService.info("index", "Starting dimension " + CURRENT_VERSION);
|
||||||
@ -19,7 +29,8 @@ BotSdk.LogService.setLogger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function startup() {
|
async function startup() {
|
||||||
await DimensionStore.updateSchema();
|
const schemas = await DimensionStore.updateSchema();
|
||||||
|
LogService.info("DimensionStore", schemas);
|
||||||
|
|
||||||
const webserver = new Webserver();
|
const webserver = new Webserver();
|
||||||
await webserver.start();
|
await webserver.start();
|
||||||
|
@ -71,7 +71,7 @@ class _MatrixStickerBot {
|
|||||||
authorName: await this.getUserId(),
|
authorName: await this.getUserId(),
|
||||||
authorReference: "https://matrix.to/#/" + (await this.getUserId()),
|
authorReference: "https://matrix.to/#/" + (await this.getUserId()),
|
||||||
license: "Imported",
|
license: "Imported",
|
||||||
licensePath: "/licenses/general-imported.txt",
|
licensePath: "/assets/licenses/general-imported.txt",
|
||||||
trackingRoomAlias: alias,
|
trackingRoomAlias: alias,
|
||||||
});
|
});
|
||||||
return this.updateStickersInPacks([pack], roomId);
|
return this.updateStickersInPacks([pack], roomId);
|
||||||
|
@ -7,13 +7,13 @@ class _LicenseMap {
|
|||||||
public readonly LICENSE_IMPORTED = "Imported";
|
public readonly LICENSE_IMPORTED = "Imported";
|
||||||
|
|
||||||
private map: { [shortcode: string]: string } = {
|
private map: { [shortcode: string]: string } = {
|
||||||
"Imported": "/licenses/general-imported.txt",
|
"Imported": "/assets/licenses/general-imported.txt",
|
||||||
"telegram": "/licenses/telegram-imported.txt",
|
"telegram": "/assets/licenses/telegram-imported.txt",
|
||||||
"GPL v3.0": "/licenses/gpl-v3.0.txt",
|
"GPL v3.0": "/assets/licenses/gpl-v3.0.txt",
|
||||||
"CC BY-NC-SA 4.0": "/licenses/cc_by-nc-sa_4.0.txt",
|
"CC BY-NC-SA 4.0": "/assets/licenses/cc_by-nc-sa_4.0.txt",
|
||||||
"CC BY-NC 4.0": "/licenses/cc_by-nc_4.0.txt",
|
"CC BY-NC 4.0": "/assets/licenses/cc_by-nc_4.0.txt",
|
||||||
"CC BY-SA 4.0": "/licenses/cc_by-sa_4.0.txt",
|
"CC BY-SA 4.0": "/assets/licenses/cc_by-sa_4.0.txt",
|
||||||
"CC BY 4.0": "/licenses/cc_by_4.0.txt",
|
"CC BY 4.0": "/assets/licenses/cc_by_4.0.txt",
|
||||||
};
|
};
|
||||||
|
|
||||||
public find(name: string): License {
|
public find(name: string): License {
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import * as git from "git-rev-sync";
|
import * as child_process from 'child_process';
|
||||||
|
|
||||||
let version = "Unknown";
|
let version = "Unknown";
|
||||||
let gitHash = null;
|
let gitHash = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
version = "v" + require("../../package.json").version;
|
version = "v" + require("../package.json").version;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// The log service isn't set up by the time we require this file
|
// The log service isn't set up by the time we require this file
|
||||||
console.error("version", error);
|
console.error("version", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gitHash = git.short();
|
gitHash = child_process
|
||||||
|
.execSync('git rev-parse --short HEAD')
|
||||||
|
.toString().trim()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// The log service isn't set up by the time we require this file
|
// The log service isn't set up by the time we require this file
|
||||||
console.error("version", error);
|
console.error("version", error);
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "es2015",
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "./build/app",
|
|
||||||
"types": [
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./src/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
15
tsconfig.app.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./web",
|
||||||
|
"outDir": "./build/web",
|
||||||
|
"types": ["bluebird", "body-parser", "jquery", "validator"]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"web/main.ts",
|
||||||
|
"web/polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"web/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
18
tsconfig.backend.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"outDir": "./build/app",
|
||||||
|
"types": ["bluebird", "body-parser", "jquery", "validator"],
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
],
|
||||||
|
"ts-node": {
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,35 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES5",
|
|
||||||
"module": "commonjs",
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"noEmitHelpers": false,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"lib": ["es2015", "dom"]
|
|
||||||
},
|
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"buildOnSave": false,
|
"compilerOptions": {
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"baseUrl": "./",
|
||||||
"forkChecker": true,
|
"outDir": "./build",
|
||||||
"useWebpackText": true
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": false, // TODO: Should be fixed
|
||||||
|
"noImplicitAny": false, // TODO: Should be fixed
|
||||||
|
"noImplicitReturns": false, // TODO: Should be fixed
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": [
|
||||||
|
"es2018",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictDomEventTypes": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"strictTemplates": true,
|
||||||
|
"types" : ["node"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": ["jasmine", "bluebird", "body-parser", "jquery", "validator"]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"web/test.ts",
|
||||||
|
"web/polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"web/**/*.spec.ts",
|
||||||
|
"web/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
111
tslint.json
@ -1,111 +0,0 @@
|
|||||||
{
|
|
||||||
"rulesDirectory": [
|
|
||||||
"node_modules/codelyzer"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"class-name": false,
|
|
||||||
"comment-format": [
|
|
||||||
true
|
|
||||||
],
|
|
||||||
"curly": false,
|
|
||||||
"eofline": false,
|
|
||||||
"forin": false,
|
|
||||||
"indent": [
|
|
||||||
true,
|
|
||||||
"spaces"
|
|
||||||
],
|
|
||||||
"label-position": true,
|
|
||||||
"max-line-length": false,
|
|
||||||
"member-access": false,
|
|
||||||
"member-ordering": [
|
|
||||||
true,
|
|
||||||
"static-after-instance",
|
|
||||||
"variables-before-functions"
|
|
||||||
],
|
|
||||||
"no-arg": true,
|
|
||||||
"no-bitwise": false,
|
|
||||||
"no-console": [
|
|
||||||
true,
|
|
||||||
"debug",
|
|
||||||
"info",
|
|
||||||
"time",
|
|
||||||
"timeEnd",
|
|
||||||
"trace"
|
|
||||||
],
|
|
||||||
"no-construct": true,
|
|
||||||
"no-debugger": true,
|
|
||||||
"no-duplicate-variable": true,
|
|
||||||
"no-empty": false,
|
|
||||||
"no-eval": true,
|
|
||||||
"no-inferrable-types": true,
|
|
||||||
"no-shadowed-variable": false,
|
|
||||||
"no-string-literal": false,
|
|
||||||
"no-switch-case-fall-through": true,
|
|
||||||
"no-trailing-whitespace": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-use-before-declare": false,
|
|
||||||
"no-var-keyword": true,
|
|
||||||
"object-literal-sort-keys": false,
|
|
||||||
"one-line": [
|
|
||||||
true,
|
|
||||||
"check-open-brace",
|
|
||||||
"check-catch",
|
|
||||||
"check-else",
|
|
||||||
"check-whitespace"
|
|
||||||
],
|
|
||||||
"quotemark": false,
|
|
||||||
"radix": true,
|
|
||||||
"semicolon": [
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"triple-equals": [],
|
|
||||||
"typedef-whitespace": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"call-signature": "nospace",
|
|
||||||
"index-signature": "nospace",
|
|
||||||
"parameter": "nospace",
|
|
||||||
"property-declaration": "nospace",
|
|
||||||
"variable-declaration": "nospace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": [
|
|
||||||
true,
|
|
||||||
"check-branch",
|
|
||||||
"check-decl",
|
|
||||||
"check-operator",
|
|
||||||
"check-separator",
|
|
||||||
"check-type"
|
|
||||||
],
|
|
||||||
"directive-selector": [
|
|
||||||
true,
|
|
||||||
"attribute",
|
|
||||||
"my",
|
|
||||||
"camelCase"
|
|
||||||
],
|
|
||||||
"component-selector": [
|
|
||||||
true,
|
|
||||||
"element",
|
|
||||||
"my",
|
|
||||||
"kebab-case"
|
|
||||||
],
|
|
||||||
"use-input-property-decorator": true,
|
|
||||||
"use-output-property-decorator": true,
|
|
||||||
"use-host-property-decorator": true,
|
|
||||||
"no-input-rename": true,
|
|
||||||
"no-output-rename": true,
|
|
||||||
"use-life-cycle-interface": true,
|
|
||||||
"use-pipe-transform-interface": true,
|
|
||||||
"component-class-suffix": true,
|
|
||||||
"directive-class-suffix": true,
|
|
||||||
"pipe-naming": [
|
|
||||||
true,
|
|
||||||
"camelCase",
|
|
||||||
"my"
|
|
||||||
],
|
|
||||||
"no-attribute-parameter-decorator": true,
|
|
||||||
"no-forward-ref": true,
|
|
||||||
"import-destructuring-spacing": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,8 +2,6 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<toaster-container></toaster-container>
|
<toaster-container></toaster-container>
|
||||||
<!-- The breadcrumb needs to be defined here otherwise it doesn't work -->
|
|
||||||
<breadcrumb [allowBootstrap]="false" [hidden]="true"></breadcrumb>
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ApplicationRef, Injector, NgModule } from "@angular/core";
|
import { ApplicationRef, Injector, NgModule } from "@angular/core";
|
||||||
import { ModalModule } from "ngx-modialog";
|
import { ModalModule } from "ngx-modialog";
|
||||||
import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap";
|
import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap";
|
||||||
import { BreadcrumbsModule } from "ng2-breadcrumbs";
|
import { BreadcrumbModule } from "xng-breadcrumb";
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { UiSwitchModule } from "angular2-ui-switch";
|
import { UiSwitchModule } from "ngx-ui-switch";
|
||||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { routing } from "./app.routing";
|
import { routing } from "./app.routing";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule } from "@angular/forms";
|
||||||
@ -120,10 +120,11 @@ import { WhiteboardWidgetComponent } from "./configs/widget/whiteboard/whiteboar
|
|||||||
import { AdminWidgetWhiteboardConfigComponent } from "./admin/widgets/whiteboard/whiteboard.component";
|
import { AdminWidgetWhiteboardConfigComponent } from "./admin/widgets/whiteboard/whiteboard.component";
|
||||||
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
||||||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
||||||
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
// AoT requires an exported function for factories
|
// AoT requires an exported function for factories
|
||||||
export function HttpLoaderFactory(http: HttpClient) {
|
export function HttpLoaderFactory(http: HttpClient) {
|
||||||
return new TranslateHttpLoader(http);
|
return new TranslateHttpLoader(http);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -140,6 +141,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||||||
BootstrapModalModule,
|
BootstrapModalModule,
|
||||||
BreadcrumbsModule,
|
BreadcrumbsModule,
|
||||||
CKEditorModule,
|
CKEditorModule,
|
||||||
|
FontAwesomeModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
@ -288,25 +290,27 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(public appRef: ApplicationRef, injector: Injector) {
|
constructor(public appRef: ApplicationRef, injector: Injector) {
|
||||||
ServiceLocator.injector = injector;
|
ServiceLocator.injector = injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
hmrOnInit(store) {
|
hmrOnInit(store) {
|
||||||
console.log("HMR store", store);
|
console.log("HMR store", store);
|
||||||
}
|
}
|
||||||
|
|
||||||
hmrOnDestroy(store) {
|
hmrOnDestroy(store) {
|
||||||
let cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
|
let cmpLocation = this.appRef.components.map(
|
||||||
// recreate elements
|
(cmp) => cmp.location.nativeElement
|
||||||
store.disposeOldHosts = createNewHosts(cmpLocation);
|
);
|
||||||
// remove styles
|
// recreate elements
|
||||||
removeNgStyles();
|
store.disposeOldHosts = createNewHosts(cmpLocation);
|
||||||
}
|
// remove styles
|
||||||
|
removeNgStyles();
|
||||||
|
}
|
||||||
|
|
||||||
hmrAfterDestroy(store) {
|
hmrAfterDestroy(store) {
|
||||||
// display new elements
|
// display new elements
|
||||||
store.disposeOldHosts();
|
store.disposeOldHosts();
|
||||||
delete store.disposeOldHosts;
|
delete store.disposeOldHosts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OnDestroy, OnInit } from "@angular/core";
|
import { Inject, Injectable, Input, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FE_Bridge } from "../../shared/models/integration";
|
import { FE_Bridge } from "../../shared/models/integration";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Subscription } from "rxjs/Subscription";
|
import { Subscription } from "rxjs/Subscription";
|
||||||
@ -8,6 +8,7 @@ import { ServiceLocator } from "../../shared/registry/locator.service";
|
|||||||
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class BridgeComponent<T> implements OnInit, OnDestroy {
|
export class BridgeComponent<T> implements OnInit, OnDestroy {
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
@ -22,7 +23,7 @@ export class BridgeComponent<T> implements OnInit, OnDestroy {
|
|||||||
protected route = ServiceLocator.injector.get(ActivatedRoute);
|
protected route = ServiceLocator.injector.get(ActivatedRoute);
|
||||||
protected scalarClientApi = ServiceLocator.injector.get(ScalarClientApiService);
|
protected scalarClientApi = ServiceLocator.injector.get(ScalarClientApiService);
|
||||||
|
|
||||||
constructor(private integrationType: string, public translate: TranslateService) {
|
constructor(@Inject(String) private integrationType: string, public translate: TranslateService) {
|
||||||
this.translate = translate;
|
this.translate = translate;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
{{'In order to bridge Slack channels, you\'ll need to authorize the bridge to access your teams and channels. Please click the button below to do so.' | translate}}
|
{{'In order to bridge Slack channels, you\'ll need to authorize the bridge to access your teams and channels. Please click the button below to do so.' | translate}}
|
||||||
</p>
|
</p>
|
||||||
<a [href]="authUrl" rel="noopener" target="_blank">
|
<a [href]="authUrl" rel="noopener" target="_blank">
|
||||||
<img src="/img/slack_auth_button.png" class="slack-auth-button" alt="sign in with slack"/>
|
<img src="/assets/img/slack_auth_button.png" class="slack-auth-button" alt="sign in with slack"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!isBridged && !needsAuth">
|
<div *ngIf="!isBridged && !needsAuth">
|
||||||
|
@ -99,11 +99,12 @@ export class TelegramBridgeConfigComponent extends BridgeComponent<TelegramConfi
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.telegram.bridgeRoom(this.roomId, this.bridge.config.portalInfo.chatId, forceUnbridge);
|
return this.telegram.bridgeRoom(this.roomId, this.bridge.config.portalInfo.chatId, forceUnbridge);
|
||||||
}).then((portalInfo: FE_PortalInfo) => {
|
}).then((portalInfo) => {
|
||||||
if ((<any>portalInfo).aborted) return;
|
if ((<any>portalInfo).aborted) return;
|
||||||
|
const loadedPortalInfo = portalInfo as FE_PortalInfo
|
||||||
|
|
||||||
this.bridge.config.portalInfo = portalInfo;
|
this.bridge.config.portalInfo = loadedPortalInfo;
|
||||||
this.bridge.config.linked = [portalInfo.chatId];
|
this.bridge.config.linked = [loadedPortalInfo.chatId];
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
this.translate.get('Bridge updated').subscribe((res: string) => {this.toaster.pop("success", res); });
|
this.translate.get('Bridge updated').subscribe((res: string) => {this.toaster.pop("success", res); });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OnDestroy, OnInit } from "@angular/core";
|
import { Inject, Injectable, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FE_ComplexBot } from "../../shared/models/integration";
|
import { FE_ComplexBot } from "../../shared/models/integration";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Subscription } from "rxjs/Subscription";
|
import { Subscription } from "rxjs/Subscription";
|
||||||
@ -8,6 +8,7 @@ import { ServiceLocator } from "../../shared/registry/locator.service";
|
|||||||
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class ComplexBotComponent<T> implements OnInit, OnDestroy {
|
export class ComplexBotComponent<T> implements OnInit, OnDestroy {
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
@ -23,7 +24,8 @@ export class ComplexBotComponent<T> implements OnInit, OnDestroy {
|
|||||||
protected route = ServiceLocator.injector.get(ActivatedRoute);
|
protected route = ServiceLocator.injector.get(ActivatedRoute);
|
||||||
protected scalarClientApi = ServiceLocator.injector.get(ScalarClientApiService);
|
protected scalarClientApi = ServiceLocator.injector.get(ScalarClientApiService);
|
||||||
|
|
||||||
constructor(private integrationType: string, public translate: TranslateService) {
|
constructor(@Inject(String) private integrationType: string, public translate: TranslateService) {
|
||||||
|
this.translate = translate;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<my-spinner></my-spinner>
|
<my-spinner></my-spinner>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!widgetComponent.isLoading">
|
<div *ngIf="!widgetComponent.isLoading">
|
||||||
<my-ibox [isCollapsible]="true" [defaultCollapsed]="widgetComponent.defaultExpandedWidgetId">
|
<my-ibox [isCollapsible]="true" [defaultCollapsed]="!!widgetComponent.defaultExpandedWidgetId">
|
||||||
<h5 class="my-ibox-title">
|
<h5 class="my-ibox-title">
|
||||||
<i class="far fa-plus-square"></i> {{ widgetComponent.defaultName | translate }} {{'Add' | translate}}
|
<i class="far fa-plus-square"></i> {{ widgetComponent.defaultName | translate }} {{'Add' | translate}}
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -3,8 +3,8 @@ import { ToasterService } from "angular2-toaster";
|
|||||||
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
||||||
import { ServiceLocator } from "../../shared/registry/locator.service";
|
import { ServiceLocator } from "../../shared/registry/locator.service";
|
||||||
import { SessionStorage } from "../../shared/SessionStorage";
|
import { SessionStorage } from "../../shared/SessionStorage";
|
||||||
import { OnInit } from "@angular/core";
|
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
import { Inject, Injectable, OnInit } from "@angular/core";
|
||||||
|
|
||||||
const SCALAR_WIDGET_LINKS = [
|
const SCALAR_WIDGET_LINKS = [
|
||||||
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?__PNAME__=",
|
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?__PNAME__=",
|
||||||
@ -16,6 +16,7 @@ const SCALAR_WIDGET_LINKS = [
|
|||||||
|
|
||||||
export const DISABLE_AUTOMATIC_WRAPPING = "";
|
export const DISABLE_AUTOMATIC_WRAPPING = "";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class WidgetComponent implements OnInit {
|
export class WidgetComponent implements OnInit {
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
@ -31,13 +32,14 @@ export class WidgetComponent implements OnInit {
|
|||||||
private window = ServiceLocator.injector.get(Window);
|
private window = ServiceLocator.injector.get(Window);
|
||||||
private scalarApi = ServiceLocator.injector.get(ScalarClientApiService);
|
private scalarApi = ServiceLocator.injector.get(ScalarClientApiService);
|
||||||
|
|
||||||
constructor(private widgetTypes: string[],
|
constructor(@Inject(String) private widgetTypes: string[],
|
||||||
public defaultName: string,
|
@Inject(String) public defaultName: string,
|
||||||
private wrapperId = "generic",
|
@Inject(String) private wrapperId = "generic",
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
private scalarWrapperId = null,
|
@Inject(String) private scalarWrapperId = null,
|
||||||
private scalarWrapperUrlParamName = "url") {
|
@Inject(String) private scalarWrapperUrlParamName = "url") {
|
||||||
this.translate = translate;
|
this.translate = translate;
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<button myToggleFullscreen="">
|
<button myToggleFullscreen="">
|
||||||
<img [src]="isFullscreen ? '/img/exit-fullscreen.png' : '/img/enter-fullscreen.png'"/>
|
<img [src]="isFullscreen ? '/assets/img/exit-fullscreen.png' : '/assets/img/enter-fullscreen.png'"/>
|
||||||
</button>
|
</button>
|
@ -1,4 +1,4 @@
|
|||||||
// component styles are encapsulated and only applied to their components
|
// component styles are encapsulated and only applied to their components
|
||||||
@import "../../../../node_modules/spinkit/scss/spinners/11-folding-cube";
|
@import "../../../../node_modules/spinkit/spinkit.min.css";
|
||||||
|
|
||||||
// see app.scss for the spinner color stuff
|
// see app.scss for the spinner color stuff
|
@ -1,6 +1,6 @@
|
|||||||
<div *ngIf="showPromoPage" class="promo">
|
<div *ngIf="showPromoPage" class="promo">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<img src="/img/logo/banner-sm.png" class="logo">
|
<img src="/assets/img/logo/banner-sm.png" class="logo">
|
||||||
<h1>{{'An open source integration manager for Matrix' | translate}}</h1>
|
<h1>{{'An open source integration manager for Matrix' | translate}}</h1>
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
{{'Self-host your favourite bots, bridges, and widgets.' | translate}}
|
{{'Self-host your favourite bots, bridges, and widgets.' | translate}}
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<pre>{{ integrationsConfig }}</pre>
|
<pre>{{ integrationsConfig }}</pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot shadowed">
|
<div class="screenshot shadowed">
|
||||||
<img src="/img/screenshot.png">
|
<img src="/assets/img/screenshot.png">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -34,52 +34,52 @@
|
|||||||
|
|
||||||
<div class="integration-list">
|
<div class="integration-list">
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/jitsi.png">
|
<img src="/assets/img/avatars/jitsi.png">
|
||||||
<span></span>
|
<span>Jitsi</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/etherpad.png">
|
<img src="/assets/img/avatars/etherpad.png">
|
||||||
<span>{{'Notes' | translate}}</span>
|
<span>{{'Notes' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/tradingview.png">
|
<img src="/assets/img/avatars/tradingview.png">
|
||||||
<span></span>
|
<span>TradingView</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/spotify.png">
|
<img src="/assets/img/avatars/spotify.png">
|
||||||
<span>Spotify</span>
|
<span>Spotify</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/youtube.png">
|
<img src="/assets/img/avatars/youtube.png">
|
||||||
<span>YouTube</span>
|
<span>YouTube</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/twitch.png">
|
<img src="/assets/img/avatars/twitch.png">
|
||||||
<span>Twitch Livestream</span>
|
<span>Twitch Livestream</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/grafana.png">
|
<img src="/assets/img/avatars/grafana.png">
|
||||||
<span>Grafana</span>
|
<span>Grafana</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/googledocs.png">
|
<img src="/assets/img/avatars/googledocs.png">
|
||||||
<span>Google Docs</span>
|
<span>Google Docs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/googlecalendar.png">
|
<img src="/assets/img/avatars/googlecalendar.png">
|
||||||
<span>{{'Google Calendar' | translate}}</span>
|
<span>{{'Google Calendar' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/bigbluebutton.png">
|
<img src="/assets/img/avatars/bigbluebutton.png">
|
||||||
<span>BigBlueButton</span>
|
<span>BigBlueButton</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/customwidget.png">
|
<img src="/assets/img/avatars/whiteboard.png">
|
||||||
<span>{{'Custom Widget' | translate}}</span>
|
<span>Whiteboard</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/whiteboard.png">
|
<img src="/assets/img/avatars/customwidget.png">
|
||||||
<span>Whiteboard</span>
|
<span>{{'Custom Widget' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -92,51 +92,51 @@
|
|||||||
|
|
||||||
<div class="integration-list">
|
<div class="integration-list">
|
||||||
<!--<div class="integration">-->
|
<!--<div class="integration">-->
|
||||||
<!--<img src="/img/avatars/github.png">-->
|
<!--<img src="/assets/img/avatars/github.png">-->
|
||||||
<!--<span>GitHub</span>-->
|
<!--<span>GitHub</span>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<!--<div class="integration">-->
|
<!--<div class="integration">-->
|
||||||
<!--<img src="/img/avatars/jira.png">-->
|
<!--<img src="/assets/img/avatars/jira.png">-->
|
||||||
<!--<span>Jira</span>-->
|
<!--<span>Jira</span>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/guggy.png">
|
<img src="/assets/img/avatars/guggy.png">
|
||||||
<span>{{'Guggy' | translate}}</span>
|
<span>{{'Guggy' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/giphy.png">
|
<img src="/assets/img/avatars/giphy.png">
|
||||||
<span>{{'Giphy' | translate}}</span>
|
<span>{{'Giphy' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/imgur.png">
|
<img src="/assets/img/avatars/imgur.png">
|
||||||
<span>{{'Imgur' | translate}}</span>
|
<span>{{'Imgur' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/google.png">
|
<img src="/assets/img/avatars/google.png">
|
||||||
<span>{{'Google Image Search' | translate}}</span>
|
<span>{{'Google Image Search' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/wikipedia.png">
|
<img src="/assets(img/avatars/wikipedia.png">
|
||||||
<span>{{'Wikipedia' | translate}}</span>
|
<span>{{'Wikipedia' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/travisci.png">
|
<img src="/assets/img/avatars/travisci.png">
|
||||||
<span>{{'Travis CI' | translate}}</span>
|
<span>{{'Travis CI' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<!--<div class="integration">-->
|
<!--<div class="integration">-->
|
||||||
<!--<img src="/img/avatars/circleci.png">-->
|
<!--<img src="/assets/img/avatars/circleci.png">-->
|
||||||
<!--<span>Circle CI</span>-->
|
<!--<span>Circle CI</span>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/rssbot.png">
|
<img src="/assets/img/avatars/rssbot.png">
|
||||||
<span>{{'RSS Notifications' | translate}}</span>
|
<span>{{'RSS Notifications' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/echo.png">
|
<img src="/assets/img/avatars/echo.png">
|
||||||
<span>{{'Echo' | translate}}</span>
|
<span>{{'Echo' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/custombots.png">
|
<img src="/assets/img/avatars/custombots.png">
|
||||||
<span>{{'Custom Bots' | translate}}</span>
|
<span>{{'Custom Bots' | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -150,23 +150,23 @@
|
|||||||
|
|
||||||
<div class="integration-list">
|
<div class="integration-list">
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/irc.png">
|
<img src="/assets/img/avatars/irc.png">
|
||||||
<span>IRC</span>
|
<span>IRC</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/telegram.png">
|
<img src="/assets/img/avatars/telegram.png">
|
||||||
<span>Telegram</span>
|
<span>Telegram</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/gitter.png">
|
<img src="/assets/img/avatars/gitter.png">
|
||||||
<span>Gitter</span>
|
<span>Gitter</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/slack.png">
|
<img src="/assets/img/avatars/slack.png">
|
||||||
<span>Slack</span>
|
<span>Slack</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="integration">
|
<div class="integration">
|
||||||
<img src="/img/avatars/webhooks.png">
|
<img src="/assets/img/avatars/webhooks.png">
|
||||||
<span>Webhooks</span>
|
<span>Webhooks</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -204,7 +204,7 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source on GitHub' | translate}}</a>
|
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source on GitHub' | translate}}</a>
|
||||||
<a href="https://matrix.org" target="_blank" class="made-for-matrix-anchor">
|
<a href="https://matrix.org" target="_blank" class="made-for-matrix-anchor">
|
||||||
<img src="/img/logo/made-for-matrix.svg" class="made-for-matrix">
|
<img src="/assets/img/logo/made-for-matrix.svg" class="made-for-matrix">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
||||||
</div>
|
</div>
|
||||||
@ -212,7 +212,7 @@
|
|||||||
|
|
||||||
<div *ngIf="!showPromoPage" class="non-promo">
|
<div *ngIf="!showPromoPage" class="non-promo">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<img src="/img/logo/banner-sm.png" class="logo">
|
<img src="/assets/img/logo/banner-sm.png" class="logo">
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source' | translate}}</a>
|
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source' | translate}}</a>
|
||||||
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
||||||
@ -261,7 +261,7 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source on GitHub' | translate}}</a>
|
<a href="https://github.com/turt2live/matrix-dimension" target="_blank">{{'source on GitHub' | translate}}</a>
|
||||||
<a href="https://matrix.org" target="_blank" class="made-for-matrix-anchor">
|
<a href="https://matrix.org" target="_blank" class="made-for-matrix-anchor">
|
||||||
<img src="/img/logo/made-for-matrix.svg" class="made-for-matrix">
|
<img src="/assets/img/logo/made-for-matrix.svg" class="made-for-matrix">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
<a href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h2 class="pageName">{{ pageName | translate }}</h2>
|
<h2 class="pageName">{{ pageName | translate }}</h2>
|
||||||
<breadcrumb [allowBootstrap]="true" class="dimension-breadcrumb"></breadcrumb>
|
<xng-breadcrumb></xng-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="quickAction">
|
<div class="quickAction">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, NavigationEnd, PRIMARY_OUTLET, Router } from "@angular/router";
|
import { ActivatedRoute, NavigationEnd, PRIMARY_OUTLET, Router } from "@angular/router";
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
import { filter } from "rxjs/operators";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "my-page-header",
|
selector: "my-page-header",
|
||||||
@ -13,12 +14,13 @@ export class PageHeaderComponent {
|
|||||||
|
|
||||||
constructor(private router: Router, private activatedRoute: ActivatedRoute, public translate: TranslateService) {
|
constructor(private router: Router, private activatedRoute: ActivatedRoute, public translate: TranslateService) {
|
||||||
this.translate = translate;
|
this.translate = translate;
|
||||||
this.router.events.filter(ev => ev instanceof NavigationEnd).subscribe((ev: NavigationEnd) => {
|
this.router.events.pipe(filter(ev => ev instanceof NavigationEnd)).subscribe((ev) => {
|
||||||
let currentRoute = this.activatedRoute.root;
|
let currentRoute = this.activatedRoute.root;
|
||||||
let url = "";
|
let url = "";
|
||||||
|
const event = ev as NavigationEnd;
|
||||||
|
|
||||||
while (currentRoute.children.length > 0) {
|
while (currentRoute.children.length > 0) {
|
||||||
let children = currentRoute.children;
|
const children = currentRoute.children;
|
||||||
children.forEach(route => {
|
children.forEach(route => {
|
||||||
if (route.snapshot.data['breadcrumb']) {
|
if (route.snapshot.data['breadcrumb']) {
|
||||||
this.translate.get(route.snapshot.data['breadcrumb']).subscribe((res: string) => {route.snapshot.data['breadcrumb'] = res});
|
this.translate.get(route.snapshot.data['breadcrumb']).subscribe((res: string) => {route.snapshot.data['breadcrumb'] = res});
|
||||||
@ -27,7 +29,7 @@ export class PageHeaderComponent {
|
|||||||
url += "/" + route.snapshot.url.map(s => s.path).join("/");
|
url += "/" + route.snapshot.url.map(s => s.path).join("/");
|
||||||
if (route.outlet !== PRIMARY_OUTLET) return;
|
if (route.outlet !== PRIMARY_OUTLET) return;
|
||||||
if (!route.routeConfig || !route.routeConfig.data) return;
|
if (!route.routeConfig || !route.routeConfig.data) return;
|
||||||
if (url === ev.urlAfterRedirects.split("?")[0]) this.pageName = route.snapshot.data.name;
|
if (url === event.urlAfterRedirects.split("?")[0]) this.pageName = route.snapshot.data.name;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ export class ToggleFullscreenDirective {
|
|||||||
onClick() {
|
onClick() {
|
||||||
// HACK: This should be behind a service in the event the library changes
|
// HACK: This should be behind a service in the event the library changes
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (screenfull.enabled) {
|
if (screenfull.isEnabled) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
screenfull.toggle();
|
screenfull.toggle();
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { FE_Integration } from "./integration";
|
|||||||
export interface FE_DimensionConfig {
|
export interface FE_DimensionConfig {
|
||||||
admins: string[];
|
admins: string[];
|
||||||
widgetBlacklist: string[];
|
widgetBlacklist: string[];
|
||||||
homesever: {
|
homeserver: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
federationUrl: string;
|
federationUrl: string;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OnDestroy, OnInit } from "@angular/core";
|
import { Injectable, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { Subscription } from "rxjs/Subscription";
|
import { Subscription } from "rxjs/Subscription";
|
||||||
import { ScalarWidgetApi } from "../shared/services/scalar/scalar-widget.api";
|
import { ScalarWidgetApi } from "../shared/services/scalar/scalar-widget.api";
|
||||||
import * as semver from "semver";
|
import * as semver from "semver";
|
||||||
@ -14,11 +14,12 @@ export interface OpenIdResponse {
|
|||||||
blocked: boolean;
|
blocked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export abstract class CapableWidget implements OnInit, OnDestroy {
|
export abstract class CapableWidget implements OnInit, OnDestroy {
|
||||||
|
|
||||||
private requestSubscription: Subscription;
|
private requestSubscription: Subscription;
|
||||||
private responseSubscription: Subscription;
|
private responseSubscription: Subscription;
|
||||||
private openIdRequest: { resolve: (a: OpenIdResponse) => void, promise: Promise<OpenIdResponse> } = null;
|
private openIdRequest: { resolve: (a: OpenIdResponse) => void } = null;
|
||||||
|
|
||||||
// The capabilities we support
|
// The capabilities we support
|
||||||
protected supportsScreenshots = false;
|
protected supportsScreenshots = false;
|
||||||
@ -92,9 +93,8 @@ export abstract class CapableWidget implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getOpenIdInfo(): Promise<OpenIdResponse> {
|
protected getOpenIdInfo(): Promise<OpenIdResponse> {
|
||||||
if (this.openIdRequest) return this.openIdRequest.promise;
|
|
||||||
const promise = new Promise<OpenIdResponse>(((resolve, _reject) => {
|
const promise = new Promise<OpenIdResponse>(((resolve, _reject) => {
|
||||||
this.openIdRequest = {resolve: resolve, promise};
|
this.openIdRequest = {resolve: resolve};
|
||||||
ScalarWidgetApi.requestOpenID();
|
ScalarWidgetApi.requestOpenID();
|
||||||
}));
|
}));
|
||||||
return promise;
|
return promise;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-stickers" *ngIf="!isLoading && !authError && (!packs || packs.length === 0)">
|
<div class="no-stickers" *ngIf="!isLoading && !authError && (!packs || packs.length === 0)">
|
||||||
<img src="/img/no_stickers.png"/>
|
<img src="/assets/img/no_stickers.png"/>
|
||||||
<span class="message">{{'You have no sticker packs.' | translate}}</span>
|
<span class="message">{{'You have no sticker packs.' | translate}}</span>
|
||||||
<button class="btn btn-link btn-sm" (click)="openIntegrationManager()">{{'Add some stickers' | translate}}</button>
|
<button class="btn btn-link btn-sm" (click)="openIntegrationManager()">{{'Add some stickers' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
6
web/assets/fonts/opensans100-roboto300.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url('./opensans100-roboto300.ttf') format('truetype');
|
||||||
|
}
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
web/assets/img/avatars/gitter.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |