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
This commit is contained in:
Tony Stipanic 2021-08-11 23:41:29 +02:00
parent 1c459424b7
commit 4954de2a96
No known key found for this signature in database
GPG Key ID: 3026BCCB6C9CC6BD
138 changed files with 11279 additions and 6422 deletions

6
.eslintignore Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,17 @@
"main": "build/app/index.js",
"license": "GPL-3.0",
"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: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:web": "rimraf build/web && webpack --mode production --progress --profile --bail",
"build:app": "rimraf build/app && tsc -p tsconfig-app.json",
"build:web": "rimraf build/web && ng build --configuration production",
"build:app": "rimraf build/app && tsc -p tsconfig.backend.json",
"lint": "npm run-script lint:app && npm run-script lint:web",
"lint:app": "tslint --project ./tsconfig-app.json -t stylish",
"lint:web": "tslint --project ./tsconfig.json -t stylish",
"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"
"lint:app": "tslint --project ./tsconfig.app.json",
"lint:web": "tslint --project ./tsconfig.json"
},
"repository": {
"type": "git",
@ -24,113 +23,121 @@
},
"author": "Travis Ralston",
"dependencies": {
"@ngx-translate/core": "^12.1.2",
"@ngx-translate/http-loader": "^6.0.0",
"@types/bluebird": "^3.5.27",
"@types/body-parser": "^1.17.0",
"@types/node": "^12.0.10",
"@types/validator": "^10.11.1",
"@angular/localize": "^12.1.1",
"@fortawesome/angular-fontawesome": "^0.9.0",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-brands-svg-icons": "^5.15.3",
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@popperjs/core": "^2.9.2",
"body-parser": "^1.19.0",
"config": "^3.1.0",
"config": "^3.3.6",
"dns-then": "^0.1.0",
"express": "^4.17.1",
"git-rev-sync": "^1.12.0",
"isipaddress": "0.0.2",
"js-yaml": "^3.13.1",
"lodash": "^4.17.19",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"matrix-bot-sdk": "^0.3.8",
"matrix-js-snippets": "^0.2.8",
"memory-cache": "^0.2.0",
"mime": "^2.4.2",
"moment": "^2.24.0",
"netmask": "^1.0.6",
"pg": "^8.5.1",
"mime": "^2.5.2",
"moment": "^2.29.1",
"netmask": "^2.0.2",
"ngx-ui-switch": "^12.0.1",
"postcss": "^8.3.5",
"random-string": "^0.2.0",
"request": "^2.88.0",
"request-promise": "^4.2.4",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"require-dir-all": "^0.4.15",
"semver": "^6.0.0",
"sequelize": "^5.18.4",
"sequelize-typescript": "^1.0.0",
"sharp": "^0.27.2",
"semver": "^7.3.5",
"sequelize": "6.6.2",
"sequelize-typescript": "^2.1.0",
"sharp": "^0.28.3",
"split-host": "^0.1.1",
"spotify-uri": "^1.0.0",
"sqlite3": "^4.2.0",
"spotify-uri": "^2.2.0",
"sqlite3": "^5.0.2",
"telegraf": "^3.30.1",
"typescript": "^3.5.2",
"typescript-ioc": "^1.2.5",
"typescript-rest": "^2.2.0",
"umzug": "^2.2.0",
"typescript": "^4.3.5",
"typescript-rest": "^3.0.4",
"umzug": "^3.0.0-beta.16",
"url": "^0.11.0",
"xml2js": "^0.4.23"
"xng-breadcrumb": "^6.7.0"
},
"devDependencies": {
"@angular-devkit/core": "^11.2.4",
"@angular/animations": "^11.2.5",
"@angular/common": "^11.2.5",
"@angular/compiler": "^11.2.5",
"@angular/core": "^11.2.5",
"@angular/forms": "^11.2.5",
"@angular/platform-browser": "^11.2.5",
"@angular/platform-browser-dynamic": "^11.2.5",
"@angular/router": "^11.2.5",
"@angularclass/hmr": "^2.1.3",
"@angular-devkit/build-angular": "^12.1.1",
"@angular-devkit/core": "^12.1.1",
"@angular-eslint/builder": "12.3.0",
"@angular-eslint/eslint-plugin": "12.3.0",
"@angular-eslint/eslint-plugin-template": "12.3.0",
"@angular-eslint/schematics": "12.3.0",
"@angular-eslint/template-parser": "12.3.0",
"@angular/animations": "^12.1.1",
"@angular/cli": "^12.1.1",
"@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",
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"@biesbjerg/ngx-translate-extract": "^7.0.3",
"@ckeditor/ckeditor5-angular": "^1.1.0",
"@ckeditor/ckeditor5-build-classic": "^12.2.0",
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.7",
"@ckeditor/ckeditor5-angular": "^2.0.2",
"@ckeditor/ckeditor5-build-classic": "^29.0.0",
"@fortawesome/fontawesome": "^1.1.8",
"@fortawesome/fontawesome-free-brands": "^5.0.13",
"@fortawesome/fontawesome-free-regular": "^5.0.13",
"@fortawesome/fontawesome-free-solid": "^5.0.13",
"@ng-bootstrap/ng-bootstrap": "^4.2.1",
"@types/jquery": "^3.3.30",
"@ng-bootstrap/ng-bootstrap": "^11.0.0-beta.2",
"@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-toaster": "^7.0.0",
"angular2-ui-switch": "^1.2.0",
"awesome-typescript-loader": "^5.2.1",
"bootstrap": "^4.3.1",
"codelyzer": "^5.1.0",
"copy-webpack-plugin": "^5.1.2",
"core-js": "^3.1.4",
"css-loader": "^3.0.0",
"cssnano": "^4.1.10",
"angular2-toaster": "^11.0.1",
"bootstrap": "^5.0.2",
"copy-webpack-plugin": "^9.0.1",
"core-js": "^3.15.2",
"css-loader": "^5.2.7",
"cssnano": "^5.0.6",
"dom-to-image": "^2.6.0",
"embed-video": "^2.0.4",
"file-loader": "^4.0.0",
"eslint": "^7.26.0",
"eslint-webpack-plugin": "^2.5.4",
"goby": "^1.1.2",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"iso-639-1": "^2.0.5",
"jquery": "^3.5.0",
"html-loader": "^2.1.2",
"html-webpack-plugin": "^5.3.2",
"iso-639-1": "^2.1.9",
"jquery": "^3.6.0",
"json-loader": "^0.5.7",
"mini-css-extract-plugin": "^0.7.0",
"ng2-breadcrumbs": "^0.1.281",
"mini-css-extract-plugin": "^2.1.0",
"ngx-modialog": "^5.0.1",
"node-sass": "^4.14.1",
"postcss-cssnext": "^3.1.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-scss": "^2.0.0",
"raw-loader": "1.0.0",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.1.1",
"postcss-scss": "^4.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^2.6.3",
"rxjs": "^6.6.6",
"rxjs-compat": "^6.5.2",
"sass-loader": "^7.1.0",
"screenfull": "^4.2.0",
"shelljs": "^0.8.3",
"spinkit": "^1.2.5",
"style-loader": "^0.23.1",
"rimraf": "^3.0.2",
"rxjs": "^6.6.7",
"rxjs-compat": "^6.6.7",
"sass-loader": "^12.1.0",
"screenfull": "^5.1.0",
"shelljs": "^0.8.4",
"spinkit": "^2.0.1",
"style-loader": "^3.1.0",
"ts-helpers": "^1.1.2",
"tslint": "^5.18.0",
"tslint-loader": "^3.5.4",
"url-loader": "^2.0.1",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2",
"ts-loader": "^9.2.3",
"ts-node": "^10.1.0",
"ts-node-dev": "^1.1.8",
"webpack": "^5.44.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"zone.js": "^0.11.4"
}
}

