5htp-core 0.5.9-1 → 0.5.9-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/components/Card/index.tsx +1 -2
- package/client/index.ts +1 -0
- package/client/services/router/index.tsx +0 -1
- package/package.json +1 -1
- package/server/app/commands.ts +1 -20
- package/server/app/container/console/index.ts +0 -5
- package/server/app/index.ts +47 -9
- package/server/app/service/index.ts +8 -5
- package/server/services/auth/old.ts +1 -1
- package/server/services/auth/router/index.ts +8 -6
- package/server/services/cache/index.ts +1 -8
- package/server/services/cron/index.ts +1 -8
- package/server/services/database/index.ts +1 -1
- package/server/services/database/stats.ts +0 -2
- package/server/services/disks/index.ts +9 -4
- package/server/services/email/transporter.ts +0 -20
- package/server/services/prisma/index.ts +0 -13
- package/server/services/router/index.ts +3 -4
- package/server/services/router/request/api.ts +2 -7
- package/server/services/socket/index.ts +1 -5
- package/types/global/utils.d.ts +46 -1
- package/types/icons.d.ts +1 -1
|
@@ -7,8 +7,7 @@ import React from 'react';
|
|
|
7
7
|
import type { ComponentChild } from 'preact';
|
|
8
8
|
|
|
9
9
|
// Core components
|
|
10
|
-
import { Logo } from '@client/components';
|
|
11
|
-
import { Link } from '@client/services/router';
|
|
10
|
+
import { Logo, Link } from '@client/components';
|
|
12
11
|
|
|
13
12
|
// Resources
|
|
14
13
|
import './index.less';
|
package/client/index.ts
CHANGED
|
@@ -55,7 +55,6 @@ const LogPrefix = '[router]'
|
|
|
55
55
|
|
|
56
56
|
// Client router can handle Client requests AND Server requests (for pages only)
|
|
57
57
|
export type { default as ClientResponse, TRouterContext } from "./response";
|
|
58
|
-
export { Link } from './components/Link';
|
|
59
58
|
|
|
60
59
|
export type Router = ClientRouter | ServerRouter;
|
|
61
60
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.5.9-
|
|
4
|
+
"version": "0.5.9-2",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
package/server/app/commands.ts
CHANGED
|
@@ -53,26 +53,7 @@ export default class CommandsManager extends Service<Config, Hooks, Application,
|
|
|
53
53
|
public priority = 2 as 2;
|
|
54
54
|
|
|
55
55
|
public commandsIndex: CommandsList = {}
|
|
56
|
-
|
|
57
|
-
/*----------------------------------
|
|
58
|
-
- LIFECYCLE
|
|
59
|
-
----------------------------------*/
|
|
60
|
-
|
|
61
|
-
protected async start() {
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
protected async ready() {
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
protected async shutdown() {
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/*----------------------------------
|
|
74
|
-
- DEFINITIONS
|
|
75
|
-
----------------------------------*/
|
|
56
|
+
|
|
76
57
|
public command<TArgs extends any[]>(
|
|
77
58
|
...args: (
|
|
78
59
|
[name: string, description: string, childrens: Command[]]
|
|
@@ -206,11 +206,6 @@ export default class Console {
|
|
|
206
206
|
this.enableLogging(origLog);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// Avoid to use lifecycle functions
|
|
210
|
-
protected async start() {}
|
|
211
|
-
public async ready() {}
|
|
212
|
-
public async shutdown() {}
|
|
213
|
-
|
|
214
209
|
private enableLogging( origLog: typeof console.log ) {
|
|
215
210
|
|
|
216
211
|
const minLogLevel = logLevels[this.config.level];
|
package/server/app/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type express from 'express';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import AppContainer from './container';
|
|
10
|
-
import ApplicationService, { StartedServicesIndex } from './service';
|
|
10
|
+
import ApplicationService, { AnyService, StartedServicesIndex } from './service';
|
|
11
11
|
import CommandsManager from './commands';
|
|
12
12
|
import ServicesContainer, {
|
|
13
13
|
ServicesContainer as ServicesContainerClass,
|
|
@@ -82,8 +82,12 @@ export abstract class Application<
|
|
|
82
82
|
public debug: boolean = false;
|
|
83
83
|
public launched: boolean = false;
|
|
84
84
|
|
|
85
|
-
protected abstract
|
|
86
|
-
|
|
85
|
+
protected abstract registered: {
|
|
86
|
+
[serviceId: string]: {
|
|
87
|
+
name: string,
|
|
88
|
+
start: () => AnyService
|
|
89
|
+
}
|
|
90
|
+
};
|
|
87
91
|
|
|
88
92
|
/*----------------------------------
|
|
89
93
|
- INIT
|
|
@@ -132,6 +136,8 @@ export abstract class Application<
|
|
|
132
136
|
console.log("Core version", CORE_VERSION);
|
|
133
137
|
const startTime = Date.now();
|
|
134
138
|
|
|
139
|
+
this.startServices();
|
|
140
|
+
|
|
135
141
|
await this.ready();
|
|
136
142
|
await this.runHook('ready');
|
|
137
143
|
|
|
@@ -151,16 +157,48 @@ export abstract class Application<
|
|
|
151
157
|
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
private startServices() {
|
|
161
|
+
|
|
162
|
+
// Print services
|
|
163
|
+
console.log('----------------------------------');
|
|
164
|
+
console.log('- SERVICES');
|
|
165
|
+
console.log('----------------------------------');
|
|
166
|
+
const printService = (service, level: number = 0) => {
|
|
167
|
+
|
|
168
|
+
console.log('-' + '-'.repeat(level * 4), service.name, '(' + service.priority + ')');
|
|
169
|
+
|
|
170
|
+
if (service.subservices) for (const subservice of service.subservices)
|
|
171
|
+
printService(subservice, level + 1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Satrt services
|
|
175
|
+
for (const serviceId in this.registered) {
|
|
176
|
+
|
|
177
|
+
const service = this.registered[serviceId];
|
|
178
|
+
printService(service, 0);
|
|
179
|
+
const instance = service.start();
|
|
180
|
+
this[service.name] = instance.getServiceInstance();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
154
184
|
protected async ready() {
|
|
155
185
|
|
|
156
|
-
|
|
157
|
-
for (const serviceName of this.serviceNames) {
|
|
186
|
+
const processService = (service: AnyService) => {
|
|
158
187
|
|
|
159
|
-
|
|
188
|
+
service.ready();
|
|
189
|
+
|
|
190
|
+
// Subservices
|
|
191
|
+
for (const serviceId in service.services)
|
|
192
|
+
processService(service.services[serviceId]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const serviceId in this.registered) {
|
|
160
196
|
|
|
161
|
-
|
|
162
|
-
|
|
197
|
+
const registeredService = this.registered[serviceId];
|
|
198
|
+
const service = this[registeredService.name];
|
|
163
199
|
|
|
200
|
+
// TODO: move to router
|
|
201
|
+
// Application.on('service.ready')
|
|
164
202
|
const routes = service.__routes;
|
|
165
203
|
if (routes) for (const route of routes) {
|
|
166
204
|
|
|
@@ -182,7 +220,7 @@ export abstract class Application<
|
|
|
182
220
|
this.Router.controllers[ route.path ] = route;
|
|
183
221
|
}
|
|
184
222
|
|
|
185
|
-
service
|
|
223
|
+
processService(service);
|
|
186
224
|
}
|
|
187
225
|
}
|
|
188
226
|
|
|
@@ -110,7 +110,7 @@ export default abstract class Service<
|
|
|
110
110
|
public config: TConfig,
|
|
111
111
|
// Make this argument appear as instanciated sercices index
|
|
112
112
|
// But actually, Setup.use returns a registered service, not yet launched
|
|
113
|
-
|
|
113
|
+
getServices: (instance: AnyService) => TServicesIndex,
|
|
114
114
|
app: TApplication | 'self'
|
|
115
115
|
) {
|
|
116
116
|
|
|
@@ -120,6 +120,9 @@ export default abstract class Service<
|
|
|
120
120
|
this.app = app === 'self'
|
|
121
121
|
? this as unknown as TApplication
|
|
122
122
|
: app
|
|
123
|
+
|
|
124
|
+
if (typeof getServices === 'function')
|
|
125
|
+
this.services = getServices(this);
|
|
123
126
|
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -143,11 +146,11 @@ export default abstract class Service<
|
|
|
143
146
|
|
|
144
147
|
public use( serviceId: string ) {
|
|
145
148
|
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
throw new Error(`Service ${
|
|
149
|
+
const registeredService = this.app.registered[serviceId];
|
|
150
|
+
if (registeredService === undefined)
|
|
151
|
+
throw new Error(`Service ${registeredService} not found.`);
|
|
149
152
|
|
|
150
|
-
return this.app[
|
|
153
|
+
return this.app[ registeredService.name ];
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
/*----------------------------------
|
|
@@ -37,9 +37,15 @@ export default class AuthenticationRouterService<
|
|
|
37
37
|
- LIFECYCLE
|
|
38
38
|
----------------------------------*/
|
|
39
39
|
|
|
40
|
-
public users
|
|
40
|
+
public users;
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
public constructor(...args) {
|
|
43
|
+
super(...args);
|
|
44
|
+
|
|
45
|
+
this.users = this.services.users;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected async ready() {
|
|
43
49
|
|
|
44
50
|
// Decode current user
|
|
45
51
|
this.parent.on('request', async (request: TRequest) => {
|
|
@@ -59,10 +65,6 @@ export default class AuthenticationRouterService<
|
|
|
59
65
|
})
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
protected async ready() {
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
68
|
protected async shutdown() {
|
|
67
69
|
|
|
68
70
|
}
|
|
@@ -96,16 +96,9 @@ export default class Cache extends Service<Config, Hooks, Application, Services>
|
|
|
96
96
|
- LIFECYCLE
|
|
97
97
|
----------------------------------*/
|
|
98
98
|
|
|
99
|
-
protected async start() {
|
|
100
|
-
|
|
101
|
-
setInterval(() => this.cleanMem(), 10000);
|
|
102
|
-
|
|
103
|
-
// Restore persisted data
|
|
104
|
-
//await this.restore();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
99
|
public async ready() {
|
|
108
100
|
|
|
101
|
+
setInterval(() => this.cleanMem(), 10000);
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
public async shutdown() {
|
|
@@ -45,7 +45,7 @@ export default class CronManager extends Service<Config, Hooks, Application, Ser
|
|
|
45
45
|
- LIFECICLE
|
|
46
46
|
----------------------------------*/
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
public async ready() {
|
|
49
49
|
|
|
50
50
|
clearInterval(CronManager.timer);
|
|
51
51
|
CronManager.timer = setInterval(() => {
|
|
@@ -54,13 +54,6 @@ export default class CronManager extends Service<Config, Hooks, Application, Ser
|
|
|
54
54
|
CronManager.taches[id].run();
|
|
55
55
|
|
|
56
56
|
}, 10000);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public async ready() {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public async shutdown() {
|
|
64
57
|
|
|
65
58
|
}
|
|
66
59
|
|
|
@@ -98,7 +98,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
|
|
|
98
98
|
public constructor(
|
|
99
99
|
parent: AnyService,
|
|
100
100
|
config: Config,
|
|
101
|
-
drivers: TRegisteredServicesIndex,
|
|
101
|
+
drivers: () => TRegisteredServicesIndex,
|
|
102
102
|
app: Application,
|
|
103
103
|
) {
|
|
104
104
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
6
|
import type { Application } from '@server/app';
|
|
7
|
-
import Service, { AnyService } from '@server/app/service';
|
|
7
|
+
import Service, { AnyService, TRegisteredServicesIndex } from '@server/app/service';
|
|
8
8
|
|
|
9
9
|
// Specific
|
|
10
10
|
import type Driver from './driver';
|
|
@@ -44,15 +44,20 @@ export default class DisksManager<
|
|
|
44
44
|
- LIFECYCLE
|
|
45
45
|
----------------------------------*/
|
|
46
46
|
|
|
47
|
-
public
|
|
47
|
+
public constructor(
|
|
48
|
+
parent: AnyService,
|
|
49
|
+
config: TConfig,
|
|
50
|
+
services: () => TRegisteredServicesIndex,
|
|
51
|
+
app: Application,
|
|
52
|
+
) {
|
|
53
|
+
|
|
54
|
+
super(parent, config, services, app);
|
|
48
55
|
|
|
49
56
|
const drivers = this.services;
|
|
50
57
|
|
|
51
58
|
if (Object.keys( drivers ).length === 0)
|
|
52
59
|
throw new Error("At least one disk driver should be mounted.");
|
|
53
60
|
|
|
54
|
-
console.log('start disks service', Object.keys( drivers ), Object.keys( this.mounted ), Object.keys( this.services ));
|
|
55
|
-
|
|
56
61
|
const defaultDisk = drivers[ this.config.default ];
|
|
57
62
|
if (defaultDisk === undefined)
|
|
58
63
|
console.log(`Default disk "${this.config.default as string}" not mounted.`);
|
|
@@ -27,25 +27,5 @@ export type TBasicConfig = {
|
|
|
27
27
|
export abstract class Transporter<TConfig extends TBasicConfig = TBasicConfig>
|
|
28
28
|
extends Service<TConfig, {}, Application, {}> {
|
|
29
29
|
|
|
30
|
-
/*----------------------------------
|
|
31
|
-
- LIFECYCLE
|
|
32
|
-
----------------------------------*/
|
|
33
|
-
|
|
34
|
-
protected async start() {
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
protected async ready() {
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
protected async shutdown() {
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/*----------------------------------
|
|
47
|
-
- ACTIONS
|
|
48
|
-
----------------------------------*/
|
|
49
|
-
|
|
50
30
|
public abstract send( emails: TCompleteEmail[] ): Promise<void>;
|
|
51
31
|
}
|
|
@@ -37,19 +37,6 @@ export type Services = {
|
|
|
37
37
|
export default class ModelsManager extends Service<Config, Hooks, Application, Services> {
|
|
38
38
|
|
|
39
39
|
public client = new PrismaClient();
|
|
40
|
-
|
|
41
|
-
/*----------------------------------
|
|
42
|
-
- LIFECICLE
|
|
43
|
-
----------------------------------*/
|
|
44
|
-
|
|
45
|
-
protected async start() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public async ready() {
|
|
51
|
-
|
|
52
|
-
}
|
|
53
40
|
|
|
54
41
|
public async shutdown() {
|
|
55
42
|
await this.client.$disconnect()
|
|
@@ -147,7 +147,7 @@ export default class ServerRouter<
|
|
|
147
147
|
public constructor(
|
|
148
148
|
parent: AnyService,
|
|
149
149
|
config: Config,
|
|
150
|
-
services: TSubservices,
|
|
150
|
+
services: () => TSubservices,
|
|
151
151
|
app: Application,
|
|
152
152
|
) {
|
|
153
153
|
|
|
@@ -174,8 +174,8 @@ export default class ServerRouter<
|
|
|
174
174
|
// Use require to avoid circular references
|
|
175
175
|
this.registerRoutes([
|
|
176
176
|
...require("metas:@/server/routes/**/*.ts"),
|
|
177
|
-
...require("metas:@/client/pages
|
|
178
|
-
...require("metas
|
|
177
|
+
...require("metas:@/client/pages/**/([a-z0-9]*).tsx"),
|
|
178
|
+
...require("metas:@/client/pages/**/([a-z0-9]*).tsx")
|
|
179
179
|
]);
|
|
180
180
|
|
|
181
181
|
// Start HTTP server
|
|
@@ -192,7 +192,6 @@ export default class ServerRouter<
|
|
|
192
192
|
----------------------------------*/
|
|
193
193
|
|
|
194
194
|
private registerRoutes(defModules: GlobImportedWithMetas<TRouteModule>) {
|
|
195
|
-
|
|
196
195
|
for (const routeModule of defModules) {
|
|
197
196
|
|
|
198
197
|
const register = routeModule.exports.__register;
|
|
@@ -21,10 +21,6 @@ import ApiClientService, {
|
|
|
21
21
|
----------------------------------*/
|
|
22
22
|
export default class ApiClientRequest extends RequestService implements ApiClientService {
|
|
23
23
|
|
|
24
|
-
protected async start() {
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
/*----------------------------------
|
|
29
25
|
- HIGH LEVEL
|
|
30
26
|
----------------------------------*/
|
|
@@ -66,9 +62,8 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
66
62
|
public createFetcher<TData extends unknown = unknown>(...[method, path, data, options]: TFetcherArgs): TFetcher<TData> {
|
|
67
63
|
return {
|
|
68
64
|
method, path, data, options,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
65
|
+
// We don't put the then and catch methods so the api consumer on server side will know it's a fetcher and not a promize to wait
|
|
66
|
+
} as TFetcher<TData>;
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
@@ -72,7 +72,7 @@ export default class WebSocketCommander<
|
|
|
72
72
|
----------------------------------*/
|
|
73
73
|
|
|
74
74
|
public loading: Promise<void> | undefined = undefined;
|
|
75
|
-
protected async
|
|
75
|
+
protected async ready() {
|
|
76
76
|
|
|
77
77
|
this.users.on('disconnect', async (userId: string) => {
|
|
78
78
|
this.disconnect(userId, 'Logout');
|
|
@@ -120,10 +120,6 @@ export default class WebSocketCommander<
|
|
|
120
120
|
console.info(`Socket commander bound to http server.`);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
public async ready() {
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
123
|
public async shutdown() {
|
|
128
124
|
this.closeAll();
|
|
129
125
|
}
|
package/types/global/utils.d.ts
CHANGED
|
@@ -55,6 +55,51 @@ declare type Routes = {
|
|
|
55
55
|
|
|
56
56
|
declare type PrimitiveValue = string | number | boolean;
|
|
57
57
|
|
|
58
|
+
/*----------------------------------
|
|
59
|
+
- COPY FROM CLI/APP/INDEX.TS
|
|
60
|
+
----------------------------------*/
|
|
61
|
+
|
|
62
|
+
type TEnvConfig = {
|
|
63
|
+
name: 'local' | 'server',
|
|
64
|
+
profile: 'dev' | 'prod',
|
|
65
|
+
version: string,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type TServiceSetup = {
|
|
69
|
+
id: string,
|
|
70
|
+
name: string,
|
|
71
|
+
config: {},
|
|
72
|
+
subservices: TServiceSubservices
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type TServiceRef = {
|
|
76
|
+
refTo: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type TServiceSubservices = {
|
|
80
|
+
[key: string]: TServiceSetup | TServiceRef
|
|
81
|
+
}
|
|
82
|
+
|
|
58
83
|
declare module '@cli/app' {
|
|
59
|
-
|
|
84
|
+
type App = {
|
|
85
|
+
|
|
86
|
+
env: TEnvConfig;
|
|
87
|
+
|
|
88
|
+
use: (referenceName: string) => TServiceRef;
|
|
89
|
+
|
|
90
|
+
setup: (...args: [
|
|
91
|
+
// { user: app.setup('Core/User') }
|
|
92
|
+
servicePath: string,
|
|
93
|
+
serviceConfig?: {},
|
|
94
|
+
serviceSubservices?: TServiceSubservices
|
|
95
|
+
] | [
|
|
96
|
+
// app.setup('User', 'Core/User')
|
|
97
|
+
serviceName: string,
|
|
98
|
+
servicePath: string,
|
|
99
|
+
serviceConfig?: {},
|
|
100
|
+
serviceSubservices?: TServiceSubservices
|
|
101
|
+
]) => TServiceSetup;
|
|
102
|
+
}
|
|
103
|
+
const app: App;
|
|
104
|
+
export = app;
|
|
60
105
|
}
|
package/types/icons.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TIcones = "solid/spinner-third"|"
|
|
1
|
+
export type TIcones = "long-arrow-right"|"times"|"solid/spinner-third"|"sack-dollar"|"bell"|"bullseye"|"project-diagram"|"user-friends"|"eye"|"lock"|"comments"|"phone"|"chalkboard-teacher"|"chart-bar"|"at"|"arrow-right"|"rocket"|"magnet"|"search"|"file-excel"|"plus-circle"|"plus"|"user-plus"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"map-marker"|"link"|"industry"|"users"|"calendar-alt"|"file-invoice"|"binoculars"|"brands/linkedin"|"lightbulb"|"long-arrow-left"|"key"|"user"|"building"|"briefcase"|"times-circle"|"suitcase"|"brands/linkedin-in"|"phone-plus"|"envelope"|"home-alt"|"planet-ringed"|"user-circle"|"crosshairs"|"comments-alt"|"paper-plane"|"user-shield"|"shield-alt"|"chart-line"|"money-bill-wave"|"star"|"file-alt"|"angle-up"|"angle-down"|"solid/crown"|"brands/discord"|"pen"|"file"|"info-circle"|"check-circle"|"exclamation-circle"|"check"|"meh-rolling-eyes"|"arrow-left"|"trash"|"solid/star"|"solid/star-half-alt"|"regular/star"|"chevron-left"|"cog"|"power-off"|"usd-circle"|"trophy"|"bars"|"chevron-right"|"edit"|"map-marker-alt"|"clock"|"arrow-to-bottom"|"ellipsis-h"|"question-circle"|"play"|"minus-circle"|"plane-departure"|"wind"|"external-link"|"ban"|"heart"|"calendar-check"|"unlink"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"code"|"font"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"
|