src/app/about/about.component.ts
import {AsyncPipe, DecimalPipe} from '@angular/common';import {Component, inject, OnInit} from '@angular/core';import {Router, RouterLink} from '@angular/router';import {APIUser} from '@grpc/spec.pb';import {StatisticsClient} from '@grpc/spec.pbsc';import {Empty} from '@ngx-grpc/well-known-types';import {PageEnvService} from '@services/page-env.service';import {UserService} from '@services/user';import escapeStringRegexp from 'escape-string-regexp';import {BytesPipe} from 'ngx-pipes';import {map, switchMap} from 'rxjs/operators';import showdown from 'showdown'; import * as versionJson from '../../version.json'; function replaceAll(str: string, find: string, replace: string): string { return str.replace(new RegExp(escapeStringRegexp(find), 'g'), replace);} function replacePairs(str: string, pairs: {[key: string]: string}): string { for (const key in pairs) { str = replaceAll(str, String(key), pairs[key]); } return str;} const aboutText = $localize`### People Π‘Π²ΠΎΠΈΠΌ ΡΡΡΠ΅ΡΡΠ²ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ Π½Π°Ρ ΠΏΡΠΎΠ΅ΠΊΡ ΠΎΠ±ΡΠ·Π°Π½ Π»ΡΠ΄ΡΠΌ, ΠΏΡΠΈΡ
ΠΎΠ΄ΡΡΠΈΠΌ ΡΡΠ΄Π° ΠΈ Π²ΠΊΠ»Π°Π΄ΡΠ²Π°ΡΡΠΈΠΌ ΡΠ²ΠΎΡ Π²ΡΠ΅ΠΌΡ ΠΈ Π·Π½Π°Π½ΠΈΡ. ΠΡΠΎ-ΡΠΎ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅Ρ ΠΌΠ°ΡΠ΅ΡΠΈΠ°Π»Ρ, Π° ΠΊΡΠΎ-ΡΠΎ ΠΏΠΎΠΌΠΎΠ³Π°Π΅Ρ Π½Π°ΠΉΡΠΈ ΠΎΡΠΈΠ±ΠΊΠΈ Π² ΡΠΆΠ΅ ΠΈΠΌΠ΅ΡΡΠΈΡ
ΡΡ. ΠΡΠΎ-ΡΠΎ ΡΠΏΠ΅ΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅ΡΡΡ Π½Π° ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠΉ ΠΌΠ°ΡΠΊΠ΅, Π° ΠΊΡΠΎ-ΡΠΎ ΡΡΠΏΠ΅Π²Π°Π΅Ρ Π·Π° Π²ΡΠ΅ΠΌ. ΠΡΠΎ-ΡΠΎ Π±Π΅Π· Π»ΠΈΡΠ½Π΅Π³ΠΎ Π²Π½ΠΈΠΌΠ°Π½ΠΈΡ ΡΠΎ ΡΡΠΎΡΠΎΠ½Ρ Π½Π°ΠΏΠΎΠ»Π½ΡΠ΅Ρ ΡΠ°ΠΉΡ ΡΠ°Π³ Π·Π° ΡΠ°Π³ΠΎΠΌ, Π° ΠΊΡΠΎ-ΡΠΎ ΡΠΎΠ±ΠΈΡΠ°Π΅Ρ ΠΎΠ²Π°ΡΠΈΠΈ ΡΠ΅Π΄ΠΊΠΈΠΌΠΈ, Π½ΠΎ ΠΆΠ³ΡΡΠΈΠΌΠΈ ΡΠΎΡΠΎ. ΠΠ°Ρ ΠΌΠ½ΠΎΠ³ΠΎ ΠΈ ΠΌΡ ΡΠ°Π·Π½ΡΠ΅, ΠΈ ΡΡΠΎ ΠΏΡΠ΅ΠΊΡΠ°ΡΠ½ΠΎ. ΠΠΎΡ Π»ΠΈΡΡ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΈΠ· Π½Π°Ρ: %users% #### "Π¦Π²Π΅ΡΠΎΠ²Π°Ρ Π΄ΠΈΡΡΠ΅ΡΠ΅Π½ΡΠΈΠ°ΡΠΈΡ ΡΡΠ°Π½ΠΎΠ²" Π’Π°ΠΊ Π·Π°Π²Π΅Π»ΠΎΡΡ, ΡΡΠΎ ΠΌΡ Π²ΡΠ΄Π΅Π»ΡΠ΅ΠΌ Π½Π΅ΠΊΠΎΡΠΎΡΡΡ
Π½Π°ΡΠΈΡ
Π»ΡΠ΄Π΅ΠΉ ΠΎΡΠΎΠ±ΡΠΌ ΡΠ²Π΅ΡΠΎΠΌ - Π·Π΅Π»Π΅Π½ΡΠΌ. ΠΠ΅ ΠΏΡΠΎΡΡΠΎ ΡΠ°ΠΊ - ΡΡΠΎ ΠΎΡΠΎΠ±Π°Ρ ΠΌΠ΅ΡΠΊΠ°. ΠΠ½Π°ΠΉΡΠ΅, Π΅ΡΠ»ΠΈ Π²Ρ Π²ΠΈΠ΄ΠΈΡΠ΅ ΠΊΠΎΠ³ΠΎ-ΡΠΎ ΠΈΠ· "Π·Π΅Π»Π΅Π½ΡΡ
", Π²Ρ Π²ΡΠ΅Π³Π΄Π° ΠΌΠΎΠΆΠ΅ΡΠ΅ ΡΡ
Π²Π°ΡΠΈΡΡ Π΅Π³ΠΎ ΠΈ ΡΠΏΡΠΎΡΠΈΡΡ ΠΎ ΡΠ΅ΠΌ ΡΠ³ΠΎΠ΄Π½ΠΎ Π²ΠΎΠΊΡΡΠ³ Π½Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΎΠ΅ΠΊΡΠ°, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ "Π·Π΅Π»Π΅Π½ΡΠ΅" - ΡΡΠΎ ΡΠ°ΠΌΡΠ΅ ΠΎΡΠ·ΡΠ²ΡΠΈΠ²ΡΠ΅ ΠΈ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΎΠ²Π°Π½Π½ΡΠ΅ Π² ΠΆΠΈΠ·Π½ΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ° Π»ΡΠ΄ΠΈ. ΠΠ΅ΠΊΠΎΡΠΎΡΠ°Ρ ΡΠ°ΡΡΡ "Π·Π΅Π»Π΅Π½ΡΡ
" Π½Π°Π΄Π΅Π»Π΅Π½Π° ΠΌΠΎΠ΄Π΅ΡΠ°ΡΠΎΡΡΠΊΠΈΠΌΠΈ ΡΡΠ½ΠΊΡΠΈΡΠΌΠΈ. ### Feedback ΠΡΠ»ΠΈ Ρ Π²Π°Ρ Π΅ΡΡΡ ΠΊΠ°ΠΊΠΈΠ΅-ΡΠΎ Π·Π°ΠΌΠ΅ΡΠ°Π½ΠΈΡ, ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΈΠ»ΠΈ ΠΈΠ½ΡΠ΅ ΠΌΡΡΠ»ΠΈ, Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΎΠ·Π²ΡΡΠΈΡΡ ΠΈΡ
Π½Π° [ΡΠΎΡΡΠΌΠ΅](/forums/), Π·Π°Π΄Π°ΡΡ Π»ΠΈΡΠ½ΠΎ ΡΠ΅ΡΠ΅Π· ΡΠΈΡΡΠ΅ΠΌΡ ΠΎΠ±ΠΌΠ΅Π½Π° ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ ΠΈΠ»ΠΈ Π½Π°ΠΏΠΈΡΠ°ΡΡ Π² "[ΠΎΠ±ΡΠ°ΡΠ½ΡΡ ΡΠ²ΡΠ·Ρ](/feedback)" Π°Π΄ΠΌΠΈΠ½ΠΈΡΡΡΠ°ΡΠΈΠΈ ΡΠ°ΠΉΡΠ°. ΠΡΠ»ΠΈ Ρ Π²Π°Ρ Π΅ΡΡΡ Π²ΠΎΠΏΡΠΎΡΡ ΠΎ ΡΠ°Π·ΠΌΠ΅ΡΠ΅Π½ΠΈΠΈ ΡΠ΅ΠΊΠ»Π°ΠΌΡ, ΠΎΠ±ΠΌΠ΅Π½Π° ΡΡΡΠ»ΠΊΠ°ΠΌΠΈ ΠΈΠ»ΠΈ ΠΏΡΠΎΠ΄Π²ΠΈΠΆΠ΅Π½ΠΈΠΈ Π²Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΎΠ΄ΡΠΊΡΠ° ΠΈΠ½ΡΠΌΠΈ ΡΠΏΠΎΡΠΎΠ±Π°ΠΌΠΈ, Π²ΡΠ΅ ΠΎΠ½ΠΈ ΠΈΠΌΠ΅ΡΡ Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ: ΠΌΡ Π½Π΅ ΡΠ°Π·ΠΌΠ΅ΡΠ°Π΅ΠΌ ΡΠ΅ΠΊΠ»Π°ΠΌΡ. ### Numbers Π’Π°ΠΊ ΡΠ»ΠΎΠΆΠΈΠ»ΠΎΡΡ, ΡΡΠΎ ΠΌΡ Π»ΡΠ±ΠΈΠΌ ΡΠ΅ΡΠΈΡΡ ΡΠ²ΠΎΡ ΡΡΠ΅ΡΠ»Π°Π²ΠΈΠ΅ Π±ΠΎΠ»ΡΡΠΈΠΌΠΈ ΡΠΈΡΡΠ°ΠΌΠΈ, Π° ΡΠ°ΠΊΠΆΠ΅ Π²ΡΠ΅ΠΌ ΠΈΡ
ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°ΡΡ. ΠΠ°ΡΠ΅ΠΌΡ Π²Π½ΠΈΠΌΠ°Π½ΠΈΡ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΈΠ· Π½ΠΈΡ
: * Π½Π° ΡΠ°ΠΉΡΠ΅ Π±ΠΎΠ»Π΅Π΅ %total-pictures% ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠΉ, %total-vehicles% Π°Π²ΡΠΎΠΌΠΎΠ±ΠΈΠ»Π΅ΠΉ, ΡΡΠΎ ΡΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΠΏΠΎΡΡΠ΄ΠΊΠ° %total-size% Π΄Π°Π½Π½ΡΡ
* Π·Π°ΡΠ΅Π³ΠΈΡΡΡΠΈΡΠΎΠ²Π°Π½ΠΎ ΠΎΠΊΠΎΠ»ΠΎ %total-users% ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ, ΠΎΡΡΠ°Π²ΠΈΠ²ΡΠΈΡ
Π±ΠΎΠ»Π΅Π΅ %total-comments% ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ ### Development Π Π°Π·ΡΠ°Π±ΠΎΡΠΊΠ° ΠΈ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ° Π²Π΅Π΄Π΅ΡΡΡ Π² ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΌ ΡΠΈΠ»Π°ΠΌΠΈ %developer% ([contributors](https://github.com/autowp/autowp/graphs/contributors)) French site translation: %fr-translator% Chinese site translation: %zh-translator% Belarusian site translation: %be-translator% Brazilian portuguese site translation: %pt-br-translator% Π‘Π°ΠΉΡ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π½Π° [Zend Framework](http://framework.zend.com/), [jQuery](http://jquery.com/), [Twitter bootstrap](http://getbootstrap.com/), Π° ΡΠ°ΠΊΠΆΠ΅ ΠΌΠ½ΠΎΠ³ΠΈΡ
Π΄ΡΡΠ³ΠΈΡ
"ΡΠΌΠ½ΡΡ
ΡΠ»ΠΎΠ²Π°Ρ
". ΠΡΡ
ΠΎΠ΄Π½ΡΠΉ ΠΊΠΎΠ΄ ΡΠ°ΠΉΡΠ° ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΎΡΠΊΡΡΡΡΠΌ, ΡΡΠΎΠ±Ρ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΆΠ΅Π»Π°ΡΡΠΈΠΉ ΠΈΠΌΠ΅Π» Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ Π²Π»ΠΈΡΡΡ Π½Π° ΡΡΡΡ ΠΈ ΠΊΠ°ΡΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΎΠ΅ΠΊΡΠ°. %github% [](https://travis-ci.org/autowp/autowp)[](https://codeclimate.com/github/autowp/autowp)[](https://coveralls.io/github/autowp/autowp?branch=master) ### ΠΠΎΠ΄Π΄Π΅ΡΠΆΠ°ΡΡ ΠΏΡΠΎΠ΅ΠΊΡ You can support our project by [finances](/donate) or [moral](/feedback).Take part in [the translation of the site](https://github.com/autowp/autowp-frontend/tree/master/src/locale) into other languages.`; @Component({ imports: [RouterLink, AsyncPipe], providers: [BytesPipe], selector: 'app-about', templateUrl: './about.component.html',})export class AboutComponent implements OnInit { readonly #userService = inject(UserService); readonly #router = inject(Router); readonly #decimalPipe = inject(DecimalPipe); readonly #bytesPipe = inject(BytesPipe); readonly #pageEnv = inject(PageEnvService); readonly #statGrpc = inject(StatisticsClient); protected readonly version = versionJson; protected readonly html$ = this.#statGrpc .getAboutData(new Empty()) .pipe( switchMap((about) => { const ids: string[] = about.contributors; ids.push(about.developer); ids.push(about.frTranslator); ids.push(about.zhTranslator); ids.push(about.beTranslator); ids.push(about.ptBrTranslator); return this.#userService.getUserMap$(ids).pipe( map((users) => ({ about, aboutText, users, })), ); }), ) .pipe( map((data) => { const contributorsHtml: string[] = []; for (const id of data.about.contributors) { contributorsHtml.push(this.userHtml(data.users.get(id))); } const markdownConverter = new showdown.Converter({}); return replacePairs(markdownConverter.makeHtml(data.aboutText), { '%be-translator%': this.userHtml(data.users.get(data.about.beTranslator)), '%developer%': this.userHtml(data.users.get(data.about.developer)), '%fr-translator%': this.userHtml(data.users.get(data.about.frTranslator)), '%github%': '<i class="bi bi-github" aria-hidden="true"></i> ' + '<a href="https://github.com/autowp/autowp">https://github.com/autowp/autowp</a>', '%pt-br-translator%': this.userHtml(data.users.get(data.about.ptBrTranslator)), '%total-comments%': data.about.totalComments.toString(), '%total-pictures%': this.#decimalPipe.transform(data.about.totalPictures) ?? '', '%total-size%': this.#bytesPipe.transform(data.about.picturesSize * 1024 * 1024, 1).toString(), '%total-users%': data.about.totalUsers.toString(), '%total-vehicles%': data.about.totalItems.toString(), '%users%': contributorsHtml.join(' '), '%zh-translator%': this.userHtml(data.users.get(data.about.zhTranslator)), }); }), ); ngOnInit(): void { setTimeout(() => this.#pageEnv.set({pageId: 136}), 0); } private userHtml(user: APIUser | null | undefined): string { if (!user) { return ''; } const span = document.createElement('span'); const classes = ['user']; if (user.deleted) { classes.push('muted'); } if (user.longAway) { classes.push('long-away'); } if (user.green) { classes.push('green-man'); } span.setAttribute('class', classes.join(' ')); const a = document.createElement('a'); a.setAttribute( 'href', this.#router.createUrlTree(['/users', user.identity ? user.identity : 'user' + user.id]).toString(), ); a.innerText = user.name; return '<i class="bi bi-person-fill" aria-hidden="true"></i> ' + span.appendChild(a).outerHTML; }}