14
proxy.conf.json Normal file
View 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
}
}

View File

@ -26,9 +26,8 @@ export default class Webserver {
private loadRoutes() {
// 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();
Server.useIoC();
Server.registerAuthenticator(new MatrixSecurity());
apis.forEach(a => Server.loadServices(router, [a]));
const routes = _.uniq(router.stack.map(r => r.route.path));

View File

@ -89,7 +89,7 @@ export class AdminStickerService {
authorName: "Telegram",
authorReference: request.packUrl,
license: "Telegram",
licensePath: "/licenses/telegram-imported.txt",
licensePath: "/assets/licenses/telegram-imported.txt",
});
const stickers = [];

View File

@ -1,6 +1,5 @@
import { GET, Path, PathParam, POST, PUT, Security } from "typescript-rest";
import TermsController, { ITerms } from "../controllers/TermsController";
import { AutoWired, Inject } from "typescript-ioc/es6";
import { ROLE_ADMIN, ROLE_USER } from "../security/MatrixSecurity";
interface CreatePolicyObject {
@ -13,44 +12,40 @@ interface CreatePolicyObject {
* Administrative API for configuring terms of service.
*/
@Path("/api/v1/dimension/admin/terms")
@AutoWired
export class AdminTermsService {
@Inject
private termsController: TermsController;
@GET
@Path("all")
@Security([ROLE_USER, ROLE_ADMIN])
public async getPolicies(): Promise<ITerms[]> {
return this.termsController.getPoliciesForAdmin();
return new TermsController().getPoliciesForAdmin();
}
@GET
@Path(":shortcode/:version")
@Security([ROLE_USER, ROLE_ADMIN])
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
@Path(":shortcode/draft")
@Security([ROLE_USER, ROLE_ADMIN])
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
@Path(":shortcode/publish/:version")
@Security([ROLE_USER, ROLE_ADMIN])
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
@Path(":shortcode/:version")
@Security([ROLE_USER, ROLE_ADMIN])
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);
}
}

View File

@ -8,7 +8,6 @@ import { ScalarStore } from "../../db/ScalarStore";
import UserScalarToken from "../../db/models/UserScalarToken";
import { ScalarClient } from "../../scalar/ScalarClient";
import * as randomString from "random-string";
import { AutoWired } from "typescript-ioc/es6";
import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache";
import { ILoggedInUser } from "../security/MatrixSecurity";
@ -23,7 +22,6 @@ export interface IAccountInfoResponse {
/**
* API controller for account management
*/
@AutoWired
export default class AccountController {
constructor() {
}

View File

@ -1,4 +1,3 @@
import { AutoWired } from "typescript-ioc/es6";
import { ILoggedInUser } from "../security/MatrixSecurity";
import TermsRecord from "../../db/models/TermsRecord";
import TermsTextRecord from "../../db/models/TermsTextRecord";
@ -50,11 +49,7 @@ export const VERSION_DRAFT = "draft";
/**
* API controller for terms of service management
*/
@AutoWired
export default class TermsController {
constructor() {
}
private async getPublishedTerms(): Promise<ICachedTerms[]> {
const cache = Cache.for(CACHE_TERMS);

View File

@ -29,7 +29,7 @@ export class DimensionWebhooksService {
@POST
@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);
if (!webhook) throw new ApiError(404, "Webhook not found");
if (!webhook.targetUrl) throw new ApiError(400, "Webhook not configured");

View File

@ -76,7 +76,7 @@ export class DimensionWidgetService {
}
// 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) => {
if (err) {
LogService.error("DimensionWidgetService", err);

View File

@ -1,7 +1,6 @@
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
import { OpenId } from "../../models/OpenId";
import AccountController, { IAccountInfoResponse, IAccountRegisteredResponse } from "../controllers/AccountController";
import { AutoWired, Inject } from "typescript-ioc/es6";
import { ILoggedInUser, ROLE_USER } from "../security/MatrixSecurity";
import { ScalarClient } from "../../scalar/ScalarClient";
@ -9,19 +8,15 @@ import { ScalarClient } from "../../scalar/ScalarClient";
* API for account management
*/
@Path("/_matrix/integrations/v1/account")
@AutoWired
export class MatrixAccountService {
@Inject
private accountController: AccountController;
@Context
private context: ServiceContext;
@POST
@Path("register")
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
@ -36,7 +31,7 @@ export class MatrixAccountService {
@Path("logout")
@Security(ROLE_USER)
public async logout(): Promise<any> {
await this.accountController.logout(this.context.request.user);
await new AccountController().logout(this.context.request.user);
return {};
}
}

View File

@ -1,5 +1,4 @@
import { Context, GET, Path, POST, Security, ServiceContext } from "typescript-rest";
import { AutoWired, Inject } from "typescript-ioc/es6";
import { ROLE_USER } from "../security/MatrixSecurity";
import TermsController, { ITermsResponse } from "../controllers/TermsController";
@ -11,26 +10,22 @@ export interface SignTermsRequest {
* API for account management
*/
@Path("/_matrix/integrations/v1/terms")
@AutoWired
export class MatrixTermsService {
@Inject
private termsController: TermsController;
@Context
private context: ServiceContext;
@GET
@Path("")
public async getAllTerms(): Promise<ITermsResponse> {
return this.termsController.getAvailableTerms();
return new TermsController().getAvailableTerms();
}
@POST
@Path("")
@Security(ROLE_USER)
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 {};
}
}

View File

@ -1,5 +1,4 @@
import { GET, Path } from "typescript-rest";
import { AutoWired } from "typescript-ioc/es6";
import { URL } from "url";
import config from "../../config";
@ -16,7 +15,6 @@ interface WellknownResponse {
* Serving of the .well-known file
*/
@Path("/.well-known/matrix")
@AutoWired
export class MatrixWellknownService {
@GET

View File

@ -2,7 +2,6 @@ import { Context, GET, Path, POST, QueryParam, Security, ServiceContext } from "
import { ApiError } from "../ApiError";
import { OpenId } from "../../models/OpenId";
import { ScalarAccountResponse, ScalarRegisterResponse } from "../../models/ScalarResponses";
import { AutoWired, Inject } from "typescript-ioc/es6";
import AccountController from "../controllers/AccountController";
import { ROLE_USER } from "../security/MatrixSecurity";
import TermsController, { ITermsResponse } from "../controllers/TermsController";
@ -14,15 +13,8 @@ import { ScalarClient } from "../../scalar/ScalarClient";
* and general account management.
*/
@Path("/api/v1/scalar")
@AutoWired
export class ScalarService {
@Inject
private accountController: AccountController;
@Inject
private termsController: TermsController;
@Context
private context: ServiceContext;
@ -33,7 +25,7 @@ export class ScalarService {
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};
}
@ -51,14 +43,14 @@ export class ScalarService {
@GET
@Path("terms")
public async getTerms(): Promise<ITermsResponse> {
return this.termsController.getAvailableTerms();
return new TermsController().getAvailableTerms();
}
@POST
@Path("terms")
@Security(ROLE_USER)
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 {};
}

View File

@ -4,7 +4,7 @@ import { Cache, CACHE_WIDGET_TITLES } from "../../MemoryCache";
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
import config from "../../config";
import { ROLE_USER } from "../security/MatrixSecurity";
import moment = require("moment");
import moment from 'moment';
interface UrlPreviewResponse {
cached_response: boolean;

View File

@ -6,7 +6,7 @@ import UserScalarToken from "./models/UserScalarToken";
import Upstream from "./models/Upstream";
import WidgetRecord from "./models/WidgetRecord";
import * as path from "path";
import * as Umzug from "umzug";
import { SequelizeStorage, Umzug } from "umzug";
import AppService from "./models/AppService";
import AppServiceUser from "./models/AppServiceUser";
import NebConfiguration from "./models/NebConfiguration";
@ -79,15 +79,20 @@ class _DimensionStore {
}
public updateSchema(): Promise<any> {
LogService.info("DimensionStore", "Updating schema...");
LogService.info("DimensionStore", "Updating schema...",);
const migrator = new Umzug({
storage: "sequelize",
storageOptions: {sequelize: this.sequelize},
migrations: {
params: [this.sequelize.getQueryInterface()],
path: path.join(__dirname, "migrations"),
}
glob: 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();

View File

@ -28,69 +28,69 @@ export class NebStore {
// TODO: Support Circle CI
// "circleci": {
// name: "Circle CI",
// avatarUrl: "/img/avatars/circleci.png",
// avatarUrl: "/assets/img/avatars/circleci.png",
// description: "Announces build results from Circle CI to the room.",
// simple: false,
// },
"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",
simple: true,
},
"giphy": {
name: "Giphy",
avatarUrl: "/img/avatars/giphy.png",
avatarUrl: "/assets/img/avatars/giphy.png",
description: "Posts a GIF from Giphy using !giphy <query>",
simple: true,
},
"guggy": {
name: "Guggy",
avatarUrl: "/img/avatars/guggy.png",
avatarUrl: "/assets/img/avatars/guggy.png",
description: "Send a reaction GIF using !guggy <query>",
simple: true,
},
// TODO: Support Github
// "github": {
// name: "Github",
// avatarUrl: "/img/avatars/github.png",
// avatarUrl: "/assets/img/avatars/github.png",
// description: "Github issue management and announcements for a repository",
// simple: false,
// },
"google": {
name: "Google",
avatarUrl: "/img/avatars/google.png",
avatarUrl: "/assets/img/avatars/google.png",
description: "Searches Google Images using !google image <query>",
simple: true,
},
"imgur": {
name: "Imgur",
avatarUrl: "/img/avatars/imgur.png",
avatarUrl: "/assets/img/avatars/imgur.png",
description: "Searches and posts images from Imgur using !imgur <query>",
simple: true,
},
// TODO: Support JIRA
// "jira": {
// name: "Jira",
// avatarUrl: "/img/avatars/jira.png",
// avatarUrl: "/assets/img/avatars/jira.png",
// description: "Jira issue management and announcements for a project",
// simple: false,
// },
"rss": {
name: "RSS",
avatarUrl: "/img/avatars/rssbot.png",
avatarUrl: "/assets/img/avatars/rssbot.png",
description: "Announces changes to RSS feeds in the room",
simple: false,
},
"travisci": {
name: "Travis CI",
avatarUrl: "/img/avatars/travisci.png",
avatarUrl: "/assets/img/avatars/travisci.png",
description: "Announces build results from Travis CI to the room",
simple: false,
},
"wikipedia": {
name: "Wikipedia",
avatarUrl: "/img/avatars/wikipedia.png",
avatarUrl: "/assets/img/avatars/wikipedia.png",
description: "Searches wikipedia using !wikipedia <query>",
simple: true,
},

View 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/')"
)
);
},
};

View File

@ -5,7 +5,7 @@ import { Column, Model, PrimaryKey, Table } from "sequelize-typescript";
underscored: false,
timestamps: false,
})
export default class AppService extends Model<AppService> {
export default class AppService extends Model {
@PrimaryKey
@Column
id: string;

View File

@ -6,7 +6,7 @@ import AppService from "./AppService";
underscored: false,
timestamps: false,
})
export default class AppServiceUser extends Model<AppServiceUser> {
export default class AppServiceUser extends Model {
@PrimaryKey
@Column
id: string;

View File

@ -6,7 +6,7 @@ import { IntegrationRecord } from "./IntegrationRecord";
underscored: false,
timestamps: false,
})
export default class CustomSimpleBotRecord extends Model<CustomSimpleBotRecord> implements IntegrationRecord {
export default class CustomSimpleBotRecord extends Model implements IntegrationRecord {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import IrcBridgeRecord from "./IrcBridgeRecord";
underscored: false,
timestamps: false,
})
export default class IrcBridgeNetwork extends Model<IrcBridgeNetwork> {
export default class IrcBridgeNetwork extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class IrcBridgeRecord extends Model<IrcBridgeRecord> {
export default class IrcBridgeRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -7,7 +7,7 @@ import NebIntegration from "./NebIntegration";
underscored: false,
timestamps: false,
})
export default class NebBotUser extends Model<NebBotUser> {
export default class NebBotUser extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -7,7 +7,7 @@ import AppService from "./AppService";
underscored: false,
timestamps: false,
})
export default class NebConfiguration extends Model<NebConfiguration> {
export default class NebConfiguration extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -7,7 +7,7 @@ import NebConfiguration from "./NebConfiguration";
underscored: false,
timestamps: false,
})
export default class NebIntegration extends Model<NebIntegration> implements IntegrationRecord {
export default class NebIntegration extends Model implements IntegrationRecord {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import NebIntegration from "./NebIntegration";
underscored: false,
timestamps: false,
})
export default class NebIntegrationConfig extends Model<NebIntegrationConfig> {
export default class NebIntegrationConfig extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -8,7 +8,7 @@ import User from "./User";
underscored: false,
timestamps: false,
})
export default class NebNotificationUser extends Model<NebNotificationUser> {
export default class NebNotificationUser extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class SlackBridgeRecord extends Model<SlackBridgeRecord> {
export default class SlackBridgeRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import StickerPack from "./StickerPack";
underscored: false,
timestamps: false,
})
export default class Sticker extends Model<Sticker> {
export default class Sticker extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import { IntegrationRecord } from "./IntegrationRecord";
underscored: false,
timestamps: false,
})
export default class StickerPack extends Model<StickerPack> implements IntegrationRecord {
export default class StickerPack extends Model implements IntegrationRecord {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class TelegramBridgeRecord extends Model<TelegramBridgeRecord> {
export default class TelegramBridgeRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import TermsTextRecord from "./TermsTextRecord";
underscored: false,
timestamps: false,
})
export default class TermsRecord extends Model<TermsRecord> {
export default class TermsRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -7,7 +7,7 @@ import TermsRecord from "./TermsRecord";
underscored: false,
timestamps: false,
})
export default class TermsSignedRecord extends Model<TermsSignedRecord> {
export default class TermsSignedRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -15,7 +15,7 @@ import TermsRecord from "./TermsRecord";
underscored: false,
timestamps: false,
})
export default class TermsTextRecord extends Model<TermsTextRecord> {
export default class TermsTextRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -15,7 +15,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class TermsUpstreamRecord extends Model<TermsUpstreamRecord> {
export default class TermsUpstreamRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -5,7 +5,7 @@ import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-types
underscored: false,
timestamps: false,
})
export default class Upstream extends Model<Upstream> {
export default class Upstream extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -5,7 +5,7 @@ import { Column, Model, PrimaryKey, Table } from "sequelize-typescript";
underscored: 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
@PrimaryKey

View File

@ -16,7 +16,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class UserScalarToken extends Model<UserScalarToken> {
export default class UserScalarToken extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -7,7 +7,7 @@ import User from "./User";
underscored: false,
timestamps: false,
})
export default class UserStickerPack extends Model<UserStickerPack> {
export default class UserStickerPack extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,7 +6,7 @@ import User from "./User";
underscored: 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
@PrimaryKey

View File

@ -6,7 +6,7 @@ import Upstream from "./Upstream";
underscored: false,
timestamps: false,
})
export default class WebhookBridgeRecord extends Model<WebhookBridgeRecord> {
export default class WebhookBridgeRecord extends Model {
@PrimaryKey
@AutoIncrement
@Column

View File

@ -6,6 +6,16 @@ import { CURRENT_VERSION } from "./version";
import { MatrixStickerBot } from "./matrix/MatrixStickerBot";
import * as BotSdk from "matrix-bot-sdk";
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.info("index", "Starting dimension " + CURRENT_VERSION);
@ -19,7 +29,8 @@ BotSdk.LogService.setLogger({
});
async function startup() {
await DimensionStore.updateSchema();
const schemas = await DimensionStore.updateSchema();
LogService.info("DimensionStore", schemas);
const webserver = new Webserver();
await webserver.start();

View File

@ -71,7 +71,7 @@ class _MatrixStickerBot {
authorName: await this.getUserId(),
authorReference: "https://matrix.to/#/" + (await this.getUserId()),
license: "Imported",
licensePath: "/licenses/general-imported.txt",
licensePath: "/assets/licenses/general-imported.txt",
trackingRoomAlias: alias,
});
return this.updateStickersInPacks([pack], roomId);

View File

@ -7,13 +7,13 @@ class _LicenseMap {
public readonly LICENSE_IMPORTED = "Imported";
private map: { [shortcode: string]: string } = {
"Imported": "/licenses/general-imported.txt",
"telegram": "/licenses/telegram-imported.txt",
"GPL v3.0": "/licenses/gpl-v3.0.txt",
"CC BY-NC-SA 4.0": "/licenses/cc_by-nc-sa_4.0.txt",
"CC BY-NC 4.0": "/licenses/cc_by-nc_4.0.txt",
"CC BY-SA 4.0": "/licenses/cc_by-sa_4.0.txt",
"CC BY 4.0": "/licenses/cc_by_4.0.txt",
"Imported": "/assets/licenses/general-imported.txt",
"telegram": "/assets/licenses/telegram-imported.txt",
"GPL v3.0": "/assets/licenses/gpl-v3.0.txt",
"CC BY-NC-SA 4.0": "/assets/licenses/cc_by-nc-sa_4.0.txt",
"CC BY-NC 4.0": "/assets/licenses/cc_by-nc_4.0.txt",
"CC BY-SA 4.0": "/assets/licenses/cc_by-sa_4.0.txt",
"CC BY 4.0": "/assets/licenses/cc_by_4.0.txt",
};
public find(name: string): License {

View File

@ -1,17 +1,19 @@
import * as git from "git-rev-sync";
import * as child_process from 'child_process';
let version = "Unknown";
let gitHash = null;
try {
version = "v" + require("../../package.json").version;
version = "v" + require("../package.json").version;
} catch (error) {
// The log service isn't set up by the time we require this file
console.error("version", error);
}
try {
gitHash = git.short();
gitHash = child_process
.execSync('git rev-parse --short HEAD')
.toString().trim()
} catch (error) {
// The log service isn't set up by the time we require this file
console.error("version", error);

View File

@ -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
View 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
View 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"
}
}
}

View File

@ -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,
"buildOnSave": false,
"awesomeTypescriptLoaderOptions": {
"forkChecker": true,
"useWebpackText": true
"compilerOptions": {
"baseUrl": "./",
"outDir": "./build",
"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
View 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"
]
}

View File

@ -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
}
}

View File

@ -2,8 +2,6 @@
</header>
<main>
<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>
</main>
<footer>

View File

@ -1,9 +1,9 @@
import { ApplicationRef, Injector, NgModule } from "@angular/core";
import { ModalModule } from "ngx-modialog";
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 { UiSwitchModule } from "angular2-ui-switch";
import { UiSwitchModule } from "ngx-ui-switch";
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { routing } from "./app.routing";
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 { TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
return new TranslateHttpLoader(http);
}
@NgModule({
@ -140,6 +141,7 @@ export function HttpLoaderFactory(http: HttpClient) {
BootstrapModalModule,
BreadcrumbsModule,
CKEditorModule,
FontAwesomeModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
@ -288,25 +290,27 @@ export function HttpLoaderFactory(http: HttpClient) {
]
})
export class AppModule {
constructor(public appRef: ApplicationRef, injector: Injector) {
ServiceLocator.injector = injector;
}
constructor(public appRef: ApplicationRef, injector: Injector) {
ServiceLocator.injector = injector;
}
hmrOnInit(store) {
console.log("HMR store", store);
}
hmrOnInit(store) {
console.log("HMR store", store);
}
hmrOnDestroy(store) {
let cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
// recreate elements
store.disposeOldHosts = createNewHosts(cmpLocation);
// remove styles
removeNgStyles();
}
hmrOnDestroy(store) {
let cmpLocation = this.appRef.components.map(
(cmp) => cmp.location.nativeElement
);
// recreate elements
store.disposeOldHosts = createNewHosts(cmpLocation);
// remove styles
removeNgStyles();
}
hmrAfterDestroy(store) {
// display new elements
store.disposeOldHosts();
delete store.disposeOldHosts;
}
hmrAfterDestroy(store) {
// display new elements
store.disposeOldHosts();
delete store.disposeOldHosts;
}
}

View File

@ -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 { ActivatedRoute } from "@angular/router";
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 { TranslateService } from "@ngx-translate/core";
@Injectable()
export class BridgeComponent<T> implements OnInit, OnDestroy {
public isLoading = true;
@ -22,7 +23,7 @@ export class BridgeComponent<T> implements OnInit, OnDestroy {
protected route = ServiceLocator.injector.get(ActivatedRoute);
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.isUpdating = false;

View File

@ -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}}
</p>
<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>
</div>
<div *ngIf="!isBridged && !needsAuth">

View File

@ -99,11 +99,12 @@ export class TelegramBridgeConfigComponent extends BridgeComponent<TelegramConfi
}
return this.telegram.bridgeRoom(this.roomId, this.bridge.config.portalInfo.chatId, forceUnbridge);
}).then((portalInfo: FE_PortalInfo) => {
}).then((portalInfo) => {
if ((<any>portalInfo).aborted) return;
const loadedPortalInfo = portalInfo as FE_PortalInfo
this.bridge.config.portalInfo = portalInfo;
this.bridge.config.linked = [portalInfo.chatId];
this.bridge.config.portalInfo = loadedPortalInfo;
this.bridge.config.linked = [loadedPortalInfo.chatId];
this.isUpdating = false;
this.translate.get('Bridge updated').subscribe((res: string) => {this.toaster.pop("success", res); });
}).catch(error => {

View File

@ -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 { ActivatedRoute } from "@angular/router";
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 { TranslateService } from "@ngx-translate/core";
@Injectable()
export class ComplexBotComponent<T> implements OnInit, OnDestroy {
public isLoading = true;
@ -23,7 +24,8 @@ export class ComplexBotComponent<T> implements OnInit, OnDestroy {
protected route = ServiceLocator.injector.get(ActivatedRoute);
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.isUpdating = false;
}

View File

@ -2,7 +2,7 @@
<my-spinner></my-spinner>
</div>
<div *ngIf="!widgetComponent.isLoading">
<my-ibox [isCollapsible]="true" [defaultCollapsed]="widgetComponent.defaultExpandedWidgetId">
<my-ibox [isCollapsible]="true" [defaultCollapsed]="!!widgetComponent.defaultExpandedWidgetId">
<h5 class="my-ibox-title">
<i class="far fa-plus-square"></i> {{ widgetComponent.defaultName | translate }} {{'Add' | translate}}
</h5>

View File

@ -3,8 +3,8 @@ import { ToasterService } from "angular2-toaster";
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
import { ServiceLocator } from "../../shared/registry/locator.service";
import { SessionStorage } from "../../shared/SessionStorage";
import { OnInit } from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
import { Inject, Injectable, OnInit } from "@angular/core";
const SCALAR_WIDGET_LINKS = [
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?__PNAME__=",
@ -16,6 +16,7 @@ const SCALAR_WIDGET_LINKS = [
export const DISABLE_AUTOMATIC_WRAPPING = "";
@Injectable()
export class WidgetComponent implements OnInit {
public isLoading = true;
@ -31,13 +32,14 @@ export class WidgetComponent implements OnInit {
private window = ServiceLocator.injector.get(Window);
private scalarApi = ServiceLocator.injector.get(ScalarClientApiService);
constructor(private widgetTypes: string[],
public defaultName: string,
private wrapperId = "generic",
constructor(@Inject(String) private widgetTypes: string[],
@Inject(String) public defaultName: string,
@Inject(String) private wrapperId = "generic",
public translate: TranslateService,
private scalarWrapperId = null,
private scalarWrapperUrlParamName = "url") {
@Inject(String) private scalarWrapperId = null,
@Inject(String) private scalarWrapperUrlParamName = "url") {
this.translate = translate;
this.isLoading = true;
this.isUpdating = false;
}

View File

@ -1,3 +1,3 @@
<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>

View File

@ -1,4 +1,4 @@
// 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

View File

@ -1,6 +1,6 @@
<div *ngIf="showPromoPage" class="promo">
<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>
<div class="banner">
{{'Self-host your favourite bots, bridges, and widgets.' | translate}}
@ -22,7 +22,7 @@
<pre>{{ integrationsConfig }}</pre>
</div>
<div class="screenshot shadowed">
<img src="/img/screenshot.png">
<img src="/assets/img/screenshot.png">
</div>
</div>
@ -34,52 +34,52 @@
<div class="integration-list">
<div class="integration">
<img src="/img/avatars/jitsi.png">
<span></span>
<img src="/assets/img/avatars/jitsi.png">
<span>Jitsi</span>
</div>
<div class="integration">
<img src="/img/avatars/etherpad.png">
<img src="/assets/img/avatars/etherpad.png">
<span>{{'Notes' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/tradingview.png">
<span></span>
<img src="/assets/img/avatars/tradingview.png">
<span>TradingView</span>
</div>
<div class="integration">
<img src="/img/avatars/spotify.png">
<img src="/assets/img/avatars/spotify.png">
<span>Spotify</span>
</div>
<div class="integration">
<img src="/img/avatars/youtube.png">
<img src="/assets/img/avatars/youtube.png">
<span>YouTube</span>
</div>
<div class="integration">
<img src="/img/avatars/twitch.png">
<img src="/assets/img/avatars/twitch.png">
<span>Twitch Livestream</span>
</div>
<div class="integration">
<img src="/img/avatars/grafana.png">
<img src="/assets/img/avatars/grafana.png">
<span>Grafana</span>
</div>
<div class="integration">
<img src="/img/avatars/googledocs.png">
<img src="/assets/img/avatars/googledocs.png">
<span>Google Docs</span>
</div>
<div class="integration">
<img src="/img/avatars/googlecalendar.png">
<img src="/assets/img/avatars/googlecalendar.png">
<span>{{'Google Calendar' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/bigbluebutton.png">
<img src="/assets/img/avatars/bigbluebutton.png">
<span>BigBlueButton</span>
</div>
<div class="integration">
<img src="/img/avatars/customwidget.png">
<span>{{'Custom Widget' | translate}}</span>
<img src="/assets/img/avatars/whiteboard.png">
<span>Whiteboard</span>
</div>
<div class="integration">
<img src="/img/avatars/whiteboard.png">
<span>Whiteboard</span>
<img src="/assets/img/avatars/customwidget.png">
<span>{{'Custom Widget' | translate}}</span>
</div>
</div>
</div>
@ -92,51 +92,51 @@
<div class="integration-list">
<!--<div class="integration">-->
<!--<img src="/img/avatars/github.png">-->
<!--<img src="/assets/img/avatars/github.png">-->
<!--<span>GitHub</span>-->
<!--</div>-->
<!--<div class="integration">-->
<!--<img src="/img/avatars/jira.png">-->
<!--<img src="/assets/img/avatars/jira.png">-->
<!--<span>Jira</span>-->
<!--</div>-->
<div class="integration">
<img src="/img/avatars/guggy.png">
<img src="/assets/img/avatars/guggy.png">
<span>{{'Guggy' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/giphy.png">
<img src="/assets/img/avatars/giphy.png">
<span>{{'Giphy' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/imgur.png">
<img src="/assets/img/avatars/imgur.png">
<span>{{'Imgur' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/google.png">
<img src="/assets/img/avatars/google.png">
<span>{{'Google Image Search' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/wikipedia.png">
<img src="/assets(img/avatars/wikipedia.png">
<span>{{'Wikipedia' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/travisci.png">
<img src="/assets/img/avatars/travisci.png">
<span>{{'Travis CI' | translate}}</span>
</div>
<!--<div class="integration">-->
<!--<img src="/img/avatars/circleci.png">-->
<!--<img src="/assets/img/avatars/circleci.png">-->
<!--<span>Circle CI</span>-->
<!--</div>-->
<div class="integration">
<img src="/img/avatars/rssbot.png">
<img src="/assets/img/avatars/rssbot.png">
<span>{{'RSS Notifications' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/echo.png">
<img src="/assets/img/avatars/echo.png">
<span>{{'Echo' | translate}}</span>
</div>
<div class="integration">
<img src="/img/avatars/custombots.png">
<img src="/assets/img/avatars/custombots.png">
<span>{{'Custom Bots' | translate}}</span>
</div>
</div>
@ -150,23 +150,23 @@
<div class="integration-list">
<div class="integration">
<img src="/img/avatars/irc.png">
<img src="/assets/img/avatars/irc.png">
<span>IRC</span>
</div>
<div class="integration">
<img src="/img/avatars/telegram.png">
<img src="/assets/img/avatars/telegram.png">
<span>Telegram</span>
</div>
<div class="integration">
<img src="/img/avatars/gitter.png">
<img src="/assets/img/avatars/gitter.png">
<span>Gitter</span>
</div>
<div class="integration">
<img src="/img/avatars/slack.png">
<img src="/assets/img/avatars/slack.png">
<span>Slack</span>
</div>
<div class="integration">
<img src="/img/avatars/webhooks.png">
<img src="/assets/img/avatars/webhooks.png">
<span>Webhooks</span>
</div>
</div>
@ -204,7 +204,7 @@
<div class="footer">
<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">
<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 href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
</div>
@ -212,7 +212,7 @@
<div *ngIf="!showPromoPage" class="non-promo">
<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">
<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>
@ -261,7 +261,7 @@
<div class="footer">
<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">
<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 href="https://matrix.to/#/#dimension:t2bot.io">#dimension:t2bot.io</a>
</div>

View File

@ -2,7 +2,7 @@
<div class="header">
<div class="title">
<h2 class="pageName">{{ pageName | translate }}</h2>
<breadcrumb [allowBootstrap]="true" class="dimension-breadcrumb"></breadcrumb>
<xng-breadcrumb></xng-breadcrumb>
</div>
<div class="quickAction">
<ng-content></ng-content>

View File

@ -1,6 +1,7 @@
import { Component } from "@angular/core";
import { ActivatedRoute, NavigationEnd, PRIMARY_OUTLET, Router } from "@angular/router";
import { TranslateService } from "@ngx-translate/core";
import { filter } from "rxjs/operators";
@Component({
selector: "my-page-header",
@ -13,12 +14,13 @@ export class PageHeaderComponent {
constructor(private router: Router, private activatedRoute: ActivatedRoute, public translate: TranslateService) {
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 url = "";
const event = ev as NavigationEnd;
while (currentRoute.children.length > 0) {
let children = currentRoute.children;
const children = currentRoute.children;
children.forEach(route => {
if (route.snapshot.data['breadcrumb']) {
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("/");
if (route.outlet !== PRIMARY_OUTLET) 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;
});
}
});

View File

@ -10,7 +10,7 @@ export class ToggleFullscreenDirective {
onClick() {
// HACK: This should be behind a service in the event the library changes
// @ts-ignore
if (screenfull.enabled) {
if (screenfull.isEnabled) {
// @ts-ignore
screenfull.toggle();
}

View File

@ -3,7 +3,7 @@ import { FE_Integration } from "./integration";
export interface FE_DimensionConfig {
admins: string[];
widgetBlacklist: string[];
homesever: {
homeserver: {
name: string;
userId: string;
federationUrl: string;

View File

@ -1,4 +1,4 @@
import { OnDestroy, OnInit } from "@angular/core";
import { Injectable, OnDestroy, OnInit } from "@angular/core";
import { Subscription } from "rxjs/Subscription";
import { ScalarWidgetApi } from "../shared/services/scalar/scalar-widget.api";
import * as semver from "semver";
@ -14,11 +14,12 @@ export interface OpenIdResponse {
blocked: boolean;
}
@Injectable()
export abstract class CapableWidget implements OnInit, OnDestroy {
private requestSubscription: 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
protected supportsScreenshots = false;
@ -92,9 +93,8 @@ export abstract class CapableWidget implements OnInit, OnDestroy {
}
protected getOpenIdInfo(): Promise<OpenIdResponse> {
if (this.openIdRequest) return this.openIdRequest.promise;
const promise = new Promise<OpenIdResponse>(((resolve, _reject) => {
this.openIdRequest = {resolve: resolve, promise};
this.openIdRequest = {resolve: resolve};
ScalarWidgetApi.requestOpenID();
}));
return promise;

View File

@ -8,7 +8,7 @@
</div>
</div>
<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>
<button class="btn btn-link btn-sm" (click)="openIntegrationManager()">{{'Add some stickers' | translate}}</button>
</div>

View 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');
}

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More