mirror of
https://gitlab.com/aviortheking/code-stats-vscode.git
synced 2025-06-07 15:59:54 +00:00
Made it Prettier(tm)
This commit is contained in:
parent
2392b782b7
commit
01cd77e519
@ -1,78 +1,81 @@
|
|||||||
|
|
||||||
import { Pulse } from "./pulse";
|
import { Pulse } from "./pulse";
|
||||||
import { getISOTimestamp, getLanguageName } from "./utils";
|
import { getISOTimestamp, getLanguageName } from "./utils";
|
||||||
import * as axios from "axios";
|
import * as axios from "axios";
|
||||||
|
|
||||||
export class CodeStatsAPI {
|
export class CodeStatsAPI {
|
||||||
private API_KEY = null;
|
private API_KEY = null;
|
||||||
private UPDATE_URL = "https://codestats.net/api/my/pulses";
|
private UPDATE_URL = "https://codestats.net/api/my/pulses";
|
||||||
private axios = null;
|
private axios = null;
|
||||||
|
|
||||||
constructor(apiKey: string, apiURL: string) {
|
|
||||||
this.API_KEY = apiKey;
|
|
||||||
this.UPDATE_URL = apiURL;
|
|
||||||
if (this.API_KEY === null || this.API_KEY === undefined || this.API_KEY === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.axios = axios.default.create({
|
|
||||||
baseURL: this.UPDATE_URL,
|
|
||||||
timeout: 10000,
|
|
||||||
headers: {
|
|
||||||
"X-API-Token": this.API_KEY,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
constructor(apiKey: string, apiURL: string) {
|
||||||
|
this.API_KEY = apiKey;
|
||||||
|
this.UPDATE_URL = apiURL;
|
||||||
|
if (
|
||||||
|
this.API_KEY === null ||
|
||||||
|
this.API_KEY === undefined ||
|
||||||
|
this.API_KEY === ""
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendUpdate(pulse: Pulse): axios.AxiosPromise {
|
this.axios = axios.default.create({
|
||||||
// If we did not have API key, don't try to update
|
baseURL: this.UPDATE_URL,
|
||||||
if (this.axios === null) {
|
timeout: 10000,
|
||||||
return null;
|
headers: {
|
||||||
}
|
"X-API-Token": this.API_KEY,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line:typedef
|
public sendUpdate(pulse: Pulse): axios.AxiosPromise {
|
||||||
const data = new ApiJSON(new Date());
|
// If we did not have API key, don't try to update
|
||||||
|
if (this.axios === null) {
|
||||||
for (let lang of pulse.xps.keys()) {
|
return null;
|
||||||
let languageName: string = getLanguageName(lang);
|
|
||||||
let xp: number = pulse.getXP(lang);
|
|
||||||
data.xps.push(new ApiXP(languageName, xp));
|
|
||||||
}
|
|
||||||
|
|
||||||
let json: string = JSON.stringify(data);
|
|
||||||
console.log(`JSON: ${json}`);
|
|
||||||
|
|
||||||
return this.axios.post(this.UPDATE_URL, json)
|
|
||||||
.then( (response) => {
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
.then ( () => {
|
|
||||||
pulse.reset();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:typedef
|
||||||
|
const data = new ApiJSON(new Date());
|
||||||
|
|
||||||
|
for (let lang of pulse.xps.keys()) {
|
||||||
|
let languageName: string = getLanguageName(lang);
|
||||||
|
let xp: number = pulse.getXP(lang);
|
||||||
|
data.xps.push(new ApiXP(languageName, xp));
|
||||||
|
}
|
||||||
|
|
||||||
|
let json: string = JSON.stringify(data);
|
||||||
|
console.log(`JSON: ${json}`);
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.post(this.UPDATE_URL, json)
|
||||||
|
.then(response => {
|
||||||
|
console.log(response);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
pulse.reset();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiJSON {
|
class ApiJSON {
|
||||||
constructor(date: Date) {
|
constructor(date: Date) {
|
||||||
this.coded_at = getISOTimestamp(new Date());
|
this.coded_at = getISOTimestamp(new Date());
|
||||||
this.xps = new Array<ApiXP>();
|
this.xps = new Array<ApiXP>();
|
||||||
}
|
}
|
||||||
|
|
||||||
coded_at: string;
|
coded_at: string;
|
||||||
xps: Array<ApiXP>;
|
xps: Array<ApiXP>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiXP {
|
class ApiXP {
|
||||||
constructor(language: string, xp: number) {
|
constructor(language: string, xp: number) {
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.xp = xp;
|
this.xp = xp;
|
||||||
}
|
}
|
||||||
|
|
||||||
language: string;
|
language: string;
|
||||||
xp: number;
|
xp: number;
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,9 @@ import { ExtensionContext } from "vscode";
|
|||||||
import { XpCounter } from "./xp-counter";
|
import { XpCounter } from "./xp-counter";
|
||||||
|
|
||||||
export function activate(context: ExtensionContext): void {
|
export function activate(context: ExtensionContext): void {
|
||||||
let controller: XpCounter = new XpCounter();
|
let controller: XpCounter = new XpCounter();
|
||||||
context.subscriptions.push(controller);
|
context.subscriptions.push(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
export function deactivate(): void {
|
export function deactivate(): void {}
|
||||||
}
|
|
||||||
|
48
src/pulse.ts
48
src/pulse.ts
@ -1,33 +1,33 @@
|
|||||||
export class Pulse {
|
export class Pulse {
|
||||||
xps: Map < string, number > ;
|
xps: Map<string, number>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.xps = new Map < string, number > ();
|
this.xps = new Map<string, number>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getXP(language: string): number {
|
||||||
|
let xp: number = this.xps.get(language);
|
||||||
|
|
||||||
|
if (xp === null || xp === undefined) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return xp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getXP(language: string): number {
|
public addXP(language: string, amount: number): void {
|
||||||
let xp: number = this.xps.get(language);
|
let xp: number = this.getXP(language);
|
||||||
|
|
||||||
if (xp === null || xp === undefined) {
|
xp += amount;
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return xp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public addXP(language: string, amount: number): void {
|
this.xps.set(language, xp);
|
||||||
let xp: number = this.getXP(language);
|
}
|
||||||
|
|
||||||
xp += amount;
|
public get getXPs(): Map<string, number> {
|
||||||
|
return this.xps;
|
||||||
|
}
|
||||||
|
|
||||||
this.xps.set(language, xp);
|
public reset(): void {
|
||||||
}
|
this.xps = new Map<string, number>();
|
||||||
|
}
|
||||||
public get getXPs(): Map < string, number > {
|
|
||||||
return this.xps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.xps = new Map < string, number > ();
|
|
||||||
}
|
|
||||||
}
|
}
|
160
src/utils.ts
160
src/utils.ts
@ -1,87 +1,95 @@
|
|||||||
// converted to ts from https://github.com/Nicd/code-stats-atom/blob/master/lib/utils.js
|
// converted to ts from https://github.com/Nicd/code-stats-atom/blob/master/lib/utils.js
|
||||||
export function getISOTimestamp(date: Date): string {
|
export function getISOTimestamp(date: Date): string {
|
||||||
const offset: number = -date.getTimezoneOffset();
|
const offset: number = -date.getTimezoneOffset();
|
||||||
const prefix: string = (offset >= 0) ? "+" : "-";
|
const prefix: string = offset >= 0 ? "+" : "-";
|
||||||
|
|
||||||
function pad(num: number): string {
|
function pad(num: number): string {
|
||||||
const norm: number = Math.abs(Math.floor(num));
|
const norm: number = Math.abs(Math.floor(num));
|
||||||
|
|
||||||
return ((norm < 10) ? "0" : "") + norm;
|
return (norm < 10 ? "0" : "") + norm;
|
||||||
}
|
}
|
||||||
|
|
||||||
return date.getFullYear() +
|
return (
|
||||||
"-" + pad(date.getMonth() + 1) +
|
date.getFullYear() +
|
||||||
"-" + pad(date.getDate()) +
|
"-" +
|
||||||
"T" + pad(date.getHours()) +
|
pad(date.getMonth() + 1) +
|
||||||
":" + pad(date.getMinutes()) +
|
"-" +
|
||||||
":" + pad(date.getSeconds()) +
|
pad(date.getDate()) +
|
||||||
prefix + pad(offset / 60) +
|
"T" +
|
||||||
pad(offset % 60);
|
pad(date.getHours()) +
|
||||||
|
":" +
|
||||||
|
pad(date.getMinutes()) +
|
||||||
|
":" +
|
||||||
|
pad(date.getSeconds()) +
|
||||||
|
prefix +
|
||||||
|
pad(offset / 60) +
|
||||||
|
pad(offset % 60)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLanguageName(langId: string): string {
|
export function getLanguageName(langId: string): string {
|
||||||
// currently supported language ids in vscode as of 2017-03-18
|
// currently supported language ids in vscode as of 2017-03-18
|
||||||
let languageNames: { [key:string]: string; } = {
|
let languageNames: { [key: string]: string } = {
|
||||||
"plaintext": "Plain text",
|
plaintext: "Plain text",
|
||||||
"Log": "Log",
|
Log: "Log",
|
||||||
"bat": "Batch",
|
bat: "Batch",
|
||||||
"clojure": "Clojure",
|
clojure: "Clojure",
|
||||||
"coffeescript": "CoffeeScript",
|
coffeescript: "CoffeeScript",
|
||||||
"c": "C",
|
c: "C",
|
||||||
"cpp": "C++",
|
cpp: "C++",
|
||||||
"csharp": "C#",
|
csharp: "C#",
|
||||||
"css": "CSS",
|
css: "CSS",
|
||||||
"diff": "Diff",
|
diff: "Diff",
|
||||||
"dockerfile": "Docker",
|
dockerfile: "Docker",
|
||||||
"elixir": "Elixir",
|
elixir: "Elixir",
|
||||||
"elm": "Elm",
|
elm: "Elm",
|
||||||
"fsharpcss": "F#",
|
fsharpcss: "F#",
|
||||||
"git-commit": "Git",
|
"git-commit": "Git",
|
||||||
"git-rebase": "Git",
|
"git-rebase": "Git",
|
||||||
"go": "Go",
|
go: "Go",
|
||||||
"groovy": "Groovy",
|
groovy: "Groovy",
|
||||||
"handlebars": "Handlebars",
|
handlebars: "Handlebars",
|
||||||
"hlsl": "HLSL",
|
hlsl: "HLSL",
|
||||||
"html": "HTML",
|
html: "HTML",
|
||||||
"ini": "Ini",
|
ini: "Ini",
|
||||||
"properties": "Properties",
|
properties: "Properties",
|
||||||
"java": "Java",
|
java: "Java",
|
||||||
"javascriptreact": "JavaScript (React)",
|
javascriptreact: "JavaScript (React)",
|
||||||
"javascript": "JavaScript",
|
javascript: "JavaScript",
|
||||||
"jsx-tags": "JavaScript (JSX)",
|
"jsx-tags": "JavaScript (JSX)",
|
||||||
"json": "JSON",
|
json: "JSON",
|
||||||
"less": "LESS",
|
less: "LESS",
|
||||||
"lua": "Lua",
|
lua: "Lua",
|
||||||
"makefile": "Makefile",
|
makefile: "Makefile",
|
||||||
"markdown": "Markdown",
|
markdown: "Markdown",
|
||||||
"objective-c": "Objective-C",
|
"objective-c": "Objective-C",
|
||||||
"perl": "Perl",
|
perl: "Perl",
|
||||||
"perl6": "Perl 6",
|
perl6: "Perl 6",
|
||||||
"php": "PHP",
|
php: "PHP",
|
||||||
"powershell": "PowerShell",
|
powershell: "PowerShell",
|
||||||
"jade": "Pug",
|
jade: "Pug",
|
||||||
"python": "Python",
|
python: "Python",
|
||||||
"r": "R",
|
r: "R",
|
||||||
"razor": "Razor",
|
razor: "Razor",
|
||||||
"ruby": "Ruby",
|
ruby: "Ruby",
|
||||||
"rust": "Rust",
|
rust: "Rust",
|
||||||
"scss": "SCSS",
|
scss: "SCSS",
|
||||||
"shaderlab": "Shaderlab",
|
shaderlab: "Shaderlab",
|
||||||
"shellscript": "Shell Script",
|
shellscript: "Shell Script",
|
||||||
"sql": "SQL",
|
sql: "SQL",
|
||||||
"swift": "Swift",
|
swift: "Swift",
|
||||||
"typescript": "TypeScript",
|
typescript: "TypeScript",
|
||||||
"typescriptreact": "TypeScript (React)",
|
typescriptreact: "TypeScript (React)",
|
||||||
"vb": "Visual Basic",
|
vb: "Visual Basic",
|
||||||
"xml": "XML",
|
xml: "XML",
|
||||||
"xsl": "XSL",
|
xsl: "XSL",
|
||||||
"yaml": "YAML"
|
yaml: "YAML"
|
||||||
};
|
};
|
||||||
|
|
||||||
let languageName: string = languageNames[langId];
|
let languageName: string = languageNames[langId];
|
||||||
|
|
||||||
if (languageName === null || languageName === undefined) {
|
if (languageName === null || languageName === undefined) {
|
||||||
return langId;
|
return langId;
|
||||||
}
|
}
|
||||||
return languageName;
|
return languageName;
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,34 @@
|
|||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
import { Disposable, workspace, window, StatusBarItem, TextDocument, StatusBarAlignment, TextDocumentChangeEvent, Range, WorkspaceConfiguration } from "vscode";
|
import {
|
||||||
|
Disposable,
|
||||||
|
workspace,
|
||||||
|
window,
|
||||||
|
StatusBarItem,
|
||||||
|
TextDocument,
|
||||||
|
StatusBarAlignment,
|
||||||
|
TextDocumentChangeEvent,
|
||||||
|
Range,
|
||||||
|
WorkspaceConfiguration
|
||||||
|
} from "vscode";
|
||||||
import { Pulse } from "./pulse";
|
import { Pulse } from "./pulse";
|
||||||
import { CodeStatsAPI } from "./code-stats-api";
|
import { CodeStatsAPI } from "./code-stats-api";
|
||||||
|
|
||||||
export class XpCounter {
|
export class XpCounter {
|
||||||
private combinedDisposable: Disposable;
|
private combinedDisposable: Disposable;
|
||||||
private statusBarItem: StatusBarItem;
|
private statusBarItem: StatusBarItem;
|
||||||
private pulse: Pulse;
|
private pulse: Pulse;
|
||||||
private api: CodeStatsAPI;
|
private api: CodeStatsAPI;
|
||||||
private updateTimeout: any;
|
private updateTimeout: any;
|
||||||
|
|
||||||
// private languages: Array<string> = ["typescript", "javascript"];
|
// private languages: Array<string> = ["typescript", "javascript"];
|
||||||
|
|
||||||
// wait 10s after each change in the document before sending an update
|
// wait 10s after each change in the document before sending an update
|
||||||
private UPDATE_DELAY = 10000;
|
private UPDATE_DELAY = 10000;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pulse = new Pulse();
|
this.pulse = new Pulse();
|
||||||
|
|
||||||
/* // print out supported language names
|
/* // print out supported language names
|
||||||
let allLanguages = languages.getLanguages().then(
|
let allLanguages = languages.getLanguages().then(
|
||||||
(result => {
|
(result => {
|
||||||
console.log(JSON.stringify(result));
|
console.log(JSON.stringify(result));
|
||||||
@ -26,77 +36,91 @@ export class XpCounter {
|
|||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.initAPI();
|
this.initAPI();
|
||||||
|
|
||||||
if (!this.statusBarItem) {
|
if (!this.statusBarItem) {
|
||||||
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
|
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
|
||||||
}
|
|
||||||
|
|
||||||
let subscriptions: Disposable[] = [];
|
|
||||||
workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this, subscriptions);
|
|
||||||
workspace.onDidChangeConfiguration(this.initAPI, this, subscriptions);
|
|
||||||
this.combinedDisposable = Disposable.from(...subscriptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
let subscriptions: Disposable[] = [];
|
||||||
this.combinedDisposable.dispose();
|
workspace.onDidChangeTextDocument(
|
||||||
this.statusBarItem.dispose();
|
this.onTextDocumentChanged,
|
||||||
|
this,
|
||||||
|
subscriptions
|
||||||
|
);
|
||||||
|
workspace.onDidChangeConfiguration(this.initAPI, this, subscriptions);
|
||||||
|
this.combinedDisposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.combinedDisposable.dispose();
|
||||||
|
this.statusBarItem.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTextDocumentChanged(event: TextDocumentChangeEvent): void {
|
||||||
|
this.updateXpCount(event.document, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateXpCount(document: TextDocument, changeCount: number): void {
|
||||||
|
let show: boolean;
|
||||||
|
if (this.isSupportedLanguage(document.languageId)) {
|
||||||
|
this.pulse.addXP(document.languageId, changeCount);
|
||||||
|
show = true;
|
||||||
|
} else {
|
||||||
|
show = false;
|
||||||
|
}
|
||||||
|
this.updateStatusBar(show, `${this.pulse.getXP(document.languageId)}`);
|
||||||
|
|
||||||
|
// each change resets the timeout so we only send updates when there is a 10s delay in updates to the document
|
||||||
|
if (this.updateTimeout !== null) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTextDocumentChanged(event: TextDocumentChangeEvent): void {
|
this.updateTimeout = setTimeout(() => {
|
||||||
this.updateXpCount(event.document, 1);
|
const promise = this.api.sendUpdate(this.pulse);
|
||||||
|
|
||||||
|
if (promise !== null) {
|
||||||
|
promise.then(() => {
|
||||||
|
this.updateStatusBar(
|
||||||
|
show,
|
||||||
|
`${this.pulse.getXP(document.languageId)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, this.UPDATE_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStatusBar(show: boolean, changeCount: string): void {
|
||||||
|
if (!show) {
|
||||||
|
this.statusBarItem.hide();
|
||||||
|
} else {
|
||||||
|
this.statusBarItem.text = `$(pencil) C::S ${changeCount}`;
|
||||||
|
this.statusBarItem.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSupportedLanguage(language: string): boolean {
|
||||||
|
// todo: check supported languages
|
||||||
|
// only update xp if one of supported languages
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private initAPI() {
|
||||||
|
let config: WorkspaceConfiguration = workspace.getConfiguration(
|
||||||
|
"codestats"
|
||||||
|
);
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateXpCount(document: TextDocument, changeCount: number): void {
|
const apiKey: string = config.get("apikey");
|
||||||
let show: boolean;
|
const apiURL: string = config.get("apiurl");
|
||||||
if (this.isSupportedLanguage(document.languageId)) {
|
console.log(
|
||||||
this.pulse.addXP(document.languageId, changeCount);
|
"code-stats-vscode setting up with API URL",
|
||||||
show = true;
|
apiURL,
|
||||||
} else {
|
"and key",
|
||||||
show = false;
|
apiKey
|
||||||
}
|
);
|
||||||
this.updateStatusBar(show, `${this.pulse.getXP(document.languageId)}`);
|
this.api = new CodeStatsAPI(apiKey, apiURL);
|
||||||
|
}
|
||||||
// each change resets the timeout so we only send updates when there is a 10s delay in updates to the document
|
|
||||||
if (this.updateTimeout !== null) {
|
|
||||||
clearTimeout(this.updateTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateTimeout = setTimeout(() => {
|
|
||||||
const promise = this.api.sendUpdate(this.pulse);
|
|
||||||
|
|
||||||
if (promise !== null) {
|
|
||||||
promise.then(() => {
|
|
||||||
this.updateStatusBar(show, `${this.pulse.getXP(document.languageId)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, this.UPDATE_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateStatusBar(show: boolean, changeCount: string): void {
|
|
||||||
if (!show) {
|
|
||||||
this.statusBarItem.hide();
|
|
||||||
} else {
|
|
||||||
this.statusBarItem.text = `$(pencil) C::S ${changeCount}`;
|
|
||||||
this.statusBarItem.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isSupportedLanguage(language: string): boolean {
|
|
||||||
// todo: check supported languages
|
|
||||||
// only update xp if one of supported languages
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private initAPI() {
|
|
||||||
let config: WorkspaceConfiguration = workspace.getConfiguration("codestats");
|
|
||||||
if (!config) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiKey: string = config.get("apikey");
|
|
||||||
const apiURL: string = config.get("apiurl");
|
|
||||||
console.log("code-stats-vscode setting up with API URL", apiURL, "and key", apiKey);
|
|
||||||
this.api = new CodeStatsAPI(apiKey, apiURL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user