parent
6cf6538719
commit
2df27100f0
@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added the data export feature to the user account page
|
||||
- Added a currencies preset to the historical market data table of the admin control panel
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the localized meta data in `html` files
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the rows with cash positions in the holdings table
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
SUPPORTED_LANGUAGE_CODES
|
||||
} from '@ghostfolio/common/config';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
@ -28,7 +28,6 @@ import { BenchmarkModule } from './benchmark/benchmark.module';
|
||||
import { CacheModule } from './cache/cache.module';
|
||||
import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
|
||||
import { ExportModule } from './export/export.module';
|
||||
import { FrontendMiddleware } from './frontend.middleware';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { ImportModule } from './import/import.module';
|
||||
import { InfoModule } from './info/info.module';
|
||||
@ -75,12 +74,6 @@ import { UserModule } from './user/user.module';
|
||||
PrismaModule,
|
||||
RedisCacheModule,
|
||||
ScheduleModule.forRoot(),
|
||||
...SUPPORTED_LANGUAGE_CODES.map((languageCode) => {
|
||||
return ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..', 'client', languageCode),
|
||||
serveRoot: `/${languageCode}`
|
||||
});
|
||||
}),
|
||||
ServeStaticModule.forRoot({
|
||||
exclude: ['/api*', '/sitemap.xml'],
|
||||
rootPath: join(__dirname, '..', 'client'),
|
||||
@ -114,10 +107,4 @@ import { UserModule } from './user/user.module';
|
||||
controllers: [AppController],
|
||||
providers: [CronService]
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(FrontendMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||
}
|
||||
}
|
||||
export class AppModule {}
|
||||
|
@ -1,232 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { environment } from '@ghostfolio/api/environments/environment';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper';
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { format } from 'date-fns';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class FrontendMiddleware implements NestMiddleware {
|
||||
public indexHtmlDe = '';
|
||||
public indexHtmlEn = '';
|
||||
public indexHtmlEs = '';
|
||||
public indexHtmlFr = '';
|
||||
public indexHtmlIt = '';
|
||||
public indexHtmlNl = '';
|
||||
public indexHtmlPt = '';
|
||||
|
||||
private static readonly DEFAULT_DESCRIPTION =
|
||||
'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.';
|
||||
|
||||
public constructor(
|
||||
private readonly configurationService: ConfigurationService
|
||||
) {
|
||||
try {
|
||||
this.indexHtmlDe = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('de'),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlEn = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlEs = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('es'),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlFr = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('fr'),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlIt = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('it'),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlNl = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('nl'),
|
||||
'utf8'
|
||||
);
|
||||
this.indexHtmlPt = fs.readFileSync(
|
||||
this.getPathOfIndexHtmlFile('pt'),
|
||||
'utf8'
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
public use(request: Request, response: Response, next: NextFunction) {
|
||||
const currentDate = format(new Date(), DATE_FORMAT);
|
||||
let featureGraphicPath = 'assets/cover.png';
|
||||
let title = 'Ghostfolio – Open Source Wealth Management Software';
|
||||
|
||||
if (request.path.startsWith('/en/blog/2022/08/500-stars-on-github')) {
|
||||
featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg';
|
||||
title = `500 Stars - ${title}`;
|
||||
} else if (request.path.startsWith('/en/blog/2022/10/hacktoberfest-2022')) {
|
||||
featureGraphicPath = 'assets/images/blog/hacktoberfest-2022.png';
|
||||
title = `Hacktoberfest 2022 - ${title}`;
|
||||
} else if (request.path.startsWith('/en/blog/2022/11/black-friday-2022')) {
|
||||
featureGraphicPath = 'assets/images/blog/black-friday-2022.jpg';
|
||||
title = `Black Friday 2022 - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith(
|
||||
'/en/blog/2022/12/the-importance-of-tracking-your-personal-finances'
|
||||
)
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/20221226.jpg';
|
||||
title = `The importance of tracking your personal finances - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith(
|
||||
'/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt'
|
||||
)
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/ghostfolio-x-sackgeld.png';
|
||||
title = `Ghostfolio auf Sackgeld.com vorgestellt - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith('/en/blog/2023/02/ghostfolio-meets-umbrel')
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/ghostfolio-x-umbrel.png';
|
||||
title = `Ghostfolio meets Umbrel - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith(
|
||||
'/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github'
|
||||
)
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/1000-stars-on-github.jpg';
|
||||
title = `Ghostfolio reaches 1’000 Stars on GitHub - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith(
|
||||
'/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio'
|
||||
)
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/20230520.jpg';
|
||||
title = `Unlock your Financial Potential with Ghostfolio - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith('/en/blog/2023/07/exploring-the-path-to-fire')
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/20230701.jpg';
|
||||
title = `Exploring the Path to FIRE - ${title}`;
|
||||
}
|
||||
|
||||
if (
|
||||
request.path.startsWith('/api/') ||
|
||||
this.isFileRequest(request.url) ||
|
||||
!environment.production
|
||||
) {
|
||||
// Skip
|
||||
next();
|
||||
} else if (request.path === '/de' || request.path.startsWith('/de/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlDe, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.',
|
||||
languageCode: 'de',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else if (request.path === '/es' || request.path.startsWith('/es/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlEs, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.',
|
||||
languageCode: 'es',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else if (request.path === '/fr' || request.path.startsWith('/fr/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlFr, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.',
|
||||
languageCode: 'fr',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else if (request.path === '/it' || request.path.startsWith('/it/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlIt, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.',
|
||||
languageCode: 'it',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else if (request.path === '/nl' || request.path.startsWith('/nl/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlNl, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.',
|
||||
languageCode: 'nl',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else if (request.path === '/pt' || request.path.startsWith('/pt/')) {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlPt, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description:
|
||||
'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.',
|
||||
languageCode: 'pt',
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
} else {
|
||||
response.send(
|
||||
interpolate(this.indexHtmlEn, {
|
||||
currentDate,
|
||||
featureGraphicPath,
|
||||
title,
|
||||
description: FrontendMiddleware.DEFAULT_DESCRIPTION,
|
||||
languageCode: DEFAULT_LANGUAGE_CODE,
|
||||
path: request.path,
|
||||
rootUrl: this.configurationService.get('ROOT_URL')
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getPathOfIndexHtmlFile(aLocale: string) {
|
||||
return path.join(__dirname, '..', 'client', aLocale, 'index.html');
|
||||
}
|
||||
|
||||
private isFileRequest(filename: string) {
|
||||
if (filename === '/assets/LICENSE') {
|
||||
return true;
|
||||
} else if (
|
||||
filename.includes('auth/ey') ||
|
||||
filename.includes(
|
||||
'personal-finance-tools/open-source-alternative-to-markets.sh'
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filename.split('.').pop() !== filename;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import helmet from 'helmet';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
import { HtmlTemplateMiddleware } from './middlewares/html-template.middleware';
|
||||
|
||||
async function bootstrap() {
|
||||
const configApp = await NestFactory.create(AppModule);
|
||||
@ -52,6 +53,8 @@ async function bootstrap() {
|
||||
);
|
||||
}
|
||||
|
||||
app.use(HtmlTemplateMiddleware);
|
||||
|
||||
const BASE_CURRENCY = configService.get<string>('BASE_CURRENCY');
|
||||
const HOST = configService.get<string>('HOST') || '0.0.0.0';
|
||||
const PORT = configService.get<number>('PORT') || 3333;
|
||||
|
128
apps/api/src/middlewares/html-template.middleware.ts
Normal file
128
apps/api/src/middlewares/html-template.middleware.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import * as fs from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { environment } from '@ghostfolio/api/environments/environment';
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
DEFAULT_ROOT_URL,
|
||||
SUPPORTED_LANGUAGE_CODES
|
||||
} from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper';
|
||||
import { format } from 'date-fns';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
const descriptions = {
|
||||
de: 'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.',
|
||||
en: 'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.',
|
||||
es: 'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.',
|
||||
fr: 'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.',
|
||||
it: 'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.',
|
||||
nl: 'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.',
|
||||
pt: 'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.'
|
||||
};
|
||||
|
||||
const title = 'Ghostfolio – Open Source Wealth Management Software';
|
||||
const titleShort = 'Ghostfolio';
|
||||
|
||||
let indexHtmlMap: { [languageCode: string]: string } = {};
|
||||
|
||||
try {
|
||||
indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce(
|
||||
(map, languageCode) => ({
|
||||
...map,
|
||||
[languageCode]: fs.readFileSync(
|
||||
join(__dirname, '..', 'client', languageCode, 'index.html'),
|
||||
'utf8'
|
||||
)
|
||||
}),
|
||||
{}
|
||||
);
|
||||
} catch {}
|
||||
|
||||
const locales = {
|
||||
'/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': {
|
||||
featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png',
|
||||
title: `Ghostfolio auf Sackgeld.com vorgestellt - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2022/08/500-stars-on-github': {
|
||||
featureGraphicPath: 'assets/images/blog/500-stars-on-github.jpg',
|
||||
title: `500 Stars - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2022/10/hacktoberfest-2022': {
|
||||
featureGraphicPath: 'assets/images/blog/hacktoberfest-2022.png',
|
||||
title: `Hacktoberfest 2022 - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2022/12/the-importance-of-tracking-your-personal-finances': {
|
||||
featureGraphicPath: 'assets/images/blog/20221226.jpg',
|
||||
title: `The importance of tracking your personal finances - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2023/02/ghostfolio-meets-umbrel': {
|
||||
featureGraphicPath: 'assets/images/blog/ghostfolio-x-umbrel.png',
|
||||
title: `Ghostfolio meets Umbrel - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github': {
|
||||
featureGraphicPath: 'assets/images/blog/1000-stars-on-github.jpg',
|
||||
title: `Ghostfolio reaches 1’000 Stars on GitHub - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio': {
|
||||
featureGraphicPath: 'assets/images/blog/20230520.jpg',
|
||||
title: `Unlock your Financial Potential with Ghostfolio - ${titleShort}`
|
||||
},
|
||||
'/en/blog/2023/07/exploring-the-path-to-fire': {
|
||||
featureGraphicPath: 'assets/images/blog/20230701.jpg',
|
||||
title: `Exploring the Path to FIRE - ${titleShort}`
|
||||
}
|
||||
};
|
||||
|
||||
const isFileRequest = (filename: string) => {
|
||||
if (filename === '/assets/LICENSE') {
|
||||
return true;
|
||||
} else if (
|
||||
filename.includes('auth/ey') ||
|
||||
filename.includes(
|
||||
'personal-finance-tools/open-source-alternative-to-markets.sh'
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filename.split('.').pop() !== filename;
|
||||
};
|
||||
|
||||
export const HtmlTemplateMiddleware = async (
|
||||
request: Request,
|
||||
response: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const path = request.originalUrl.replace(/\/$/, '');
|
||||
let languageCode = path.substr(1, 2);
|
||||
|
||||
if (!SUPPORTED_LANGUAGE_CODES.includes(languageCode)) {
|
||||
languageCode = DEFAULT_LANGUAGE_CODE;
|
||||
}
|
||||
|
||||
const currentDate = format(new Date(), DATE_FORMAT);
|
||||
const rootUrl = process.env.ROOT_URL || DEFAULT_ROOT_URL;
|
||||
|
||||
if (
|
||||
path.startsWith('/api/') ||
|
||||
isFileRequest(path) ||
|
||||
!environment.production
|
||||
) {
|
||||
// Skip
|
||||
next();
|
||||
} else {
|
||||
const indexHtml = interpolate(indexHtmlMap[languageCode], {
|
||||
currentDate,
|
||||
languageCode,
|
||||
path,
|
||||
rootUrl,
|
||||
description: descriptions[languageCode],
|
||||
featureGraphicPath:
|
||||
locales[path]?.featureGraphicPath ?? 'assets/cover.png',
|
||||
title: locales[path]?.title ?? title
|
||||
});
|
||||
|
||||
return response.send(indexHtml);
|
||||
}
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { Environment } from '@ghostfolio/api/services/interfaces/environment.interface';
|
||||
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
||||
import { DEFAULT_CURRENCY, DEFAULT_ROOT_URL } from '@ghostfolio/common/config';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import { bool, cleanEnv, host, json, num, port, str } from 'envalid';
|
||||
@ -47,7 +47,7 @@ export class ConfigurationService {
|
||||
REDIS_HOST: str({ default: 'localhost' }),
|
||||
REDIS_PASSWORD: str({ default: '' }),
|
||||
REDIS_PORT: port({ default: 6379 }),
|
||||
ROOT_URL: str({ default: 'http://localhost:4200' }),
|
||||
ROOT_URL: str({ default: DEFAULT_ROOT_URL }),
|
||||
STRIPE_PUBLIC_KEY: str({ default: '' }),
|
||||
STRIPE_SECRET_KEY: str({ default: '' }),
|
||||
TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }),
|
||||
|
@ -1,14 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="h-100 position-relative" lang="en">
|
||||
<html class="h-100 position-relative" lang="${languageCode}">
|
||||
<head>
|
||||
<title>Ghostfolio – Open Source Wealth Management Software</title>
|
||||
<title>${title}</title>
|
||||
<base href="/" />
|
||||
<meta charset="utf-8" />
|
||||
<meta content="yes" name="apple-mobile-web-app-capable" />
|
||||
<meta
|
||||
content="Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms."
|
||||
name="description"
|
||||
/>
|
||||
<meta content="${description}" name="description" />
|
||||
<meta
|
||||
content="app, asset, cryptocurrency, dashboard, etf, finance, management, performance, portfolio, software, stock, trading, wealth, web3"
|
||||
name="keywords"
|
||||
@ -19,29 +16,20 @@
|
||||
content="Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies"
|
||||
name="twitter:description"
|
||||
/>
|
||||
<meta content="https://ghostfol.io/assets/cover.png" name="twitter:image" />
|
||||
<meta
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
name="twitter:title"
|
||||
/>
|
||||
<meta content="${rootUrl}/${featureGraphicPath}" name="twitter:image" />
|
||||
<meta content="${title}" name="twitter:title" />
|
||||
<meta
|
||||
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
||||
name="viewport"
|
||||
/>
|
||||
<meta content="#FFFFFF" name="theme-color" />
|
||||
<meta content="" property="og:description" />
|
||||
<meta
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
property="og:title"
|
||||
/>
|
||||
<meta content="${title}" property="og:title" />
|
||||
<meta content="website" property="og:type" />
|
||||
<meta content="https://ghostfol.io" property="og:url" />
|
||||
<meta content="https://ghostfol.io/assets/cover.png" property="og:image" />
|
||||
<meta content="2023-08-10T00:00:00+00:00" property="og:updated_time" />
|
||||
<meta
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
property="og:site_name"
|
||||
/>
|
||||
<meta content="${rootUrl}${path}" property="og:url" />
|
||||
<meta content="${rootUrl}/${featureGraphicPath}" property="og:image" />
|
||||
<meta content="${currentDate}T00:00:00+00:00" property="og:updated_time" />
|
||||
<meta content="${title}" property="og:site_name" />
|
||||
|
||||
<link
|
||||
href="../assets/apple-touch-icon.png"
|
||||
|
@ -1,63 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="h-100 position-relative" lang="${languageCode}">
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<base href="/" />
|
||||
<meta charset="utf-8" />
|
||||
<meta content="yes" name="apple-mobile-web-app-capable" />
|
||||
<meta content="${description}" name="description" />
|
||||
<meta
|
||||
content="app, asset, cryptocurrency, dashboard, etf, finance, management, performance, portfolio, software, stock, trading, wealth, web3"
|
||||
name="keywords"
|
||||
/>
|
||||
<meta content="yes" name="mobile-web-app-capable" />
|
||||
<meta content="summary_large_image" name="twitter:card" />
|
||||
<meta
|
||||
content="Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies"
|
||||
name="twitter:description"
|
||||
/>
|
||||
<meta content="${rootUrl}/${featureGraphicPath}" name="twitter:image" />
|
||||
<meta content="${title}" name="twitter:title" />
|
||||
<meta
|
||||
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
||||
name="viewport"
|
||||
/>
|
||||
<meta content="#FFFFFF" name="theme-color" />
|
||||
<meta content="" property="og:description" />
|
||||
<meta content="${title}" property="og:title" />
|
||||
<meta content="website" property="og:type" />
|
||||
<meta content="${rootUrl}${path}" property="og:url" />
|
||||
<meta content="${rootUrl}/${featureGraphicPath}" property="og:image" />
|
||||
<meta content="${currentDate}T00:00:00+00:00" property="og:updated_time" />
|
||||
<meta content="${title}" property="og:site_name" />
|
||||
|
||||
<link
|
||||
href="../assets/apple-touch-icon.png"
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
/>
|
||||
<link
|
||||
href="../assets/favicon-32x32.png"
|
||||
rel="icon"
|
||||
sizes="32x32"
|
||||
type="image/png"
|
||||
/>
|
||||
<link
|
||||
href="../assets/favicon-16x16.png"
|
||||
rel="icon"
|
||||
sizes="16x16"
|
||||
type="image/png"
|
||||
/>
|
||||
<link href="../assets/site.webmanifest" rel="manifest" />
|
||||
</head>
|
||||
<body>
|
||||
<gf-root></gf-root>
|
||||
|
||||
<script src="../ionicons/ionicons.esm.js" type="module"></script>
|
||||
<script nomodule="" src="ionicons.js"></script>
|
||||
|
||||
<noscript
|
||||
>Please enable JavaScript to continue using this application.</noscript
|
||||
>
|
||||
</body>
|
||||
</html>
|
@ -40,6 +40,7 @@ export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
|
||||
export const DEFAULT_LANGUAGE_CODE = 'en';
|
||||
export const DEFAULT_PAGE_SIZE = 50;
|
||||
export const DEFAULT_REQUEST_TIMEOUT = ms('3 seconds');
|
||||
export const DEFAULT_ROOT_URL = 'http://localhost:4200';
|
||||
|
||||
export const EMERGENCY_FUND_TAG_ID = '4452656d-9fa4-4bd0-ba38-70492e31d180';
|
||||
|
||||
|
@ -235,7 +235,7 @@ export function isCurrency(aSymbol = '') {
|
||||
}
|
||||
|
||||
export function interpolate(template: string, context: any) {
|
||||
return template.replace(/[$]{([^}]+)}/g, (_, objectPath) => {
|
||||
return template?.replace(/[$]{([^}]+)}/g, (_, objectPath) => {
|
||||
const properties = objectPath.split('.');
|
||||
return properties.reduce(
|
||||
(previous, current) => previous?.[current],
|
||||
|
Loading…
x
Reference in New Issue
Block a user