From 47fec015c18a8585ea447cecdfe65774eb3c7b4e Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Fri, 31 Dec 2021 18:50:21 +0100 Subject: [PATCH] Implementing basic blogging support --- angular.json | 4 +- package-lock.json | 72 ++++++++++++++++--- package.json | 2 +- src/common/SupportedFormats.ts | 2 +- src/frontend/app/app.module.ts | 17 +++-- .../app/model/network/network.service.ts | 21 ++++++ src/frontend/app/pipes/FileDTOToPathPipe.ts | 14 ++++ src/frontend/app/pipes/MDFilesFilterPipe.ts | 13 ++++ .../gallery/blog/blog.gallery.component.css | 6 ++ .../gallery/blog/blog.gallery.component.html | 16 +++++ .../ui/gallery/blog/blog.gallery.component.ts | 35 +++++++++ .../app/ui/gallery/blog/blog.service.ts | 27 +++++++ .../app/ui/gallery/gallery.component.css | 32 ++++++++- .../app/ui/gallery/gallery.component.html | 37 ++++++++-- .../app/ui/gallery/gallery.component.ts | 5 +- 15 files changed, 274 insertions(+), 29 deletions(-) create mode 100644 src/frontend/app/pipes/FileDTOToPathPipe.ts create mode 100644 src/frontend/app/pipes/MDFilesFilterPipe.ts create mode 100644 src/frontend/app/ui/gallery/blog/blog.gallery.component.css create mode 100644 src/frontend/app/ui/gallery/blog/blog.gallery.component.html create mode 100644 src/frontend/app/ui/gallery/blog/blog.gallery.component.ts create mode 100644 src/frontend/app/ui/gallery/blog/blog.service.ts diff --git a/angular.json b/angular.json index a68b0213..333aebea 100644 --- a/angular.json +++ b/angular.json @@ -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": { diff --git a/package-lock.json b/package-lock.json index 8d716a8f..93a2609a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8552fdfb..67a188f2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/common/SupportedFormats.ts b/src/common/SupportedFormats.ts index 1380131e..9642899f 100644 --- a/src/common/SupportedFormats.ts +++ b/src/common/SupportedFormats.ts @@ -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: { diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index fdb4820d..0c367ba4 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -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, diff --git a/src/frontend/app/model/network/network.service.ts b/src/frontend/app/model/network/network.service.ts index 53110a31..8a62f44c 100644 --- a/src/frontend/app/model/network/network.service.ts +++ b/src/frontend/app/model/network/network.service.ts @@ -57,6 +57,27 @@ export class NetworkService { .catch(err); } + public getText(url: string): Promise { + + + 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(url: string, data: any = {}): Promise { return this.callJson('post', url, data); } diff --git a/src/frontend/app/pipes/FileDTOToPathPipe.ts b/src/frontend/app/pipes/FileDTOToPathPipe.ts new file mode 100644 index 00000000..0a845e26 --- /dev/null +++ b/src/frontend/app/pipes/FileDTOToPathPipe.ts @@ -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); + } +} diff --git a/src/frontend/app/pipes/MDFilesFilterPipe.ts b/src/frontend/app/pipes/MDFilesFilterPipe.ts new file mode 100644 index 00000000..b4957b17 --- /dev/null +++ b/src/frontend/app/pipes/MDFilesFilterPipe.ts @@ -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')); + } +} diff --git a/src/frontend/app/ui/gallery/blog/blog.gallery.component.css b/src/frontend/app/ui/gallery/blog/blog.gallery.component.css new file mode 100644 index 00000000..24ad1c34 --- /dev/null +++ b/src/frontend/app/ui/gallery/blog/blog.gallery.component.css @@ -0,0 +1,6 @@ +.blog { +} + +.card-body { + overflow: hidden; +} diff --git a/src/frontend/app/ui/gallery/blog/blog.gallery.component.html b/src/frontend/app/ui/gallery/blog/blog.gallery.component.html new file mode 100644 index 00000000..5bca521c --- /dev/null +++ b/src/frontend/app/ui/gallery/blog/blog.gallery.component.html @@ -0,0 +1,16 @@ +
+
+
+ + + + + {{md}} + +
+
+
+
+
diff --git a/src/frontend/app/ui/gallery/blog/blog.gallery.component.ts b/src/frontend/app/ui/gallery/blog/blog.gallery.component.ts new file mode 100644 index 00000000..b5705fc0 --- /dev/null +++ b/src/frontend/app/ui/gallery/blog/blog.gallery.component.ts @@ -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 { + this.markdowns = []; + for (const f of this.mdFiles) { + this.markdowns.push(await this.blogService.getMarkDown(f)); + } + } +} + diff --git a/src/frontend/app/ui/gallery/blog/blog.service.ts b/src/frontend/app/ui/gallery/blog/blog.service.ts new file mode 100644 index 00000000..72c21406 --- /dev/null +++ b/src/frontend/app/ui/gallery/blog/blog.service.ts @@ -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 } = {}; + + constructor(private networkService: NetworkService) { + } + + + public getMarkDown(file: FileDTO): Promise { + 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).then((val: string) => { + this.cache[filePath] = val; + }); + } + return Promise.resolve(this.cache[filePath]); + } + +} + diff --git a/src/frontend/app/ui/gallery/gallery.component.css b/src/frontend/app/ui/gallery/gallery.component.css index 6e60732a..b3ac592d 100644 --- a/src/frontend/app/ui/gallery/gallery.component.css +++ b/src/frontend/app/ui/gallery/gallery.component.css @@ -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 { diff --git a/src/frontend/app/ui/gallery/gallery.component.html b/src/frontend/app/ui/gallery/gallery.component.html index 5cb16bcc..11897b92 100644 --- a/src/frontend/app/ui/gallery/gallery.component.html +++ b/src/frontend/app/ui/gallery/gallery.component.html @@ -27,7 +27,7 @@ -
+