Push all to new repo
50
.eslintrc.json
Executable 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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
42
.gitignore
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
3
.vscode/settings.json
vendored
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"angular.enable-strict-mode-prompt": false
|
||||||
|
}
|
15
LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) {{ year }}, {{ author }}
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
27
README.md
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
# ngx-retroblog
|
||||||
|
|
||||||
|
Alternate version of [ngx-dumblog](https://github.com/pedroCX486/ngx-dumblog), with Markdown support! With customization on the settings.json file, lightweight size (no bootstrap!) and more. The editor you need to compile from the [showdown-canvas](https://github.com/pedroCX486/showdown-canvas) project.
|
||||||
|
|
||||||
|
## Customizations
|
||||||
|
|
||||||
|
On the settings.json file you'll find options to customize your blog title, avatar, bio, social networks (leave the fields empty, don't delete them if you don't want them showing up!) and ability to select how many posts will be shown on the home page.
|
||||||
|
|
||||||
|
## Creating and editing posts
|
||||||
|
|
||||||
|
You have to compile and host a build of the [showdown-canvas](https://tildegit.org/pedrocx486/showdown-canvas) editor. From there you can create and edit posts. To have those posts reflect on your blog, you need to place the files it generates in the assets/posts folder of your install.
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
|
||||||
|
First run `npm install` then run `npm start` and it should open automatically on `http://localhost:4200/`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `npm run build` to build it. You can also run `custom-build.bat` to build it and then have the index.html title changed after build. But **first** change the variable BLOGNAME inside the .bat file! I intend to add a tool in the future capable of reading the name from the settings.json and then updating the index.html after the build, instead of keeping this .bat file.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Just do a pull request. :-)
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
Because I always thought Jekyll to be too convoluted and I wanted something dumb and simple. The code isn't an example of awesomeness but it does the job and pretty well at that.
|
105
angular.json
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"ngx-retroblog": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
},
|
||||||
|
"@schematics/angular:application": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/ngx-retroblog",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/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": "ngx-retroblog:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "ngx-retroblog:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "ngx-retroblog:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "ngx-retroblog",
|
||||||
|
"cli": {
|
||||||
|
"defaultCollection": "@angular-eslint/schematics"
|
||||||
|
}
|
||||||
|
}
|
23743
package-lock.json
generated
Executable file
43
package.json
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "ngx-retroblog",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve --open",
|
||||||
|
"build": "ng lint && ng build --configuration production",
|
||||||
|
"build:subfolder": "ng build --base-href=\"/blog/\" --configuration production",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"lint": "ng lint"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "~13.3.0",
|
||||||
|
"@angular/common": "~13.3.0",
|
||||||
|
"@angular/compiler": "~13.3.0",
|
||||||
|
"@angular/core": "~13.3.0",
|
||||||
|
"@angular/forms": "~13.3.0",
|
||||||
|
"@angular/platform-browser": "~13.3.0",
|
||||||
|
"@angular/platform-browser-dynamic": "~13.3.0",
|
||||||
|
"@angular/router": "~13.3.0",
|
||||||
|
"rxjs": "~7.5.0",
|
||||||
|
"showdown": "^2.1.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.11.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "~13.3.7",
|
||||||
|
"@angular-eslint/builder": "13.2.1",
|
||||||
|
"@angular-eslint/eslint-plugin": "13.2.1",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "13.2.1",
|
||||||
|
"@angular-eslint/schematics": "13.2.1",
|
||||||
|
"@angular-eslint/template-parser": "13.2.1",
|
||||||
|
"@angular/cli": "~13.3.7",
|
||||||
|
"@angular/compiler-cli": "~13.3.0",
|
||||||
|
"@types/node": "^12.11.1",
|
||||||
|
"@types/showdown": "^2.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "5.17.0",
|
||||||
|
"@typescript-eslint/parser": "5.17.0",
|
||||||
|
"eslint": "^8.12.0",
|
||||||
|
"typescript": "~4.6.2"
|
||||||
|
}
|
||||||
|
}
|
30
src/app/app-routing.module.ts
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { ArchivesComponent } from './archives/archives.component';
|
||||||
|
import { PostsComponent } from './posts/posts.component';
|
||||||
|
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: HomeComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'archives',
|
||||||
|
component: ArchivesComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'posts',
|
||||||
|
component: PostsComponent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppRoutingModule {
|
||||||
|
}
|
3
src/app/app.component.html
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
<app-navigation></app-navigation>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
0
src/app/app.component.scss
Executable file
37
src/app/app.component.ts
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppComponent implements OnInit {
|
||||||
|
|
||||||
|
urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.navigate();
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(): void {
|
||||||
|
if (this.urlParams.get('post')) {
|
||||||
|
this.router.navigate(['/posts'], { queryParams: { post: this.urlParams.get('post') }, skipLocationChange: true });
|
||||||
|
} else {
|
||||||
|
switch (this.urlParams.get('page')) { // Keeping this style of check in case more pages get added in the future.
|
||||||
|
case 'archives':
|
||||||
|
this.router.navigate(['/archives'], { skipLocationChange: true });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.router.navigate(['/'], { skipLocationChange: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/app/app.module.ts
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
import { NavigationComponent } from '@shared/components/navigation/navigation.component';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { PostsComponent } from './posts/posts.component';
|
||||||
|
import { ArchivesComponent } from './archives/archives.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
NavigationComponent,
|
||||||
|
HomeComponent,
|
||||||
|
PostsComponent,
|
||||||
|
ArchivesComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
HttpClientModule,
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppModule { }
|
27
src/app/archives/archives.component.html
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="placard">
|
||||||
|
<div class="placard-title">Archives</div>
|
||||||
|
<div class="placard-text">Here you can find all the blog posts.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<input type="text" placeholder="Search by title" (keyup)="searchArchive($event.target!.value)">
|
||||||
|
|
||||||
|
<button class="btn-action" type="button" (click)="searchArchive('', true);">Clear</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-group" *ngIf="filteredArchives.length > 0">
|
||||||
|
<ng-container *ngFor="let archive of filteredArchives">
|
||||||
|
<a href="./?post={{archive.filename}}">
|
||||||
|
<div class="list-item mouse-hover title">
|
||||||
|
{{archive.postTitle}}
|
||||||
|
<div class="timestamp" *ngIf="!!archive.timestamp">{{parseTimestamp(archive.timestamp)}}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="margin-y text-center" *ngIf="!!settings.username">
|
||||||
|
<a href="./">
|
||||||
|
<button type="button" class="margins-sides btn-secondary btn-fat">.HOME.</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
9
src/app/archives/archives.component.scss
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
input[type="text"] {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (hover: none) {
|
||||||
|
input[type="text"] {
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
}
|
57
src/app/archives/archives.component.ts
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { IArchive } from '@shared/models/archive.model';
|
||||||
|
import { ISettings } from '@shared/models/settings.model';
|
||||||
|
import { HelperService } from '@shared/services/helper.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-archives',
|
||||||
|
templateUrl: './archives.component.html',
|
||||||
|
styleUrls: ['./archives.component.scss']
|
||||||
|
})
|
||||||
|
export class ArchivesComponent implements OnInit {
|
||||||
|
|
||||||
|
urlParams = new URLSearchParams(window.location.search);
|
||||||
|
archives: IArchive[] = [];
|
||||||
|
filteredArchives: IArchive[] = [];
|
||||||
|
settings: ISettings = {} as ISettings;
|
||||||
|
|
||||||
|
constructor(private helperService: HelperService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings(): void {
|
||||||
|
this.helperService.getSettings().subscribe({
|
||||||
|
next: (data: ISettings) => {
|
||||||
|
this.settings = data;
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.loadArchive();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadArchive(): void {
|
||||||
|
this.helperService.getArchive().subscribe(data => {
|
||||||
|
this.archives = data;
|
||||||
|
this.filteredArchives = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTimestamp(timestamp: string): string {
|
||||||
|
return !!timestamp ? '(' + this.helperService.parseTimestamp(timestamp) + ')' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
searchArchive(arg: string, clear?: boolean): void {
|
||||||
|
if (!!arg) {
|
||||||
|
this.filteredArchives = this.archives.filter(entry => entry.postTitle.toLowerCase().includes(arg.toLowerCase()));
|
||||||
|
} else {
|
||||||
|
this.filteredArchives = this.archives;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
(document.getElementById('search') as HTMLInputElement).value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/app/home/home.component.html
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="card margins" *ngFor="let content of contents">
|
||||||
|
<div class="card-header">
|
||||||
|
<a class="title" href="./?post={{content.filename}}">{{content.postTitle}}</a>
|
||||||
|
<div class="timestamp" *ngIf="!!content.timestamp">
|
||||||
|
<strong>Posted on</strong> {{helperService.parseTimestamp(content.timestamp)}}
|
||||||
|
</div>
|
||||||
|
<div class="timestamp" *ngIf="!!content.editedTimestamp">
|
||||||
|
<strong>Edited on</strong> {{helperService.parseTimestamp(content.editedTimestamp)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text" [innerHTML]="generateHTML(content.postContent)"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="./?page=archives">
|
||||||
|
<button type="button" class="btn-fat btn-primary margins-sides margin-y">.ARCHIVES.</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
0
src/app/home/home.component.scss
Executable file
82
src/app/home/home.component.ts
Executable file
|
@ -0,0 +1,82 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { IArchive } from '@shared/models/archive.model';
|
||||||
|
import { PostModel } from '@shared/models/post.model';
|
||||||
|
import { ISettings } from '@shared/models/settings.model';
|
||||||
|
import { HelperService } from '@shared/services/helper.service';
|
||||||
|
import * as showdown from 'showdown';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrls: ['./home.component.scss']
|
||||||
|
})
|
||||||
|
export class HomeComponent implements OnInit {
|
||||||
|
|
||||||
|
contents: PostModel[] = [];
|
||||||
|
archives: IArchive[] = [];
|
||||||
|
settings: ISettings = {} as ISettings;
|
||||||
|
postsLoaded = 0;
|
||||||
|
|
||||||
|
showdownmd: any;
|
||||||
|
|
||||||
|
constructor(public helperService: HelperService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.showdownmd = new showdown.Converter();
|
||||||
|
|
||||||
|
this.helperService.getSettings().subscribe({
|
||||||
|
next: (data: ISettings) => {
|
||||||
|
this.settings = data;
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.loadArchive();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadArchive(): void {
|
||||||
|
this.helperService.getArchive().subscribe({
|
||||||
|
next: (data: IArchive[]) => {
|
||||||
|
this.archives = data;
|
||||||
|
this.settings.maxPosts = this.archives.length < this.settings.maxPosts ? this.archives.length : this.settings.maxPosts;
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.loadPosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPosts(): void {
|
||||||
|
if (this.postsLoaded < this.settings.maxPosts) {
|
||||||
|
this.helperService.getPost('./assets/posts/' + this.archives[this.postsLoaded].filename + '.json').subscribe({
|
||||||
|
next: (data: PostModel) => {
|
||||||
|
this.contents.push(data);
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.continue();
|
||||||
|
},
|
||||||
|
error: error => {
|
||||||
|
this.continue(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue(error?: any): void {
|
||||||
|
if (error) {
|
||||||
|
const errorPost = new PostModel();
|
||||||
|
errorPost.postTitle = 'Aw shucks!';
|
||||||
|
errorPost.postContent = 'We couldn\'t load this post! Sorry! <strong>(' + error.status + ' ' + error.statusText + ')</strong>';
|
||||||
|
errorPost.filename = 'not-found';
|
||||||
|
this.contents.push(errorPost);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.postsLoaded += 1;
|
||||||
|
this.loadPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateHTML(markdown: string): void {
|
||||||
|
return this.showdownmd.makeHtml(markdown);
|
||||||
|
}
|
||||||
|
}
|
57
src/app/posts/posts.component.html
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
<div class="card margins">
|
||||||
|
<h4 class="card-header">
|
||||||
|
<a class="title" href="./?post={{content.filename}}">{{content.postTitle}}</a>
|
||||||
|
<div class="timestamp" *ngIf="content.draft">
|
||||||
|
<strong>Draft: Post not published.</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp" *ngIf="!!content.timestamp">
|
||||||
|
<strong>Posted on</strong> {{content.timestamp}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp" *ngIf="!!content.editedTimestamp">
|
||||||
|
<strong>Edited on</strong> {{content.editedTimestamp}}
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text" [innerHTML]="generateHTML(content.postContent)"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-box" *ngIf="!!settings.username">
|
||||||
|
<div class="avatar">
|
||||||
|
<img src="./assets/{{settings.avatar}}" *ngIf="!!settings.avatar"><br>
|
||||||
|
</div>
|
||||||
|
<div class="bio-container">
|
||||||
|
<div class="bio-username">{{settings.username}}</div>
|
||||||
|
<div class="bio-text">{{settings.bio}}</div>
|
||||||
|
<div class="social-networks">
|
||||||
|
<a *ngIf="!!settings.twitter" href="{{settings.twitter}}" target="_blank">
|
||||||
|
<img src="./assets/social/twitter.png">
|
||||||
|
</a>
|
||||||
|
<a *ngIf="!!settings.mastodon" href="{{settings.mastodon}}" target="_blank">
|
||||||
|
<img src="./assets/social/mastodon.png">
|
||||||
|
</a>
|
||||||
|
<a *ngIf="!!settings.instagram" href="{{settings.instagram}}" target="_blank">
|
||||||
|
<img src="./assets/social/instagram.png">
|
||||||
|
</a>
|
||||||
|
<a *ngIf="!!settings.youtube" href="{{settings.youtube}}" target="_blank">
|
||||||
|
<img src="./assets/social/youtube.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="margin-y text-center" *ngIf="settings.username">
|
||||||
|
<a href="./">
|
||||||
|
<button type="button" class="margins-sides btn-secondary btn-fat">.HOME.</button>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<a href="./?page=archives">
|
||||||
|
<button type="button" class="margin-top margins-sides btn-primary btn-fat">
|
||||||
|
.ARCHIVES.
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
56
src/app/posts/posts.component.scss
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
.profile-box {
|
||||||
|
display: flex !important;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 5rem !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: rgb(56, 56, 56);
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
padding: 0 1rem 0 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar img {
|
||||||
|
border: 1px solid black;
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 150px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio-container {
|
||||||
|
padding-left: 1rem;
|
||||||
|
|
||||||
|
.bio-username {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
text-shadow: 0px 0px 10px #00bc97;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio-text {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-networks {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (hover: none) {
|
||||||
|
.margins {
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-box {
|
||||||
|
width: 90%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
src/app/posts/posts.component.ts
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { PostModel } from '@shared/models/post.model';
|
||||||
|
import { ISettings } from '@shared/models/settings.model';
|
||||||
|
import { HelperService } from '@shared/services/helper.service';
|
||||||
|
import * as showdown from 'showdown';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-posts',
|
||||||
|
templateUrl: './posts.component.html',
|
||||||
|
styleUrls: ['./posts.component.scss']
|
||||||
|
})
|
||||||
|
export class PostsComponent implements OnInit {
|
||||||
|
|
||||||
|
urlParams = new URLSearchParams(window.location.search);
|
||||||
|
content: PostModel = new PostModel();
|
||||||
|
settings: ISettings = {} as ISettings;
|
||||||
|
|
||||||
|
showdownmd: any;
|
||||||
|
|
||||||
|
constructor(private titleService: Title, private helperService: HelperService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.showdownmd = new showdown.Converter();
|
||||||
|
|
||||||
|
this.helperService.getSettings().subscribe({
|
||||||
|
next: (data: ISettings) => {
|
||||||
|
this.settings = data;
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
if (this.urlParams.get('post')) {
|
||||||
|
this.loadPost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPost(): void {
|
||||||
|
this.helperService.getPost('./assets/posts/' + this.urlParams.get('post') + '.json').subscribe({
|
||||||
|
next: (data: PostModel) => {
|
||||||
|
this.content = data;
|
||||||
|
this.content.timestamp = !!this.content.timestamp ? this.helperService.parseTimestamp(data.timestamp) : '';
|
||||||
|
this.content.editedTimestamp = !!this.content.editedTimestamp ? this.helperService.parseTimestamp(data.editedTimestamp) : '';
|
||||||
|
this.titleService.setTitle(this.content.postTitle + ' ~ ' + this.settings.blogTitle);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.content.postTitle = 'Whoops!';
|
||||||
|
this.content.postContent = 'We couldn\'t load this post! <strong>(' + error.status + ')</strong>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generateHTML(markdown: string): void {
|
||||||
|
return this.showdownmd.makeHtml(markdown);
|
||||||
|
}
|
||||||
|
}
|
0
src/assets/.gitkeep
Executable file
BIN
src/assets/avatar.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
7
src/assets/posts/archive.json
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"postTitle": "Hello World!",
|
||||||
|
"timestamp": "1654062776",
|
||||||
|
"filename": "hello-world"
|
||||||
|
}
|
||||||
|
]
|
8
src/assets/posts/hello-world.json
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"postTitle": "Hello World!",
|
||||||
|
"timestamp": "1654062776",
|
||||||
|
"editedTimestamp": "",
|
||||||
|
"postContent": "Hello World!",
|
||||||
|
"filename": "hello-world",
|
||||||
|
"draft": false
|
||||||
|
}
|
15
src/assets/settings.json
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"blogTitle": "Your Blog",
|
||||||
|
"avatar": "avatar.png",
|
||||||
|
"username": "Username",
|
||||||
|
"bio": "Lorem ipsum...",
|
||||||
|
"twitter": "",
|
||||||
|
"mastodon": "",
|
||||||
|
"instagram": "",
|
||||||
|
"youtube": "",
|
||||||
|
"maxPosts": 5,
|
||||||
|
"customUrl": {
|
||||||
|
"title": "GitHub",
|
||||||
|
"url": "https://github.com/"
|
||||||
|
}
|
||||||
|
}
|
BIN
src/assets/social/facebook.png
Executable file
After Width: | Height: | Size: 517 B |
BIN
src/assets/social/instagram.png
Executable file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/social/linkedin.png
Executable file
After Width: | Height: | Size: 488 B |
BIN
src/assets/social/mastodon.png
Executable file
After Width: | Height: | Size: 3 KiB |
BIN
src/assets/social/twitter.png
Executable file
After Width: | Height: | Size: 956 B |
BIN
src/assets/social/youtube.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
3
src/environments/environment.prod.ts
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
export const environment = {
|
||||||
|
production: true,
|
||||||
|
};
|
16
src/environments/environment.ts
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
|
export const environment = {
|
||||||
|
production: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For easier debugging in development mode, you can import the following file
|
||||||
|
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||||
|
*
|
||||||
|
* This import should be commented out in production mode because it will have a negative impact
|
||||||
|
* on performance if an error is thrown.
|
||||||
|
*/
|
||||||
|
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
BIN
src/favicon.ico
Normal file
After Width: | Height: | Size: 361 KiB |
15
src/index.html
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>...</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>Hey, any chance you could turn JS on? This blog runs on Angular. But you ain't missing anything so... Your choice. Cheers!</noscript>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!-- There is fuck. -->
|
12
src/main.ts
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
import { enableProdMode } from '@angular/core';
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
import { environment } from './environments/environment';
|
||||||
|
|
||||||
|
if (environment.production) {
|
||||||
|
enableProdMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.catch(err => console.error(err));
|
53
src/polyfills.ts
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||||
|
* You can add your own extra polyfills to this file.
|
||||||
|
*
|
||||||
|
* This file is divided into 2 sections:
|
||||||
|
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||||
|
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||||
|
* file.
|
||||||
|
*
|
||||||
|
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||||
|
* automatically update themselves. This includes recent versions of Safari, Chrome (including
|
||||||
|
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
|
||||||
|
*
|
||||||
|
* Learn more in https://angular.io/guide/browser-support
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* BROWSER POLYFILLS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||||
|
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||||
|
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||||
|
* will put import in the top of bundle, so user need to create a separate file
|
||||||
|
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||||
|
* into that file, and then add the following code before importing zone.js.
|
||||||
|
* import './zone-flags';
|
||||||
|
*
|
||||||
|
* The flags allowed in zone-flags.ts are listed here.
|
||||||
|
*
|
||||||
|
* The following flags will work for all browsers.
|
||||||
|
*
|
||||||
|
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||||
|
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||||
|
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||||
|
*
|
||||||
|
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||||
|
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||||
|
*
|
||||||
|
* (window as any).__Zone_enable_cross_context_check = true;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Zone JS is required by default for Angular itself.
|
||||||
|
*/
|
||||||
|
import 'zone.js'; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* APPLICATION IMPORTS
|
||||||
|
*/
|
13
src/shared/components/navigation/navigation.component.html
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="topnav" *ngIf="!!settings.username">
|
||||||
|
<a class="no-focus" href="./">{{settings.blogTitle}}</a>
|
||||||
|
<a href="./" [class.active]="isCurrentPage('/')">Home</a>
|
||||||
|
<a href="./?page=archives" [class.active]="isCurrentPage('/archives')">Archives</a>
|
||||||
|
<a *ngIf="!!settings?.customUrl?.url" href="{{settings?.customUrl?.url}}" target="_blank">{{settings?.customUrl?.title}}</a>
|
||||||
|
<a href="javascript:void(0);" class="icon" (click)="openMenu()">
|
||||||
|
☰︎
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="right-align no-focus" href="http://github.com/pedroCX486/ngx-dumblog" target="_blank" rel="noopener">
|
||||||
|
Powered by<br />ngx-dumblog
|
||||||
|
</a>
|
||||||
|
</div>
|
76
src/shared/components/navigation/navigation.component.scss
Executable file
|
@ -0,0 +1,76 @@
|
||||||
|
.topnav {
|
||||||
|
min-height: 48px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #333;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 17px;
|
||||||
|
border-right: rgb(159, 159, 159) 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a:last-child {
|
||||||
|
border-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a:hover:not(.no-focus) {
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a.active {
|
||||||
|
background-color: rgb(97, 0, 162);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.topnav a:not(:first-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a.icon {
|
||||||
|
float: right;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav.responsive {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav.responsive a {
|
||||||
|
float: none;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav.responsive .icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-align {
|
||||||
|
float: right !important;
|
||||||
|
font-size: 8px !important;
|
||||||
|
font-family: 'pc98';
|
||||||
|
}
|
48
src/shared/components/navigation/navigation.component.ts
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ISettings } from '@shared/models/settings.model';
|
||||||
|
import { HelperService } from '@shared/services/helper.service';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navigation',
|
||||||
|
templateUrl: './navigation.component.html',
|
||||||
|
styleUrls: ['./navigation.component.scss']
|
||||||
|
})
|
||||||
|
export class NavigationComponent implements OnInit {
|
||||||
|
|
||||||
|
settings: ISettings = {} as ISettings;
|
||||||
|
|
||||||
|
constructor(private router: Router, private helperService: HelperService, private titleService: Title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(): void {
|
||||||
|
this.helperService.getSettings().subscribe({
|
||||||
|
next: (data: ISettings) => {
|
||||||
|
this.settings = data;
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.titleService.setTitle(this.settings.blogTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openMenu(): void {
|
||||||
|
const element = document.getElementsByClassName('topnav')[0];
|
||||||
|
if (element.className === 'topnav') {
|
||||||
|
element.className += ' responsive';
|
||||||
|
} else {
|
||||||
|
element.className = 'topnav';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentPage(url: string): boolean {
|
||||||
|
return this.router.url === url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
src/shared/models/archive.model.ts
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IArchive {
|
||||||
|
postTitle: string;
|
||||||
|
timestamp: string;
|
||||||
|
filename: string;
|
||||||
|
}
|
8
src/shared/models/post.model.ts
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
export class PostModel {
|
||||||
|
postTitle = '';
|
||||||
|
timestamp = '';
|
||||||
|
editedTimestamp = '';
|
||||||
|
postContent = '';
|
||||||
|
filename = '';
|
||||||
|
draft = false;
|
||||||
|
}
|
17
src/shared/models/settings.model.ts
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
export interface ISettings {
|
||||||
|
blogTitle: string;
|
||||||
|
avatar: string;
|
||||||
|
username: string;
|
||||||
|
bio: string;
|
||||||
|
twitter: string;
|
||||||
|
mastodon: string;
|
||||||
|
instagram: string;
|
||||||
|
youtube: string;
|
||||||
|
maxPosts: number;
|
||||||
|
customUrl: ICustomUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICustomUrl {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
28
src/shared/services/helper.service.ts
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class HelperService {
|
||||||
|
constructor(private httpClient: HttpClient) { }
|
||||||
|
|
||||||
|
getSettings(): Observable<any> {
|
||||||
|
return this.httpClient.get('./assets/settings.json?latest=' + this.generateTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
getArchive(): Observable<any> {
|
||||||
|
return this.httpClient.get('./assets/posts/archive.json?latest=' + this.generateTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
getPost(url: string): Observable<any> {
|
||||||
|
return this.httpClient.get(url + '?latest='+ this.generateTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTimestamp(): string {
|
||||||
|
return Math.round((new Date()).getTime() / 1000).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTimestamp(timestamp: string): string {
|
||||||
|
return new Date(Number(timestamp) * 1000).toUTCString();
|
||||||
|
}
|
||||||
|
}
|
59
src/shared/styles/buttons.scss
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
// Buttons
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.6rem;
|
||||||
|
border: none;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
font-family: 'pxplus_ibm_vga8regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
.btn-fat {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary
|
||||||
|
.btn-primary {
|
||||||
|
background-color: rgb(97, 0, 162);
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
box-shadow: 0px 0px 7px rgb(97, 0, 162);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #00d199;
|
||||||
|
color: #444;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
box-shadow: 0px 0px 7px #00d199;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tertiary
|
||||||
|
.btn-tertiary {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-tertiary:hover {
|
||||||
|
box-shadow: 0px 0px 7px #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action
|
||||||
|
.btn-action {
|
||||||
|
background-color: #00905e;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action:hover {
|
||||||
|
box-shadow: 0px 0px 7px #00905e;
|
||||||
|
}
|
36
src/shared/styles/cards.scss
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
// Cards
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: none;
|
||||||
|
background-color: rgb(97, 0, 162);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #00ffea !important;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: 'pc98';
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: #b5b5b5;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'pc98';
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: rgb(46, 46, 46);
|
||||||
|
margin-top: -10px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .card-text {
|
||||||
|
// }
|
BIN
src/shared/styles/fonts/PC_9800.woff
Executable file
BIN
src/shared/styles/fonts/PC_9800.woff2
Executable file
BIN
src/shared/styles/fonts/PxPlus_IBM_VGA8.ttf
Executable file
BIN
src/shared/styles/fonts/PxPlus_IBM_VGA8.woff
Executable file
BIN
src/shared/styles/fonts/pc-9800.ttf
Executable file
17
src/shared/styles/inputs.scss
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
// Inputs
|
||||||
|
::placeholder {
|
||||||
|
color: rgb(208, 208, 208);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgb(138, 138, 138);
|
||||||
|
resize: vertical;
|
||||||
|
min-width: 5%;
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
font-size: 15px;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
font-family: 'pxplus_ibm_vga8regular';
|
||||||
|
}
|
17
src/shared/styles/lists.scss
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
// Lists
|
||||||
|
.list-group {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
padding: 1rem;
|
||||||
|
border: rgb(88, 88, 88) solid 1px;
|
||||||
|
background-color: rgb(46, 46, 46);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item:hover {
|
||||||
|
background-color: rgb(52, 39, 62);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
16
src/shared/styles/placard.scss
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
// Placard
|
||||||
|
.placard {
|
||||||
|
background-color: rgb(97, 0, 162);
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 2rem;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: white;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placard-title {
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 3.5rem;
|
||||||
|
}
|
23
src/shared/styles/utils.scss
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
//Margins
|
||||||
|
.margins {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margins-sides {
|
||||||
|
width: 40%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top {
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-y {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alignments
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
42
src/styles.scss
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
// Imports
|
||||||
|
@import "./shared/styles/buttons.scss";
|
||||||
|
@import "./shared/styles/cards.scss";
|
||||||
|
@import "./shared/styles/inputs.scss";
|
||||||
|
@import "./shared/styles/lists.scss";
|
||||||
|
@import "./shared/styles/placard.scss";
|
||||||
|
@import "./shared/styles/utils.scss";
|
||||||
|
|
||||||
|
// Global
|
||||||
|
@font-face {
|
||||||
|
font-family: "pxplus_ibm_vga8regular";
|
||||||
|
src: url("./shared/styles/fonts/PxPlus_IBM_VGA8.woff") format("woff"),
|
||||||
|
url("./shared/styles/fonts//PxPlus_IBM_VGA8.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "pc98";
|
||||||
|
src: url("./shared/styles/fonts/PC_9800.woff2") format("woff2"),
|
||||||
|
url("./shared/styles/fonts/PC_9800.woff") format("woff"),
|
||||||
|
url("./shared/styles/fonts/pc-9800.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body
|
||||||
|
body {
|
||||||
|
line-height: 1.2;
|
||||||
|
font-family: "pxplus_ibm_vga8regular";
|
||||||
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anchors
|
||||||
|
a {
|
||||||
|
color: #00ffea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: #00905e;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
15
tsconfig.app.json
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts",
|
||||||
|
"src/polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
40
tsconfig.json
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@shared/*": [
|
||||||
|
"src/shared/*"
|
||||||
|
],
|
||||||
|
"@env/*": [
|
||||||
|
"src/environments/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": [
|
||||||
|
"es2020",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": false
|
||||||
|
}
|
||||||
|
}
|