1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-25 02:04:15 +02:00

Implementing basic blogging support

This commit is contained in:
Patrik J. Braun 2021-12-31 18:50:21 +01:00
parent 38dfcf03cd
commit 47fec015c1
15 changed files with 274 additions and 29 deletions

View File

@ -84,7 +84,9 @@
"leaflet.markercluster/dist/MarkerCluster.Default.css",
"src/frontend/styles.css"
],
"scripts": [],
"scripts": [
"node_modules/marked/lib/marked.js"
],
"i18nMissingTranslation": "warning"
},
"configurations": {

72
package-lock.json generated
View File

@ -529,15 +529,6 @@
}
}
},
"@angular/http": {
"version": "7.2.16",
"resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.16.tgz",
"integrity": "sha512-yvjbNyzFSmmz4UTjCdy5M8mk0cZqf9TvSf8yN5UVIwtw4joyuUdlgJCuin0qSbQOKIf/JjHoofpO2JkPCGSNww==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/language-service": {
"version": "11.2.14",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-11.2.14.tgz",
@ -3506,6 +3497,12 @@
"@types/leaflet": "*"
}
},
"@types/marked": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.5.tgz",
"integrity": "sha512-shRZ7XnYFD/8n8zSjKvFdto1QNSf4tONZIlNEZGrJe8GsOE8DL/hG1Hbl8gZlfLnjS7+f5tZGIaTgfpyW38h4w==",
"dev": true
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -7606,6 +7603,12 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"emoji-toolkit": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-6.6.0.tgz",
"integrity": "sha512-pEu0kow2p1N8zCKnn/L6H0F3rWUBB3P3hVjr/O5yl1fK7N9jU4vO4G7EFapC5Y3XwZLUCY0FZbOPyTkH+4V2eQ==",
"dev": true
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@ -11406,6 +11409,23 @@
"minimatch": "^3.0.0"
}
},
"katex": {
"version": "0.13.24",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
"integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==",
"dev": true,
"requires": {
"commander": "^8.0.0"
},
"dependencies": {
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true
}
}
},
"keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@ -12153,6 +12173,12 @@
"object-visit": "^1.0.0"
}
},
"marked": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
"dev": true
},
"matchdep": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@ -13280,6 +13306,28 @@
}
}
},
"ngx-markdown": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-11.1.3.tgz",
"integrity": "sha512-z32q8l76ubrcP62L03mdvrizwueLBHV10LkT8MEDnFcjmY+8J1PytxFJ9EBTJpvc+CaPolgAoi7felN2XJZTSg==",
"dev": true,
"requires": {
"@types/marked": "^2.0.0",
"emoji-toolkit": "^6.0.1",
"katex": "^0.13.0",
"marked": "^2.0.0",
"prismjs": "^1.23.0",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"ngx-toastr": {
"version": "13.2.1",
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-13.2.1.tgz",
@ -15586,6 +15634,12 @@
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ=="
},
"prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==",
"dev": true
},
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",

View File

@ -65,7 +65,6 @@
"@angular/compiler-cli": "11.2.14",
"@angular/core": "11.2.14",
"@angular/forms": "11.2.14",
"@angular/http": "7.2.16",
"@angular/language-service": "11.2.14",
"@angular/localize": "11.2.14",
"@angular/platform-browser": "11.2.14",
@ -128,6 +127,7 @@
"ngx-bootstrap": "6.2.0",
"ngx-clipboard": "14.0.1",
"ngx-cookie-service": "11.0.2",
"ngx-markdown": "11.1.3",
"ngx-device-detector": "2.0.10",
"ngx-toastr": "13.2.1",
"nyc": "15.1.0",

View File

@ -15,7 +15,7 @@ export const SupportedFormats = {
'ogg'
],
MetaFiles: [
'gpx', 'pg2conf'
'gpx', 'pg2conf', 'md'
],
// These formats need to be transcoded (with the build-in ffmpeg support)
TranscodeNeed: {

View File

@ -53,7 +53,7 @@ import {SettingsService} from './ui/settings/settings.service';
import {ShareSettingsComponent} from './ui/settings/share/share.settings.component';
import {BasicSettingsComponent} from './ui/settings/basic/basic.settings.component';
import {OtherSettingsComponent} from './ui/settings/other/other.settings.component';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';
import {DefaultUrlSerializer, UrlSerializer, UrlTree} from '@angular/router';
import {IndexingSettingsComponent} from './ui/settings/indexing/indexing.settings.component';
import {LanguageComponent} from './ui/language/language.component';
@ -106,7 +106,11 @@ import {AlbumsService} from './ui/albums/albums.service';
import {GallerySearchQueryBuilderComponent} from './ui/gallery/search/query-builder/query-bulder.gallery.component';
import {SavedSearchPopupComponent} from './ui/albums/saved-search-popup/saved-search-popup.component';
import {AlbumsSettingsComponent} from './ui/settings/albums/albums.settings.component';
import { MarkdownModule } from 'ngx-markdown';
import {GalleryBlogComponent} from './ui/gallery/blog/blog.gallery.component';
import {MDFilesFilterPipe} from './pipes/MDFilesFilterPipe';
import {FileDTOToPathPipe} from './pipes/FileDTOToPathPipe';
import {BlogService} from './ui/gallery/blog/blog.service';
@Injectable()
export class MyHammerConfig extends HammerGestureConfig {
@ -172,7 +176,8 @@ Marker.prototype.options.icon = iconDefault;
TimepickerModule.forRoot(),
LoadingBarModule,
LeafletModule,
LeafletMarkerClusterModule
LeafletMarkerClusterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
],
declarations: [AppComponent,
LoginComponent,
@ -194,6 +199,7 @@ Marker.prototype.options.icon = iconDefault;
GalleryGridComponent,
GalleryDirectoryComponent,
GalleryLightboxComponent,
GalleryBlogComponent,
GalleryMapComponent,
GalleryMapLightboxComponent,
FrameComponent,
@ -242,7 +248,9 @@ Marker.prototype.options.icon = iconDefault;
DurationPipe,
FileSizePipe,
GPXFilesFilterPipe,
StringifySearchQuery
MDFilesFilterPipe,
StringifySearchQuery,
FileDTOToPathPipe
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true},
@ -257,6 +265,7 @@ Marker.prototype.options.icon = iconDefault;
GalleryCacheService,
GalleryService,
MapService,
BlogService,
SearchQueryParserService,
AutoCompleteService,
AuthenticationService,

View File

@ -57,6 +57,27 @@ export class NetworkService {
.catch(err);
}
public getText<T>(url: string): Promise<string> {
this.loadingBarService.useRef().start();
const process = (res: string): string => {
this.loadingBarService.useRef().complete();
return res;
};
const err = (error: any) => {
this.loadingBarService.useRef().complete();
return this.handleError(error);
};
return this.http.get(this.apiBaseUrl + url, {responseType: 'text'})
.toPromise()
.then(process)
.catch(err);
}
public postJson<T>(url: string, data: any = {}): Promise<T> {
return this.callJson('post', url, data);
}

View File

@ -0,0 +1,14 @@
import {Pipe, PipeTransform} from '@angular/core';
import {FileDTO} from '../../../common/entities/FileDTO';
import {Utils} from '../../../common/Utils';
@Pipe({name: 'toPath'})
export class FileDTOToPathPipe implements PipeTransform {
transform(metaFile: FileDTO): string | null {
if (!metaFile) {
return null;
}
return Utils.concatUrls('api/gallery/content/', metaFile.directory.path, metaFile.directory.name, metaFile.name);
}
}

View File

@ -0,0 +1,13 @@
import {Pipe, PipeTransform} from '@angular/core';
import {FileDTO} from '../../../common/entities/FileDTO';
@Pipe({name: 'mdFiles'})
export class MDFilesFilterPipe implements PipeTransform {
transform(metaFiles: FileDTO[]): FileDTO[] | null {
if (!metaFiles) {
return null;
}
return metaFiles.filter((f: FileDTO): boolean => f.name.toLocaleLowerCase().endsWith('.md'));
}
}

View File

@ -0,0 +1,6 @@
.blog {
}
.card-body {
overflow: hidden;
}

View File

@ -0,0 +1,16 @@
<div class="blog">
<div class="card">
<div class="card-body" style="min-height: 112px" [style.height]="collapsed ? '112px':''">
<ng-container *ngFor="let md of markdowns; let i = index">
<markdown
*ngIf="!collapsed"
[data]="md">
</markdown>
<span *ngIf="collapsed">
{{md}}
</span>
<hr *ngIf="i != markdowns.length-1">
</ng-container>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
import {Component, Input} from '@angular/core';
import {FileDTO} from '../../../../../common/entities/FileDTO';
import {BlogService} from './blog.service';
import {OnChanges} from '../../../../../../node_modules/@angular/core';
@Component({
selector: 'app-gallery-blog',
templateUrl: './blog.gallery.component.html',
styleUrls: ['./blog.gallery.component.css']
})
export class GalleryBlogComponent implements OnChanges {
@Input() mdFiles: FileDTO[];
@Input() collapsed: boolean;
markdowns: string[] = [];
constructor(public blogService: BlogService) {
}
onError($event: string): void {
}
ngOnChanges(): void {
this.loadMarkdown().catch(console.error);
}
async loadMarkdown(): Promise<void> {
this.markdowns = [];
for (const f of this.mdFiles) {
this.markdowns.push(await this.blogService.getMarkDown(f));
}
}
}

View File

@ -0,0 +1,27 @@
import {Injectable} from '@angular/core';
import {NetworkService} from '../../../model/network/network.service';
import {FileDTO} from '../../../../../common/entities/FileDTO';
import {Utils} from '../../../../../common/Utils';
@Injectable()
export class BlogService {
cache: { [key: string]: Promise<string> | string } = {};
constructor(private networkService: NetworkService) {
}
public getMarkDown(file: FileDTO): Promise<string> {
const filePath = Utils.concatUrls(file.directory.path, file.directory.name, file.name);
if (!this.cache[filePath]) {
this.cache[filePath] = this.networkService.getText('/gallery/content/' + filePath);
(this.cache[filePath] as Promise<string>).then((val: string) => {
this.cache[filePath] = val;
});
}
return Promise.resolve(this.cache[filePath]);
}
}

View File

@ -1,15 +1,41 @@
.container-fluid {
width: 100%;
padding:0;
padding: 0;
margin-top: -20px;
}
.blog-map-row {
width: 100%;
display: flex;
margin-top: 0.2em;
margin-bottom: 0.2em;
position: relative;
min-height: 80px;
}
.btn-blog-details {
width: calc(100% - 5px);
position: absolute;
bottom: 0;
margin-left: 2px;
margin-right: 2px;
background-image: linear-gradient(transparent, #aaa);
border: 0;
}
app-gallery-blog {
float: left;
width: 100%;
margin-right: 0.25em;
margin-left: 2px;
}
app-gallery-map {
margin-right: 0;
margin-left: auto;
display: block;
height: 80px;
width: 100px;
position: absolute;
right: 4px
}
.directories {

View File

@ -27,7 +27,7 @@
</ng-container>
<div body class="container-fluid" >
<div body class="container-fluid">
<ng-container *ngIf="galleryService.content.value.error">
<div class="alert alert-danger" role="alert">
{{galleryService.content.value.error}}
@ -40,9 +40,21 @@
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="galleryService.content.value.directory.media"
[gpxFiles]="galleryService.content.value.directory.metaFile | gpxFiles"></app-gallery-map>
<div class="blog-map-row">
<ng-container
*ngIf="galleryService.content.value.directory.metaFile && (galleryService.content.value.directory.metaFile | mdFiles).length>0">
<app-gallery-blog [collapsed]="!blogOpen"
[mdFiles]="galleryService.content.value.directory.metaFile | mdFiles"></app-gallery-blog>
<button class="btn btn-blog-details" (click)="blogOpen=!blogOpen"><span
class="oi oi-chevron-{{blogOpen ? 'top' : 'bottom'}}"></span>
</button>
</ng-container>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="galleryService.content.value.directory.media"
[gpxFiles]="galleryService.content.value.directory.metaFile | gpxFiles"></app-gallery-map>
</div>
<app-gallery-grid [media]="galleryService.content.value.directory.media"
[lightbox]="lightbox"></app-gallery-grid>
@ -58,10 +70,21 @@
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="galleryService.content.value.searchResult.media"
[gpxFiles]="galleryService.content.value.searchResult.metaFile | gpxFiles"></app-gallery-map>
<div class="blog-map-row">
<ng-container
*ngIf="galleryService.content.value.searchResult.metaFile && (galleryService.content.value.searchResult.metaFile | mdFiles).length>0">
<app-gallery-blog [collapsed]="!blogOpen"
[mdFiles]="galleryService.content.value.searchResult.metaFile | mdFiles"></app-gallery-blog>
<button class="btn btn-blog-details" (click)="blogOpen=!blogOpen"><span
class="oi oi-chevron-{{blogOpen ? 'top' : 'bottom'}}"></span>
</button>
</ng-container>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="galleryService.content.value.searchResult.media"
[gpxFiles]="galleryService.content.value.searchResult.metaFile | gpxFiles"></app-gallery-map>
</div>
<app-gallery-grid [media]="galleryService.content.value.searchResult.media"
[lightbox]="lightbox"></app-gallery-grid>

View File

@ -30,6 +30,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
public showSearchBar = false;
public showShare = false;
public showRandomPhotoBuilder = false;
public blogOpen = false;
public directories: SubDirectoryDTO[] = [];
public isPhotoWithLocation = false;
@ -42,6 +43,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
timer: null,
sorting: null
};
private collator = new Intl.Collator(undefined, {numeric: true});
constructor(public galleryService: GalleryService,
private authService: AuthenticationService,
@ -54,7 +56,6 @@ export class GalleryComponent implements OnInit, OnDestroy {
PageHelper.showScrollY();
}
updateTimer(t: number): void {
if (this.shareService.sharingSubject.value == null) {
return;
@ -159,8 +160,6 @@ export class GalleryComponent implements OnInit, OnDestroy {
}
};
private collator = new Intl.Collator(undefined, {numeric: true});
private sortDirectories(): void {
if (!this.directories) {
return;