From 6d87a1a4e8e5daeef28edcf570febafcc9760257 Mon Sep 17 00:00:00 2001 From: Niklas Liljestrand Date: Wed, 2 Dec 2020 13:50:44 +0200 Subject: [PATCH] Develop (#73) Merge from Develop to Master - Lot's of small and some bigger fixes - New advanced search (still in beta) - Layout changes --- Dockerfile | 4 +- package-lock.json | 6 +- package.json | 4 +- src/app/app.component.ts | 325 ++++++-- src/app/app.html | 121 ++- src/app/app.module.ts | 8 +- src/app/app.scss | 34 +- src/app/models/facsimile.model.ts | 10 +- src/app/models/occurrence.model.ts | 6 + src/app/models/single-occurrence.model.ts | 1 - .../models/toc-accordion-menu-option.model.ts | 5 + src/app/services/comments/comment.service.ts | 29 +- .../elastic-search/elastic-search.d.ts | 78 ++ .../elastic-search/elastic-search.service.ts | 397 +++++++++ .../services/facsimile/facsimile.service.ts | 10 +- src/app/services/gallery/gallery.service.ts | 9 + src/app/services/html/html-cache.service.ts | 4 +- src/app/services/md/md-content.service.ts | 18 +- .../services/occurrence/occurence.service.ts | 1 - .../reference-data/reference-data.service.ts | 2 +- .../services/search/search-data.service.ts | 12 + .../semantic-data/semantic-data.service.ts | 402 ++++++++- .../services/settings/read-popover.service.ts | 1 + .../settings/user-settings.service.ts | 8 +- src/app/services/texts/text.service.ts | 89 +- .../services/toc/table-of-contents.service.ts | 2 - src/app/services/tooltips/tooltip.service.ts | 71 +- src/app/services/tutorial/tutorial.service.ts | 76 +- src/assets/custom_css/custom.css | 2 +- src/assets/i18n/en.json | 417 ++++++---- src/assets/i18n/fi.json | 523 +++++++----- src/assets/i18n/sv.json | 549 +++++++----- src/assets/images/img_placeholder.png | Bin 0 -> 2443 bytes src/assets/images/symbol_illustration.gif | Bin 0 -> 567 bytes src/components/comments/comments.html | 25 + src/components/comments/comments.scss | 17 + src/components/comments/comments.ts | 87 +- src/components/components.module.ts | 15 +- src/components/cover/cover.html | 1 + src/components/cover/cover.ts | 2 +- .../date-histogram/date-histogram.html | 35 + .../date-histogram/date-histogram.scss | 69 ++ .../date-histogram/date-histogram.ts | 91 ++ .../digital-edition-list.component.ts | 45 +- .../digital-edition-list.html | 18 +- .../digital-edition-list.scss | 5 +- src/components/facsimiles/facsimiles.html | 58 +- src/components/facsimiles/facsimiles.scss | 13 + src/components/facsimiles/facsimiles.ts | 208 +++-- .../illustrations/illustrations.html | 18 + .../illustrations/illustrations.scss | 11 + src/components/illustrations/illustrations.ts | 141 ++++ src/components/introduction/introduction.html | 4 +- src/components/introduction/introduction.ts | 19 +- src/components/list-of-songs/list-of-songs.ts | 2 +- src/components/manuscripts/manuscripts.html | 1 + src/components/manuscripts/manuscripts.scss | 10 + src/components/manuscripts/manuscripts.ts | 12 +- src/components/read-text/read-text.html | 7 +- src/components/read-text/read-text.scss | 26 +- src/components/read-text/read-text.ts | 191 +++-- .../simple-search/simple-search.html | 2 +- .../simple-search/simple-search.scss | 5 + src/components/simple-search/simple-search.ts | 85 +- .../static-pages-toc-drilldown-menu.html | 39 +- .../static-pages-toc-drilldown-menu.ts | 21 +- .../table-of-content-letter-filter.html | 4 + .../table-of-content-letter-filter.scss | 3 + .../table-of-content-letter-filter.ts | 22 + .../table-of-contents-accordion.html | 161 ++-- .../table-of-contents-accordion.scss | 55 +- .../table-of-contents-accordion.ts | 307 ++++++- .../table-of-contents-drilldown-menu.html | 30 +- .../table-of-contents-drilldown-menu.scss | 22 + .../table-of-contents-drilldown-menu.ts | 303 ++++--- src/components/text-changer/text-changer.html | 22 +- src/components/text-changer/text-changer.scss | 92 ++ src/components/text-changer/text-changer.ts | 169 ++-- src/components/title-logo/title-logo.ts | 4 + src/components/toc-menu/toc-menu.ts | 1 + src/components/top-menu/top-menu.html | 64 +- src/components/top-menu/top-menu.scss | 20 +- src/components/top-menu/top-menu.ts | 18 + src/components/variations/variations.html | 3 +- src/components/variations/variations.ts | 77 +- src/config-sample.json | 787 +++++++++++------- src/pages/content/content.ts | 3 + src/pages/cover/cover.module.ts | 2 +- src/pages/cover/cover.scss | 12 +- src/pages/cover/cover.ts | 72 +- src/pages/editions/editions.html | 2 +- src/pages/elastic-search/elastic-search.html | 214 +++++ .../elastic-search/elastic-search.module.ts | 51 ++ src/pages/elastic-search/elastic-search.scss | 292 +++++++ src/pages/elastic-search/elastic-search.ts | 726 ++++++++++++++++ src/pages/facsimile-zoom/facsimile-zoom.html | 51 +- src/pages/facsimile-zoom/facsimile-zoom.scss | 26 +- src/pages/facsimile-zoom/facsimile-zoom.ts | 76 +- src/pages/filter/filter.html | 33 +- src/pages/filter/filter.module.ts | 14 + src/pages/filter/filter.scss | 17 + src/pages/filter/filter.ts | 72 +- src/pages/home/home.html | 25 +- src/pages/home/home.ts | 4 + src/pages/illustration/illustration.html | 14 +- src/pages/illustration/illustration.scss | 3 + src/pages/illustration/illustration.ts | 42 +- src/pages/introduction/introduction.html | 56 +- src/pages/introduction/introduction.scss | 199 ++++- src/pages/introduction/introduction.ts | 388 ++++++++- .../media-collection/media-collection.html | 17 +- .../media-collection/media-collection.scss | 6 +- .../media-collection/media-collection.ts | 28 +- .../media-collections/media-collections.html | 8 +- .../media-collections/media-collections.ts | 24 +- src/pages/music/music.ts | 4 + .../occurrences-result/occurrences-result.ts | 116 ++- src/pages/occurrences/occurrences.html | 116 ++- src/pages/occurrences/occurrences.scss | 57 +- src/pages/occurrences/occurrences.ts | 233 +++++- src/pages/pdf/pdf.ts | 4 + src/pages/person-search/person-search.html | 7 +- src/pages/person-search/person-search.ts | 160 ++-- src/pages/place-search/place-search.html | 6 +- src/pages/place-search/place-search.ts | 97 ++- src/pages/read-popover/read-popover.html | 10 +- src/pages/read-popover/read-popover.module.ts | 2 +- src/pages/read-popover/read-popover.scss | 20 +- src/pages/read-popover/read-popover.ts | 74 +- src/pages/read/read.html | 141 ++-- src/pages/read/read.scss | 180 +++- src/pages/read/read.ts | 773 +++++++++++++---- .../reference-data-modal.html | 6 +- .../reference-data-modal.scss | 5 +- .../reference-data-modal.ts | 20 +- src/pages/search-app/search-app.ts | 4 + .../semantic-data-modal.html | 22 + .../semantic-data-modal.ts | 131 ++- src/pages/share-popover/share-popover.html | 31 + .../share-popover/share-popover.module.ts | 27 + src/pages/share-popover/share-popover.scss | 27 + src/pages/share-popover/share-popover.ts | 84 ++ .../single-edition-part.ts | 2 +- src/pages/single-edition/single-edition.ts | 123 ++- src/pages/tag-search/tag-search.html | 6 +- src/pages/tag-search/tag-search.ts | 119 +-- src/pages/title/title.html | 15 + src/pages/title/title.module.ts | 32 + src/pages/title/title.scss | 3 + src/pages/title/title.ts | 188 +++++ .../user-settings-popover.html | 59 +- .../user-settings-popover.ts | 2 - src/pages/work-search/work-search.html | 8 +- src/pages/work-search/work-search.ts | 167 ++-- src/pages/work-search/worksearch.scss | 14 +- src/theme/_project-variables.scss | 15 +- src/theme/_tei.scss | 20 +- src/theme/variables.scss | 3 +- tsconfig.json | 6 +- tslint.json | 6 +- 160 files changed, 9704 insertions(+), 2589 deletions(-) create mode 100644 src/app/services/elastic-search/elastic-search.d.ts create mode 100644 src/app/services/elastic-search/elastic-search.service.ts create mode 100644 src/assets/images/img_placeholder.png create mode 100644 src/assets/images/symbol_illustration.gif create mode 100644 src/components/date-histogram/date-histogram.html create mode 100644 src/components/date-histogram/date-histogram.scss create mode 100644 src/components/date-histogram/date-histogram.ts create mode 100644 src/components/illustrations/illustrations.html create mode 100644 src/components/illustrations/illustrations.scss create mode 100644 src/components/illustrations/illustrations.ts create mode 100644 src/components/table-of-content-letter-filter/table-of-content-letter-filter.html create mode 100644 src/components/table-of-content-letter-filter/table-of-content-letter-filter.scss create mode 100644 src/components/table-of-content-letter-filter/table-of-content-letter-filter.ts create mode 100644 src/pages/elastic-search/elastic-search.html create mode 100644 src/pages/elastic-search/elastic-search.module.ts create mode 100644 src/pages/elastic-search/elastic-search.scss create mode 100644 src/pages/elastic-search/elastic-search.ts create mode 100644 src/pages/share-popover/share-popover.html create mode 100644 src/pages/share-popover/share-popover.module.ts create mode 100644 src/pages/share-popover/share-popover.scss create mode 100644 src/pages/share-popover/share-popover.ts create mode 100644 src/pages/title/title.html create mode 100644 src/pages/title/title.module.ts create mode 100644 src/pages/title/title.scss create mode 100644 src/pages/title/title.ts diff --git a/Dockerfile b/Dockerfile index 93b43269..065c0610 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:10-alpine RUN apk update RUN apk add --no-cache g++ gcc libgcc libstdc++ linux-headers make python git -RUN git clone --depth 1 -b master https://github.com/slsfi/digital_edition_web.git +RUN git clone --depth 1 -b develop https://github.com/slsfi/digital_edition_web.git WORKDIR /digital_edition_web @@ -20,7 +20,7 @@ RUN mkdir www RUN npm install RUN npm install cheerio RUN npm install rev-hash -RUN npm install -g ionic +RUN npm i -g @ionic/cli RUN npm i -g cordova RUN npm i -g native-run RUN npm i -g @sentry/browser diff --git a/package-lock.json b/package-lock.json index f492bbea..2c7c0a20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4516,9 +4516,9 @@ "integrity": "sha512-alGvLE4JBTeGhUuvLwYKnneIkt1dPhGIBTXgNG4G4pbaNpwdZNrTY+YCYj6Dj4lVCgyEJvQcHYTWcXuuT5swyg==" }, "ngx-pinch-zoom": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/ngx-pinch-zoom/-/ngx-pinch-zoom-1.2.5.tgz", - "integrity": "sha512-zZJ9t+RifdHZDuNy0vU//yontD4rT/0J3dHvDbKxfnBGXoTuCnaYYUeDtFUIjYSu71xxpELANjeNwna2D02hzw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/ngx-pinch-zoom/-/ngx-pinch-zoom-2.4.4.tgz", + "integrity": "sha512-36GZ+ck16m5xrDSz8c/ZX0u5grAelPbi6/hL/UvNSpkgkolamlsg5p+FwMwPgoHiOmwsYQpTo5FPeWmwrS9cHg==", "requires": { "tslib": "^1.9.0" } diff --git a/package.json b/package.json index 93cabe1a..4baaeefd 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "ionic-app-scripts build", "watch": "ionic-app-scripts watch", + "start": "ionic-app-scripts serve --wwwDir", "serve:before": "watch", "emulate:before": "build", "deploy:before": "build", @@ -53,8 +54,9 @@ "ionic-plugin-deeplinks": "1.0.19", "ionicons": "~3.0.0", "leaflet": "^1.4.0", + "lodash": "^4.17.15", "ngx-drag-scroll": "1.7.9", - "ngx-pinch-zoom": "^1.2.3", + "ngx-pinch-zoom": "^2.4.4", "ngx-sharebuttons": "^3.0.0", "rev-hash": "^3.0.0", "rxjs": "5.5.11", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c2a0c894..be612c53 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component, ViewChild, Pipe, PipeTransform, ChangeDetectionStrategy, ViewEncapsulation, ChangeDetectorRef } from '@angular/core'; +import { Component, ViewChild, Pipe, PipeTransform, ChangeDetectionStrategy, + ViewEncapsulation, ChangeDetectorRef, Renderer, Renderer2 } from '@angular/core'; import './rxjs-operators'; import { Nav, Platform, MenuController, IonicPage, Events, App, NavParams, AlertController } from 'ionic-angular'; import { LangChangeEvent, TranslateService/*, TranslatePipe*/ } from '@ngx-translate/core'; @@ -48,6 +49,7 @@ export class DigitalEditionsApp { splitPane = false; collectionsList: any[]; collectionsListWithTOC: any[]; + tocData: any; pdfCollections: any[]; currentContentName: string; showBackButton = true; @@ -62,19 +64,24 @@ export class DigitalEditionsApp { menuConditionals = { songTypesMenuOpen: false } + tocLoaded = false; googleAnalyticsID: string; collectionDownloads: Array; currentCollectionId = ''; currentCollectionName = ''; + currentCollection: any; currentMarkdownId = null; + openCollectionFromToc = false; + + pageFirstLoad = true; accordionTOC = false; accordionMusic = false; storageCollections = {}; - + collectionSortOrder: any; browserWarning: string; browserWarningInfo: string; @@ -98,13 +105,13 @@ export class DigitalEditionsApp { pagesThatShallShow = { tocMenu: ['FeaturedFacsimilePage'], - tocMenuIfNotAccordion: ['SingleEditionPage', 'CoverPage'], + tableOfContentsMenu: ['SingleEditionPage', 'CoverPage', 'TitlePage'], aboutMenu: ['AboutPage'], - contentMenu: ['HomePage', 'EditionsPage', 'ContentPage', 'MusicPage', 'FeaturedFacsimilePage'] + contentMenu: ['HomePage', 'EditionsPage', 'ContentPage', 'MusicPage', 'FeaturedFacsimilePage', 'ElasticSearchPage'] } pagesWithoutMenu = []; - pagesWithClosedMenu = ['HomePage', 'HomePage']; + pagesWithClosedMenu = ['HomePage']; public options: Array; public songTypesOptions: { @@ -134,7 +141,7 @@ export class DigitalEditionsApp { musicAccordion: false, songTypesAccordion: false, galleryAccordion: false, - collectionsAccordion: false, + collectionsAccordion: [false], aboutMenuAccordion: false, pdfAccordion: false } @@ -171,6 +178,9 @@ export class DigitalEditionsApp { hasCover = true; tocItems: GeneralTocItem[]; + galleryInReadMenu = true; + splitReadCollections: any[]; + constructor( private platform: Platform, public translate: TranslateService, @@ -190,7 +200,8 @@ export class DigitalEditionsApp { public cdRef: ChangeDetectorRef, private alertCtrl: AlertController, private tutorial: TutorialService, - private galleryService: GalleryService + private galleryService: GalleryService, + private renderer: Renderer ) { // Check for IE11 @@ -226,6 +237,21 @@ export class DigitalEditionsApp { } catch (e) { this.showBooks = false; } + + try { + this.splitReadCollections = this.genericSettingsService.show('TOC.splitReadCollections'); + if ( this.splitReadCollections === null || this.splitReadCollections.length <= 0 ) { + this.splitReadCollections = ['']; + } + } catch (e) { + this.splitReadCollections = ['']; + } + + try { + this.collectionSortOrder = this.config.getSettings('app.CollectionSortOrder'); + } catch (e) { + this.collectionSortOrder = undefined; + } this.sideMenuMobileConfig(); this.songTypesMenuMarkdownConfig(); this.aboutMenuMarkdownConfig(); @@ -235,6 +261,18 @@ export class DigitalEditionsApp { this.accordionTOC = false; } + try { + this.openCollectionFromToc = this.config.getSettings('OpenCollectionFromToc'); + } catch (e) { + this.openCollectionFromToc = false; + } + + try { + this.galleryInReadMenu = this.config.getSettings('ImageGallery.ShowInReadMenu'); + } catch (e) { + this.galleryInReadMenu = true; + } + try { this.accordionMusic = this.config.getSettings('AccordionMusic'); } catch (e) { @@ -306,6 +344,22 @@ export class DigitalEditionsApp { showSplitPane() { this.splitPaneOpen = true; + + this.closeSplitPane(); + } + + closeSplitPane() { + setTimeout(() => { + const shadow = document.querySelector('.shadow'); + + if (shadow !== null) { + shadow.addEventListener('click', () => { + if (this.splitPaneOpen) { + this.splitPaneOpen = false; + } + }); + } + }, 1); } disableSplitPane() { @@ -369,7 +423,8 @@ export class DigitalEditionsApp { openCollectionPage(collection) { this.currentContentName = collection.title; const params = { collection: collection, fetch: false, id: collection.id }; - this.nav.setRoot('single-edition', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('single-edition', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); } getCollectionsWithTOC() { @@ -379,7 +434,12 @@ export class DigitalEditionsApp { return; } - collections = this.sortListRoman(collections); + if ( this.collectionSortOrder === undefined ) { + collections = this.sortListRoman(collections); + } else if ( Object.keys(this.collectionSortOrder).length > 0 ) { + collections = this.sortListDefined(collections, this.collectionSortOrder); + } + const collectionsTmp = []; const pdfCollections = []; collections.forEach(collection => { @@ -415,6 +475,7 @@ export class DigitalEditionsApp { } }); this.collectionsListWithTOC = collectionsTmp; + if (this.showBooks) { this.pdfCollections = pdfCollections; this.pdfCollections.sort(function (a, b) { @@ -461,6 +522,23 @@ export class DigitalEditionsApp { return list; } + sortListDefined(list, sort) { + for (const coll of list) { + const order = sort[coll.id]; + coll['order'] = order; + } + + list.sort((a, b) => { + if (typeof a['order'] === 'number') { + return (a['order'] - b['order']); + } else { + return ((a['order'] < b['order']) ? -1 : ((a['order'] > b['order']) ? 1 : 0)); + } + }); + + return list; + } + openMusicAccordionItem(musicAccordionItem, findInMusicAccordion?) { Object.keys(this.musicAccordion).forEach(key => this.musicAccordion[key].selected = false); if (findInMusicAccordion) { @@ -497,7 +575,9 @@ export class DigitalEditionsApp { try { this.simpleAccordionsExpanded.songTypesAccordion = this.config.getSettings('AccordionsExpandedDefault.SongTypes'); this.simpleAccordionsExpanded.musicAccordion = this.config.getSettings('AccordionsExpandedDefault.Music'); - this.simpleAccordionsExpanded.collectionsAccordion = this.config.getSettings('AccordionsExpandedDefault.Collections'); + for ( let i = 0; i < this.splitReadCollections.length; i++ ) { + this.simpleAccordionsExpanded.collectionsAccordion[i] = this.config.getSettings('AccordionsExpandedDefault.Collections'); + } } catch (e) { } } @@ -528,11 +608,13 @@ export class DigitalEditionsApp { if (this.aboutMenuMarkdownAccordion !== undefined) { this.aboutMenuMarkdownAccordion.ngOnChanges(aboutMarkdownMenu.children); } + this.cdRef.detectChanges(); }).bind(this)(); } } getCollectionTOC(collectionID) { + console.log('Getting collection TOC in app component'); this.tableOfContentsService.getTableOfContents(collectionID) .subscribe( tocItems => { @@ -557,7 +639,7 @@ export class DigitalEditionsApp { 'tablet', 'windows', ] - platforms.map(p => console.log(`${p}: ${this.platform.is(p)}`)); + // platforms.map(p => console.log(`${p}: ${this.platform.is(p)}`)); this.splashScreen.hide(); this.languageService.getLanguage().subscribe((lang: string) => { this.language = lang; @@ -573,9 +655,24 @@ export class DigitalEditionsApp { } }); this.events.publish('pdfview:open', { 'isOpen': false }); + + /* const tableOfContentsMenu: HTMLElement = document.querySelector('.split-pane-side'); + const menuResizer = document.querySelector('.menuResizer'); + const initialtWidth = Number(tableOfContentsMenu.offsetWidth); + menuResizer.addEventListener('drag', (event: MouseEvent) => { + this.resizeTOCMenu(event, tableOfContentsMenu, initialtWidth); + }); + this.cdRef.detectChanges(); */ }); } + resizeTOCMenu( event: MouseEvent, splitPaneItem, initialtWidth ) { + const relativeWidth = Number(String(event.clientX).replace('px', '')); + if ( relativeWidth > 0 ) { + splitPaneItem.style.minWidth = (initialtWidth + relativeWidth) + 'px'; + } + } + toggleMusicAccordion() { this.simpleAccordionsExpanded.musicAccordion = !this.simpleAccordionsExpanded.musicAccordion; } @@ -601,6 +698,10 @@ export class DigitalEditionsApp { } registerEventListeners() { + this.events.subscribe('digital-edition-list:open', (collection) => { + console.log('listened to digital-edition-list:open'); + this.openCollection(collection); + }); this.events.subscribe('CollectionWithChildrenPdfs:highlight', (collectionID) => { for (const collection of this.collectionsListWithTOC) { if (String(collection.id) === String(collectionID)) { @@ -611,12 +712,19 @@ export class DigitalEditionsApp { menuID: selectedMenu, component: 'app-component' }); - this.simpleAccordionsExpanded.collectionsAccordion = true; + for ( let i = 0; i < this.splitReadCollections.length; i++ ) { + this.simpleAccordionsExpanded.collectionsAccordion[i] = true; + } } else { collection['highlight'] = false; } } }); + + this.events.subscribe('exitActiveCollection', () => { + this.enableContentMenu(); + }); + // Unselect accordion items that doesn't belong to current menu this.events.subscribe('SelectedItemInMenu', (menu) => { if (menu.component === 'table-of-contents-accordion-component' || this.currentAccordionMenu !== menu.menuID) { @@ -648,10 +756,12 @@ export class DigitalEditionsApp { // Check if there is a need to expand // Otherwise we might change smth after user clicks on accordion - if (expand && !this.simpleAccordionsExpanded.collectionsAccordion) { - this.simpleAccordionsExpanded.collectionsAccordion = true; - } else if (!expand && this.simpleAccordionsExpanded.collectionsAccordion) { - this.simpleAccordionsExpanded.collectionsAccordion = false; + for ( let i = 0; i < this.splitReadCollections.length; i++ ) { + if (expand && !this.simpleAccordionsExpanded.collectionsAccordion[i]) { + this.simpleAccordionsExpanded.collectionsAccordion[i] = true; + } else if (!expand && this.simpleAccordionsExpanded.collectionsAccordion) { + this.simpleAccordionsExpanded.collectionsAccordion[i] = false; + } } this.cdRef.detectChanges(); }); @@ -690,22 +800,37 @@ export class DigitalEditionsApp { } }); this.events.subscribe('tableOfContents:loaded', (data) => { - if (data.searchTocItem) { + this.tocData = data; + this.tocLoaded = true; + if (data.searchTocItem) { for (const collection of this.collectionsListWithTOC) { - if (Number(collection.id) === Number(data.collectionID)) { + if ((data.collectionID !== undefined && String(collection.id) === String(data.collectionID.id)) + || (data.collectionID !== undefined && Number(collection.id) === Number(data.collectionID))) { collection.expanded = true; - this.simpleAccordionsExpanded.collectionsAccordion = true; + for ( let i = 0; i < this.splitReadCollections.length; i++ ) { + this.simpleAccordionsExpanded.collectionsAccordion[i] = true; + } + + if ( data.chapterID ) { + data.itemId = Number(data.collectionID) + '_' + Number(data.publicationID) + '_' + data.chapterID; + } + + if ( data.itemId === undefined && data.collectionID !== undefined && data.publicationID !== undefined) { + data.itemId = String(data.collectionID) + '_' + String(data.publicationID); + } - collection.accordionToc.toc = data.tocItems.children; collection.accordionToc = { toc: data.tocItems.children, searchTocItem: true, + searchItemId: data.itemId, searchPublicationId: Number(data.publicationID), - searchTitle: data.search_title ? data.search_title : null + searchCollectionId: Number(data.collectionID), + searchTitle: null } - + collection.accordionToc.toc = data.tocItems.children; + this.currentCollection = collection; break; } } @@ -713,12 +838,23 @@ export class DigitalEditionsApp { this.options = data.tocItems.children; this.currentCollectionId = data.tocItems.collectionId; this.currentCollectionName = data.tocItems.text; + this.enableTableOfContentsMenu(); + }); + + this.events.subscribe('exitedTo', (page) => { + this.setupPageSettings(page); }); this.events.subscribe('ionViewWillEnter', (currentPage) => { + this.tocLoaded = false; const homeUrl = document.URL.indexOf('/#/home'); if (homeUrl >= 0) { this.setupPageSettings(currentPage); + } else if ( document.URL.indexOf('/#/') > 0 ) { + if ( this.splitPaneOpen === false && this.pageFirstLoad === true ) { + this.showSplitPane(); + this.pageFirstLoad = false; + } } }); @@ -735,22 +871,34 @@ export class DigitalEditionsApp { }); this.currentContentName = 'Digital Publications'; const params = {}; - this.nav.setRoot('EditionsPage', params, { animate: false }); + const nav = this.app.getActiveNavs(); + this.enableContentMenu(); + nav[0].setRoot('EditionsPage', params, { animate: false }); }); this.events.subscribe('topMenu:about', () => { this.events.publish('SelectedItemInMenu', { menuID: 'topMenu', component: 'app-component' }); + this.enableAboutMenu(); this.languageService.getLanguage().subscribe((lang: string) => { this.language = lang; if (this.aboutMenuMarkdown && this.aboutOptionsMarkdown.toc && this.aboutOptionsMarkdown.toc.length) { - const firstAboutPageID = this.aboutOptionsMarkdown.toc[0].id; + let firstAboutPageID = this.aboutOptionsMarkdown.toc[0].id; + if ( this.config.getSettings('StaticPagesMenus')[0]['initialAboutPage'] !== undefined ) { + firstAboutPageID = this.language + '-' + this.config.getSettings('StaticPagesMenus')[0]['initialAboutPage']; + } this.openStaticPage(firstAboutPageID); } else { + this.enableAboutMenu(); - this.openStaticPage(this.language + '-03-01'); + if ( this.config.getSettings('StaticPagesMenus')[0]['initialAboutPage'] === undefined ) { + this.staticPagesMenus['initialAboutPage'] = this.language + '-03-01'; + } else { + this.staticPagesMenus['initialAboutPage'] = this.language + '-' + this.config.getSettings('StaticPagesMenus')[0]['initialAboutPage']; + } + this.openStaticPage(this.staticPagesMenus['initialAboutPage']); } }); }); @@ -764,7 +912,8 @@ export class DigitalEditionsApp { // Open music accordion as well this.simpleAccordionsExpanded.musicAccordion = true; const params = {}; - this.nav.setRoot('music', params, { animate: false }); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('music', params, { animate: false }); }); this.events.subscribe('topMenu:front', () => { this.events.publish('SelectedItemInMenu', { @@ -799,19 +948,25 @@ export class DigitalEditionsApp { this.getMediaCollections(); }); }); + + this.events.subscribe('topMenu:elasticSearch', () => { + const params = {}; + this.nav.push('elastic-search', params, { animate: false }).then(e => { + }); + this.tocLoaded = true; + // this.resetCurrentCollection(); + this.enableContentMenu(); + }); + } + + resetCurrentCollection() { + this.currentCollection = null; + this.currentCollectionId = null; + this.currentCollectionName = ''; + this.options = null; } mobileSplitPaneDetector() { - this.events.subscribe('splitPaneToggle:disable', () => { - if (this.userSettingsService.isMobile()) { - this.hideSplitPane(); - } - }); - this.events.subscribe('splitPaneToggle:enable', () => { - if (this.userSettingsService.isMobile()) { - this.showSplitPane(); - } - }); } doFor(needle, haystack, callback) { @@ -855,18 +1010,21 @@ export class DigitalEditionsApp { this.enableTableOfContentsMenu(); }); - this.doFor(p, pagesWith.tocMenuIfNotAccordion, () => { - if (!this.accordionTOC) { + this.doFor(p, pagesWith.tableOfContentsMenu, () => { + console.log('Enabling TOC Menu', p, this.openCollectionFromToc, pagesWith.tableOfContentsMenu); + if (this.openCollectionFromToc) { this.enableTableOfContentsMenu(); } }); this.doFor(p, pagesWith.aboutMenu, () => { + console.log('enabling about menu for ' + p); this.enableAboutMenu(); }); this.doFor(p, pagesWith.contentMenu, () => { + console.log('enabling content menu for ' + p); this.enableContentMenu(); }); @@ -1034,12 +1192,24 @@ export class DigitalEditionsApp { } enableTableOfContentsMenu() { - this.menu.enable(true, 'tableOfContentsMenu'); - if (this.platform.is('core')) { - this.events.publish('title-logo:show', true); + if (this.tocLoaded) { + console.log('Toc is loaded'); + try { + this.menu.enable(true, 'tableOfContentsMenu'); + if (this.platform.is('core')) { + this.events.publish('title-logo:show', true); + } else { + this.events.publish('title-logo:show', false); + } + } catch (e) { + console.log('error att App.enableTableOfContentsMenu'); + } } else { - this.events.publish('title-logo:show', false); + console.log('Toc is not loaded'); } + + + } openPlaymanTraditionPage() { @@ -1054,7 +1224,8 @@ export class DigitalEditionsApp { openStaticPage(id: string) { const params = { id: id }; - this.nav.setRoot('content', params); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('content', params); } openPage(page, selectedMenu?) { @@ -1072,7 +1243,12 @@ export class DigitalEditionsApp { /*if ( this.platform.is('mobile') ) { this.events.publish('splitPaneToggle:disable'); }*/ - this.nav.setRoot(page); + try { + const nav = this.app.getActiveNavs(); + nav[0].setRoot(page); + } catch (e) { + console.error('Error opening page'); + } } openPersonSearchPage(searchPage, selectedMenu?) { @@ -1085,12 +1261,15 @@ export class DigitalEditionsApp { component: 'app-component' }); } + if ( searchPage.object_subtype === undefined || searchPage.object_subtype === '' ) { + searchPage.object_subtype = encodeURI('subtype'); + } const params = { type: searchPage.object_type, subtype: searchPage.object_subtype }; - - this.nav.setRoot('person-search', params); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('person-search', params); } openFirstPage(collection: DigitalEdition) { @@ -1103,6 +1282,7 @@ export class DigitalEditionsApp { } const nav = this.app.getActiveNavs(); + console.log('Opening read from App.openFirstPage()'); nav[0].setRoot('read', params); } @@ -1118,6 +1298,7 @@ export class DigitalEditionsApp { openCollection(collection: any) { + console.log(collection, '<<-- open this...'); if (this.hasCover === false) { this.getTocRoot(collection); } else { @@ -1135,10 +1316,25 @@ export class DigitalEditionsApp { } else { this.currentContentName = collection.title; const params = { collection: collection, fetch: false, id: collection.id }; - this.nav.setRoot('single-edition', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); + + const nav = this.app.getActiveNavs(); + nav[0].setRoot('single-edition', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); } this.cdRef.detectChanges(); } + if (this.openCollectionFromToc) { + this.currentCollection = collection; + console.log('currentCollection', collection); + console.log(this.options, 'options of the fn'); + try { + // if (this.options) { + this.enableTableOfContentsMenu(); + // } + } catch (e) { + console.log('Error enabling enableTableOfContentsMenu'); + } + } + this.currentCollectionId = collection.id; } onShowAccordion(show: boolean) { @@ -1148,13 +1344,15 @@ export class DigitalEditionsApp { /* Legacy code */ openGalleries() { const params = { fetch: true }; - this.nav.setRoot('galleries', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('galleries', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); } /* Legacy code */ openGalleryPage(galleryPage: string) { const params = { galleryPage: galleryPage, fetch: false }; - this.nav.setRoot('image-gallery', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('image-gallery', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); } getMediaCollections() { @@ -1173,20 +1371,25 @@ export class DigitalEditionsApp { t_all = translation; }, error => { } ); - mediaCollectionMenu.unshift({ 'id': 'all', 'title': t_all }); + mediaCollectionMenu.unshift({ 'id': 'all', 'title': t_all, 'highlight': true }); + mediaCollectionMenu.forEach(item => { + item['is_gallery'] = true; + }); this.mediaCollectionOptions['toc_exists'] = true; this.mediaCollectionOptions['expanded'] = false; this.mediaCollectionOptions['loading'] = false; this.mediaCollectionOptions['accordionToc'] = { toc: mediaCollectionMenu, - searchTocItem: false, + searchTocItem: true, searchTitle: '', // If toc item has to be searched by unique title also currentPublicationId: null }; this.mediaCollectionOptions['has_children_pdfs'] = false; this.mediaCollectionOptions['isDownload'] = false; this.mediaCollectionOptions['highlight'] = false; - this.mediaCollectionOptions['title'] = ''; + this.mediaCollectionOptions['title'] = 'media'; + this.mediaCollectionOptions['id'] = 'mediaCollections'; + this.mediaCollectionOptions['collectionId'] = 'mediaCollections'; } else { this.mediaCollectionOptions = []; } @@ -1194,12 +1397,28 @@ export class DigitalEditionsApp { } } + openMediaCollections() { + this.mediaCollectionOptions['accordionToc']['toc'].forEach(element => { + if (element.id === 'all') { + element.highlight = true; + } else { + element.highlight = false; + } + }); const params = {}; - this.nav.setRoot('media-collections', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); + const nav = this.app.getActiveNavs(); + nav[0].setRoot('media-collections', params, { animate: false, direction: 'forward', animation: 'ios-transition' }); } openMediaCollection(gallery) { + this.mediaCollectionOptions['accordionToc']['toc'].forEach(element => { + if (gallery.id === element.id) { + element.highlight = true; + } else { + element.highlight = false; + } + }); const nav = this.app.getActiveNavs(); const params = { mediaCollectionId: gallery.id, mediaTitle: this.makeTitle(gallery.image_path), fetch: false }; nav[0].push('media-collection', params, { animate: true, direction: 'forward', animation: 'ios-transition' }); diff --git a/src/app/app.html b/src/app/app.html index cba1f5e5..246c5d02 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -18,7 +18,8 @@
- +
@@ -28,39 +29,40 @@ {{"TOC.About" | translate }}

- - - -

{{"TOC.Read" | translate }}

-
-
- - - - -

{{ collection.title }}

- -
-
- -
-
- -
-
-
- - - - -

{{"TOC.MediaCollections" | translate }}

-
-
- - -
-
-
+ + +
+ + +

{{"TOC.Read" | translate }}

+

{{"TOC.Read" + i | translate }}

+
+
+ + + + + +

{{ collection.title }}

+ +
+
+ +
+
+ +
+
+
+ + + + +

{{"TOC.MediaCollections" | translate }}

+
+
+
+
@@ -130,6 +132,28 @@
+ + + + + +

{{"TOC.MediaCollections" | translate }}

+
+
+ + + +

{{ collection.title }}

+
+ +

{{ collection.title }}

+
+
+
+
+
+ @@ -137,11 +161,6 @@ {{"TOC.Facsimiles" | translate }}

- -

- {{"TOC.ImageGallery" | translate }} -

-

{{ps.translation | translate }}

@@ -187,11 +206,16 @@
{{"TOC.Collections" | translate }}
- -

{{collection.title}}

- - -
+ + + +

{{collection.title}}

+ + +
+
+
+
@@ -221,6 +245,7 @@ + @@ -246,13 +271,17 @@ - + + + + - \ No newline at end of file + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8b5c0e5f..b73f347a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,8 @@ import { SplashScreen } from '@ionic-native/splash-screen'; import { ShareButtonsModule } from 'ngx-sharebuttons'; import { GenericSettingsService } from './services/settings/generic-settings.service'; import { SongService } from './services/song/song.service'; +import { SharePopoverPage } from '../pages/share-popover/share-popover'; +import { SharePopoverPageModule } from '../pages/share-popover/share-popover.module'; import { PinchZoomModule } from 'ngx-pinch-zoom'; import { FacsimileZoomPageModule } from '../pages/facsimile-zoom/facsimile-zoom.module'; import { PersonSearchPageModule } from '../pages/person-search/person-search.module'; @@ -85,7 +87,7 @@ export function createConfigLoader(http: HttpClient): ConfigLoader { ReferenceDataModalPage ], imports: [ - BrowserModule, + BrowserModule, HttpModule, HttpClientModule, PinchZoomModule, @@ -121,6 +123,7 @@ export function createConfigLoader(http: HttpClient): ConfigLoader { UserSettingsPopoverPageModule, IllustrationPageModule, SearchAppPageModule, + SharePopoverPageModule // PdfViewerModule, ], providers: [ @@ -157,7 +160,8 @@ export function createConfigLoader(http: HttpClient): ConfigLoader { ReferenceDataModalPage, FacsimileZoomModalPage, IllustrationPage, - SearchAppPage + SearchAppPage, + SharePopoverPage ] }) export class AppModule { } diff --git a/src/app/app.scss b/src/app/app.scss index 86bb0d74..b5127040 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -15,6 +15,13 @@ // for the .md, .ios, or .wp mode classes. The mode class is // automatically applied to the element in the app. +body { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + ion-split-pane { top: 60px !important; @@ -26,11 +33,18 @@ ion-split-pane { } } +ion-label { + overflow: visible!important; +} + .tei { font-family: $font-app-tei; - user-select: text !important; + // user-select: text !important; + } +.selectable { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; } + ion-app, ion-app.md { font-family: $font-family-base; font-size: 1.4rem; @@ -496,7 +510,6 @@ ion-nav{ } .introjs-fixParent { - z-index: auto !important; opacity: 1.0 !important; -webkit-transform: none !important; -moz-transform: none !important; @@ -1047,4 +1060,19 @@ tr.introjs-showElement > th { .introjs-tooltiptext { font-size: 14px !important; } -} \ No newline at end of file +} +/*.menuResizer { + height: 100%; + width: 5px; + + position: absolute; + right: 0; + top: 0; + bottom: 0; + + background: #e2e2e2; + + z-index: 99; + + cursor: col-resize; +}*/ diff --git a/src/app/models/facsimile.model.ts b/src/app/models/facsimile.model.ts index 23ba9437..41c40a4f 100644 --- a/src/app/models/facsimile.model.ts +++ b/src/app/models/facsimile.model.ts @@ -7,8 +7,10 @@ export class Facsimile { public page_nr: number; public itemId: string; public manuscript_id: number; + public publication_facsimile_collection_id: number; + public number_of_pages: number; - public title = ''; + public title: any; public content = ''; public images = []; public zoomedImages = []; @@ -18,14 +20,16 @@ export class Facsimile { constructor(facsimileInfo: any) { this.id = facsimileInfo.id; this.zoom = 1; - this.page = facsimileInfo.pre_page_count + facsimileInfo.page_nr; + this.page = facsimileInfo.start_page_number + facsimileInfo.page_nr; this.page_nr = facsimileInfo.page_nr; this.pages = facsimileInfo.pages; - this.pre_page_count = facsimileInfo.pre_page_count; + this.pre_page_count = facsimileInfo.start_page_number; this.type = facsimileInfo.type; this.title = facsimileInfo.title; this.itemId = facsimileInfo.itemId; this.manuscript_id = facsimileInfo.manuscript_id; + this.publication_facsimile_collection_id = facsimileInfo.publication_facsimile_collection_id; + this.number_of_pages = facsimileInfo.number_of_pages; } } diff --git a/src/app/models/occurrence.model.ts b/src/app/models/occurrence.model.ts index 3617fc00..d1a27c5b 100644 --- a/src/app/models/occurrence.model.ts +++ b/src/app/models/occurrence.model.ts @@ -51,6 +51,7 @@ export class OccurrenceResult { name: string; first_name: string; last_name: string; + full_name: string; sortBy: string; object_type?: string; occurrences: any[]; @@ -66,4 +67,9 @@ export class OccurrenceResult { region?: any; source?: any; type?: any; + author_data?: any; + publisher?: any; + published_year?: any; + journal?: any; + isbn?: any; } diff --git a/src/app/models/single-occurrence.model.ts b/src/app/models/single-occurrence.model.ts index 81216c8c..492ea730 100644 --- a/src/app/models/single-occurrence.model.ts +++ b/src/app/models/single-occurrence.model.ts @@ -34,5 +34,4 @@ export class SingleOccurrence { public variant?: string; public volume?: string; public landscape?: string; - public publication_name?: string; } diff --git a/src/app/models/toc-accordion-menu-option.model.ts b/src/app/models/toc-accordion-menu-option.model.ts index 79e5913c..6515f60b 100644 --- a/src/app/models/toc-accordion-menu-option.model.ts +++ b/src/app/models/toc-accordion-menu-option.model.ts @@ -1,5 +1,6 @@ // MenuOptionModel interface export interface TocAccordionMenuOptionModel { + collapsed?: boolean; id?: any; // If the option has sub items and the iconName is null, // the default icon will be 'ios-arrow-down'. @@ -30,6 +31,8 @@ export interface TocAccordionMenuOptionModel { type?: string; + description?: string; + publication_id?: any; facsimile_id?: any; @@ -63,4 +66,6 @@ export interface TocAccordionMenuOptionModel { search_children_id?: any; important?: boolean; + + is_gallery?: boolean; } diff --git a/src/app/services/comments/comment.service.ts b/src/app/services/comments/comment.service.ts index 050dae27..b4f8841a 100644 --- a/src/app/services/comments/comment.service.ts +++ b/src/app/services/comments/comment.service.ts @@ -21,29 +21,36 @@ export class CommentService { const parts = id2.split(';'); const collection_id = parts[0].split('_')[0]; const pub_id = parts[0].split('_')[1]; + const section_id = parts[0].split('_')[2]; - const commentId = parts[0]; if (!parts[1]) { parts[1] = ''; } + const commentId = collection_id + '_' + pub_id + (section_id === undefined && section_id !== '') ? '_' + section_id : ''; const introURL = '/text/' + collection_id + '/' + pub_id + '/com'; const commentIdURL = '/text/' + collection_id + '/' + pub_id + '/com/' + parts[1]; - let url: string; + let url: String = ''; if (parts[1]) { - if (parts[1].length > 1) { + if (parts[1].length > 1 ) { url = commentIdURL; } } else { url = introURL; } + if ( section_id !== undefined && section_id !== '' ) { + url = introURL + '/' + section_id + '/' + section_id; + } else { + url = introURL; + } + if (this.cache.hasHtml(commentId)) { return this.cache.getHtmlAsObservable(id2); } else { - return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + url + return this.http.get( + this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + url ) .map(res => { const body = res.json(); @@ -80,4 +87,16 @@ export class CommentService { return Observable.throw(errMsg); } + getCorrespondanceMetadata(pub_id) { + return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + + '/correspondence/publication/metadata/' + pub_id + '') + .map(res => { + const body = res.json(); + + return body || ' - no content - '; + }) + .catch(this.handleError); + } + } diff --git a/src/app/services/elastic-search/elastic-search.d.ts b/src/app/services/elastic-search/elastic-search.d.ts new file mode 100644 index 00000000..29cf5c02 --- /dev/null +++ b/src/app/services/elastic-search/elastic-search.d.ts @@ -0,0 +1,78 @@ +interface SearchQuery { + queries: string[] + highlight: object + from: number + size: number + facetGroups?: FacetGroups + range?: TimeRange + sort?: object[] +} + +interface AggregationQuery { + queries: string[] + facetGroups?: FacetGroups + range?: TimeRange +} + +interface TimeRange { + from?: string | number + to?: string | number +} + +interface FacetGroups { + [facetGroupKey: string]: Facets +} + +interface Facets { + [facetKey: string]: Facet +} + +interface Facet { + doc_count: number + key: string | number + key_as_string?: string + selected?: boolean +} + +interface Aggregations { + [key: string]: Aggregation +} + +interface Aggregation { + terms?: Terms + date_histogram?: DateHistogram +} + +interface Terms { + size: number + field: string +} + +interface DateHistogram { + field: string + calendar_interval: string, + format: string +} + +interface SuggestionsQuery { + query: string +} + +interface SuggestionsConfig { + [aggregationKey: string]: { + field: string + size: number + } +} + +interface AggregationsData { + [aggregationKey: string]: AggregationData +} + +interface AggregationData { + buckets?: Facet[] + filtered?: { + buckets: Facet[] + } +} + diff --git a/src/app/services/elastic-search/elastic-search.service.ts b/src/app/services/elastic-search/elastic-search.service.ts new file mode 100644 index 00000000..e5c59e68 --- /dev/null +++ b/src/app/services/elastic-search/elastic-search.service.ts @@ -0,0 +1,397 @@ +import { Injectable } from '@angular/core' +import { Http, Response } from '@angular/http' +import { Observable } from 'rxjs/Observable' +import { ConfigService } from '@ngx-config/core' + + +@Injectable() +export class ElasticSearchService { + + private searchApiPath = '/search/elastic/' + private termApiPath = '/search/mtermvector/' + private indices = [] + private apiEndpoint: string + private machineName: string + private source = [] + private aggregations: Aggregations = {} + private suggestions: SuggestionsConfig = {} + private fixedFilters: object[] + + constructor(private http: Http, private config: ConfigService) { + // Should fail if config is missing. + try { + this.apiEndpoint = this.config.getSettings('app.apiEndpoint') + this.machineName = this.config.getSettings('app.machineName') + this.indices = this.config.getSettings('ElasticSearch.indices') + this.source = this.config.getSettings('ElasticSearch.source') + this.aggregations = this.config.getSettings('ElasticSearch.aggregations') + this.suggestions = this.config.getSettings('ElasticSearch.suggestions') + } catch (e) { + console.error('Failed to load Elastic Search Service. Configuration error.', e.message) + throw e + } + // Should not fail if config is missing. + try { + this.fixedFilters = this.config.getSettings('ElasticSearch.fixedFilters') + } catch (e) { + console.error('Failed to load Elastic Search Service. Configuration error.', e.message) + } + } + + executeTermQuery(terms: String[], ids: String[]): Observable { + const payload = { + 'ids' : ids, + 'parameters': { + 'fields': [ + 'textDataIndexed' + ], + 'term_statistics': false, + 'field_statistics' : false + } + } + + return this.http.post(this.getTermUrl() + '/' + terms, payload) + .map(this.extractData) + .catch(this.handleError) + } + + /** + * Returns hits. + */ + executeSearchQuery(options: SearchQuery): Observable { + const payload = this.generateSearchQueryPayload(options) + + return this.http.post(this.getSearchUrl(), payload) + .map(this.extractData) + .catch(this.handleError) + } + + /** + * Returns aggregations that are used for faceted search. + */ + executeAggregationQuery(options: AggregationQuery): Observable { + const payload = this.generateAggregationQueryPayload(options) + + return this.http.post(this.getSearchUrl(), payload) + .map(this.extractData) + .catch(this.handleError) + } + + /** + * Returns facet suggestions. + */ + executeSuggestionsQuery(options: SuggestionsQuery): Observable { + const payload = this.generateSuggestionsQueryPayload(options) + + return this.http.post(this.getSearchUrl(), payload) + .map(this.extractData) + .catch(this.handleError) + } + + private generateSearchQueryPayload({ + queries, + highlight, + from, + size, + range, + facetGroups, + sort, + }: SearchQuery): object { + const payload: any = { + from, + size, + _source: this.source, + query: { + bool: { + must: [] + } + }, + sort, + } + + // Add free text query. + queries.forEach(query => { + if (query) { + payload.query.bool.must.push({ + query_string: { + query, + } + }) + } + }) + + // Include highlighted text matches to hits if a query is present. + if (queries.some(query => !!query)) { + payload.highlight = highlight + } + + // Add date range filter. + if (range) { + payload.query.bool.must.push({ + range: { + orig_date_certain: { + gte: range.from, + lte: range.to, + } + } + }) + } + + // Add fixed filters that apply to all queries. + if (this.fixedFilters) { + this.fixedFilters.forEach(filter => { + payload.query.bool.must.push(filter) + }) + } + + if (facetGroups) { + this.injectFacetsToPayload(payload, facetGroups) + } + + console.log('search payload', payload) + + return payload + } + + private generateAggregationQueryPayload({ + queries, + range, + facetGroups, + }: AggregationQuery): object { + const payload: any = { + from: 0, + size: 0, + _source: this.source, + query: { + bool: { + should: [] + } + }, + } + + // Add free text query. + queries.forEach(query => { + if (query) { + payload.query.bool.should.push({ + query_string: { + query, + } + }) + } + }) + + // Add fixed filters that apply to all queries. + if (this.fixedFilters) { + this.fixedFilters.forEach(filter => { + payload.query.bool.should.push(filter) + }) + } + + if (facetGroups || range) { + this.injectFilteredAggregationsToPayload(payload, facetGroups, range) + } else { + this.injectUnfilteredAggregationsToPayload(payload) + } + + console.log('aggregation payload', payload) + + return payload + } + + private generateSuggestionsQueryPayload({ + query, + }: SuggestionsQuery): object { + const payload: any = { + from: 0, + size: 0, + _source: this.source, + aggs: {}, + } + + for (const [aggregationKey, suggestion] of Object.entries(this.suggestions)) { + const aggregation = this.aggregations[aggregationKey] + if (aggregation.terms) { + payload.aggs[aggregationKey] = { + filter: { + bool: { + should: [ + { + wildcard: { + [suggestion.field]: { + value: `*${query}*`, + } + } + }, + { + fuzzy: { + [suggestion.field]: { + value: query, + } + } + } + ] + } + }, + aggs: { + filtered: { + terms: { + field: aggregation.terms.field, + size: suggestion.size, + } + } + } + } + } + } + + console.log('suggestions payload', payload) + + return payload + } + + private injectFacetsToPayload(payload: any, facetGroups: FacetGroups) { + Object.entries(facetGroups).forEach(([facetGroupKey, facets]: [string, Facets]) => { + const terms = this.filterSelectedFacetKeys(facets) + if (terms.length > 0) { + payload.query.bool.filter = payload.query.bool.filter || [] + payload.query.bool.filter.push({ + terms: { + [this.aggregations[facetGroupKey].terms.field]: terms, + } + }) + } + }) + } + + private filterSelectedFacetKeys(facets: Facets): string[] { + return Object.values(facets).filter(facet => facet.selected).map((facet: any) => facet.key) + } + + private injectUnfilteredAggregationsToPayload(payload: any) { + payload.aggs = {} + for (const [key, aggregation] of Object.entries(this.aggregations)) { + payload.aggs[key] = aggregation + } + return payload + } + + /** + * Inspired by an article that uses an old version of elastic: + * https://madewithlove.com/faceted-search-using-elasticsearch/ + * + * Up to date documentation: + * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html + */ + private injectFilteredAggregationsToPayload(payload: any, facetGroups?: FacetGroups, range?: TimeRange) { + payload.aggs = {} + for (const [key, aggregation] of Object.entries(this.aggregations)) { + const filteredAggregation = this.generateFilteredAggregation(key, aggregation, facetGroups, range) + + // If filtered aggregation doesn't have filters, then use an unfiltered aggregation. + payload.aggs[key] = filteredAggregation || aggregation + } + return payload + } + + private generateFilteredAggregation( + aggregationKey: string, + aggregation: Aggregation, + facetGroups?: FacetGroups, + range?: TimeRange + ) { + + const filtered = { + filter: { + bool: { + // Selected facets go here as filters. + filter: [] + } + }, + aggs: { + // Aggregation goes here. + filtered: aggregation, + } + } + + // Add term filters. + if (facetGroups) { + Object.entries(facetGroups).forEach(([groupKey, facets]: [string, Facets]) => { + // Don't filter itself. + if (aggregationKey !== groupKey) { + const selectedFacetKeys = this.filterSelectedFacetKeys(facets) + if (selectedFacetKeys.length > 0) { + filtered.filter.bool.filter.push({ + terms: { + [this.getAggregationField(groupKey)]: selectedFacetKeys, + } + }) + } + } + }) + } + + // Add date range filter. + if (range && !aggregation.date_histogram) { + filtered.filter.bool.filter.push({ + range: { + orig_date_certain: { + gte: range.from, + lte: range.to, + } + } + }) + } + + if (filtered.filter.bool.filter.length > 0) { + return filtered + } else { + return null + } + } + + isDateHistogramAggregation(aggregationKey: string): boolean { + return !!this.aggregations[aggregationKey]['date_histogram'] + } + + isTermsAggregation(aggregationKey: string): boolean { + return !!this.aggregations[aggregationKey]['terms'] + } + + getAggregationKeys(): string[] { + return Object.keys(this.aggregations) + } + + getAggregationField(key: string): string { + const agg = this.aggregations[key] + return (agg.terms || agg.date_histogram).field + } + + private getSearchUrl(): string { + return this.apiEndpoint + '/' + this.machineName + this.searchApiPath + this.indices.join(',') + } + + private getTermUrl(): string { + return this.apiEndpoint + '/' + this.machineName + this.termApiPath + this.indices.join(',') + } + + private extractData(res: Response) { + const body = res.json() + return body || {} + } + + private handleError(error: Response | any) { + let errMsg: string + if (error instanceof Response) { + const body = error.json() || '' + const err = body.error || JSON.stringify(body) + errMsg = `${error.status} - ${error.statusText || ''} ${err}` + } else { + errMsg = error.message ? error.message : error.toString() + } + console.error('Eleastic Search query failed.', error) + return Observable.throw(errMsg) + } + +} + + diff --git a/src/app/services/facsimile/facsimile.service.ts b/src/app/services/facsimile/facsimile.service.ts index b02b2c75..7750e712 100644 --- a/src/app/services/facsimile/facsimile.service.ts +++ b/src/app/services/facsimile/facsimile.service.ts @@ -21,10 +21,14 @@ export class FacsimileService { this.facsimileImageUrl + facs_id + '/' + image_nr + '/' + zoom; } - getFacsimiles(publication_id) { + getFacsimiles(publication_id, chapter?: string) { + const parts = String(publication_id).split('_'); + if ( parts[2] !== undefined ) { + chapter = String(parts[2]).split(';')[0]; + } return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + this.facsimilesUrl + publication_id + this.config.getSettings('app.machineName') + this.facsimilesUrl + publication_id + ((chapter) ? '/' + chapter + '' : '') ).map(res => { const body = res.json(); return body; @@ -52,7 +56,7 @@ export class FacsimileService { getFacsimilePage (legacy_id): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + - `/facsimile/page/${legacy_id}`) + `/facsimiles/${legacy_id}`) .map(this.extractData) .catch(this.handleError); } diff --git a/src/app/services/gallery/gallery.service.ts b/src/app/services/gallery/gallery.service.ts index b64d98c2..dcbadd73 100644 --- a/src/app/services/gallery/gallery.service.ts +++ b/src/app/services/gallery/gallery.service.ts @@ -70,6 +70,15 @@ export class GalleryService { .catch(this.handleError); } + getMediaMetadata (id: string, lang: String): Observable { + return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/media/image/metadata/' + + id + '/' + lang + ) + .map(this.extractData) + .catch(this.handleError); + } + private extractData(res: Response) { const body = res.json(); return body || { }; diff --git a/src/app/services/html/html-cache.service.ts b/src/app/services/html/html-cache.service.ts index afa0b791..d93d6674 100644 --- a/src/app/services/html/html-cache.service.ts +++ b/src/app/services/html/html-cache.service.ts @@ -25,7 +25,7 @@ export class HtmlCacheService { } else { } - this.docFrags[id] = range.createContextualFragment(html); + this.docFrags[id] = range.createContextualFragment(html.replace(/images\//g, 'assets/images/')); } getHtml(id) { @@ -53,7 +53,7 @@ export class HtmlCacheService { return this.htmlCache[id] || false; } } else { - return ' - no cached html - '; + return ''; } } diff --git a/src/app/services/md/md-content.service.ts b/src/app/services/md/md-content.service.ts index 89249b00..4992327f 100644 --- a/src/app/services/md/md-content.service.ts +++ b/src/app/services/md/md-content.service.ts @@ -65,17 +65,21 @@ export class MdContentService { } } - private getNodeById(id, node) { + /** + * Find a node by id in a JSON tree + */ + getNodeById(id, tree) { const reduce = [].reduce; - function runner(result, rnode) { - if (result || !rnode) { return result; } - return rnode.id === id && rnode || - runner(null, rnode.children) || - reduce.call(Object(rnode), runner, result); + const runner = (result, node) => { + if (result || !node) { return result; } + return node.id === id && node || + runner(null, node.children) || + reduce.call(Object(node), runner, result); } - return runner(null, node); + return runner(null, tree); } + private extractData(res: Response) { const body = res.json(); return body || { }; diff --git a/src/app/services/occurrence/occurence.service.ts b/src/app/services/occurrence/occurence.service.ts index d7d0f520..2466a7e1 100644 --- a/src/app/services/occurrence/occurence.service.ts +++ b/src/app/services/occurrence/occurence.service.ts @@ -14,7 +14,6 @@ export class OccurrenceService { return this.http.get( this.config.getSettings('app.apiEndpoint') + '/occurrences/' + object_type + '/' + id) .map(res => { const body = res.json(); - return body || ' - no content - '; }) .catch(this.handleError); diff --git a/src/app/services/reference-data/reference-data.service.ts b/src/app/services/reference-data/reference-data.service.ts index 66df9d86..c640ead8 100644 --- a/src/app/services/reference-data/reference-data.service.ts +++ b/src/app/services/reference-data/reference-data.service.ts @@ -22,7 +22,7 @@ export class ReferenceDataService { .map(res => { const body = res.json(); - return body[0] || ' - no content - '; + return body[0] || ''; }) .catch(this.handleError); } diff --git a/src/app/services/search/search-data.service.ts b/src/app/services/search/search-data.service.ts index 55bef6be..50e3e254 100644 --- a/src/app/services/search/search-data.service.ts +++ b/src/app/services/search/search-data.service.ts @@ -104,6 +104,18 @@ export class SearchDataService { .catch(this.handleError); } + + getProjectCollections() { + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + + '/collections') + .map(res => { + const body = res.json(); + + return body || ' - no content - '; + }) + .catch(this.handleError); + } + getGalleryOccurrences( type, id ) { return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + diff --git a/src/app/services/semantic-data/semantic-data.service.ts b/src/app/services/semantic-data/semantic-data.service.ts index 0ed249af..33f97ad7 100644 --- a/src/app/services/semantic-data/semantic-data.service.ts +++ b/src/app/services/semantic-data/semantic-data.service.ts @@ -9,6 +9,11 @@ export class SemanticDataService { textCache: any; useLegacy: boolean; + elasticSubjectIndex: string; + elasticLocationIndex: string; + elasticWorkIndex: string; + elasticTagIndex: string; + flattened: any; constructor(private http: Http, private config: ConfigService) { try { @@ -16,8 +21,14 @@ export class SemanticDataService { } catch (e) { this.useLegacy = false; } + this.elasticSubjectIndex = 'subject'; + this.elasticLocationIndex = 'location'; + this.elasticWorkIndex = 'work'; + this.elasticTagIndex = 'tag'; + this.flattened = []; } + getFilterCollections(): Observable { return this.http.get('assets/filterCollections.json') .map(this.extractData) @@ -25,15 +36,54 @@ export class SemanticDataService { } getFilterPersonTypes(): Observable { - return this.http.get('assets/filterPersonTypes.json') - .map(this.extractData) - .catch(this.handleError); + const payload: any = { + size: 0, + query: { + bool: { + must : [{ + term: { project_id : this.config.getSettings('app.projectId') } + }] + } + }, + aggs : { + types : { + terms : { + field : 'type.keyword' + } + } + } + } + return this.http.post(this.getSearchUrl(this.elasticSubjectIndex), payload) + .map(this.extractData) + .catch(this.handleError) + } + + getFilterCategoryTypes(): Observable { + const payload: any = { + size: 0, + query: { + bool: { + must : [{ + term: { project_id : this.config.getSettings('app.projectId') } + }] + } + }, + aggs : { + types : { + terms : { + field : 'tag_type.keyword' + } + } + } + } + return this.http.post(this.getSearchUrl(this.elasticTagIndex), payload) + .map(this.extractData) + .catch(this.handleError) } getPlace(id: string): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/tooltips/location/' + id + - ((this.useLegacy) ? '/' + this.useLegacy + '/' : '/')) + this.config.getSettings('app.machineName') + '/location/' + id) .map(res => { const body = res.json(); @@ -44,11 +94,9 @@ export class SemanticDataService { getPerson(id: string): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/tooltips/subject/' + id + - ((this.useLegacy) ? '/' + this.useLegacy + '/' : '/')) + this.config.getSettings('app.machineName') + '/subject/' + id) .map(res => { const body = res.json(); - return body || ' - no content - '; }) .catch(this.handleError); @@ -56,8 +104,18 @@ export class SemanticDataService { getTag(id: string): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/tooltips/tag/' + id + - ((this.useLegacy) ? '/' + this.useLegacy + '/' : '/')) + this.config.getSettings('app.machineName') + '/tag/' + id) + .map(res => { + const body = res.json(); + + return body || ' - no content - '; + }) + .catch(this.handleError); + } + + getWork(id: string): Observable { + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/work/' + id) .map(res => { const body = res.json(); @@ -68,7 +126,7 @@ export class SemanticDataService { getSemanticData(id: string): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + 'tooltips/tag/' + id) + this.config.getSettings('app.machineName') + '/tag/' + id) .map(res => { const body = res.json(); @@ -79,7 +137,7 @@ export class SemanticDataService { } getAllPerson(): Observable { - return this.http.get(this.config.getSettings('app.apiEndpoint') + '/tooltips/subjects') + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/subjects') .map(res => { const body = res.json(); @@ -109,6 +167,17 @@ export class SemanticDataService { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + '/subject/occurrences/' + ((subject_id) ? subject_id + '/' : '')) + .map(res => { + const body = res.json(); + return body || ' - no content - '; + }) + .catch(this.handleError); + } + + getSubjects(): Observable { + + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/subjects') .map(res => { const body = res.json(); @@ -117,12 +186,284 @@ export class SemanticDataService { .catch(this.handleError); } + getSubjectsElastic(from, searchText?, filters?) { + let showPublishedStatus = 2; + if ( filters === null ) { + filters = {}; + } + try { + showPublishedStatus = this.config.getSettings('PersonSearch.ShowPublishedStatus'); + } catch (e) { + showPublishedStatus = 2; + } + const payload: any = { + from: from, + size: 200, + sort: [ + { 'full_name.keyword' : {'order' : 'asc'} } + ], + query: { + bool: { + must : [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId') } + }, + { + 'term' : { 'published' : showPublishedStatus } + }, + { + 'term' : { 'sub_deleted' : 0 } + }], + } + } + } + + if (filters !== undefined && filters['filterPersonTypes'] !== undefined && filters['filterPersonTypes'].length > 0) { + payload.from = 0; + payload.size = 1000; + payload.query.bool.must.push({bool: {should: []}}); + filters['filterPersonTypes'].forEach(element => { + payload.query.bool.must[payload.query.bool.must.length - 1].bool. + should.push({'term': {'type.keyword': String(element.name)}}); + }); + } + + // Add date range filter. + if (filters.filterYearMax && filters.filterYearMin) { + payload.from = 0; + payload.size = 1000; + payload.query.bool.must.push({ + range: { + date_born_date: { + gte: filters.filterYearMin + '-01-01', + lte: filters.filterYearMax + '-01-01', + } + } + }) + } + // Seach for first character of name + if (searchText !== undefined && searchText !== '' && String(searchText).length === 1) { + payload.from = 0; + payload.size = 5000; + payload.query.bool.must.push({regexp: {'full_name.keyword': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + } else if ( searchText !== undefined && searchText !== '' ) { + payload.from = 0; + payload.size = 5000; + payload.sort = ['_score'], + payload.query.bool.must.push({fuzzy: {'full_name': { + 'value': `${String(searchText)}`}}}); + } + + return this.http.post(this.getSearchUrl(this.elasticSubjectIndex), payload) + .map(this.extractData) + .catch(this.handleError) + } + + getSingleObjectElastic(type, id) { + const payload: any = { + from: 0, + size: 200, + query: { + bool: { + should : [{ + bool: { + must: [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId') } + }, + { + 'term' : { 'id' : id } + }] + } + }, + { + bool: { + must: [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId') } + }, + { + 'term' : { 'legacy_id' : id } + }] + } + }] + } + } + } + + if ( type === 'work' ) { + payload.query.bool.should[0].bool.must[1]['term'] = {'man_id': id}; + } + + // remove if the ID is not strictly numerical + if ( /^\d+$/.test(id) === false ) { + delete payload.query.bool.should[0]; + } + + return this.http.post(this.getSearchUrl(type), payload) + .map(this.extractData) + .catch(this.handleError) + } + + getLocationElastic(from, searchText?) { + let showPublishedStatus = 2; + try { + showPublishedStatus = this.config.getSettings('LocationSearch.ShowPublishedStatus'); + } catch (e) { + showPublishedStatus = 2; + } + const payload: any = { + from: from, + size: 200, + sort: [ + { 'name.keyword' : 'asc' } + ], + query: { + bool: { + must : [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId') } + }, + { + 'term' : { 'published' : showPublishedStatus } + }, + { + 'term' : { 'loc_deleted' : 0 } + }], + } + } + } + // Seach for first character of name + if (searchText !== undefined && searchText !== '' && String(searchText).length === 1) { + payload.from = 0; + payload.size = 5000; + payload.query.bool.must.push({regexp: {'name.keyword': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + } else if ( searchText !== undefined && searchText !== '' ) { + payload.from = 0; + payload.size = 5000; + payload.sort = ['_score'], + payload.query.bool.must.push({fuzzy: {'name': { + 'value': `${String(searchText)}`}}}); + } + return this.http.post(this.getSearchUrl(this.elasticLocationIndex), payload) + .map(this.extractData) + .catch(this.handleError) + } + + getWorksElastic(from, searchText?) { + const payload: any = { + from: from, + size: 200, + sort: [ + { 'author_data.last_name.keyword' : 'asc' } + ], + query: { + bool: { + should : [{ + bool: { + must: [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId')}, + }, + { + 'term' : { 'deleted' : 0 } + }] + } + }, + { + bool: { + must: [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId')} + }, + { + 'term' : { 'deleted' : 0 } + }] + } + }] + } + } + } + // Seach for first character of name + if (searchText !== undefined && searchText !== '' && String(searchText).length === 1) { + payload.from = 0; + payload.size = 5000; + payload.query.bool.should[0].bool.must.push({regexp: {'title.keyword': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + payload.query.bool.should[1].bool.must.push({regexp: {'title.keyword': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + } else if ( searchText !== undefined && searchText !== '' ) { + payload.from = 0; + payload.size = 5000; + payload.sort = ['_score'], + payload.query.bool.should[0].bool.must.push({fuzzy: {'title': { + 'value': `${String(searchText)}`}}}); + payload.query.bool.should[1].bool.must.push({regexp: {'author_data.full_name': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + } + return this.http.post(this.getSearchUrl(this.elasticWorkIndex), payload) + .map(this.extractData) + .catch(this.handleError) + } + + getTagElastic(from, searchText?, filters?) { + let showPublishedStatus = 2; + try { + showPublishedStatus = this.config.getSettings('LocationSearch.ShowPublishedStatus'); + } catch (e) { + showPublishedStatus = 2; + } + const payload: any = { + from: from, + size: 800, + sort: [ + { 'name.keyword' : 'asc' } + ], + query: { + bool: { + must : [{ + 'term' : { 'project_id' : this.config.getSettings('app.projectId') } + }, + { + 'term' : { 'published' : showPublishedStatus } + }, + { + 'term' : { 'tag_deleted' : 0 } + }], + } + } + } + + // Seach for first character of name + if (searchText !== undefined && searchText !== '' && String(searchText).length === 1) { + payload.from = 0; + payload.size = 5000; + payload.query.bool.must.push({regexp: {'name.keyword': { + 'value': `${String(searchText)}.*|${String(searchText).toLowerCase()}.*`}}}); + } else if ( searchText !== undefined && searchText !== '' ) { + payload.from = 0; + payload.size = 5000; + payload.sort = ['_score'], + payload.query.bool.must.push({fuzzy: {'name': { + 'value': `${String(searchText)}`}}}); + } + + if (filters !== undefined && filters['filterCategoryTypes'] !== undefined) { + payload.from = 0; + payload.size = 1000; + payload.query.bool.must.push({bool: {should: []}}); + filters['filterCategoryTypes'].forEach(element => { + payload.query.bool.must[payload.query.bool.must.length - 1].bool. + should.push({'term': {'tag_type.keyword': String(element.name)}}); + }); + } + + return this.http.post(this.getSearchUrl(this.elasticTagIndex), payload) + .map(this.extractData) + .catch(this.handleError) + } + getSubjectOccurrencesById(id: string): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/occurrences/subject/' + id) .map(res => { const body = res.json(); - return body || ' - no content - '; }) .catch(this.handleError); @@ -133,7 +474,6 @@ export class SemanticDataService { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/occurrences/' + type + '/' + id) .map(res => { const body = res.json(); - return body || ' - no content - '; }) .catch(this.handleError); @@ -163,7 +503,7 @@ export class SemanticDataService { getWorkOccurrencesById(id: string): Observable { - return this.http.get(this.config.getSettings('app.apiEndpoint') + '/occurrences/work/' + id) + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + '/workregister/work/project/occurrences/' + id) .map(res => { const body = res.json(); @@ -199,7 +539,7 @@ export class SemanticDataService { getWorkOccurrences(): Observable { return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/tag/occurrences/') + this.config.getSettings('app.machineName') + '/workregister/manifestations/') .map(res => { const body = res.json(); @@ -227,6 +567,36 @@ export class SemanticDataService { .catch(this.handleError); } + getPublicationTOC(collection_id) { + return this.http.get(this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/toc/' + collection_id) + .map(res => { + const data = res.json(); + this.flatten(data); + return this.flattened; + }, + error => { + console.log(error); + }) + .catch(this.handleError); + } + + private flatten(toc) { + if ( toc.children ) { + for (let i = 0, count = toc.children.length; i < count; i++) { + if ( toc.children[i].itemId !== undefined && toc.children[i].itemId !== '') { + this.flattened.push(toc.children[i]); + } + this.flatten(toc.children[i]); + } + } + } + + private getSearchUrl(index: any): string { + return this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/search/elastic/' + index + } + private extractData(res: Response) { const body = res.json(); return body || {}; diff --git a/src/app/services/settings/read-popover.service.ts b/src/app/services/settings/read-popover.service.ts index c507a481..1b418847 100644 --- a/src/app/services/settings/read-popover.service.ts +++ b/src/app/services/settings/read-popover.service.ts @@ -15,6 +15,7 @@ export class ReadPopoverService { 'personInfo': false, 'abbreviations': false, 'placeInfo': false, + 'workInfo': false, 'changes': false, 'pageNumbering': false, 'pageBreakOriginal': false, diff --git a/src/app/services/settings/user-settings.service.ts b/src/app/services/settings/user-settings.service.ts index da9d8bcf..84d96e08 100644 --- a/src/app/services/settings/user-settings.service.ts +++ b/src/app/services/settings/user-settings.service.ts @@ -26,18 +26,12 @@ export class UserSettingsService { detectPlatform() { this.storage.get('mode').then((mode) => { // mode is either desktop or mobile - console.log(`my mode is ${mode}...`); if (mode) { - console.log('thus ${mode}'); this._mode = mode; } else { - console.log('thus some other'); - if (this.platform.is('core') || this.platform.is('tablet')) { this._mode = 'desktop'; - console.log('perhaps desktop'); } else { - console.log('perhaps mobile'); this._mode = 'mobile'; } } @@ -124,7 +118,7 @@ export class UserSettingsService { set splitPaneOpen(maybe: boolean) { this._splitPaneOpen = maybe; - console.log(this._splitPaneOpen); + // console.log(this._splitPaneOpen); this.storage.set('splitPane', maybe); } diff --git a/src/app/services/texts/text.service.ts b/src/app/services/texts/text.service.ts index 76fc2d1b..f76acb48 100644 --- a/src/app/services/texts/text.service.ts +++ b/src/app/services/texts/text.service.ts @@ -13,8 +13,11 @@ export class TextService { private titlePageUrl = '/text/tit/'; private variationsUrl = '/text/var/'; private manuscriptsUrl = '/text/ms/'; + private illustrationsImage: string; textCache: any; + apiEndPoint: string; + appMachineName: string; constructor(private http: Http, private config: ConfigService, private cache: TextCacheService) { @@ -22,23 +25,75 @@ export class TextService { getEstablishedText(id: string): Observable { - + this.appMachineName = this.config.getSettings('app.machineName'); + this.apiEndPoint = this.config.getSettings('app.apiEndpoint'); const id2 = id.replace('_est', ''); const parts = id2.split(';'); const c_id = `${id}`.split('_')[0]; const pub_id = `${id}`.split('_')[1]; let ch_id = null; if ( `${id}`.split('_')[2] !== undefined ) { - ch_id = `${id}`.split('_')[2]; + ch_id = String(`${id}`.split('_')[2]).split(';')[0]; } - const textId = parts[0]; - return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/text/' + c_id + '/' + pub_id + '/est' + ((ch_id === null) ? '' : '/' + ch_id)) + if ( ch_id === '' ) { + ch_id = null; + } + + const textId = id; + + return this.http.get( `${this.apiEndPoint}/${this.appMachineName}/text/${c_id}/${pub_id}/est${((ch_id === null) ? '' : '/' + ch_id)}`) .map(res => { const body = res.json(); + + try { + if (this.config.getSettings('settings.showReadTextIllustrations')) { + const showIllustration = this.config.getSettings('settings.showReadTextIllustrations'); + let galleryId = 44; + try { + galleryId = this.config.getSettings('settings.galleryCollectionMapping')[c_id]; + } catch ( err ) { + + } + + if (!showIllustration.includes(c_id)) { + const parser = new DOMParser(); + body.content = parser.parseFromString(body.content, 'text/html'); + const images: any = body.content.querySelectorAll('img.est_figure_graphic'); + for (let i = 0; i < images.length; i++) { + images[i].classList.add('hide-illustration'); + } + + const s = new XMLSerializer(); + body.content = s.serializeToString(body.content); + this.cache.setHtmlCache(textId, body.content.replace(/images\/verk\//g, `${this.apiEndPoint}/${this.appMachineName}/gallery/get/${galleryId}/`)); + const ret = this.cache.getHtml(id); + if ( !ret ) { + return body.content; + } + return this.cache.getHtml(id); + } + } + } catch (e) { + console.error(e) + } + + const se = new XMLSerializer(); + try { + const parser = new DOMParser(); + body.content = parser.parseFromString(body.content, 'text/html'); + body.content = se.serializeToString(body.content); this.cache.setHtmlCache(textId, body.content); - return this.cache.getHtml(id); + } catch ( err ) { + console.log(err); + } + + const cachedHTML = this.cache.getHtml(id); + if ( cachedHTML && cachedHTML !== '' ) { + return cachedHTML; + } else { + return body.content; + } }) .catch(this.handleError); } @@ -79,6 +134,20 @@ export class TextService { .catch(this.handleError); } + + getCoverPage(id: string, lang: string): Observable { + const data = `${id}`.split('_'); + const c_id = data[0]; + const pub_id = (data.length > 1) ? data[1] : 1; + + return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + '/text/' + c_id + '/' + pub_id + '/cover/' + lang) + .map(res => { + return res.json(); + }) + .catch(this.handleError); + } + getVariations(id: string): Observable { const c_id = `${id}`.split('_')[0]; const pub_id = `${id}`.split('_')[1]; @@ -91,12 +160,16 @@ export class TextService { .catch(this.handleError); } - getManuscripts(id: string): Observable { + getManuscripts(id: string, chapter?: string): Observable { const c_id = `${id}`.split('_')[0]; const pub_id = `${id}`.split('_')[1]; + if ( chapter !== undefined && chapter !== null ) { + chapter = String(chapter).split(';')[0]; + } + return this.http.get( this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + '/text/' + c_id + '/' + pub_id + '/ms') + this.config.getSettings('app.machineName') + '/text/' + c_id + '/' + pub_id + '/ms' + ((chapter) ? '/' + chapter + '' : '')) .map(res => { return res.json(); }) diff --git a/src/app/services/toc/table-of-contents.service.ts b/src/app/services/toc/table-of-contents.service.ts index 3e191e93..f7d72d72 100644 --- a/src/app/services/toc/table-of-contents.service.ts +++ b/src/app/services/toc/table-of-contents.service.ts @@ -85,8 +85,6 @@ export class TableOfContentsService { private handleError (error: Response | any) { let errMsg: string; - console.error('snafu', error); - if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); diff --git a/src/app/services/tooltips/tooltip.service.ts b/src/app/services/tooltips/tooltip.service.ts index 06d4b9ee..8e5fcc8f 100644 --- a/src/app/services/tooltips/tooltip.service.ts +++ b/src/app/services/tooltips/tooltip.service.ts @@ -9,52 +9,97 @@ import { CommentService } from '../comments/comment.service'; @Injectable() export class TooltipService { - private personTooltipUrl = '/tooltips/subject/'; private placeTooltipUrl = '/tooltips/locations/'; - - constructor(private http: Http, private config: ConfigService, private commentService: CommentService) {} + private apiEndPoint: string; + private projectMachineName: string; + constructor(private http: Http, private config: ConfigService, private commentService: CommentService) { + this.apiEndPoint = this.config.getSettings('app.apiEndpoint'); + this.projectMachineName = this.config.getSettings('app.machineName'); + } getPersonTooltip(id: string): Observable { + let url = ''; + const legacyPrefix = this.config.getSettings('app.legacyIdPrefix'); + + url = `${this.apiEndPoint}/${this.projectMachineName}/subject/${legacyPrefix}${id}` - return this.http.get( this.config.getSettings('app.apiEndpoint') + this.personTooltipUrl + id) + return this.http.get(url) .map(res => { const body = res.json(); - return body[0] || {'name': 'Error', 'description': 'Person data not found'}; + return body[0] || {'name': body.full_name, 'description': body.description, + 'date_deceased': body.date_deceased, + 'date_born': body.date_born}; }) .catch(this.handleError); } getPlaceTooltip(id: string): Observable { + let url = ''; + const legacyPrefix = this.config.getSettings('app.legacyIdPrefix'); + + url = `${this.apiEndPoint}/${this.projectMachineName}/location/${legacyPrefix}${id}` + + return this.http.get( url ) + .map(res => { + const body = res.json(); + return body[0] || {'name': body.name, 'description': body.description}; + }) + .catch(this.handleError); + } + + getTagTooltip(id: string): Observable { + let url = ''; + const legacyPrefix = this.config.getSettings('app.legacyIdPrefix'); + + url = `${this.apiEndPoint}/${this.projectMachineName}/tag/${legacyPrefix}${id}` - return this.http.get( this.config.getSettings('app.apiEndpoint') + this.placeTooltipUrl + id) + return this.http.get( url ) .map(res => { const body = res.json(); - return body[0] || {'name': 'Error', 'description': 'Place data not found'}; + return body[0] || {'name': 'Tag', 'description': body.description}; }) .catch(this.handleError); } + getWorkTooltip(id: string): Observable { + let url = ''; + url = `${this.apiEndPoint}/${this.projectMachineName}/work/${id}` + + return this.http.get( url ) + .map(res => { + const body = res.json(); + return body[0] || {'name': 'Work', 'description': body.title}; + }) + .catch(this.handleError); + } + + decodeHtmlEntity(str: string) { + return str.replace(/&#(\d+);/g, function(match, dec) { + return String.fromCharCode(dec); + }); + } + + /** * Can be used to fetch tooltip in situations like these: * * */ - getCommentTooltip(id: string) { + getCommentTooltip(id: string): Observable { const parts = id.split(';'); const htmlId = parts[0]; - const elementId = parts[1].replace('end', 'en'); - - + const elementId = parts[parts.length - 1].replace('end', 'en'); return this.commentService.getComment(parts[0]).map( data => { const range = document.createRange(); const doc = range.createContextualFragment(data); - const element = doc.querySelector('#' + elementId).nextElementSibling; + const element = doc.querySelector('.' + elementId); + const formatedCommentData = element.innerHTML.replace(/(<)/g, '<').replace(/(>)/g, '>'); return { 'name': 'Comment', - 'description': element.innerHTML.replace(/(<([^>]+)>)/ig, '').replace(/^p\d+/gi, '') } + 'description': element.innerHTML = formatedCommentData } || {'name': 'Error', 'description': element.innerHTML}; }, error => { diff --git a/src/app/services/tutorial/tutorial.service.ts b/src/app/services/tutorial/tutorial.service.ts index cdada04a..03ed23fd 100644 --- a/src/app/services/tutorial/tutorial.service.ts +++ b/src/app/services/tutorial/tutorial.service.ts @@ -32,16 +32,24 @@ export class TutorialService { tutorialTexts => { this.tutorialTexts = tutorialTexts; this.tutorialSteps = this.config.getSettings('TutorialSteps'); - console.log(this.tutorialSteps); for (const i in this.tutorialSteps) { const str = this.tutorialSteps[i].intro; this.tutorialSteps[i].intro = tutorialTexts[str] || `${str} Untranslated text`; } setTimeout(() => { - this.storage.get('tutorial-done').then((seen) => { - if (!seen) { - this.intro(); + this.storage.get('all_tutorial_steps').then((steps) => { + if (steps !== undefined && steps !== null) { + this.tutorialSteps = steps; } + this.storage.get('tutorial-done').then((seen) => { + try { + if (this.config.getSettings('showTutorial')) { + this.intro(); + } + } catch (e) { + console.error('Missing showTutorial from config.json'); + } + }); }); }, 1000); @@ -50,13 +58,26 @@ export class TutorialService { this.registerListeners(); } + private async redoIntro() { + setTimeout(() => { + this.storage.get('all_tutorial_steps').then((steps) => { + if (steps !== undefined && steps !== null) { + this.tutorialSteps = steps; + this.intro(); + } + }); + }, 1000); + } private async intro() { - const intro = introJs(); const steps = this.tutorialSteps.filter((step) => {return this.canBeSeen(step, this.currentPage)}); + if ( steps.length === 0 ) { + return false; + } + + const intro = introJs(); - console.log(steps); intro.setOptions({ steps: steps, disableInteraction: false, @@ -67,12 +88,13 @@ export class TutorialService { prevLabel: this.tutorialTexts.prevLabel, skipLabel: this.tutorialTexts.skipLabel, doneLabel: this.tutorialTexts.doneLabel, + overlayOpacity: 0.45, keyboardNavigation: true, scrollToElement: true, }); this.canBeSeen = this.canBeSeen.bind(this); intro.onbeforechange((elem) => { - console.log('step', elem.selector); + // console.log('step', elem.selector); }); intro.onchange((elem) => { @@ -83,23 +105,34 @@ export class TutorialService { intro.oncomplete((elem) => { this.storage.set('tutorial-done', true); }); + + intro.onexit(() => { + this.storage.set('tutorial-done', true); + }); + intro.start(); } iHaveSeen(selector) { const i = this.getStep(selector, true); - console.log(`i have seen ${selector} : ${i}`); if (i) { this.tutorialSteps[i].alreadySeen = true; } - console.log(this.tutorialSteps); + this.storage.set('all_tutorial_steps', this.tutorialSteps); } canBeSeen(step, page) { // i have not already seen it and it is not disabled on this page // It is visible by default or has been specifically enabled for this page - return !step.alreadySeen && !step.hideOn.includes(page) && - (step.show || step.showOn.includes(page)); + if ( String(step.element).includes('#') ) { + if ( document.getElementById(String(step.element).replace('#', '')) !== null && step.show ) { + return !step.alreadySeen; + } else { + return false; + } + } else { + return !step.alreadySeen; + } } getStep(selector, returnIndex = false): any { @@ -109,19 +142,20 @@ export class TutorialService { // with no element attribute. for (const i in this.tutorialSteps) { const step = this.tutorialSteps[i]; - if (!step.element && this.canBeSeen(step, this.currentPage)) { - if (returnIndex) { - return i; - } else { - return step; + if (!step.element) { + if (!step.element && this.canBeSeen(step, this.currentPage)) { + if (returnIndex) { + return i; + } else { + return step; + } } } } } for (const i in this.tutorialSteps) { const step = this.tutorialSteps[i]; - console.log(i, selector, step.element); - if (step.element && '#' + step.element === selector) { + if (step.element && step.element === '#' + selector) { if (returnIndex) { return i; } else { @@ -135,7 +169,7 @@ export class TutorialService { for (const i in this.tutorialSteps) { const step = this.tutorialSteps[i]; this.tutorialSteps[i].alreadySeen = false; - this.storage.set('tutorial-step-' + step.id, false); + this.storage.set('all_tutorial_steps', this.tutorialSteps); } this.storage.set('tutorial-done', false); @@ -150,5 +184,9 @@ export class TutorialService { this.reset(); this.intro(); }); + + this.events.subscribe('help:continue', () => { + this.redoIntro(); + }); } } diff --git a/src/assets/custom_css/custom.css b/src/assets/custom_css/custom.css index 46f06ebe..20acc5ba 100644 --- a/src/assets/custom_css/custom.css +++ b/src/assets/custom_css/custom.css @@ -1,4 +1,4 @@ /* THIS FILE WILL BE REPLACED BY PROJECT SPECIFIC ITEMS DO NOT MODIFY THIS FILE -*/ \ No newline at end of file +*/ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 407abef9..d83e5894 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,181 +1,238 @@ { - "BrowserWarning": "Your browser is not supported", - "BrowserWarningInfo": "Internet Explorer is not supported by this page. Please use Edge, Firefox or another browser.", - "BrowserWarningClose": "Close", - "TopMenu": { - "Home": "Home", - "Read": "Content", - "Info": "About" - }, - "Tabs": { - "Home": "Home", - "Read": "Read", - "Info": "More Information" - }, - "Read": { - "DigitalEditions": "Digital Editions", - "OpenInTab": "Öppna i ny flik", - "Popover": { - "All": "Choose all", - "Appearance": "Appearance", - "Show": "Show", - "Comments": "Comments", - "PersonInfo": "People", - "PlaceInfo": "Places", - "Changes": "Changes", - "Abbreviations": "Abbreviations", - "PageNumbering": "Page Numbering", - "PageBreakOriginal": "Original Page Breaks", - "PageBreakEdition": "Edition Page Breaks" - }, - "DownloadDigitalVersion": "Download a Digital Version", - "CommentsFor": "Comments to", - "ReadFromBeginning": "From the beginning", - "Established": { - "Title": "Established text", - "AddButtonText": "Established text view" - }, - "Comments": { - "Title": "Comments", - "AddButtonText": "Comments view" - }, - "Manuscripts": { - "Title": "Manuscript", - "AddButtonText": "Manuscript view", - "SelectManuscript": "Välj manuscript", - "ShowNormalized": "Visa ändringar inarbetade", - "ShowChanges": "Visa ändringar", - "OpenFacsimile": "Open fascimile" - }, - "Variations": { - "Title": "Variations", - "AddButtonText": "Variation view" - }, - "Facsimiles": { - "Title": "Facsimiles", - "AddButtonText": "Facsimile view", - "SelectFacsimiles": "Choose Facsimile", - "Page": "Sida", - "Of": "av", - "OpenManuscript": "Open manuscript" - }, - "TitlePage": { - "Title": "Titel page" - }, - "Introduction": { - "Title": "" - }, - "ShowAll": "Show all" - }, - "About": { - "Title": "About the Edition", - "UI": "User Interface", - "Language": "Language", - "Information": "Information" - }, - "TOC": { - "Home": "Home", - "Read": "Read", - "About": "About Hilma Granqvist", - "Facsimiles": "Assorted Facsimile", - "ImageGallery": "Image Gallery", - "PersonSearch": "Persons", - "PlaceSearch": "Places", - "TagSearch": "Subjects", - "MusicianSearch": "Musicians Search", - "WriterSearch": "Writers Search", - "SongTypes": "Song Types", - "WorkSearch": "Works", - "Collections": "Publications", - "Books": "Books", - "All": "All" - }, - "GalleryPages": { - "0": "Headline 1", - "1": "Headline 2", - "2": "THeadline 3" - }, - "MobilePages": { - "header": "Pages" - }, - "BackButton": { - "default": "Go Back" - }, - "HomePage": { - "current": "Current", - "publishedEditions": "Published Editions:", - "digitalEditionHeading": "Archive of Hilma Granqvist", - "selectEdition": "View" - }, - "SearchApp": { - "simpleSearch": "Simple Search", - "advancedSearch": "Advanced Search", - "filter": "Filter", - "matchesOn": "Match(es):", - "numberOf": "match(es)", - "pagenumber": "p.", - "search-holder": "Seach text", - "hitCount": "Hit Count", - "type": "Type", - "timeline": "Timeline", - "freetext": "Freetext", - "start": "Start", - "end": "End", - "relevance": "Relevance", - "alphabetical": "Alphabetical", - "oldest": "Oldest", - "genre": "Genre", - "person-register": "Persons", - "place-register": "Places ", - "tag-register": "Subjects/Tags", - "verk": "Works", - "sort-by": "Sort by" - }, - "Occurrences": { - "Title": "Occurrences", - "Manuscript": "Manuscript", - "Variation": "Variation", - "Commentary": "Commentary", - "Facsimile": "Facsimile", - "Established": "Established", - "longitude": "long.", - "latitude": "lat.", - "city": "City", - "region": "Region", - "country": "Place", - "place_of_birth": "Place of Birth", - "type": "Type", - "source": "Source", - "description": "Description", - "no_occurrences": "No occurrences", - "download": "download pdf", - "articles": "Articles" - }, - "est": "Established text", - "ms": "Manuscript", - "com": "Commentary", - "tit": "Title", - "var": "Variation", - "inl": "Introduction", - "locations": "Locations", - "location": "Location", - "tags": "Tags", - "tag": "Tags", - "photos": "photos", - "subjects": "Subjects", - "subject": "Subject", - "collections": "Collections", - "works": "Works", - "song": "Melody", - "Reference": { - "title": "Refer", - "thisPage": "Refer to this page", - "established": "Refer to this text", - "intro": "Refer to this introduction" - }, - "MusicPage": { - "Title": "Music", - "PageTitle": "Music volumes", - "SubTitle": "FSFD" - } -} \ No newline at end of file + "BrowserWarning": "Your browser is not supported", + "BrowserWarningInfo": "Internet Explorer is not supported by this page. Please use Edge, Firefox or another browser.", + "BrowserWarningClose": "Close", + "TutorialTexts": { + "nextLabel": "Next", + "prevLabel": "Previous", + "skipLabel": "Skip", + "doneLabel": "Done", + "WelcomeText": "Welcome, here is a presentation of the user interface.", + "MenuToggleText": "Click here to show or hide the menu.", + "searchIcon": "Click here for search.", + "ReadTocItemText": "Here you can see all digital publications and volumes.", + "DownloadCacheText": "With this button you can save the data locally, and it will be faster next time." + }, + "TopMenu": { + "Home": "Home", + "Read": "Content", + "Info": "About" + }, + "Tabs": { + "Home": "Home", + "Read": "Read", + "Info": "More Information" + }, + "Read": { + "DigitalEditions": "Digital Editions", + "OpenInTab": "Open in new tab", + "Popover": { + "All": "Choose all", + "Appearance": "Appearance", + "Show": "Show", + "Comments": "Comments", + "PersonInfo": "People", + "PlaceInfo": "Places", + "Changes": "Changes", + "Abbreviations": "Abbreviations", + "PageNumbering": "Page Numbering", + "PageBreakOriginal": "Original Page Breaks", + "PageBreakEdition": "Edition Page Breaks" + }, + "DownloadDigitalVersion": "Download a Digital Version", + "DownloadPDF": "", + "CommentsFor": "Comments to", + "ReadFromBeginning": "From the beginning", + "Established": { + "Title": "Established text", + "AddButtonText": "Established text view" + }, + "Comments": { + "Title": "Comments", + "AddButtonText": "Comments view" + }, + "Manuscripts": { + "Title": "Manuscript", + "AddButtonText": "Manuscript view", + "SelectManuscript": "Välj manuscript", + "ShowNormalized": "Visa ändringar inarbetade", + "ShowChanges": "Visa ändringar", + "OpenFacsimile": "Open fascimile" + }, + "Variations": { + "Title": "Variations", + "AddButtonText": "Variation view" + }, + "Facsimiles": { + "Title": "Facsimiles", + "AddButtonText": "Facsimile view", + "SelectFacsimiles": "Choose Facsimile", + "Page": "Page", + "Of": "of", + "OpenManuscript": "Open manuscript", + "ExternalHeading" : "External Facsimiles" + }, + "TitlePage": { + "Title": "Titel page" + }, + "Introduction": { + "Title": "" + }, + "CoverPage": { + "Title": "Cover" + }, + "ShowAll": "Show all" + }, + "About": { + "Title": "About the Edition", + "UI": "User Interface", + "Language": "Language", + "Information": "Information" + }, + "TOC": { + "Home": "Home", + "Read": "Read", + "Read1": "Read", + "Read2": "Read", + "ReadN": "Read", + "About": "About Hilma Granqvist", + "Facsimiles": "Assorted Facsimile", + "ImageGallery": "Image Gallery", + "PersonSearch": "Persons", + "PlaceSearch": "Places", + "TagSearch": "Subjects", + "MusicianSearch": "Musicians Search", + "WriterSearch": "Writers Search", + "SongTypes": "Song Types", + "WorkSearch": "Works", + "Collections": "Publications" + }, + "GalleryPages": { + "0": "Headline 1", + "1": "Headline 2", + "2": "THeadline 3" + }, + "MobilePages": { + "header": "Pages" + }, + "BackButton": { + "default": "Go Back" + }, + "HomePage": { + "current": "Current", + "publishedEditions": "Published Editions:", + "digitalEditionHeading": "Archive of Hilma Granqvist", + "selectEdition": "View" + }, + "SearchApp": { + "simpleSearch": "Simple Search", + "advancedSearch": "Advanced Search", + "filter": "Filter", + "matchesOn": "Match(es):", + "numberOf": "match(es)", + "pagenumber": "p.", + "search-holder": "Seach text", + "hitCount": "Hit Count", + "type": "Type", + "timeline": "Timeline", + "freetext": "Freetext", + "start": "Start", + "end": "End", + "relevance": "Relevance", + "alphabetical": "Alphabetical", + "oldest": "Oldest", + "genre": "Genre", + "person-register": "Persons", + "place-register": "Places ", + "tag-register": "Subjects/Tags", + "verk": "Works", + "sort-by": "Sort by" + }, + "Filter":{ + "personTypes": "Personkategori", + "tagTypes": "Ämneskategori", + "year": "Född mellan", + "apply": "Apply" + }, + "Occurrences": { + "Title": "Occurrences", + "Manuscript": "Manuscript", + "Variation": "Variation", + "Commentary": "Commentary", + "Facsimile": "Facsimile", + "Established": "Established", + "longitude": "long.", + "latitude": "lat.", + "city": "City", + "region": "Region", + "country": "Place", + "place_of_birth": "Place of Birth", + "type": "Type", + "source": "Source", + "description": "Description", + "no_occurrences": "No occurrences", + "download": "download pdf", + "articles": "Articles", + "publisher": "Publisher", + "journal": "Journal", + "isbn": "ISBN", + "published_year": "Year of publication", + "authors": "Authors" + }, + "ElasticSearch": { + "NoHits": "No matches", + "Found": "Found", + "SearchField": "Add search field", + "From": "From", + "To": "To", + "Search": "Search", + "Years": "Year", + "ShowMore": "Show more", + "ShowLess": "Show less", + "SortBy": "Sort by", + "Relevance": "Relevans", + "OldestFirst": "Oldest first", + "NewestFirst": "Newest first" + }, + "Type": "Type", + "Genre": "Genre", + "Collection": "Collections", + "Location": "Location", + "Subjects": "Subjects", + "Tags": "Taggs", + "Person": "Person", + "LetterSenderName": "Sender", + "LetterReceiverName": "Reciever", + "LetterSenderLocation": "Sender place", + "LetterReceiverLocation": "Reciever place", + "est": "Established text", + "ms": "Manuscript", + "com": "Commentary", + "tit": "Title", + "var": "Variation", + "inl": "Introduction", + "locations": "Locations", + "location": "Location", + "tags": "Tags", + "tag": "Tags", + "subjects": "Subjects", + "subject": "Subject", + "collections": "Collections", + "works": "Works", + "BC": "f.Kr.", + "Reference": { + "title": "Refer", + "urn": "Link", + "thisPage": "Refer to this page", + "established": "Refer to this text", + "intro": "Refer to this introduction" + }, + "established": "Readtext", + "comments": "Commentary", + "facsimiles": "Fascimile", + "manuscripts": "Manuscript", + "variations": "Variations", + "introduction": "Introduction", + "songexample": "Song", + "illustrations": "Illustrationer" +} diff --git a/src/assets/i18n/fi.json b/src/assets/i18n/fi.json index 417444ed..d5c3eb15 100644 --- a/src/assets/i18n/fi.json +++ b/src/assets/i18n/fi.json @@ -1,209 +1,316 @@ { - "BrowserWarning": "Din webbläsare stöds inte", - "BrowserWarningInfo": "Internet Explorer stöds inte av denna sida. Använd Edge, Firefox eller nån annan webbläsare.", - "BrowserWarningClose": "Stäng", - "TopMenu": { - "Home": "Startsida", - "Read": "Innehåll", - "Info": "Om" - }, - "Tabs": { - "Home": "Hem", - "Read": "Läs digitalt", - "Info": "Mer information" - }, - "Read": { - "DigitalEditions": "Digitala utgåvor", - "OpenInTab": "Öppna i ny flik", - "Popover": { - "All": "Välj alla", - "Appearance": "Utseende", - "Show": "Visningsalternativ", - "Comments": "Kommentarer", - "PersonInfo": "Personupplysningar", - "PlaceInfo": "Platsupplysningar", - "Changes": "Ändringar", - "Abbreviations": "Förkortningar", - "PageNumbering": "Stycke-/versnummer", - "PageBreakOriginal": "Sidnummer / sidbrytning i orig.", - "PageBreakEdition": "Sidnummer i den tryckta utgåvan" - }, - "DownloadDigitalVersion": "Ladda ner digital version", - "DownloadPDF": "Ladda ner PDF", - "CommentsFor": "Kommentarer till", - "ReadFromBeginning": "Läs från början", - "Established": { - "Title": "Lästext", - "AddButtonText": "Lästextvy" - }, - "Comments": { - "Title": "Kommentarer", - "AddButtonText": "Kommentarvy" - }, - "Manuscripts": { - "Title": "Manuskript", - "AddButtonText": "Manuskriptvy", - "SelectManuscript": "Välj manuskript", - "ShowNormalized": "Visa ändringar inarbetade", - "ShowChanges": "Visa ändringar", - "OpenFacsimile": "Öppna faksimil" - }, - "Variations": { - "Title": "Variationer", - "AddButtonText": "Variationsvy" - }, - "Facsimiles": { - "Title": "Faksimiler", - "AddButtonText": "Faksimilvy", - "SelectFacsimiles": "Välj faksimil", - "Page": "Sida", - "Of": "av", - "OpenManuscript": "Öppna manuskript" - }, - "SongExample": { - "Title": "Sång exempel", - "AddButtonText": "Sång exempel" - }, - "TitlePage": { - "Title": "Titel" - }, - "Introduction": { - "Title": "Inledning", - "AddButtonText": "Inledning" - }, - "ShowAll": "Visa alla", - "ShowSearchResults": "Förekomster" - }, - "About": { - "Title": "Om utgåvan", - "UI": "Användargränssnitt", - "Language": "Språk", - "Information": "Information" - }, - "TOC": { - "Home": "Till startsidan", - "Read": "Läs", - "Facsimiles": "Faksimil i urval", - "ImageGallery": "Bildgalleri", - "PersonSearch": "Personsök", - "PlaceSearch": "Platsregister", - "TagSearch": "Ämnesord", - "WorkSearch": "Verkregister", - "MusicianSearch": "Spelmän", - "WriterSearch": "Upptecknare", - "SongTypes": "Låttyper", - "Music": "Musik", - "Collections": "Kategorier", - "Books": "Kirjat", - "All": "kaikki" - }, - "GalleryPages": { - "0": "Finland framställdt i teckningar", - "1": "En resa i Finland", - "2": "Topelius liv och verk" - }, - "MobilePages": { - "header": "Sidor" - }, - "BackButton": { - "default": "Tillbaka" - }, - "HomePage": { - "current": "Aktuellt", - "publishedEditions": "Publicerade utgåvor:", - "digitalEditionHeading": "Zacharias Topelius verk i digital utgåva", - "selectEdition": "Se denna" - }, - "SearchApp": { - "simpleSearch": "Sök", - "advancedSearch": "Avancerad sökning", - "filter": "Filter", - "matchesOn": "Sökträff(ar):", - "numberOf": "träffar", - "pagenumber": "s.", - "search-holder": "Söktext", - "hitCount": "Antal träffar", - "type": "Typ", - "timeline": "Tidsperiod", - "freetext": "Fritext", - "start": "Start", - "end": "Slut", - "relevance": "Relevans", - "alphabetical": "Alfabetet", - "oldest": "Äldst", - "genre": "Genre", - "person-register": "Personregister", - "place-register": "Platsregister ", - "tag-register": "Ämnesord", - "verk": "Verkregister", - "sort-by": "Sortera enligt" - }, - "Occurrences": { - "Title": "Förekomster", - "Manuscript": "Manuskript", - "Variation": "Variant", - "Commentary": "Kommentar", - "Facsimile": "Faksimil", - "Established": "Lästext", - "Song": "Sång", - "Info": "Information", - "NoInfoFound": "Ingen information hittades.", - "OccurrencesSmall": "förekomster", - "Found": "Hittade", - "Result": "Resultat", - "longitude": "long.", - "latitude": "lat.", - "city": "Stad", - "region": "Region", - "country": "Plats", - "place_of_birth": "Ort", - "type": "Typ", - "source": "Källa", - "description": "Beskrivning", - "no_occurrences": "Inga förekomster", - "download": "ladda ner pdf", - "articles": "Artiklar" - }, - "est": "Lästext", - "ms": "Manuskript", - "com": "Kommentar", - "tit": "Titel", - "var": "Variant", - "inl": "Inledning", - "locations": "Platser", - "location": "Plats", - "tags": "Ämnesord", - "tag": "Ämnesord", - "subjects": "Personer", - "recorder": "Upptecknare", - "playman": "Upptecknare", - "collections": "Samlingar", - "photos": "kuvaa", - "song": "Melodi", - "works": "Verk", - "Reference": { - "title": "Hänvisa", - "thisPage": "Hänvisa till denna sida", - "established": "Hänvisa till denna lästext", - "intro": "Hänvisa till denna inledning" - }, - "Song": { - "Playman": "Spelman", - "playman": "Spelman", - "SongType": "Låttyp", - "SongName": "Låtens namn", - "Landscape": "Landskap", - "Place": "Ort", - "Recorder": "Upptecknare", - "recorder": "Upptecknare", - "Year": "Uppteckningsår", - "OCL": "Originalsamlingens placering", - "OCS": "Originalsamlingens signum", - "Volume": "Volym", - "SongNumber": "Sångnummer", - "Comments": "Kommentarer", - "SongText": "Sångtext", - "Download": "Ladda ner" - - } -} \ No newline at end of file + "BrowserWarning": "Selainta ei tueta", + "BrowserWarningInfo": "Tämä sivusto ei tue Internet Exploreria. Käytä Edgeä, Firefoxia, Chromea tai mitä tahansa muu selain.", + "BrowserWarningClose": "Sulje", + "TutorialTexts": { + "nextLabel": "Seuraava", + "prevLabel": "Edellinen", + "skipLabel": "Ohita", + "doneLabel": "Valmis", + "WelcomeText": "Tervetuloa, tässä on käyttöliittymän esittely.", + "searchIcon": "Klicka här för att söka i utgåvan.", + "MenuToggleText": "Piilota tai näytä sivuvalikko napsauttamalla tätä.", + "ReadTocItemText": "Täällä voit nähdä kaikki digitaaliset julkaisut.", + "DownloadCacheText": "Tällä painikkeella voit tallentaa tiedot paikallisesti, niin se on nopeampaa seuraavan kerran." + }, + "TopMenu": { + "Home": "Etusivu", + "Read": "Lue", + "Info": "Topeliuksesta ja editiosta", + "Music": "Musik", + "SimpleSearch": "Hae", + "AdvancedSearch": "Hae" + }, + "Tabs": { + "Home": "Koti", + "Read": "Lue digitaalisesti", + "Info": "Lisää aiheesta" + }, + "Read": { + "DigitalEditions": "Digitaaliset julkaisut", + "OpenInTab": "Avaa uuteen välilehteen", + "Popover": { + "All": "Valitse kaikki", + "Appearance": "Asetukset", + "Show": "Näkymävaihtoehdot", + "Comments": "Kommentit", + "PersonInfo": "Henkilöt", + "PlaceInfo": "Paikannimet", + "WorkInfo": "Teokset", + "Changes": "Toimittajan tekemät muutokset", + "Abbreviations": "Lyhennykset", + "PageNumbering": "Kappale-/rivinumerointi", + "PageBreakOriginal": "Alkuteoksen sivunumerot/sivunvaihdot", + "PageBreakEdition": "Painetun ZTS-edition sivunumerot" + }, + "DownloadDigitalVersion": "Lataa digitaalinen versio", + "DownloadPDF": "Lataa PDF", + "CommentsFor": "Kommentaari", + "ReadFromBeginning": "Lue", + "Established": { + "Title": "Lukuteksti", + "AddButtonText": "Lukuteksti" + }, + "Comments": { + "Title": "Kommentaari", + "AddButtonText": "Kommentaari", + "Manuscript": { + "Title": "Manuskriptbeskrivning", + "LegacyId": "Brevsignum", + "Sender": "Avsändare", + "Receiver": "Mottagare", + "Archive": "Arkiv", + "Collection": "Samling, signum", + "Type": "Form", + "Status": "Status", + "Format": "Format", + "Leafs": "Lägg", + "Sheets": "Antal blad", + "Pages": "Sidor brevtext", + "Color": "Färg", + "Quality": "Kvalitet", + "Pattern": "Mönster", + "State": "Tillstånd", + "Material": "Skrivmaterial", + "Other": "Övrigt" + } + }, + "Manuscripts": { + "Title": "Käsikirjoitus", + "AddButtonText": "Käsikirjoitus", + "SelectManuscript": "Valitse käsikirjoitus", + "ShowNormalized": "Näytä muutokset toteutettuina", + "ShowChanges": "Näytä muutokset", + "OpenFacsimile": "Näytä faksimilet" + }, + "Variations": { + "Title": "Variantit", + "AddButtonText": "Variantit" + }, + "Facsimiles": { + "Title": "Faksimile", + "AddButtonText": "Faksimilet", + "SelectFacsimiles": "Näytä faksimilet", + "Page": "Sivu", + "Of": "", + "OpenManuscript": "Käsikirjoitus", + "ExternalHeading" : "Ulkopuoliset faksimilet" + }, + "SongExample": { + "Title": "Sång exempel", + "AddButtonText": "Sång exempel" + }, + "TitlePage": { + "Title": "Kansi/nimiölehti/esipuhe" + }, + "Introduction": { + "Title": "Johdanto", + "AddButtonText": "Johdanto" + }, + "Illustrations": { + "AddButtonText": "Kuvat", + "Goto": "Näytä esiintymä", + "Nothing": "Ei kuvia", + "Showall": "Näytä kaikki" + }, + "CoverPage": { + "Title": "Kansi" + }, + "ShowAll": "Näytä kaikki", + "ShowSearchResults": "Esiintymät" + }, + "About": { + "Title": "Topeliuksesta ja editiosta", + "UI": "Käyttöliittymä", + "Language": "Kieli", + "Information": "Tietoa" + }, + "MediaCollections": { + "MediaCollections": "Faksimilet" + }, + "TOC": { + "Home": "Etusivulle", + "About": "Topeliuksesta ja editiosta", + "Read": "Lue", + "Read1": "Lue", + "Read2": "Lue", + "ReadN": "Lue", + "Facsimiles": "Valikoidut faksimilet", + "ImageGallery": "Kuvagalleria", + "MediaCollections": "Album", + "PersonSearch": "Henkilöhakemisto", + "PlaceSearch": "Paikkahakemisto", + "TagSearch": "Ämnesord", + "WorkSearch": "Teoshakemisto", + "MusicianSearch": "Spelmän", + "WriterSearch": "Upptecknare", + "SongTypes": "Låttyper", + "Music": "Musik", + "Collections": "Kategorier", + "PlaymanTradition": "Finlandssvensk spelmanstradition", + "Books": "Böcker", + "All": "Alla" + }, + "GalleryPages": { + "0": "Finland framställdt i teckningar", + "1": "En resa i Finland", + "2": "Topeliuksen elämä ja teokset" + }, + "MobilePages": { + "header": "Sivut" + }, + "BackButton": { + "default": "Takaisin" + }, + "HomePage": { + "current": "Ajankohtaista", + "publishedEditions": "Julkaistut editiot:", + "digitalEditionHeading": "Zacharias Topeliuksen teokset", + "selectEdition": "Lue" + }, + "Filter":{ + "personTypes": "Personkategori", + "tagTypes": "Ämneskategori", + "year": "Född mellan", + "apply": "Verkställ" + }, + "SearchApp": { + "simpleSearch": "Hae", + "advancedSearch": "Tarkennettu haku", + "filter": "Rajaukset", + "matchesOn": "Osuma(a):", + "numberOf": "osumaa", + "pagenumber": "s.", + "search-holder": "Hakuteksti", + "hitCount": "Osumaa", + "type": "Tyyppi", + "timeline": "Aikajakso", + "freetext": "Vapaateksti", + "start": "Alku", + "end": "Loppu", + "relevance": "Relevanssi", + "alphabetical": "Aakkostus", + "oldest": "Vanhin", + "genre": "Tekstilaji", + "person-register": "Henkilöhakemisto", + "place-register": "Paikkahakemisto", + "tag-register": "Ämnesord", + "verk": "Teoshakemisto", + "sort-by": "Lajittele" + }, + "Occurrences": { + "Title": "Esiintymät", + "Manuscript": "Käsikirjoitus", + "Variation": "Variantti", + "Commentary": "Kommentti", + "Facsimile": "Faksimile", + "Established": "Lukuteksti", + "Song": "Sång", + "Info": "Infoa", + "NoInfoFound": "Ei löydetty.", + "OccurrencesSmall": "esiitymää", + "Found": "Löydettiin", + "Result": "Esiintymää", + "longitude": "long.", + "latitude": "lat.", + "city": "Stad", + "region": "Region", + "country": "Plats", + "place_of_birth": "Ort", + "type": "Typ", + "source": "Källa", + "description": "Beskrivning", + "no_occurrences": "Ei esiintymiä", + "download": "lataa pdf", + "articles": "Artiklar", + "publisher": "Kustantaja", + "journal": "Journal", + "isbn": "ISBN", + "published_year": "Julkaisuvuosi", + "authors": "Kirjoittaja" + }, + "ElasticSearch": { + "NoHits": "Haku ei vastannut yhtään asiakirjaa", + "Found": "Löydettiin", + "SearchField": "Lisää hakukenttä", + "From": "Vuodesta", + "To": "Vuoteen", + "Search": "Hae", + "Years": "Vuodet", + "ShowMore": "Näytä enemmän", + "ShowLess": "Näytä vähemmän", + "SortBy": "Lajittele", + "Relevance": "Relevanssi", + "OldestFirst": "Vanhimmat ensin", + "NewestFirst": "Uusimmat ensin" + }, + "Type": "Tekstityyppi", + "Genre": "Tekstilaji", + "Collection": "Editio", + "Location": "Paikka", + "Subjects": "Aihe", + "Tags": "Asiansanat", + "Person": "Henkilö", + "LetterSenderName": "Lähettäjä", + "LetterReceiverName": "Vastaanottaja", + "LetterSenderLocation": "Lähettäjän paikkakunta", + "LetterReceiverLocation": "Vastaanottajan paikkakunta", + "est": "Lukuteksti", + "ms": "Käsikirjoitus", + "com": "Kommentti", + "tit": "Nimiölehti/esipuhe", + "var": "Variantti", + "inl": "Johdanto", + "locations": "Paikat", + "location": "Paikka", + "tags": "Aihesanat", + "tag": "Aihesana", + "subjects": "Henkilöt", + "subject": "Henkilö", + "collections": "Samlingar", + "works": "Verk", + "photos": "foton", + "recorder": "Upptecknare", + "playman": "Spelman", + "song": "Melodi", + "Text": "PDF", + "BC": "f.Kr.", + "Reference": { + "title": "Lähdeviittaus", + "urn": "Linkitä", + "thisPage": "Lähdevittaus", + "established": "Lähdevittaus tähän lukutekstiin", + "intro": "Lähdevittaus tähän johdantoon" + }, + "Song": { + "Playman": "Spelman", + "playman": "Spelman", + "SongType": "Låttyp", + "SongName": "Låtens namn", + "Landscape": "Landskap", + "Place": "Ort", + "Recorder": "Upptecknare", + "recorder": "Upptecknare", + "Year": "Uppteckningsår", + "OCL": "Originalsamlingens placering", + "OCS": "Originalsamlingens signum", + "Volume": "Volym", + "SongNumber": "Sångnummer", + "Comments": "Kommentarer", + "SongText": "Sångtext", + "Download": "Ladda ner" + }, + "MusicPage": { + "Title": "Musik", + "PageTitle": "Musikvolymerna", + "SubTitle": "FSFD" + }, + "established": "Lukuteksti", + "comments": "Kommentaari", + "facsimiles": "Faksimilet", + "manuscripts": "Käsikirjoitukset", + "variations": "Variantit", + "introduction": "Johdanto", + "songexample": "Sång", + "illustrations": "Kuvat" +} diff --git a/src/assets/i18n/sv.json b/src/assets/i18n/sv.json index ef387a94..240cc26a 100644 --- a/src/assets/i18n/sv.json +++ b/src/assets/i18n/sv.json @@ -1,232 +1,319 @@ { - "BrowserWarning": "Din webbläsare stöds inte", - "BrowserWarningInfo": "Internet Explorer stöds inte av denna sida. Använd Edge, Firefox eller nån annan webbläsare.", - "BrowserWarningClose": "Stäng", - "TutorialTexts": { - "nextLabel": "Nästa", - "prevLabel": "Föregående", - "skipLabel": "Hoppa över", - "doneLabel": "Klar", - "WelcomeText": "Välkommen, här följer en presentation av anvndargränssnittet.", - "MenuToggleText": "Klicka här för att dölja eller visa sidomenyn.", - "ReadTocItemText": "Här kan du se alla digital publikationer och volymer.", - "DownloadCacheText": "Med denna knapp kan du spara datan lokalt, så går det snabbare nästa gång." - }, - "TopMenu": { - "Home": "Startsida", - "Read": "Innehåll", - "Info": "Om", - "Music": "Musik" - }, - "Tabs": { - "Home": "Hem", - "Read": "Läs digitalt", - "Info": "Mer information" - }, - "Read": { - "DigitalEditions": "Digitala utgåvor", - "OpenInTab": "Öppna i ny flik", - "Popover": { - "All": "Välj alla", - "Appearance": "Utseende", - "Show": "Visningsalternativ", - "Comments": "Kommentarer", - "PersonInfo": "Personupplysningar", - "PlaceInfo": "Platsupplysningar", - "Changes": "Ändringar", - "Abbreviations": "Förkortningar", - "PageNumbering": "Stycke-/versnummer", - "PageBreakOriginal": "Sidnummer / sidbrytning i orig.", - "PageBreakEdition": "Sidnummer i den tryckta utgåvan" - }, - "DownloadDigitalVersion": "Ladda ner digital version", - "DownloadPDF": "Ladda ner PDF", - "CommentsFor": "Kommentarer till", - "ReadFromBeginning": "Läs från början", - "Established": { - "Title": "Lästext", - "AddButtonText": "Lästextvy" - }, - "Comments": { - "Title": "Kommentarer", - "AddButtonText": "Kommentarvy" - }, - "Manuscripts": { - "Title": "Manuskript", - "AddButtonText": "Manuskriptvy", - "SelectManuscript": "Välj manuskript", - "ShowNormalized": "Visa ändringar inarbetade", - "ShowChanges": "Visa ändringar", - "OpenFacsimile": "Öppna faksimil" - }, - "Variations": { - "Title": "Variationer", - "AddButtonText": "Variationsvy" - }, - "Facsimiles": { - "Title": "Faksimil", - "AddButtonText": "Faksimilvy", - "SelectFacsimiles": "Välj faksimil", - "Page": "Sida", - "Of": "av", - "OpenManuscript": "Öppna manuskript" - }, - "SongExample": { - "Title": "Sång exempel", - "AddButtonText": "Sång exempel" - }, - "TitlePage": { - "Title": "Titel" - }, - "Introduction": { - "Title": "Inledning", - "AddButtonText": "Inledning" - }, - "ShowAll": "Visa alla", - "ShowSearchResults": "Förekomster" - }, - "About": { - "Title": "Om utgåvan", - "UI": "Användargränssnitt", - "Language": "Språk", - "Information": "Information" - }, - "MediaCollections": { - "MediaCollections": "Faksimilsamlinar" - }, - "TOC": { - "Home": "Till startsidan", - "About": "Om utgåvan", - "Read": "Läs", - "Facsimiles": "Faksimil i urval", - "ImageGallery": "Bildgalleri", - "MediaCollections": "Album", - "PersonSearch": "Personsök", - "PlaceSearch": "Platsregister", - "TagSearch": "Ämnesord", - "WorkSearch": "Verkregister", - "MusicianSearch": "Spelmän", - "WriterSearch": "Upptecknare", - "SongTypes": "Låttyper", - "Music": "Musik", - "Collections": "Kategorier", - "PlaymanTradition": "Finlandssvensk spelmanstradition", - "Books": "Böcker", - "All": "Alla" - }, - "GalleryPages": { - "0": "Finland framställdt i teckningar", - "1": "En resa i Finland", - "2": "Topelius liv och verk" - }, - "MobilePages": { - "header": "Sidor" - }, - "BackButton": { - "default": "Tillbaka" - }, - "HomePage": { - "current": "Aktuellt", - "publishedEditions": "Publicerade utgåvor:", - "digitalEditionHeading": "Zacharias Topelius verk i digital utgåva", - "selectEdition": "Se denna" - }, - "SearchApp": { - "simpleSearch": "Sök", - "advancedSearch": "Avancerad sökning", - "filter": "Filter", - "matchesOn": "Sökträff(ar):", - "numberOf": "träffar", - "pagenumber": "s.", - "search-holder": "Söktext", - "hitCount": "Antal träffar", - "type": "Typ", - "timeline": "Tidsperiod", - "freetext": "Fritext", - "start": "Start", - "end": "Slut", - "relevance": "Relevans", - "alphabetical": "Alfabetet", - "oldest": "Äldst", - "genre": "Genre", - "person-register": "Personregister", - "place-register": "Platsregister ", - "tag-register": "Ämnesord", - "verk": "Verkregister", - "sort-by": "Sortera enligt" - }, - "Occurrences": { - "Title": "Förekomster", - "Manuscript": "Manuskript", - "Variation": "Variant", - "Commentary": "Kommentar", - "Facsimile": "Faksimil", - "Established": "Lästext", - "Song": "Sång", - "Info": "Information", - "NoInfoFound": "Ingen information hittades.", - "OccurrencesSmall": "förekomster", - "Found": "Hittade", - "Result": "Resultat", - "longitude": "long.", - "latitude": "lat.", - "city": "Stad", - "region": "Region", - "country": "Plats", - "place_of_birth": "Ort", - "type": "Typ", - "source": "Källa", - "description": "Beskrivning", - "no_occurrences": "Inga förekomster", - "download": "ladda ner pdf", - "articles": "Artiklar" - }, - "est": "Lästext", - "ms": "Manuskript", - "com": "Kommentar", - "tit": "Titel", - "var": "Variant", - "inl": "Inledning", - "locations": "Platser", - "location": "Plats", - "tags": "Ämnesord", - "tag": "Ämnesord", - "subjects": "Personer", - "subject": "Person", - "collections": "Samlingar", - "works": "Verk", - "photos": "foton", - "recorder": "Upptecknare", - "playman": "Spelman", - "song": "Melodi", - "Text": "PDF", - "Reference": { - "title": "Hänvisa", - "thisPage": "Hänvisa till denna sida", - "established": "Hänvisa till denna lästext", - "intro": "Hänvisa till denna inledning" - }, - "Song": { - "Playman": "Spelman", - "playman": "Spelman", - "SongType": "Låttyp", - "SongName": "Låtens namn", - "Landscape": "Landskap", - "Place": "Ort", - "Recorder": "Upptecknare", - "recorder": "Upptecknare", - "Year": "Uppteckningsår", - "OCL": "Originalsamlingens placering", - "OCS": "Originalsamlingens signum", - "Volume": "Volym", - "SongNumber": "Sångnummer", - "Comments": "Kommentarer", - "SongText": "Sångtext", - "Download": "Ladda ner" - }, - "MusicPage": { - "Title": "Musik", - "PageTitle": "Musikvolymerna", - "SubTitle": "FSFD" - } -} \ No newline at end of file + "BrowserWarning": "Din webbläsare stöds inte", + "BrowserWarningInfo": "Internet Explorer stöds inte av denna sida. Använd Edge, Firefox, Chrome eller nån annan webbläsare.", + "BrowserWarningClose": "Stäng", + "TutorialTexts": { + "nextLabel": "Nästa", + "prevLabel": "Föregående", + "skipLabel": "Hoppa över", + "doneLabel": "Klar", + "searchIcon": "Klicka här för att söka i utgåvan.", + "WelcomeText": "Välkommen, här följer en presentation av användargränssnittet.", + "MenuToggleText": "Klicka här för att dölja eller visa sidomenyn.", + "ReadTocItemText": "Här kan du se alla digital publikationer och volymer.", + "DownloadCacheText": "Med denna knapp kan du spara datan lokalt, så går det snabbare nästa gång." + }, + "TopMenu": { + "Home": "Startsida", + "Read": "Läs", + "Info": "Om", + "Music": "Musik", + "SimpleSearch": "Sök", + "AdvancedSearch": "Sök" + }, + "Tabs": { + "Home": "Hem", + "Read": "Läs digitalt", + "Info": "Mer information" + }, + "Read": { + "DigitalEditions": "Digitala utgåvor", + "OpenInTab": "Öppna i ny flik", + "Popover": { + "All": "Välj alla", + "Appearance": "Utseende", + "Show": "Visningsalternativ", + "Comments": "Kommentarer", + "PersonInfo": "Personupplysningar", + "PlaceInfo": "Platsupplysningar", + "WorkInfo": "Verkupplysningar", + "Changes": "Utgivarens ändringar", + "Abbreviations": "Förkortningar", + "PageNumbering": "Stycke-/radnumrering", + "PageBreakOriginal": "Sidnummer/sidbrytning i originalet", + "PageBreakEdition": "Sidnummer i ZTS tryckta utgåva" + }, + "DownloadDigitalVersion": "Ladda ner digital version", + "DownloadPDF": "Ladda ner PDF", + "CommentsFor": "Kommentarer till", + "ReadFromBeginning": "Läs från början", + "Established": { + "Title": "Lästext", + "AddButtonText": "Lästext" + }, + "Comments": { + "Title": "Kommentarer", + "AddButtonText": "Kommentarer", + "Manuscript": { + "Title": "Manuskriptbeskrivning", + "LegacyId": "Brevsignum", + "Sender": "Avsändare", + "Receiver": "Mottagare", + "Archive": "Arkiv", + "Collection": "Samling, signum", + "Type": "Form", + "Status": "Status", + "Format": "Format", + "Leafs": "Lägg", + "Sheets": "Antal blad", + "Pages": "Sidor brevtext", + "Color": "Färg", + "Quality": "Kvalitet", + "Pattern": "Mönster", + "State": "Tillstånd", + "Material": "Skrivmaterial", + "Other": "Övrigt" + } + }, + "Manuscripts": { + "Title": "Manuskript", + "AddButtonText": "Manuskript", + "SelectManuscript": "Välj manuskript", + "ShowNormalized": "Visa ändringar inarbetade", + "ShowChanges": "Visa ändringar", + "OpenFacsimile": "Öppna faksimil" + }, + "Variations": { + "Title": "Varianter", + "AddButtonText": "Varianter" + }, + "Facsimiles": { + "Title": "Faksimil", + "AddButtonText": "Faksimil", + "SelectFacsimiles": "Välj faksimil", + "Page": "Sida", + "Of": "av", + "OpenManuscript": "Öppna manuskript", + "ExternalHeading" : "Externa faksimil" + }, + "SongExample": { + "Title": "Sång exempel", + "AddButtonText": "Sång exempel" + }, + "TitlePage": { + "Title": "Omslag/titelblad/förord" + }, + "CoverPage": { + "Title": "Omslag" + }, + "Introduction": { + "Title": "Inledning", + "AddButtonText": "Inledning" + }, + "Illustrations": { + "AddButtonText": "Illustrationer", + "Goto": "Gå till position i lästext", + "Nothing": "Inga illustrationer", + "Showall": "Visa alla" + }, + "ShowAll": "Visa alla", + "ShowSearchResults": "Förekomster" + }, + "About": { + "Title": "Om", + "UI": "Användargränssnitt", + "Language": "Språk", + "Information": "Information" + }, + "MediaCollections": { + "MediaCollections": "Faksimilsamlingar", + "GotoMediaCollection": "Gå till bildbanken" + }, + "TOC": { + "Home": "Till startsidan", + "About": "Om", + "Read": "Läs", + "Read1": "Läs", + "Read2": "Läs", + "ReadN": "Läs", + "Facsimiles": "Faksimil i urval", + "ImageGallery": "Bildgalleri", + "MediaCollections": "Album", + "PersonSearch": "Personregister", + "PlaceSearch": "Ortregister", + "TagSearch": "Ämnesord", + "WorkSearch": "Verkregister", + "MusicianSearch": "Spelmän", + "WriterSearch": "Upptecknare", + "SongTypes": "Låttyper", + "Music": "Musik", + "Collections": "Kategorier", + "PlaymanTradition": "Finlandssvensk spelmanstradition", + "Books": "Böcker", + "All": "Alla" + }, + "GalleryPages": { + "0": "Finland framställdt i teckningar", + "1": "En resa i Finland", + "2": "Topelius liv och verk" + }, + "MobilePages": { + "header": "Sidor" + }, + "BackButton": { + "default": "Tillbaka" + }, + "HomePage": { + "current": "Aktuellt", + "publishedEditions": "Publicerade utgåvor:", + "digitalEditionHeading": "Zacharias Topelius verk i digital utgåva", + "selectEdition": "Se denna" + }, + "SearchApp": { + "simpleSearch": "Sök", + "advancedSearch": "Avancerad sökning", + "filter": "Filter", + "matchesOn": "Sökträff(ar):", + "numberOf": "träffar", + "pagenumber": "s.", + "search-holder": "Söktext", + "hitCount": "Antal träffar", + "type": "Typ", + "timeline": "Tidsperiod", + "freetext": "Fritext", + "start": "Start", + "end": "Slut", + "relevance": "Relevans", + "alphabetical": "Alfabetet", + "oldest": "Äldst", + "genre": "Genre", + "person-register": "Personregister", + "place-register": "Platsregister ", + "tag-register": "Ämnesord", + "verk": "Verkregister", + "sort-by": "Sortera enligt" + }, + "Occurrences": { + "Title": "Förekomster", + "Manuscript": "Manuskript", + "Variation": "Variant", + "Commentary": "Kommentar", + "Facsimile": "Faksimil", + "Established": "Lästext", + "Song": "Sång", + "Info": "Information", + "NoInfoFound": "Ingen information hittades.", + "OccurrencesSmall": "förekomster", + "Found": "Hittade", + "Result": "Resultat", + "longitude": "long.", + "latitude": "lat.", + "city": "Stad", + "region": "Region", + "country": "Plats", + "place_of_birth": "Ort", + "type": "Typ", + "source": "Källa", + "description": "Beskrivning", + "no_occurrences": "Inga förekomster", + "download": "ladda ner pdf", + "articles": "Artiklar", + "publisher": "Utgivare", + "journal": "Journal", + "isbn": "ISBN", + "published_year": "Utgivningsår", + "authors": "Författare" + }, + "ElasticSearch": { + "NoHits": "Din sökning matchade inga dokument", + "Found": "Hittades", + "SearchField": "Lägg till sökfält", + "From": "Från", + "To": "Till", + "Search": "Sök", + "Years": "År", + "ShowMore": "Visa mera", + "ShowLess": "Visa mindre", + "SortBy": "Sortera enligt", + "Relevance": "Relevans", + "OldestFirst": "Äldst först", + "NewestFirst": "Nyast först", + "WordCount": "Antal träffar (estimat)", + "NoWordHits": "inga träffar" + }, + "Filter":{ + "personTypes": "Personkategori", + "tagTypes": "Ämneskategori", + "year": "Född mellan", + "apply": "Verkställ" + }, + "Type": "Typ", + "Genre": "Genre", + "Collection": "Utgåvor", + "Location": "Plats", + "Subjects": "Ämnen", + "Tags": "Taggar", + "Person": "Person", + "LetterSenderName": "Avsändare", + "LetterReceiverName": "Mottagare", + "LetterSenderLocation": "Avsändarort", + "LetterReceiverLocation": "Mottagarort", + "est": "Lästext", + "ms": "Manuskript", + "com": "Kommentar", + "tit": "Titel", + "var": "Variant", + "inl": "Inledning", + "locations": "Platser", + "location": "Plats", + "tags": "Ämnesord", + "tag": "Ämnesord", + "subjects": "Personer", + "subject": "Person", + "collections": "Samlingar", + "works": "Verk", + "photos": "foton", + "recorder": "Upptecknare", + "playman": "Spelman", + "song": "Melodi", + "Text": "PDF", + "BC": "f.Kr.", + "Reference": { + "title": "Hänvisa", + "urn": "Länka", + "thisPage": "Hänvisa till denna sida", + "established": "Hänvisa till denna lästext", + "intro": "Hänvisa till denna inledning" + }, + "Song": { + "Playman": "Spelman", + "playman": "Spelman", + "SongType": "Låttyp", + "SongName": "Låtens namn", + "Landscape": "Landskap", + "Place": "Ort", + "Recorder": "Upptecknare", + "recorder": "Upptecknare", + "Year": "Uppteckningsår", + "OCL": "Originalsamlingens placering", + "OCS": "Originalsamlingens signum", + "Volume": "Volym", + "SongNumber": "Sångnummer", + "Comments": "Kommentarer", + "SongText": "Sångtext", + "Download": "Ladda ner" + }, + "MusicPage": { + "Title": "Musik", + "PageTitle": "Musikvolymerna", + "SubTitle": "FSFD" + }, + "established": "Lästext", + "comments": "Kommentarer", + "facsimiles": "Faksimil", + "manuscripts": "Manuskript", + "variations": "Varianter", + "introduction": "Inledning", + "songexample": "Sång", + "illustrations": "Illustrationer" +} diff --git a/src/assets/images/img_placeholder.png b/src/assets/images/img_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..edd15b90d9919b6fd52e9b810238b050517ee255 GIT binary patch literal 2443 zcmbVO3s4hh9^Vvd=~Gd9RrGq>m9cV|>~0c5c1z@uq!1txA}9|BTs9k6^4O3pBxpN6 zdiA2#)_S!bTt!QjGPU5fV~)EDC{?L*g0w0yzL45Md|;zv6-!U-w>&c1p*^RW+3dI9 z_xpbT-~aLbch{#IlAe7k{3!^6p4BC5jNl#yuF#Mv;Qzeltrl>5(vh6wf}p2o2$u+| z`d}smJwC{0X7kzlG?M0Q63WCCG7^u?0niW>8|QIQw1wf}LZ*nds}TSG0|?BTRLIh3 zJ*Ic4nPN71g_Bvl!jMU?u+W4FiCYNAdPqQEV|WVo*sOLJ=}{r0yd)S4(w&f zqb&zA=L+69NVSupc+Q#0an^}MrB6tP)oLLz1U!E!Yd3LjSL6eG7!AcUDnwWpW4IV2 zGI5;5a8im#VNw#q#-Mu6#F{G}hT@n2mB~mc_7D_Qjfvu^{|h$Jq?vQtDBv?|qly^R zVJ|}9KqN^uXXTv0Fi9$tg^Fh^ z=4dL$gkiK;CYLM31SU6&2}KMe#wZ$RFuAgjR?rjn7dcsQ3MuRF<`K@OP(EZb>jH_b z7(YFWnbPr*m4!zuhNNiWB&ZNtC;`TVj8C(ZMBvdp7^8I;Gl29r>S7G$;>^68axw`; zAX<-7dg!m|cTwfPt^U#SH<|iLO7(ZD9}g~FOxcSV&|FbO@COyz&{%5FUuJD|?SYmx z0SA3h7zP?JcnGv`#tx{Rph357D0vxzg0wnKLZ)(4*?WsD5m{kJI-C7jLwrWo_K|JHZZSu$X znXdC$dwW;*Xg{f{7NMtVM(*_zL+9Of`Fn@f9s2f(yu7?dQHacfoM>M*BlMN+hL_o@ z{HWb>NE8pj$cqe<^RM>L2lvKy?l@5_AFexjB{n_fP4NDjhddsS%VyEF$N%#@)Bt5J zDa%Vl7a0sKU%%1X_eVPI*4?{z7s{UKdPkc9Z>XvAp4w{MP`NvEUi+u}y33$MP3?L6 zfiC}$e|vRxOT$)UY4yPUUF>l1!OxAM+R)mQktM!xO|Ukj?|#W=*|8gIE~}tj>+1{} z(dwPUJI`Lfc;g0E*FC`6oojU z%YA*&8?Et;x%FJrkh|Wu{L<34?`|H#p82YO-9h57-#WZEJE*}!E2>jJ zKAEG<_#t=BR~P()gXbE%zg#&rbdB-qosp4|kCbl@)%)O@m2H2I0q?yJ-%HF0>n4h{ z!Qt`Kf?MmC?AcveF(3KUhNEA^A&*(a;hWlKCnY6K-FB=W?J(a;g=FRtt33RkvNG*b z*Gv{V+&!qbP7k~7PXMfMN=7O*z7c?3sd2`eJRBv5MO-l2$ zQ19jL*B0Kte)a0r_4)NnI&a0^udFQSxb@nrF&&?Z`a0V8tnM$&k~FNzd0+pBjYqDm X8W7c~hRt4;@VTVZ8Z-@w%h&!4i&T>p literal 0 HcmV?d00001 diff --git a/src/assets/images/symbol_illustration.gif b/src/assets/images/symbol_illustration.gif new file mode 100644 index 0000000000000000000000000000000000000000..8d44dc77ea618c36672be06c4d5ca8150c4f1d5d GIT binary patch literal 567 zcmZ?wbhEHbKaIlq*-R zT)uqy(xpomFJ8O=H2wVfvuDqq0o!pBsPXXOy?giW*s){NrcLYDuU`$cY}Kk2D}cI} zFJ8QO?%cU^=FFKgWy<8qlY4r4+S=M$TU%RNT54-+YieprOG^t13bL}YQd3h?Qc{wW zlM@pYHN<&x^qnMqxo~06FQy8O) za}bLHCzqUfkBA!&n}e#li=eMXlVq@fv7(Zakp#DUlb4sgik^nKk(p~yQ;>_2o)ZTj zufFx}uplW@eXr0EC1cIdCT3S-J$r8*VP%`$jIw@eyrPOiCPp59O;-M176y8TB2G-K k&9?sRx{lJGp)z>}Tni2{Okoe4!Ljh*iiOT@jSLLd0BL2%#Q*>R literal 0 HcmV?d00001 diff --git a/src/components/comments/comments.html b/src/components/comments/comments.html index fcfdbcfb..1b921697 100644 --- a/src/components/comments/comments.html +++ b/src/components/comments/comments.html @@ -1,7 +1,32 @@
+
+

{{ "Read.Comments.Manuscript.Title" | translate }}

+
    +
  • {{ "Read.Comments.Manuscript.LegacyId" | translate }}: {{letter['legacy_id']}}
  • +
  • {{ "Read.Comments.Manuscript.Sender" | translate }}: {{sender}}
  • +
  • {{ "Read.Comments.Manuscript.Receiver" | translate }}: {{receiver}}
  • +
  • {{ "Read.Comments.Manuscript.Archive" | translate }}: {{letter['source_archive']}}
  • +
  • {{ "Read.Comments.Manuscript.Collection" | translate }}: {{letter['source_collection_id']}}
  • +
+
    +
  • {{ "Read.Comments.Manuscript.Type" | translate }}: {{letter['material_type']}}
  • +
  • {{ "Read.Comments.Manuscript.Status" | translate }}: {{letter['material_source']}}
  • +
  • {{ "Read.Comments.Manuscript.Format" | translate }}: {{letter['material_format']}}
  • +
  • {{ "Read.Comments.Manuscript.Leafs" | translate }}: {{letter['leaf_count']}}
  • +
  • {{ "Read.Comments.Manuscript.Sheets" | translate }}: {{letter['sheet_count']}}
  • +
  • {{ "Read.Comments.Manuscript.Pages" | translate }}: {{letter['page_count']}}
  • +
  • {{ "Read.Comments.Manuscript.Color" | translate }}: {{letter['material_color']}}
  • +
  • {{ "Read.Comments.Manuscript.Quality" | translate }}: {{letter['material_quality']}}
  • +
  • {{ "Read.Comments.Manuscript.Pattern" | translate }}: {{letter['material_pattern']}}
  • +
  • {{ "Read.Comments.Manuscript.State" | translate }}: {{letter['material_state']}}
  • +
  • {{ "Read.Comments.Manuscript.Material" | translate }}: {{letter['material']}}
  • +
  • {{ "Read.Comments.Manuscript.Other" | translate }}: {{letter['material_notes']}}
  • +
+
diff --git a/src/components/comments/comments.scss b/src/components/comments/comments.scss index 6df9521a..1eb0affa 100644 --- a/src/components/comments/comments.scss +++ b/src/components/comments/comments.scss @@ -18,4 +18,21 @@ comments { margin-bottom: 0rem !important; display: block !important; } + div.ms{ + padding-top: 2rem; + } + .ms li{ + list-style: none; + line-height: 1.5rem; + } + .ms ul{ + padding-left: 0px; + } } + +@media (max-width: 800px) { + .tei img.comment { + width: 20px; + height: 20px; + } +} \ No newline at end of file diff --git a/src/components/comments/comments.ts b/src/components/comments/comments.ts index c56f1086..ae86db88 100644 --- a/src/components/comments/comments.ts +++ b/src/components/comments/comments.ts @@ -24,6 +24,10 @@ export class CommentsComponent { public text: any; protected errorMessage: string; listenFunc: Function; + manuscript: any; + sender: any; + receiver: any; + letter: any; constructor( protected readPopoverService: ReadPopoverService, @@ -49,6 +53,7 @@ export class CommentsComponent { } else { this.setText(); } + this.getCorrespondanceMetadata(); } setText() { @@ -63,7 +68,7 @@ export class CommentsComponent { this.doAnalytics(); }, error => { - console.log('Error loading comments...', this.link); + console.error('Error loading comments...', this.link); this.errorMessage = error } ); @@ -82,7 +87,13 @@ export class CommentsComponent { } openNewView( event, id: any, type: string ) { - this.events.publish('show:view', type, id); + let openId = id; + let chapter = null; + if (String(id).includes('ch')) { + openId = String(String(id).split('ch')[0]).trim(); + chapter = 'ch' + String(String(id).split('ch')[1]).trim(); + } + this.events.publish('show:view', type, openId, chapter); } openNewIntro( event, id: any ) { @@ -97,34 +108,54 @@ export class CommentsComponent { event.preventDefault(); // This is tagging in href to another page e.g. introduction try { - const elem: HTMLElement = event.target as HTMLElement; - const targetId = String(elem.getAttribute('href')).split('#')[1]; - let target = document.getElementsByName(targetId)[0] as HTMLElement; - if ( target !== null && target !== undefined ) { - this.scrollToHTMLElement(target, true); - } else if ( targetId !== null && targetId !== undefined ) { + const elem: HTMLAnchorElement = event.target as HTMLAnchorElement; + let targetId = ''; + let colPub = ''; + if ( String(elem.getAttribute('href')).includes('#') === false ) { + targetId = String(elem.getAttribute('href')); + colPub = String(elem.getAttribute('href')); + } else { + targetId = String(elem.getAttribute('href')).split('#')[1]; + colPub = String(elem.getAttribute('href')).split('#')[0]; + } + let target = document.getElementsByName(targetId)[0] as HTMLAnchorElement; + if ( targetId !== null && targetId !== undefined && (elem.classList.contains('xref') !== false || + elem.classList.contains('xreference') !== false) ) { // Check if intro or same publication id // Check class ref_introduction, readingtext // Also check if already open and if same publication? if ( elem.classList !== undefined ) { const list = elem.classList; - if ( list.contains('introduction') ) { - this.openNewIntro(event, {id: String(elem.getAttribute('href')).split('#')[0].trim()}); - } else if ( list.contains('readingtext') ) { - this.openNewView(event, String(elem.getAttribute('href')).split('#')[0].trim(), 'established'); - } else if ( list.contains('comment') ) { - this.openNewView(event, String(elem.getAttribute('href')).split('#')[0].trim(), 'comments'); + if ( list.contains('introduction') || list.contains('ref_introduction') ) { + this.openNewIntro(event, {id: colPub}); + } else if ( list.contains('readingtext') || list.contains('ref_readingtext') ) { + this.openNewView(event, colPub, 'established'); + } else if ( list.contains('comment') || list.contains('ref_comment') ) { + if ( target !== null && target !== undefined ) { + // this.scrollToHTMLElement(target, true); + } else { + this.openNewView(event, colPub, 'comments'); + } } } // Some other text, open in new window setTimeout(function() { - target = document.getElementsByName(targetId)[0] as HTMLElement; + target = document.getElementsByName(targetId)[0] as HTMLAnchorElement; + // Add arrow at correct place, not first occurrence of anchor + if ( !elem.classList.contains('comment') && !elem.classList.contains('ref_comment') && + (target === undefined || target.classList.contains('teiComment')) ) { + target = document.getElementsByName(targetId)[document.getElementsByName(targetId).length - 1] as HTMLAnchorElement; + } if ( target !== null && target !== undefined ) { this.scrollToHTMLElement(target, false); } }.bind(this), 500); + + } else if ( target !== null && target !== undefined && elem.classList.contains('ext') === false ) { + this.scrollToHTMLElement(target, true); } else if ( elem.classList !== undefined && elem.classList.contains('ext') ) { - const ref = window.open(elem.getAttribute('href'), '_blank', 'location=no'); + const anchor = elem; + const ref = window.open(anchor.href, '_blank', 'location=no'); } } catch ( e ) {} @@ -269,7 +300,29 @@ export class CommentsComponent { }, timeOut); } } catch ( e ) { - console.log(e); + console.error(e); } } + + getCorrespondanceMetadata() { + this.commentService.getCorrespondanceMetadata(String(this.link).split('_')[1]).subscribe( + text => { + if (text.length > 0) { + text['subjects'].forEach(subject => { + if ( subject['avsändare'] ) { + this.sender = subject['avsändare']; + } + if ( subject['mottagare'] ) { + this.receiver = subject['mottagare']; + } + }); + } + this.letter = text['letter']; + this.doAnalytics(); + }, + error => { + this.errorMessage = error + } + ); + } } diff --git a/src/components/components.module.ts b/src/components/components.module.ts index bbbf5785..12268861 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -21,6 +21,9 @@ import { DigitalEditionListChildrenComponent } from './digital-edition-list-chil import { TableOfContentsAccordionComponent } from './table-of-contents-accordion/table-of-contents-accordion'; import { ListOfSongsComponent } from './list-of-songs/list-of-songs'; import { NgxExtendedPdfViewerComponent } from './ngx-extended-pdf-viewer/ngx-extended-pdf-viewer.component'; +import { IllustrationsComponent } from './illustrations/illustrations'; +import { TableOfContentLetterFilterComponent } from './table-of-content-letter-filter/table-of-content-letter-filter'; +import { DateHistogram } from './date-histogram/date-histogram' export function createTranslateLoader(http: HttpClient) { @@ -43,7 +46,10 @@ export function createTranslateLoader(http: HttpClient) { DigitalEditionListChildrenComponent, TableOfContentsAccordionComponent, ListOfSongsComponent, - NgxExtendedPdfViewerComponent + NgxExtendedPdfViewerComponent, + IllustrationsComponent, + TableOfContentLetterFilterComponent, + DateHistogram, ], imports: [ IonicModule, @@ -72,7 +78,12 @@ export function createTranslateLoader(http: HttpClient) { DigitalEditionListChildrenComponent, TableOfContentsAccordionComponent, ListOfSongsComponent, - NgxExtendedPdfViewerComponent + NgxExtendedPdfViewerComponent, + TableOfContentLetterFilterComponent, + DateHistogram, + IllustrationsComponent, + TableOfContentLetterFilterComponent, + DateHistogram, ], providers: [ UserSettingsService diff --git a/src/components/cover/cover.html b/src/components/cover/cover.html index 258a4462..d9b31573 100644 --- a/src/components/cover/cover.html +++ b/src/components/cover/cover.html @@ -1,6 +1,7 @@
{ this.errorMessage = error; - console.log(this.errorMessage); + console.error(this.errorMessage); } ); } diff --git a/src/components/date-histogram/date-histogram.html b/src/components/date-histogram/date-histogram.html new file mode 100644 index 00000000..38356cd3 --- /dev/null +++ b/src/components/date-histogram/date-histogram.html @@ -0,0 +1,35 @@ + + + + + + {{'ElasticSearch.From' | translate}} + + + + + + {{'ElasticSearch.To' | translate}} + + + + +
+ + +
+
+
+
+ +
+
+
+
+
{{year.doc_count}} : {{year.key_as_string}}
+
+
+
{{year.key_as_string}}
+
+
+
diff --git a/src/components/date-histogram/date-histogram.scss b/src/components/date-histogram/date-histogram.scss new file mode 100644 index 00000000..2f5489e3 --- /dev/null +++ b/src/components/date-histogram/date-histogram.scss @@ -0,0 +1,69 @@ +date-histogram { + .reset-buttons { + height: 32px; + ion-icon { + font-size: 1.5em; + } + } + + .years { + position: relative; + margin: 20px 0 0 50px; + cursor: crosshair; + } + + .year { + position: relative; + height: 3px; + } + .yearIsDecade { + height: 4px; + border-bottom: 1px solid #aaaaaa; + } + .yearBar { + position: relative; + height: 100%; + width: 0; + background-color: #538393; + } + .yearDetails { + display: none; + position: absolute; + right: 0; + top: -20px; + padding: 2px; + font-size: 12px !important; + color: color($colors, grey); + background-color: rgba(255,255,255,0.5); + } + + .year:hover { + background-color: color($colors, secondary); + } + .year:hover > .yearBar, .yearBarInRange { + background-color: color($colors, location); + } + .year:hover .yearDetails { + display: block; + } + + .decadeMarker { + position: relative; + height: 0px; + } + + .decade { + position: absolute; + left: -55px; + top: -7px; + width: 50px; + font-size: 12px !important; + color: color($colors, grey); + text-align: right; + } + +} +.list-md .item-input:last-child { + border-bottom: 1px solid #538292 !important; + box-shadow: inset 0 -1px 0 0 #538292 !important; +} diff --git a/src/components/date-histogram/date-histogram.ts b/src/components/date-histogram/date-histogram.ts new file mode 100644 index 00000000..c868062c --- /dev/null +++ b/src/components/date-histogram/date-histogram.ts @@ -0,0 +1,91 @@ +import { Component, Input } from '@angular/core' +import { Events } from 'ionic-angular' + +/** + * Visualize and select time ranges using from elastic search date histogram data. + * + * TODO: Add drag to select range. + * TODO: Add more granular months selection. + */ +@Component({ + selector: 'date-histogram', + templateUrl: 'date-histogram.html' +}) +export class DateHistogram { + + @Input() years: [Facet] + @Input() change: Function + + from: string + to: string + max = 0 + + ngOnInit() { + this.updateMax(); + } + + ngOnChanges() { + this.updateMax(); + } + + private updateMax() { + this.max = this.years.reduce(function (current, year) { + return Math.max(current, year.doc_count); + }, 0) + } + + reset() { + this.from = null; + this.to = null; + this.change(null, null); + } + + // Send the change event to the parent component + onChange() { + const fromTime = new Date(this.from).getTime() + // Add one year to get the full year. + const toTime = new Date(`${parseInt(this.to) + 1}`).getTime(); + + if (fromTime <= toTime) { + this.change(fromTime, toTime); + } + // this.change( + // this.years.find(year => year.key_as_string === this.from), + // this.years.find(year => year.key_as_string === this.to) + // ) + } + + getYearRelativeToMax(year: Facet) { + return `${Math.floor(year.doc_count / this.max * 100)}%` + } + + isDecade(year: Facet) { + return parseInt(year.key_as_string) % 10 === 0 + } + + selectYear(selected: Facet) { + if (!this.from) { + this.from = selected.key_as_string; + } else if (!this.to && parseInt(selected.key_as_string) >= parseInt(this.from)) { + this.to = selected.key_as_string; + } else { + this.from = selected.key_as_string; + this.to = null; + } + + this.onChange(); + } + + isYearInRange(year: Facet) { + const yearNumber = parseInt(year.key_as_string) + if (year.key_as_string === this.from || year.key_as_string === this.to) { + return true; + + } else if (this.from && this.to) { + return yearNumber >= parseInt(this.from) && yearNumber <= parseInt(this.to); + + } else { + return false; + } + } +} diff --git a/src/components/digital-edition-list/digital-edition-list.component.ts b/src/components/digital-edition-list/digital-edition-list.component.ts index c5800976..79d78567 100644 --- a/src/components/digital-edition-list/digital-edition-list.component.ts +++ b/src/components/digital-edition-list/digital-edition-list.component.ts @@ -30,6 +30,7 @@ export class DigitalEditionList implements OnInit { tocItems: GeneralTocItem[]; hasCover = true; hideBooks = false; + collectionSortOrder: any; @Input() layoutType: string; @Input() collectionsToShow?: Array; @@ -55,6 +56,11 @@ export class DigitalEditionList implements OnInit { } catch (e) { this.hasCover = true; } + try { + this.collectionSortOrder = this.config.getSettings('app.CollectionSortOrder'); + } catch (e) { + this.collectionSortOrder = undefined; + } } ngOnInit() { @@ -100,9 +106,12 @@ export class DigitalEditionList implements OnInit { .subscribe( digitalEditions => { this.digitalEditions = digitalEditions; - const de = digitalEditions; + let de = digitalEditions; this.events.publish('DigitalEditionList:recieveData', { digitalEditions }); this.setPDF(de); + if ( this.collectionSortOrder !== undefined && Object.keys(this.collectionSortOrder).length > 0 ) { + de = this.sortListDefined(de, this.collectionSortOrder); + } if (this.collectionsToShow !== undefined && this.collectionsToShow.length > 0) { this.filterCollectionsToShow(de); } @@ -111,15 +120,36 @@ export class DigitalEditionList implements OnInit { ); } + sortListDefined(list, sort) { + for (const coll of list) { + const order = sort[coll.id]; + coll['order'] = order; + } + + list.sort((a, b) => { + if (typeof a['order'] === 'number') { + return (a['order'] - b['order']); + } else { + return ((a['order'] < b['order']) ? -1 : ((a['order'] > b['order']) ? 1 : 0)); + } + }); + + return list; + } + shortText(edition_id: string): Array { let textData = ''; try { const lang = this.translate.currentLang; - textData = this.editionShortTexts[lang][edition_id] || + if ( this.editionShortTexts[lang][edition_id] !== undefined ) { + textData = this.editionShortTexts[lang][edition_id] || this.editionShortTexts[lang].default; - return textData.split('\n'); + return textData.split('\n'); + } else { + return String['']; + } } catch (e) { - console.log(e); + // console.error(e); } return textData.split('\n'); } @@ -148,7 +178,6 @@ export class DigitalEditionList implements OnInit { } setPDF(de) { - console.log(de); let tresh = false; for (let i = 0; i < de.length; i++) { if (i === (de.length / 2) && de.length % 2 === 0) { @@ -206,6 +235,7 @@ export class DigitalEditionList implements OnInit { } const nav = this.app.getActiveNavs(); + console.log('Opening read from DigitalEditionList.openFirstPage()'); nav[0].setRoot('read', params); } @@ -214,10 +244,7 @@ export class DigitalEditionList implements OnInit { if (this.hasCover === false) { this.getTocRoot(collection); } else { - const nav = this.app.getActiveNavs(); - let params; - params = { collection: collection, fetch: true, collectionID: collection.id }; - nav[0].setRoot('cover', params); + this.events.publish('digital-edition-list:open', collection); } } else { this.downloadPDF(collection); diff --git a/src/components/digital-edition-list/digital-edition-list.html b/src/components/digital-edition-list/digital-edition-list.html index a2136360..22e5d883 100644 --- a/src/components/digital-edition-list/digital-edition-list.html +++ b/src/components/digital-edition-list/digital-edition-list.html @@ -1,18 +1,18 @@
- - + - +

{{edition.title}}

{{row}}

@@ -26,17 +26,17 @@

{{edition.title}}

+ (click)="openCollection(edition, false)"> - + - +

{{edition.title}}

{{row}}

@@ -74,14 +74,14 @@

{{edition.title}}

justify-content: center;" *ngFor="let edition of digitalEditions" menuClose (click)="openCollection(edition, false)" col-sm-6 col-sm-3> - + - +

{{edition.title}}

{{row}}

@@ -146,4 +146,4 @@

{{edition.title}}

-
\ No newline at end of file +
diff --git a/src/components/digital-edition-list/digital-edition-list.scss b/src/components/digital-edition-list/digital-edition-list.scss index 4cad9580..b1f5962f 100644 --- a/src/components/digital-edition-list/digital-edition-list.scss +++ b/src/components/digital-edition-list/digital-edition-list.scss @@ -32,4 +32,7 @@ digital-editions-list { } } } -} \ No newline at end of file + .digital-edition-list-card-content{ + padding-top: 35px; + } +} diff --git a/src/components/facsimiles/facsimiles.html b/src/components/facsimiles/facsimiles.html index 1d41fe43..c8f24491 100644 --- a/src/components/facsimiles/facsimiles.html +++ b/src/components/facsimiles/facsimiles.html @@ -1,7 +1,7 @@ -
+
- {{facsimile.title}} +
{{"Read.OpenInTab" | translate}}
- +
+
+ {{"Read.Facsimiles.ExternalHeading" | translate}} +
+
+ + {{"Read.Facsimiles.Page" | translate}} ({{"Read.Facsimiles.Of" | translate}} - {{(images)?images.length:0}}): + {{numberOfPages ? numberOfPages : (images ? images.length : 0)}}): -
- +
+ + + +
-
- + + + +
- - - @@ -60,6 +85,7 @@
-
\ No newline at end of file +
diff --git a/src/components/facsimiles/facsimiles.scss b/src/components/facsimiles/facsimiles.scss index a4c3dbac..75569d51 100644 --- a/src/components/facsimiles/facsimiles.scss +++ b/src/components/facsimiles/facsimiles.scss @@ -1,11 +1,16 @@ facsimiles { .leftFacsimileArrow{ left: 0; + right: auto !important; } .page_number_input input{ } + .external_url a{ + white-space: normal; + } + .tab-container{ display: flex; justify-content: flex-start; @@ -96,5 +101,13 @@ facsimiles { img{ cursor: move; + // https://github.com/hammerjs/hammer.js/issues/1050#issuecomment-267595556 + // Need to use !important because something is trying to override the value. + touch-action: none !important; + } + + .external_url{ + display: grid; + line-height: 2; } } diff --git a/src/components/facsimiles/facsimiles.ts b/src/components/facsimiles/facsimiles.ts index bd75ebd8..59249c08 100644 --- a/src/components/facsimiles/facsimiles.ts +++ b/src/components/facsimiles/facsimiles.ts @@ -1,4 +1,4 @@ -import { Component, Input, EventEmitter, Output } from '@angular/core'; +import { Component, Input, EventEmitter, Output, SecurityContext } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { ModalController, NavParams, Events, ViewController, Platform } from 'ionic-angular'; import { FacsimileZoomModalPage } from '../../pages/facsimile-zoom/facsimile-zoom'; @@ -9,6 +9,7 @@ import { ConfigService } from '@ngx-config/core'; import { TranslateService } from '@ngx-translate/core'; import { SongService } from '../../app/services/song/song.service'; import { IfObservable } from 'rxjs/observable/IfObservable'; + /** * Generated class for the FacsimilesComponent component. * @@ -41,14 +42,23 @@ export class FacsimilesComponent { manualPageNumber: number; zoom = 1.0; angle = 0; - latestDeltaX = null; - latestDeltaY = null; + latestDeltaX = 0 + latestDeltaY = 0 + prevX = 0 + prevY = 0 + isExternal = false; facsUrl = ''; + externalURLs = []; facsimilePagesInfinite = false; - + // If defined, this size will be appended to the image url. + // So define only if the image API supports it. + facsSize: number; facsPage: any; facsNumber = 0; + facsimileDefaultZoomLevel = 1; + numberOfPages: number; + chapter: string; constructor( protected sanitizer: DomSanitizer, @@ -68,6 +78,13 @@ export class FacsimilesComponent { this.manualPageNumber = 1; this.text = ''; this.facsimiles = []; + + const parts = String(this.itemId).split('_') + this.chapter = null; + if ( parts[2] !== undefined ) { + this.chapter = parts[2]; + } + if (this.params.get('facsimilePage') !== undefined) { this.facsimilePage = this.params.get('facsimilePage'); // To account for index at 0 @@ -75,12 +92,17 @@ export class FacsimilesComponent { } else { this.facsimilePage = 0; } + try { + this.facsimileDefaultZoomLevel = this.config.getSettings('settings.facsimileDefaultZoomLevel'); + } catch (e) { + this.facsimileDefaultZoomLevel = 1; + } } openNewFacs(event: Event, id: any) { event.preventDefault(); event.stopPropagation(); - id.viewType = 'facsimile'; + id.viewType = 'facsimiles'; this.openNewFacsimileView.emit(id); } @@ -123,15 +145,17 @@ export class FacsimilesComponent { this.config.getSettings('app.machineName') + `/song-example/page/image/${this.facsID}/`; this.facsNumber = this.facsNr; + this.facsSize = null + } else if (this.facsID && this.facsNr) { this.facsUrl = this.config.getSettings('app.apiEndpoint') + '/' + this.config.getSettings('app.machineName') + `/facsimile/page/image/${this.facsID}/`; this.facsNumber = this.facsNr; + this.facsSize = null + } else { - this.facsUrl = this.config.getSettings('app.apiEndpoint') + '/' + - this.config.getSettings('app.machineName') + - `/facsimile/page/image/${this.params.get('collectionID')}/`; + this.facsSize = this.facsimileDefaultZoomLevel; this.getFacsimilePageInfinite(); } } else { @@ -145,11 +169,54 @@ export class FacsimilesComponent { getFacsimilePageInfinite() { this.facsimileService.getFacsimilePage(this.itemId).subscribe( facs => { - this.facsPage = facs; - this.facsNumber = facs['page_number']; + this.facsimiles = []; + if ( String(this.itemId).indexOf('ch') > 0 ) { + facs.forEach( fac => { + let section = String(this.itemId).split(';')[0]; + section = String((String(section).split('_')[2]).replace('ch', '')); + if ( String(fac['section_id']) === section ) { + this.facsPage = fac; + } + }); + if ( this.facsPage === undefined ) { + this.facsPage = facs[0]; + } + } else { + this.facsPage = facs[0]; + } + + this.manualPageNumber = this.activeImage = this.facsimilePage = this.facsNumber = (this.facsPage['page_nr'] + this.facsPage['start_page_number']); + this.numberOfPages = this.facsPage['number_of_pages']; + + this.facsPage['title'] = this.sanitizer.bypassSecurityTrustHtml(this.facsPage['title']); + + this.selectedFacsimile = this.facsPage; + this.selectedFacsimile.f_col_id = this.facsPage['publication_facsimile_collection_id']; + this.selectedFacsimile.title = this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustHtml(this.facsPage['title'])); + + // add all + for (const f of facs) { + const tmp = f; + tmp.title = this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustHtml(tmp.title)); + const facsimile = new Facsimile(tmp); + facsimile.itemId = this.itemId; + facsimile.manuscript_id = f.publication_manuscript_id; + this.facsimiles.push(facsimile); + if ( f['external_url'] !== null ) { + this.isExternal = true; + this.externalURLs.push({'title': f['title'], 'url': f['external_url']}); + } + } + + if ( this.facsPage['external_url'] === undefined || this.facsPage['external_url'] === null ) { + this.facsUrl = this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + + `/facsimiles/${this.facsPage['publication_facsimile_collection_id']}/`; + this.isExternal = false; + } }, error => { - console.log('Error loading facsimiles...'); + console.error('Error loading facsimiles...'); this.errorMessage = error } ); @@ -170,7 +237,7 @@ export class FacsimilesComponent { if (itemId) { this.itemId = itemId } - this.facsimileService.getFacsimiles(this.itemId).subscribe( + this.facsimileService.getFacsimiles(this.itemId, this.chapter).subscribe( facs => { // in order to get id attributes for tooltips this.facsimiles = []; @@ -180,7 +247,7 @@ export class FacsimilesComponent { if (f.publication_facsimile_collection_id === undefined && f['publication_facsimile_collection_id'] !== undefined) { f.publication_facsimile_collection_id = f['publication_facsimile_collection_id']; } - const f_url = this.facsimileService.getFacsimileImage(f.publication_facsimile_collection_id, i, 1); + const f_url = this.facsimileService.getFacsimileImage(f.publication_facsimile_collection_id, i, this.facsimileDefaultZoomLevel); facsimile.images.push(f_url); const zf_url = this.facsimileService.getFacsimileImage(f.publication_facsimile_collection_id, i, 4); facsimile.zoomedImages.push(zf_url); @@ -198,7 +265,7 @@ export class FacsimilesComponent { } } } else { - this.selectedFacsimile = this.facsimiles[0]; + this.selectedFacsimile = this.facsimiles[this.facsimiles.length - 1]; } this.images = this.selectedFacsimile.images; this.activeImage = 0; @@ -207,7 +274,7 @@ export class FacsimilesComponent { this.doAnalytics(); }, error => { - console.log('Error loading facsimiles...', this.itemId); + console.error('Error loading facsimiles...', this.itemId); this.errorMessage = error } ) @@ -235,6 +302,13 @@ export class FacsimilesComponent { if (facs) { this.selectedFacsimile = facs; this.itemId = this.selectedFacsimile.itemId; + this.facsNumber = facs.page; + this.facsPage = facs.page; + this.manualPageNumber = facs.page; + this.numberOfPages = facs.number_of_pages; + this.facsUrl = this.config.getSettings('app.apiEndpoint') + '/' + + this.config.getSettings('app.machineName') + + `/facsimiles/${facs.publication_facsimile_collection_id}/`; } this.text = this.sanitizer.bypassSecurityTrustHtml( this.selectedFacsimile.content.replace(/images\//g, 'assets/images/') @@ -246,8 +320,10 @@ export class FacsimilesComponent { previous() { if (this.facsimilePagesInfinite) { - this.prevFacsimileUrl(); - this.manualPageNumber = Number(this.manualPageNumber) - 1; + if (this.manualPageNumber > 1) { + this.prevFacsimileUrl(); + this.manualPageNumber = Number(this.manualPageNumber) - 1; + } return; } this.activeImage = (this.activeImage - 1); @@ -263,8 +339,14 @@ export class FacsimilesComponent { next() { if (this.facsimilePagesInfinite) { - this.nextFacsimileUrl(); - this.manualPageNumber = Number(this.manualPageNumber) + 1; + if ( (Number(this.manualPageNumber) + 1) <= this.numberOfPages ) { + this.nextFacsimileUrl(); + this.manualPageNumber = Number(this.manualPageNumber) + 1; + } else { + this.facsNumber = 1; + this.manualPageNumber = 1; + } + // this.manualPageNumber = Number(this.manualPageNumber) + 1; return; } this.activeImage = (this.activeImage + 1); @@ -293,28 +375,40 @@ export class FacsimilesComponent { openZoom() { let modal = null; + let params: object; + this.facsSize = 4; + if (this.facsimilePagesInfinite) { - const params = { - 'facsimilePagesInfinite': true, - 'facsUrl': this.facsUrl, - 'facsID': this.facsID, - 'facsNr': this.facsNr - }; + // TODO: images array contains 0 index that is invalid since page numbers are 1 based. + const images = [] + for (let i = 0; i < this.numberOfPages; i++) { + images.push(this.facsUrl + i + '/' + this.facsSize) + } - modal = this.modalController.create(FacsimileZoomModalPage, - params, - { cssClass: 'facsimile-zoom-modal' } - ); + params = { + facsimilePagesInfinite: false, + facsUrl: this.facsUrl, + facsID: this.facsID, + facsNr: this.facsNr, + facsSize: this.facsSize, + images, + activeImage: this.manualPageNumber, + }; } else { - modal = this.modalController.create(FacsimileZoomModalPage, - { 'images': this.selectedFacsimile.zoomedImages, 'activeImage': this.activeImage }, - { cssClass: 'facsimile-zoom-modal' } - ); + params = { + images: this.selectedFacsimile.zoomedImages, + activeImage: this.activeImage, + }; } + modal = this.modalController.create(FacsimileZoomModalPage, + params, + { cssClass: 'facsimile-zoom-modal' } + ); + modal.present(); modal.onDidDismiss(data => { - console.log('dismissed', data); + console.error('dismissed', data); }); } @@ -335,21 +429,25 @@ export class FacsimilesComponent { } } - zoomReset() { + resetFacsimile() { this.zoom = 1 + (Math.random() * (0.00001 - 0.00000001) + 0.00000001); this.angle = 0; + this.prevX = 0; + this.prevY = 0; } handleSwipeEvent(event) { const img = event.target; - let x = event.deltaX; - let y = event.deltaY; - if ( this.latestDeltaX !== null ) { - x = this.latestDeltaX; - y = this.latestDeltaY; - this.latestDeltaX = null; - this.latestDeltaY = null; - } + // Store latest zoom adjusted delta. + // NOTE: img must have touch-action: none !important; + // otherwise deltaX and deltaY will give wrong values on mobile. + this.latestDeltaX = event.deltaX / this.zoom + this.latestDeltaY = event.deltaY / this.zoom + + // Get current position from last position and delta. + let x = this.prevX + this.latestDeltaX + let y = this.prevY + this.latestDeltaY + if ( this.angle === 90 ) { const tmp = x; x = y; @@ -364,28 +462,34 @@ export class FacsimilesComponent { y = tmp; x = x * -1; } + if (img !== null) { img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + x + 'px, ' + y + 'px, 0px)'; } } - setLatestPos(e) { - this.latestDeltaX = e.clientX - e.offsetX; - this.latestDeltaY = e.clientY - e.offsetY; + onMouseUp(e) { + // Update the previous position on desktop by adding the latest delta. + this.prevX += this.latestDeltaX + this.prevY += this.latestDeltaY + } + + onTouchEnd(e) { + // Update the previous position on mobile by adding the latest delta. + this.prevX += this.latestDeltaX + this.prevY += this.latestDeltaY } onMouseWheel(e) { const img = e.target; - this.latestDeltaY = e.clientY - e.offsetY; - this.latestDeltaX = e.clientX - e.offsetX; if ( e.deltaY > 0 ) { this.zoomIn(); - img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.latestDeltaX + 'px, ' + - this.latestDeltaY + 'px, 0px)'; + img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.prevX + 'px, ' + + this.prevY + 'px, 0px)'; } else { this.zoomOut(); - img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.latestDeltaX + 'px, ' + - this.latestDeltaY + 'px, 0px)'; + img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.prevX + 'px, ' + + this.prevY + 'px, 0px)'; } } diff --git a/src/components/illustrations/illustrations.html b/src/components/illustrations/illustrations.html new file mode 100644 index 00000000..a5a0d3ee --- /dev/null +++ b/src/components/illustrations/illustrations.html @@ -0,0 +1,18 @@ + + + + +
+
+ illustration + +
+
+
+ + +

{{"Read.Illustrations.Nothing" | translate}}

+
diff --git a/src/components/illustrations/illustrations.scss b/src/components/illustrations/illustrations.scss new file mode 100644 index 00000000..84fda01a --- /dev/null +++ b/src/components/illustrations/illustrations.scss @@ -0,0 +1,11 @@ +illustrations { + .illustration-image-wrapper { + margin: 10px 0; + img { + cursor: zoom-in; + } + } + .scroll-to-illustration-button { + text-transform: initial; + } +} diff --git a/src/components/illustrations/illustrations.ts b/src/components/illustrations/illustrations.ts new file mode 100644 index 00000000..db79ebb8 --- /dev/null +++ b/src/components/illustrations/illustrations.ts @@ -0,0 +1,141 @@ +import { Component, Input, OnDestroy } from '@angular/core'; +import { NavParams, Events } from 'ionic-angular'; +import { TextService } from '../../app/services/texts/text.service'; +import { ModalController } from 'ionic-angular'; +import { ConfigService } from '@ngx-config/core'; +import { FacsimileZoomModalPage } from '../../pages/facsimile-zoom/facsimile-zoom'; +/** + * Generated class for the IllustrationsComponent component. + * + * See https://angular.io/api/core/Component for more info on Angular + * Components. + */ +@Component({ + selector: 'illustrations', + templateUrl: 'illustrations.html' +}) +export class IllustrationsComponent { + @Input() itemId: string; + illustrationsPath = 'assets/images/illustrations/2/'; + imgPath: any; + images: Array = []; + selectedImage: Array = []; + viewAll = false; + showOne = false; + apiEndPoint: string; + projectMachineName: string; + constructor( + public navParams: NavParams, + private textService: TextService, + private modalCtrl: ModalController, + private config: ConfigService, + private events: Events + ) { } + ngOnInit() { + this.getIllustrationImages(); + this.apiEndPoint = this.config.getSettings('app.apiEndpoint'); + this.projectMachineName = this.config.getSettings('app.machineName'); + this.doAnalytics(); + } + + ngOnDestroy() { + this.events.unsubscribe('give:illustration'); + } + + ngAfterViewInit() { + document.body.addEventListener('click', (event: any) => { + if (event.target.previousElementSibling || event.target.classList.contains('est_figure_graphic')) { + try { + if (this.config.getSettings('settings.showReadTextIllustrations')) { + const showIllustration = this.config.getSettings('settings.showReadTextIllustrations'); + + if ( showIllustration.includes(this.itemId.split('_')[1])) { + if (event.target.classList.contains('est_figure_graphic') || event.target.classList.contains('doodle')) { + this.events.subscribe('give:illustration', (image) => { + if (image) { + this.showOne = true; + this.viewAll = false; + this.images = [image]; + } else { + this.showOne = false; + } + }); + } + } else { + if (event.target.previousElementSibling.classList.contains('est_figure_graphic') || + event.target.classList.contains('doodle')) { + this.events.subscribe('give:illustration', (image) => { + if (image) { + this.showOne = true; + this.viewAll = false; + this.images = [image]; + } else { + this.showOne = false; + } + }); + } + } + } + } catch (e) { + console.error(e) + } + } + }); + } + + toggleViewAll() { + this.viewAll = !this.viewAll; + this.showOne = false; + + if (this.viewAll) { + this.getIllustrationImages(); + } + } + + zoomImage(image) { + this.selectedImage = [image]; + const illustrationZoomModal = this.modalCtrl.create( + FacsimileZoomModalPage, + { 'images': this.selectedImage, 'activeImage': 0 }, + { cssClass: 'facsimile-zoom-modal' } + ); + illustrationZoomModal.present(); + } + + scrollToPositionInText(image) { + image = image.replace('http:', ''); + const target = document.querySelector(`[src="${image}"]`); + target.scrollIntoView({'behavior': 'smooth', 'block': 'center'}); + } + + private getIllustrationImages() { + this.images = []; + this.textService.getEstablishedText(this.itemId).subscribe(text => { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(text, 'text/html'); + const images: any = xmlDoc.querySelectorAll('img.est_figure_graphic'); + const doodles: any = xmlDoc.querySelectorAll('img.doodle'); + for (let i = 0; i < images.length ; i++) { + const image = {src: images[i].src, class: 'illustration'}; + this.images.push(image); + } + for (let i = 0; i < doodles.length ; i++) { + const image = {src: '/assets/images/verk/' + String(doodles[i].dataset.id).replace('tag_', '') + '.jpg', class: 'doodle'}; + console.log(image); + this.images.push(image); + } + }); + } + + doAnalytics() { + try { + (window).ga('send', 'event', { + eventCategory: 'Illustration', + eventLabel: 'Illustration', + eventAction: String(this.itemId), + eventValue: 10 + }); + } catch ( e ) { + } + } +} diff --git a/src/components/introduction/introduction.html b/src/components/introduction/introduction.html index 258a4462..413fc51a 100644 --- a/src/components/introduction/introduction.html +++ b/src/components/introduction/introduction.html @@ -1,7 +1,9 @@ +
\ No newline at end of file +> diff --git a/src/components/introduction/introduction.ts b/src/components/introduction/introduction.ts index 18d9cfd9..42a878d0 100644 --- a/src/components/introduction/introduction.ts +++ b/src/components/introduction/introduction.ts @@ -25,6 +25,7 @@ export class IntroductionComponent { public text: any; protected errorMessage: string; + textLoading: Boolean = true; constructor( protected readPopoverService: ReadPopoverService, @@ -42,15 +43,17 @@ export class IntroductionComponent { this.langService.getLanguage().subscribe(lang => { this.textService.getIntroduction(String(this.itemId).split('_')[0], lang).subscribe( res => { + this.textLoading = false; // in order to get id attributes for tooltips this.text = this.sanitizer.bypassSecurityTrustHtml( res.content.replace(/images\//g, 'assets/images/') .replace(/\.png/g, '.svg') ); }, - error => {this.errorMessage = error} + error => {this.errorMessage = error; this.textLoading = false; } ); }); + this.doAnalytics(); } ngAfterViewInit() { @@ -70,7 +73,19 @@ export class IntroductionComponent { try { element.scrollIntoView({'behavior': 'smooth', 'block': 'center'}); } catch ( e ) { - console.log(e); + console.error(e); + } + } + + doAnalytics() { + try { + (window).ga('send', 'event', { + eventCategory: 'Introduction', + eventLabel: 'Introduction', + eventAction: String(this.itemId), + eventValue: 10 + }); + } catch ( e ) { } } } diff --git a/src/components/list-of-songs/list-of-songs.ts b/src/components/list-of-songs/list-of-songs.ts index e32c8380..3a3f873b 100644 --- a/src/components/list-of-songs/list-of-songs.ts +++ b/src/components/list-of-songs/list-of-songs.ts @@ -197,7 +197,7 @@ export class ListOfSongsComponent { this.cdRef.detectChanges(); }, error => { - console.log('ListOfSongsComponent', error); + console.error('ListOfSongsComponent', error); this.isLoading = false; } ); diff --git a/src/components/manuscripts/manuscripts.html b/src/components/manuscripts/manuscripts.html index 749df2d2..687abacc 100644 --- a/src/components/manuscripts/manuscripts.html +++ b/src/components/manuscripts/manuscripts.html @@ -19,6 +19,7 @@
diff --git a/src/components/manuscripts/manuscripts.scss b/src/components/manuscripts/manuscripts.scss index 3bb3f2e5..487ece19 100644 --- a/src/components/manuscripts/manuscripts.scss +++ b/src/components/manuscripts/manuscripts.scss @@ -90,4 +90,14 @@ manuscripts { .XMLcomment{ display: none; } + + img.teiManuscript.symbol{ + width: 0.7rem; + display: inline; + } + + .tei img { + width: 0.9rem; + display: inline; + } } diff --git a/src/components/manuscripts/manuscripts.ts b/src/components/manuscripts/manuscripts.ts index a716cb98..1b984a08 100644 --- a/src/components/manuscripts/manuscripts.ts +++ b/src/components/manuscripts/manuscripts.ts @@ -25,9 +25,10 @@ export class ManuscriptsComponent { selection: 0; manuscripts: any; selectedManuscript: any; - normalized = true; + normalized = false; errorMessage: string; msID: string; + chapter: string; constructor( protected sanitizer: DomSanitizer, @@ -45,6 +46,11 @@ export class ManuscriptsComponent { } ngOnInit() { + const parts = String(this.itemId).split('_') + this.chapter = null; + if ( parts[2] !== undefined ) { + this.chapter = parts[2]; + } this.msID = this.itemId + '_ms'; this.setText(); } @@ -88,7 +94,7 @@ export class ManuscriptsComponent { } getManuscript() { - this.textService.getManuscripts(this.itemId).subscribe( + this.textService.getManuscripts(this.itemId, this.chapter).subscribe( res => { // in order to get id attributes for tooltips console.log('recieved manuscript ,..,', res.manuscripts); @@ -97,7 +103,7 @@ export class ManuscriptsComponent { }, err => console.error(err), () => { - console.log('fetched manuscripts'); + console.error('fetched manuscripts'); } ); } diff --git a/src/components/read-text/read-text.html b/src/components/read-text/read-text.html index 1e9dd5da..e2cb1496 100644 --- a/src/components/read-text/read-text.html +++ b/src/components/read-text/read-text.html @@ -1,13 +1,14 @@ -
{{toolTipText}}
- +
+> + diff --git a/src/components/read-text/read-text.scss b/src/components/read-text/read-text.scss index 733c5dee..b0cce3dd 100644 --- a/src/components/read-text/read-text.scss +++ b/src/components/read-text/read-text.scss @@ -1,4 +1,21 @@ read-text { + img.hide-illustration { + visibility: hidden; + height: 10px; + & ~ p.tei:first-of-type::after { + content: ''; + cursor: pointer; + background-image: url('/assets/images/img_placeholder.png'); + height: 18px; + width: 20px; + margin-left: 5px; + margin-bottom: -3px; + display: inline-block; + background-repeat: no-repeat; + background-size: cover; + } + } + a.xreference.ref_illustration { img { max-width: 12px; @@ -7,7 +24,7 @@ read-text { } } match{ - background-color: rgba( yellow, 0.4 ); + background-color: rgba( yellow, 0.4 ) !important; } .tei.anchor_lemma img{ width: 0.9rem; @@ -38,7 +55,7 @@ read-text { position: absolute; pointer-events: none; } - + .toolTip:after { border-color: rgba(136, 183, 213, 0); border-right-color: #88b7d5; @@ -63,4 +80,9 @@ read-text { .inl_ms_arrow{ display: inline !important; } + + img.doodle{ + width: 0.7rem; + display: inline; + } } diff --git a/src/components/read-text/read-text.ts b/src/components/read-text/read-text.ts index ae0e3dc8..92a00345 100644 --- a/src/components/read-text/read-text.ts +++ b/src/components/read-text/read-text.ts @@ -1,3 +1,4 @@ + import { Component, Input, ElementRef, Renderer } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { ReadPopoverService } from '../../app/services/settings/read-popover.service'; @@ -6,6 +7,7 @@ import { Storage } from '@ionic/storage'; import { ToastController, Events, ModalController } from 'ionic-angular'; import { IllustrationPage } from '../../pages/illustration/illustration'; import { ConfigService } from '@ngx-config/core'; +import { TextCacheService } from '../../app/services/texts/text-cache.service'; /** * Generated class for the ReadTextComponent component. @@ -25,9 +27,9 @@ export class ReadTextComponent { public text: any; protected errorMessage: string; defaultView: string; - showToolTip: boolean; - toolTipPosition: object; - toolTipText: string; + apiEndPoint: string; + appMachineName: string; + textLoading: Boolean = true; constructor( public events: Events, @@ -41,10 +43,9 @@ export class ReadTextComponent { private config: ConfigService, protected modalController: ModalController ) { + this.appMachineName = this.config.getSettings('app.machineName'); + this.apiEndPoint = this.config.getSettings('app.apiEndpoint'); this.defaultView = this.config.getSettings('defaults.ReadModeView'); - this.showToolTip = false; - this.toolTipPosition = { top: 40 + 'px', left: 100 + 'px' }; - this.toolTipText = ''; } ngOnInit() { @@ -63,23 +64,83 @@ export class ReadTextComponent { ngAfterViewInit() { this.renderer.listen(this.elementRef.nativeElement, 'click', (event) => { - if (event.target.classList.contains('variantScrollTarget') && this.readPopoverService.show.comments) { - if (event.target !== undefined) { - this.showTooltip(event); + try { + if (this.config.getSettings('settings.showReadTextIllustrations')) { + const showIllustration = this.config.getSettings('settings.showReadTextIllustrations'); + + if (event.target.classList.contains('doodle')) { + const image = {src: '/assets/images/verk/' + String(event.target.dataset.id).replace('tag_', '') + '.jpg', class: 'doodle'}; + this.events.publish('give:illustration', image); + } + if ( showIllustration.includes(this.link.split('_')[1])) { + if (event.target.classList.contains('est_figure_graphic')) { + // Check if we have the "illustrations" tab open, if not, open + if ( document.querySelector('illustrations') === null ) { + this.openNewView(event, null, 'illustrations'); + } + const image = {src: event.target.src, class: 'illustration'}; + this.events.publish('give:illustration', image); + } + } else { + if (event.target.previousElementSibling !== null && + event.target.previousElementSibling.classList.contains('est_figure_graphic')) { + // Check if we have the "illustrations" tab open, if not, open + if ( document.querySelector('illustrations') === null ) { + this.openNewView(event, null, 'illustrations'); + } + const image = {src: event.target.previousElementSibling.src, class: 'illustration'}; + this.events.publish('give:illustration', image); + } } } + } catch (e) { + console.error(e); + } + + if (event.target.parentNode.classList.contains('ref_illustration')) { const hashNumber = event.target.parentNode.hash; const imageNumber = hashNumber.split('#')[1]; this.openIllustration(imageNumber); } }); - this.renderer.listen(this.elementRef.nativeElement, 'mouseover', (event) => { - if ((event.target.parentNode.classList.contains('tooltiptrigger') || event.target.classList.contains('tooltiptrigger')) && - this.readPopoverService.show.comments) { - if (event.target !== undefined) { - this.showTooltip(event); + + const checkExist = setInterval(function() { + if ( this.link !== undefined ) { + const linkData = this.link.split(';'); + if ( linkData[1] ) { + const target = document.getElementsByName('' + linkData[1] + '')[0] as HTMLAnchorElement; + if ( target ) { + this.scrollToHTMLElement(target, false); + clearInterval(checkExist); + } + } else { + clearInterval(checkExist); } + } else { + clearInterval(checkExist); + } + }.bind(this), 100); + + } + + openNewView( event, id: any, type: string ) { + let openId = id; + let chapter = null; + if (String(id).includes('ch')) { + openId = String(String(id).split('ch')[0]).trim(); + chapter = 'ch' + String(String(id).split('ch')[1]).trim(); + } + this.events.publish('show:view', type, openId, chapter); + } + + private setIllustrationImages() { + this.textService.getEstablishedText(this.link).subscribe(text => { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(text, 'text/html'); + const images: any = xmlDoc.querySelectorAll('img.est_figure_graphic'); + for (let i = 0; i < images.length ; i++) { + images[i].classList.add('show-illustration'); } }); } @@ -100,10 +161,18 @@ export class ReadTextComponent { getCacheText(id: string) { this.storage.get(id).then((content) => { + this.textLoading = false; + const c_id = String(this.link).split('_')[0]; + let galleryId = 44; + try { + galleryId = this.config.getSettings('settings.galleryCollectionMapping')[c_id]; + } catch ( err ) { + + } this.text = content; this.text = this.sanitizer.bypassSecurityTrustHtml( - content.replace(/images\//g, 'assets/images/') - .replace(/\.png/g, '.svg').replace(/class=\"([a-z A-Z _ 0-9]{1,140})\"/g, 'class=\"tei $1\"') + content.replace(/images\/verk\//g, `${this.apiEndPoint}/${this.appMachineName}/gallery/get/${galleryId}/`) + .replace(/\.png/g, '.svg').replace(/class=\"([a-z A-Z _ 0-9]{1,140})\"/g, 'class=\"tei $1\"').replace(/images\//g, 'assets/images/') ); this.matches.forEach(function (val) { const re = new RegExp('(' + val + ')', 'g'); @@ -128,23 +197,70 @@ export class ReadTextComponent { getEstText() { this.textService.getEstablishedText(this.link).subscribe( text => { + this.textLoading = false; + const c_id = String(this.link).split('_')[0]; + let galleryId = 44; + try { + galleryId = this.config.getSettings('settings.galleryCollectionMapping')[c_id]; + } catch ( err ) { + + } + text = text.replace(/images\/verk\//g, `${this.apiEndPoint}/${this.appMachineName}/gallery/get/${galleryId}/`); + text = text.replace(/\.png/g, '.svg'); + text = text.replace(/class=\"([a-z A-Z _ 0-9]{1,140})\"/g, 'class=\"tei $1\"'); + text = text.replace(/images\//g, 'assets/images/'); this.text = this.sanitizer.bypassSecurityTrustHtml( - text.replace(/images\//g, 'assets/images/') - .replace(/\.png/g, '.svg').replace(/class=\"([a-z A-Z _ 0-9]{1,140})\"/g, 'class=\"tei $1\"') + text ); - if (this.matches instanceof Array) { + if (this.matches instanceof Array && this.matches.length > 0) { + let tmpText: any = ''; this.matches.forEach(function (val) { - const re = new RegExp('(' + val + ')', 'g'); - this.text = this.sanitizer.bypassSecurityTrustHtml( + const re = new RegExp('(' + val + ')', 'ig'); + tmpText = this.sanitizer.bypassSecurityTrustHtml( text.replace(re, '$1') ); }.bind(this)); + this.text = tmpText; } }, - error => { this.errorMessage = error } + error => { this.errorMessage = error; this.textLoading = false; } ); } + private scrollToHTMLElement(element: HTMLElement, addTag: boolean, timeOut = 8000) { + try { + element.scrollIntoView({'behavior': 'smooth', 'block': 'start'}); + const tmp = element.previousElementSibling as HTMLElement; + let addedArrow = false; + + if ( tmp !== null && tmp !== undefined && tmp.classList.contains('anchor_lemma') ) { + tmp.style.display = 'inline'; + setTimeout(function() { + tmp.style.display = 'none'; + }, 2000); + addedArrow = true; + } else { + const tmpImage: HTMLImageElement = new Image(); + tmpImage.src = 'assets/images/ms_arrow_right.svg'; + tmpImage.classList.add('inl_ms_arrow'); + element.parentElement.insertBefore(tmpImage, element); + setTimeout(function() { + element.parentElement.removeChild(tmpImage); + }, timeOut); + addedArrow = true; + } + + if ( addTag && !addedArrow ) { + element.innerHTML = ''; + setTimeout(function() { + element.innerHTML = ''; + }, timeOut); + } + } catch ( e ) { + console.error(e); + } + } + doAnalytics() { try { (window).ga('send', 'event', { @@ -156,35 +272,4 @@ export class ReadTextComponent { } catch ( e ) { } } - - showTooltip(origin: any) { - let elem = []; - if (origin.target.nextSibling !== null && origin.target.nextSibling !== undefined && - !String(origin.target.nextSibling.className).includes('tooltiptrigger')) { - elem = origin.target; - } else if (origin.target.parentNode.nextSibling !== null && origin.target.parentNode.nextSibling !== undefined) { - elem = origin.target.parentNode; - } - if (elem['nextSibling'] !== null && elem['nextSibling'] !== undefined) { - if (elem['nextSibling'].className !== undefined && String(elem['nextSibling'].className).includes('tooltip')) { - this.toolTipPosition = { - top: (elem['offsetTop'] - (elem['offsetHeight'] / 2) + 4) + - 'px', left: (elem['offsetLeft'] + elem['offsetWidth'] + 4) + 'px' - }; - this.showToolTip = true; - this.toolTipText = elem['nextSibling'].textContent; - if ((elem['offsetParent'].clientWidth) < ((elem['offsetLeft'] + elem['offsetWidth'] + 70))) { - this.toolTipPosition = { - top: (elem['offsetTop'] - (elem['offsetHeight'] / 2) + 40) + - 'px', left: (elem['offsetLeft'] + elem['offsetWidth'] - 100) + 'px' - }; - } - - setTimeout(() => { - this.showToolTip = false; - this.toolTipText = ''; - }, 5000); - } - } - } } diff --git a/src/components/simple-search/simple-search.html b/src/components/simple-search/simple-search.html index adb5bfe9..83b82a16 100644 --- a/src/components/simple-search/simple-search.html +++ b/src/components/simple-search/simple-search.html @@ -159,4 +159,4 @@ - \ No newline at end of file + diff --git a/src/components/simple-search/simple-search.scss b/src/components/simple-search/simple-search.scss index d77a1fd8..5ec64d55 100644 --- a/src/components/simple-search/simple-search.scss +++ b/src/components/simple-search/simple-search.scss @@ -58,6 +58,11 @@ simple-search { white-space: normal; } + .textType.title{ + color: rgba(color($colors, work), 0.7); + display: inline; + font-size: 0.9rem !important; + } .textType.person{ color: rgba(color($colors, person), 0.7); display: inline; diff --git a/src/components/simple-search/simple-search.ts b/src/components/simple-search/simple-search.ts index 0415e782..5e454dec 100644 --- a/src/components/simple-search/simple-search.ts +++ b/src/components/simple-search/simple-search.ts @@ -1,4 +1,5 @@ -import { Component, Input, ViewChild, HostListener, ElementRef } from '@angular/core'; +import { SemanticDataService } from './../../app/services/semantic-data/semantic-data.service'; +import { Component, Input, ViewChild, HostListener, ElementRef, ChangeDetectorRef } from '@angular/core'; import { App, Platform, Events, NavParams, Searchbar, ViewController } from 'ionic-angular'; import { SearchDataService } from '../../app/services/search/search-data.service'; import { TableOfContentsCategory, GeneralTocItem } from '../../app/models/table-of-contents.model'; @@ -59,6 +60,8 @@ export class SimpleSearchComponent { desktop: 250 } + collectionTOCs = []; + objectTypes = { all: ['location', 'tag', 'subject', 'recorder', 'playman', 'person'], subjects: ['subject', 'recorder', 'playman', 'person'] @@ -73,7 +76,9 @@ export class SimpleSearchComponent { private _eref: ElementRef, public userSettingsService: UserSettingsService, public viewctrl: ViewController, - private storage: Storage) { + public semanticDataService: SemanticDataService, + private storage: Storage, + private cf: ChangeDetectorRef) { this.apiEndPoint = this.config.getSettings('app.apiEndpoint'); this.projectMachineName = this.config.getSettings('app.machineName'); this.showPageNumbers = this.config.getSettings('simpleSearch.showPageNumbers'); @@ -114,6 +119,8 @@ export class SimpleSearchComponent { this.getFacsimileLookupData(); + this.getTOCLookupData(); + if (navParams.get('searchResult') !== undefined) { this.searchString = navParams.get('searchResult'); this.onInput(null, this.searchString); @@ -162,6 +169,26 @@ export class SimpleSearchComponent { ); } + getTOCLookupData() { + this.search.getProjectCollections().subscribe( + res => { + res.forEach(element => { + if ( this.collectionTOCs[element['id']] === undefined ) { + this.semanticDataService.getPublicationTOC(element['id']).subscribe( + toc_data => { + this.collectionTOCs[element['id']] = toc_data; + }, + error => { + } + ); + } + }); + }, + error => { this.errorMessage = error } + ); + } + + getPublicationNameByFacsimileId(fId) { for (let i = 0; i < this.facsimileLookupData.length; i++) { if (String(this.facsimileLookupData[i]['pf_id']) === String(fId)) { @@ -226,6 +253,24 @@ export class SimpleSearchComponent { } } + getPublicationTOCName(collection_id, publication_id, data) { + if ( this.collectionTOCs[collection_id] !== undefined ) { + this.updatePublicationNames(collection_id, publication_id, data, this.collectionTOCs[collection_id]); + } + } + + public updatePublicationNames(collection_id, publication_id, data, toc) { + toc.forEach( item => { + const id = collection_id + '_' + publication_id; + const itemArray = String(item['itemId']).split('_'); + const itemId = itemArray[0] + '_' + itemArray[1]; + if ( id === itemId ) { + data['publication_name'] = item['text']; + return true; + } + }); + } + configOccurrencePdf() { try { this.downloadOccurrencePdf = this.config.getSettings('simpleSearch.downloadOccurrencePdf'); @@ -332,7 +377,26 @@ export class SimpleSearchComponent { this.search.getAll(searchString, val, 1).subscribe( res => { // in order to get id attributes for tooltips - this.searchResult = res + // getPublicationTOCName(); + res.forEach(function(element, index, object) { + const source = element['_source']; + const pubId = source['publication_id']; + let colID = source['collection_id'] || source['publication_collection_id']; + if ( colID === undefined ) { + const path = String(source['path']).split('/'); + colID = path[path.length - 1]; + colID = String(colID).split('_')[0]; + } + colID = String(colID).split(',')[0]; + this.getPublicationTOCName(colID, pubId, source); + if ( source['publication_name'] === undefined ) { + source['publication_name'] = source['publication_data'][0]['pubname']; + } + if ( source['publication_data'] !== undefined && source['publication_data'][0]['collection_published'] === 0 ) { + delete object[index]; + } + }.bind(this)); + this.searchResult = res; this.formatSearchresult(val); this.mergeResults('texts'); this.mergeResults('subjects'); @@ -441,6 +505,7 @@ export class SimpleSearchComponent { } this.searchFacets[0].children.sort(); } + } if (this.hasKey('collections', this.searchFacets) === false && this.displayResult[type].length > 0) { @@ -458,7 +523,7 @@ export class SimpleSearchComponent { for (let j = 0; j < this.searchFacets.length; j++) { if (this.searchFacets[j].type === this.displayResult[type][i]['textType']) { const facet: Facet = new Facet; - facet.name = this.displayResult[type][i]['text']; + facet.name = this.displayResult[type][i]['publication_name'] || this.displayResult[type][i]['text']; facet.checked = this.checkedDefault; facet.count = 1; facet.type = this.displayResult[type][i]['textType']; @@ -544,6 +609,7 @@ export class SimpleSearchComponent { 'score': element._score, 'facsimilePage': ((subjectData['publication_facsimile_page']) ? subjectData['publication_facsimile_page'] : null), 'publication_name': subjectData['publication_name'], + 'publication_collection_id': subjectData['collection_id'], 'collection_name': subjectData['collection_name'], 'publication_id': subjectData['publication_id'], 'publication_version_id': subjectData['publication_version_id'], @@ -576,6 +642,7 @@ export class SimpleSearchComponent { 'facsimilePage': ((tagData['publication_facsimile_page']) ? tagData['publication_facsimile_page'] : null), 'publication_name': tagData['publication_name'], 'collection_name': tagData['collection_name'], + 'publication_collection_id': tagData['collection_id'], 'publication_id': tagData['publication_id'], 'publication_version_id': tagData['publication_version_id'], 'publication_manuscript_id': tagData['publication_manuscript_id'], @@ -603,6 +670,7 @@ export class SimpleSearchComponent { 'identifier': String('song' + songData['song_name']).toLowerCase().trim().replace(' ', ''), 'origDate': songData['song_original_publication_date'], 'object_id': String(songData['song_id']).toLowerCase(), + 'publication_collection_id': songData['collection_id'], 'song_performer_born_name': songData['song_performer_born_name'], 'song_performer_firstname': songData['song_performer_firstname'], 'song_performer_lastname': songData['song_performer_lastname'], @@ -647,6 +715,7 @@ export class SimpleSearchComponent { 'score': element._score, 'facsimilePage': ((locationData['publication_facsimile_page']) ? locationData['publication_facsimile_page'] : null), 'publication_name': locationData['publication_name'], + 'publication_collection_id': locationData['collection_id'], 'collection_name': locationData['collection_name'], 'publication_id': locationData['publication_id'], 'publication_version_id': locationData['publication_version_id'], @@ -721,7 +790,11 @@ export class SimpleSearchComponent { } const pubName = (pData !== undefined) ? pData['p_name'] : null; const colName = (pData !== undefined) ? pData['pc_name'] : null; - const pubId = this.getPublicationIdNameByFacsimileId(fId); + let pubId = this.getPublicationIdNameByFacsimileId(fId); + if ( pubId === undefined || pubId === null ) { + pubId = element['_source']['publication_id']; + } + if (element._score > 1) { this.displayResult['texts'].push( { @@ -737,7 +810,7 @@ export class SimpleSearchComponent { 'score': element._score, 'facsimilePage': facsimilePage, 'SLSCollection': SLSCollection, - 'publication_name': pubName, + 'publication_name': element['_source']['publication_name'], 'publication_id': pubId, 'collection_name': colName } diff --git a/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.html b/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.html index 9665dd51..7040a5bd 100644 --- a/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.html +++ b/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.html @@ -1,24 +1,23 @@ - - - {{"TOC.Home" | translate }} - - + + + + - - - - -

- {{item.title}} -

- -
- -

- {{item.title}} -

-
+ + + + +

+ {{item.title}} +

+ +
+ +

+ {{item.title}} +

+
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.ts b/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.ts index aab38195..e81677b5 100644 --- a/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.ts +++ b/src/components/static-pages-toc-drilldown-menu/static-pages-toc-drilldown-menu.ts @@ -115,15 +115,15 @@ export class StaticPagesTocDrilldownMenuComponent { /** * Find a node by id in a JSON tree */ - getNodeById(id, node) { + getNodeById(id, tree) { const reduce = [].reduce; - function runner(result, rnode) { - if (result || !rnode) { return result; } - return rnode.id === id && rnode || - runner(null, rnode.children) || - reduce.call(Object(rnode), runner, result); + const runner = (result, node) => { + if (result || !node) { return result; } + return node.id === id && node || + runner(null, node.children) || + reduce.call(Object(node), runner, result); } - return runner(null, node); + return runner(null, tree); } ngOnInit() { @@ -136,8 +136,11 @@ export class StaticPagesTocDrilldownMenuComponent { } unDrill() { - this.titleStack.pop(); - this.menuStack.pop(); + // don't go to far + if ( this.menuStack.length > 1 ) { + this.titleStack.pop(); + this.menuStack.pop(); + } } open(item: StaticPage) { diff --git a/src/components/table-of-content-letter-filter/table-of-content-letter-filter.html b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.html new file mode 100644 index 00000000..f5908a1f --- /dev/null +++ b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.html @@ -0,0 +1,4 @@ + +
+ {{text}} +
diff --git a/src/components/table-of-content-letter-filter/table-of-content-letter-filter.scss b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.scss new file mode 100644 index 00000000..8772fa20 --- /dev/null +++ b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.scss @@ -0,0 +1,3 @@ +table-of-content-letter-filter { + +} diff --git a/src/components/table-of-content-letter-filter/table-of-content-letter-filter.ts b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.ts new file mode 100644 index 00000000..f3270984 --- /dev/null +++ b/src/components/table-of-content-letter-filter/table-of-content-letter-filter.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +/** + * Generated class for the TableOfContentLetterFilterComponent component. + * + * See https://angular.io/api/core/Component for more info on Angular + * Components. + */ +@Component({ + selector: 'table-of-content-letter-filter', + templateUrl: 'table-of-content-letter-filter.html' +}) +export class TableOfContentLetterFilterComponent { + + text: string; + + constructor() { + console.log('Hello TableOfContentLetterFilterComponent Component'); + this.text = 'Hello World'; + } + +} diff --git a/src/components/table-of-contents-accordion/table-of-contents-accordion.html b/src/components/table-of-contents-accordion/table-of-contents-accordion.html index 8a20db25..87a92f31 100644 --- a/src/components/table-of-contents-accordion/table-of-contents-accordion.html +++ b/src/components/table-of-contents-accordion/table-of-contents-accordion.html @@ -1,83 +1,104 @@ - -

{{ "Read.TitlePage.Title" | translate }}

+ + {{collectionName}} - -

{{ "Read.Introduction.Title" | translate }}

+ + + {{ "BackButton.default" | translate }} - - - - - -

{{ option.text }}

-
+
+ + + + + + + + +

{{ "Read.CoverPage.Title" | translate }}

+
+ +

{{ "Read.TitlePage.Title" | translate }}

+
+ +

{{ "Read.Introduction.Title" | translate }}

+
+ + + + + +

{{ menuItem.text }}

+ {{ menuItem.description }} +
+
+ + + + +

{{ menuItem.text }}

+
+
+ + +
+
+
- - - - + + + + + loading ... + + + + + + +
+ + + + + +

{{ option.text }}

-
- - -
- +
+ + + + +

{{ option.text }}

+

{{"TOC.SongTypes" | translate }}

+
+
+ + +
+
+
- - - - + + + - - - - - - - - - -

{{ option.text }}

-
-
- - - - -

{{ option.text }}

-

{{"TOC.SongTypes" | translate }}

-
-
- - -
-
-
-
-
- - - - - + + + + -
\ No newline at end of file + +
diff --git a/src/components/table-of-contents-accordion/table-of-contents-accordion.scss b/src/components/table-of-contents-accordion/table-of-contents-accordion.scss index 90061890..0a72674c 100644 --- a/src/components/table-of-contents-accordion/table-of-contents-accordion.scss +++ b/src/components/table-of-contents-accordion/table-of-contents-accordion.scss @@ -1,4 +1,35 @@ table-of-contents-accordion { + ion-label{ + cursor: pointer; + } + + .collection-name-top{ + background-color: rgba(color($colors, primary), $alpha: 0.2) !important; + } + .collection-name-top ion-label{ + white-space: pre-wrap !important; + } + .collection-filters { + ion-label { + display: flex; + justify-content: space-around; + + button { + .button-inner ion-icon { + color: #ddd; + } + + &.active { + background-color:#4a6770; + color: #fff; + + &:hover { + background-color: #547a86; + } + } + } + } + } ion-item.item-ios [item-left], ion-item.item-ios [item-right], ion-item.item-md [item-left], @@ -7,7 +38,7 @@ table-of-contents-accordion { ion-item.item-wp [item-right] { margin-left: 0; } - + ion-item.item-ios ion-icon[item-left] + .item-inner, ion-item.item-ios ion-icon[item-left] + .item-input, ion-item.item-md ion-icon[item-left] + .item-inner, ion-item.item-md ion-icon[item-left] + .item-input, @@ -15,12 +46,24 @@ table-of-contents-accordion { ion-item.item-wp ion-icon[item-left] + .item-input { margin-left: 8px; } - + ion-list.accordion-menu ion-item.header ion-icon.header-icon { will-change: transform; transition: transform 0.3s ease; } - + + .item.item-block.item-md.option { + margin: 0 !important; + } + + .options.recursive-suboptions { + margin-left: 20px; + } + + .accordion-menu .list .list-md { + background-color: #ecece4; + } + ion-list.accordion-menu ion-item.header ion-icon.header-icon.rotate { -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); @@ -28,18 +71,18 @@ table-of-contents-accordion { -o-transform: rotate(90deg); transform: rotate(90deg); } - + ion-list.accordion-menu div.options { will-change: height; transition: height 0.3s ease; overflow-y: hidden; height: 0; } - + ion-list.accordion-menu ion-item.item.item-block.item-ios.no-icon .item-inner { margin-left: 48px; } - + ion-list.accordion-menu ion-item.item.item-block.item-md.no-icon .item-inner, ion-list.accordion-menu ion-item.item.item-block.item-wp.no-icon .item-inner { margin-left: 40px; diff --git a/src/components/table-of-contents-accordion/table-of-contents-accordion.ts b/src/components/table-of-contents-accordion/table-of-contents-accordion.ts index e4591a09..2bc84c94 100644 --- a/src/components/table-of-contents-accordion/table-of-contents-accordion.ts +++ b/src/components/table-of-contents-accordion/table-of-contents-accordion.ts @@ -40,6 +40,7 @@ class InnerMenuOptionModel { facs_nr?: any; children?: Array; is_child?: boolean; + is_gallery?: boolean; itemId?: any; markdownID?: any; datafile?: any; @@ -47,11 +48,12 @@ class InnerMenuOptionModel { song_id?: any; amountOfParents?: any; openByDefault?: boolean; - collectionId?: any; loading?: boolean; children_id?: any; search_children_id?: any; important?: boolean; + description: any; + collapsed: boolean; public static fromMenuOptionModel( option: TocAccordionMenuOptionModel, @@ -76,6 +78,7 @@ class InnerMenuOptionModel { if (option.text) { innerMenuOptionModel.text = option.text; + innerMenuOptionModel.description = option.description; } else if (option.title) { innerMenuOptionModel.text = option.title; } @@ -84,6 +87,8 @@ class InnerMenuOptionModel { innerMenuOptionModel.page_nr = option.page_nr || null; innerMenuOptionModel.facs_nr = option.facs_nr || null; + innerMenuOptionModel.is_gallery = option.is_gallery || false; + if (option.itemId) { innerMenuOptionModel.itemId = option.itemId; @@ -139,12 +144,16 @@ class InnerMenuOptionModel { option.children.forEach(subItem => { const innerSubItem = InnerMenuOptionModel.fromMenuOptionModel(subItem, innerMenuOptionModel, true, searchingTocItem); - + innerSubItem.collapsed = subItem.collapsed; if (innerSubItem.text) { if (storeChildren) { - childrenToc[innerMenuOptionModel.children_id].push(innerSubItem); + if ( childrenToc[innerMenuOptionModel.children_id].indexOf(innerSubItem) === -1 ) { + childrenToc[innerMenuOptionModel.children_id].push(innerSubItem); + } } else { - innerMenuOptionModel.subOptions.push(innerSubItem); + if ( innerMenuOptionModel.subOptions.indexOf(innerSubItem) === -1 ) { + innerMenuOptionModel.subOptions.push(innerSubItem); + } } } @@ -154,7 +163,6 @@ class InnerMenuOptionModel { innerSubItem.parent.selected = true; innerSubItem.parent.expanded = true; } - }); } @@ -181,10 +189,11 @@ export class TableOfContentsAccordionComponent { toc: Array, searchTocItem?: Boolean, searchPublicationId?: Number, + searchItemId?: String, searchTitle?: String }) { if (value && value.toc && value.toc.length > 0) { - if (value.searchTocItem) { + if (value.searchTocItem !== undefined && value.searchTocItem === true) { this.searchingForTocItem = true; } this.menuOptions = value.toc; @@ -194,7 +203,9 @@ export class TableOfContentsAccordionComponent { // Map the options to our internal models this.menuOptions.forEach(option => { const innerMenuOption = InnerMenuOptionModel.fromMenuOptionModel(option, null, false, value.searchTocItem); - this.collapsableItems.push(innerMenuOption); + if ( this.collapsableItems.indexOf(innerMenuOption) === -1 && innerMenuOption.type !== 'folder' ) { + this.collapsableItems.push(innerMenuOption); + } // Check if there's any option marked as selected if (option.selected) { @@ -214,15 +225,16 @@ export class TableOfContentsAccordionComponent { console.log('found menu item'); } - if (value.searchTocItem) { + if (value.searchTocItem !== undefined && value.searchTocItem) { // Find toc item and open its parents - if (value.searchPublicationId && value.searchTitle) { - this.findTocByPubAndTitle(this.collapsableItems, value.searchPublicationId, value.searchTitle); + if (value.searchItemId) { + value.searchItemId = String(value.searchItemId).replace('_nochapter', '').replace(':chapterID', ''); + this.findTocByPubOnly(this.collapsableItems, value.searchItemId); this.events.publish('typesAccordion:change', { expand: true }); - } else if (value.searchPublicationId) { - this.findTocByPubOnly(this.collapsableItems, value.searchPublicationId); + } else if (value.searchPublicationId && value.searchTitle) { + this.findTocByPubAndTitle(this.collapsableItems, value.searchPublicationId, value.searchTitle); this.events.publish('typesAccordion:change', { expand: true }); @@ -239,9 +251,9 @@ export class TableOfContentsAccordionComponent { } } } - this.searchingForTocItem = false; } + this.activeMenuTree = this.collapsableItems; } @Input('settings') @@ -257,6 +269,7 @@ export class TableOfContentsAccordionComponent { @Input() showBackButton?: Boolean; @Input() isMarkdown?: Boolean; @Input() isGallery?: Boolean; + @Input() open: Boolean; @Output() selectOption = new EventEmitter(); currentItem: GeneralTocItem; @@ -265,13 +278,33 @@ export class TableOfContentsAccordionComponent { searchTocItemInAccordionByTitle = false; tocItemSearchChildrenCounter = 0; foundTocItem = false; - coverSelected: boolean; titleSelected: boolean; + introductionSelected: boolean; + coverSelected: boolean; root: any; hasCover: boolean; + hasTitle: boolean; hasIntro: boolean; playmanTraditionPageInMusicAccordion = false; playmanTraditionPageID = '03-03'; + + chronologicalOrderActive: boolean; + thematicOrderActive = true; + alphabethicOrderActive: boolean; + + visibleactiveMenuTree = []; + visibleTitleStack = []; + + chronologicalactiveMenuTree = []; + chronologicalTitleStack = []; + + alphabeticalactiveMenuTree: any[]; + alphabeticalTitleStack: any[]; + + activeMenuTree = []; + + sortableLetters = []; + constructor( public platform: Platform, public events: Events, @@ -284,27 +317,39 @@ export class TableOfContentsAccordionComponent { public userSettingsService: UserSettingsService, public translate: TranslateService ) { + this.collectionId = JSON.stringify(this.collectionId); + + this.registerEventListeners(); this.setConfigs(); // Handle the redirect event this.events.subscribe(SideMenuRedirectEvent, (data: SideMenuRedirectEventData) => { this.updateSelectedOption(data); }); + try { + this.sortableLetters = this.config.getSettings('settings.sortableLetters'); + } catch (e) { + this.sortableLetters = []; + } + this.events.subscribe('SelectedItemInMenu', (menu) => { if (this.collectionId !== menu.menuID && this.currentOption) { this.currentOption.selected = false; this.currentOption = null; this.cdRef.detectChanges(); + } else { + console.log('this.collectionId', this.collectionId); } }); - this.coverSelected = false; this.titleSelected = false; + this.introductionSelected = false; + this.coverSelected = false; try { - this.hasCover = this.config.getSettings('HasCover'); + this.hasTitle = this.config.getSettings('HasTitle'); } catch (e) { - this.hasCover = false; + this.hasTitle = false; } try { @@ -313,10 +358,19 @@ export class TableOfContentsAccordionComponent { this.hasIntro = false; } - if ( this.hasCover ) { - this.coverSelected = true; - } else if ( this.hasIntro ) { + try { + this.hasCover = this.config.getSettings('HasCover'); + } catch (e) { + this.hasCover = false; + } + + const currentPage = String(window.location.href); + if ( currentPage.includes('publication-introduction') ) { + this.introductionSelected = true; + } else if ( currentPage.includes('publication-title') ) { this.titleSelected = true; + } else if ( currentPage.includes('publication-cover') ) { + this.coverSelected = true; } this.events.subscribe('tableOfContents:findMarkdownTocItem', (data) => { @@ -351,19 +405,142 @@ export class TableOfContentsAccordionComponent { this.unSelectSelectedTocItemEventListener(); } + constructAlphabeticalTOC(data) { + this.alphabeticalactiveMenuTree = []; + this.alphabeticalTitleStack = []; + const list = this.flattenList(data.tocItems); + + for (const child of list) { + if (child.type !== 'section_title') { + this.alphabeticalactiveMenuTree.push(child); + } + } + + this.alphabeticalactiveMenuTree.sort( + (a, b) => + (a.text !== undefined && b.text !== undefined) ? + ((String(a.text).toUpperCase() < String(b.text).toUpperCase()) ? -1 : + (String(a.text).toUpperCase() > String(b.text).toUpperCase()) ? 1 : 0) : 0 + ); + this.storage.set('toc_alfabetical_' + data['collectionID'], this.alphabeticalactiveMenuTree); + } + + constructChronologialTOC(data) { + this.chronologicalactiveMenuTree = []; + this.chronologicalTitleStack = []; + + const list = this.flattenList(data.tocItems); + + for (const child of list) { + if (child.date && child.type !== 'section_title') { + this.chronologicalactiveMenuTree.push(child); + } + } + + this.chronologicalactiveMenuTree.sort((a, b) => (a.date < b.date) ? -1 : (a.date > b.date) ? 1 : 0); + let prevYear = ''; + + const itemArray = []; + let childItems = []; + for ( let i = 0; i < this.chronologicalactiveMenuTree.length; i++) { + const item = this.chronologicalactiveMenuTree[i]; + const currentYear = String(item['date']).slice(0, 4); + if ( prevYear === '' ) { + prevYear = currentYear; + itemArray.push({type: 'section_title', text: prevYear, subOptions: []}); + } + + if ( prevYear !== currentYear ) { + itemArray[itemArray.length - 1].subOptions = childItems; + itemArray[itemArray.length - 1].childrenCount = true; + childItems = []; + prevYear = currentYear; + itemArray.push({type: 'section_title', text: prevYear}); + } + childItems.push(this.chronologicalactiveMenuTree[i]); + } + if ( itemArray.length > 0 ) { + itemArray[itemArray.length - 1].subOptions = childItems; + itemArray[itemArray.length - 1].childrenCount = true; + } else { + itemArray[0] = {}; + itemArray[0].subOptions = childItems; + itemArray[0].childrenCount = true; + } + this.chronologicalactiveMenuTree = itemArray; + this.storage.set('toc_chronological_' + data['collectionID'], this.chronologicalactiveMenuTree); + } + + flattenList(data) { + data.childrenCount = 0; + let list = [data]; + if (!data.children) { + return list; + } + for (const child of data.children) { + list = list.concat(this.flattenList(child)); + } + return list; + } + + registerEventListeners() { + this.events.subscribe('tableOfContents:loaded', (data) => { + this.storage.get('toc_alfabetical_' + data['collectionID']).then((toc) => { + if ( toc === null || data['collectionID'] !== toc['collectionID'] ) { + this.constructAlphabeticalTOC(data); + } else { + this.alphabeticalactiveMenuTree = toc; + } + }); + this.storage.get('toc_chronologial_' + data['collectionID']).then((toc) => { + if ( toc === null || data['collectionID'] !== toc['collectionID'] ) { + this.constructChronologialTOC(data); + } else { + this.chronologicalactiveMenuTree = toc; + } + }); + }); + } + + setActiveSortingType(e) { + const thematic = e.target.id === 'thematic' || e.target.parentElement.parentElement.id === 'thematic'; + const alphabetic = e.target.id === 'alphabetical' || e.target.parentElement.parentElement.id === 'alphabetical'; + const chronological = e.target.id === 'chronological' || e.target.parentElement.parentElement.id === 'chronological'; + + if (thematic) { + this.alphabethicOrderActive = false; + this.chronologicalOrderActive = false; + this.thematicOrderActive = true; + } else if (alphabetic) { + this.alphabethicOrderActive = true; + this.chronologicalOrderActive = false; + this.thematicOrderActive = false; + } else if (chronological) { + this.alphabethicOrderActive = false; + this.chronologicalOrderActive = true; + this.thematicOrderActive = false; + } + } + ngOnChanges(about) { if ( Array.isArray(about) ) { this.menuOptions = about; this.collapsableItems = new Array(); + this.activeMenuTree = []; let foundSelected = false; // Map the options to our internal models this.menuOptions.forEach(option => { const innerMenuOption = InnerMenuOptionModel.fromMenuOptionModel(option, null, false, false); - this.collapsableItems.push(innerMenuOption); + if ( this.collapsableItems.indexOf(innerMenuOption) === -1 ) { + this.collapsableItems.push(innerMenuOption); + } + if ( this.activeMenuTree.indexOf(innerMenuOption) === -1 ) { + this.activeMenuTree.push(innerMenuOption); + } // Check if there's any option marked as selected - if (option.selected) { + if (option.selected !== undefined && option.selected === true) { this.selectedOption = innerMenuOption; } else if (innerMenuOption.childrenCount) { innerMenuOption.subOptions.forEach(subItem => { @@ -411,15 +588,20 @@ export class TableOfContentsAccordionComponent { if (!data) { return; } + this.titleSelected = false; + this.introductionSelected = false; + this.coverSelected = false; this.currentOption = null; - if ( this.hasCover ) { - this.coverSelected = true; - } else if ( this.hasIntro ) { + if ( data.selected === 'title' ) { this.titleSelected = true; + } else if ( data.selected === 'cover' ) { + this.coverSelected = true; + } else { + this.introductionSelected = true; } this.unSelectAllItems(this.collapsableItems); - this.cdRef.detectChanges(); + // this.cdRef.detectChanges(); }); } @@ -498,9 +680,7 @@ export class TableOfContentsAccordionComponent { for (const item of list) { if (item.subOptions && item.subOptions.length) { this.findTocByPubOnly(item.subOptions, publicationID); - } else if ( - item.publication_id && - (String(item.publication_id) === String(publicationID) || Number(item.publication_id) === Number(publicationID)) + } else if ((String(item.itemId) === String(publicationID) || Number(item.publication_id) === Number(publicationID)) ) { item.selected = true; this.currentOption = item; @@ -513,8 +693,10 @@ export class TableOfContentsAccordionComponent { ngOnDestroy() { this.events.unsubscribe(SideMenuRedirectEvent); - this.events.unsubscribe('tableOfContents:unSelectSelectedTocItem'); this.events.unsubscribe('SelectedItemInMenu'); + this.events.unsubscribe('tableOfContents:findMarkdownTocItem'); + this.events.unsubscribe('tableOfContents:loaded'); + this.events.unsubscribe('tableOfContents:unSelectSelectedTocItem'); } // Send the selected option to the caller component @@ -536,15 +718,17 @@ export class TableOfContentsAccordionComponent { if (this.isMarkdown) { this.selectMarkdown(item); - } else if (this.isGallery) { + } else if (item.is_gallery) { this.selectGallery(item); - } else { + } else if ( item.itemId !== undefined ) { + this.storage.set('currentTOCItem', item); const params = {root: this.options, tocItem: item, collection: {title: item.text}}; const nav = this.app.getActiveNavs(); - this.coverSelected = false; this.titleSelected = false; + this.introductionSelected = false; + this.coverSelected = false; if (item.url) { params['url'] = item.url; @@ -604,7 +788,10 @@ export class TableOfContentsAccordionComponent { this.events.publish('title-logo:show', false); } + console.log('Opening read from TableOfContentsAccordionComponent.openFirstPage()'); nav[0].setRoot('read', params); + } else { + this.storage.set('currentTOCItem', item); } } @@ -652,7 +839,8 @@ export class TableOfContentsAccordionComponent { openIntroduction() { const params = {root: this.root, tocItem: null, collection: {title: 'Introduction'}}; params['collectionID'] = this.collectionId; - this.titleSelected = true; + this.introductionSelected = true; + this.titleSelected = false; this.coverSelected = false; const nav = this.app.getActiveNavs(); if (this.platform.is('mobile')) { @@ -666,13 +854,29 @@ export class TableOfContentsAccordionComponent { const params = {root: this.root, tocItem: null, collection: {title: 'Title Page'}}; params['collectionID'] = this.collectionId; params['firstItem'] = '1'; - this.coverSelected = true; + this.titleSelected = true; + this.coverSelected = false; + this.introductionSelected = false; + const nav = this.app.getActiveNavs(); + if (this.platform.is('mobile')) { + nav[0].push('title-page', params); + } else { + nav[0].setRoot('title-page', params); + } + } + + openCoverPage() { + const params = {root: this.root, tocItem: null, collection: {title: 'Cover Page'}}; + params['collectionID'] = this.collectionId; + params['firstItem'] = '1'; this.titleSelected = false; + this.coverSelected = true; + this.introductionSelected = false; const nav = this.app.getActiveNavs(); if (this.platform.is('mobile')) { - nav[0].push('cover', params); + nav[0].push('cover-page', params); } else { - nav[0].setRoot('cover', params); + nav[0].setRoot('cover-page', params); } } @@ -701,8 +905,18 @@ export class TableOfContentsAccordionComponent { exit() { this.collectionId = null; this.collectionName = null; + + this.events.publish('exitActiveCollection'); + + this.activeMenuTree = []; + this.collapsableItems = []; + const nav = this.app.getActiveNavs(); nav[0].setRoot('EditionsPage', [], {animate: false, direction: 'back', animation: 'ios-transition'}); + + this.alphabethicOrderActive = false; + this.chronologicalOrderActive = false; + this.thematicOrderActive = false; } /** @@ -721,8 +935,20 @@ export class TableOfContentsAccordionComponent { targetOption.subOptions = searchChildrenToc[targetOption.search_children_id]; } } - // Toggle the selected option - targetOption.expanded = !targetOption.expanded; + + if ( targetOption.collapsed === undefined || String(targetOption.collapsed) === '' ) { + // collapsed is inverted expanded + targetOption.collapsed = targetOption.expanded; + targetOption.expanded = !targetOption.expanded; + } else { + // Toggle the selected option + targetOption.expanded = targetOption.collapsed; + targetOption.collapsed = !targetOption.expanded; + } + + if ( targetOption.itemId !== undefined ) { + this.select(targetOption); + } } // Reset the entire menu @@ -730,11 +956,12 @@ export class TableOfContentsAccordionComponent { this.collapsableItems.forEach(option => { if (!option.selected) { option.expanded = false; + option.collapsed = true; } if (option.childrenCount) { option.subOptions.forEach(subItem => { - if (subItem.selected) { + if (subItem.selected || subItem['collapsed'] === false) { // Expand the parent if any of // its childs is selected subItem.parent.expanded = true; diff --git a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.html b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.html index 82801711..f8dd28d0 100644 --- a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.html +++ b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.html @@ -1,17 +1,33 @@ - -
{{titleStack[0]}}
-
- + +
{{titleStack[0]}}
+ + + +
{{ "Read.Back" | translate }}
+
+ + + + + +
+

{{ "Read.TitlePage.Title" | translate }}

- +

{{ "Read.Introduction.Title" | translate }}

- +

{{item.text}}

@@ -19,7 +35,7 @@
- + {{item.text}} diff --git a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.scss b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.scss index cc8f1b2f..7ea068ec 100644 --- a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.scss +++ b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.scss @@ -17,4 +17,26 @@ table-of-contents-drilldown-menu { white-space: normal; font-size: 1rem !important; } + + .collection-filters { + ion-label { + display: flex; + justify-content: space-around; + + button { + .button-inner ion-icon { + color: #ddd; + } + + &.active { + background-color:#4a6770; + color: #fff; + + &:hover { + background-color: #547a86; + } + } + } + } + } } diff --git a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.ts b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.ts index edb9e936..fb42ff03 100644 --- a/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.ts +++ b/src/components/table-of-contents-drilldown-menu/table-of-contents-drilldown-menu.ts @@ -6,6 +6,9 @@ import { IntroductionPage } from '../../pages/introduction/introduction'; import { Storage } from '@ionic/storage'; import { TableOfContentsCategory, GeneralTocItem } from '../../app/models/table-of-contents.model'; import { TableOfContentsService } from '../../app/services/toc/table-of-contents.service'; +import { ConfigService, } from '@ngx-config/core'; +import { TocItem } from '../../app/models/toc-item.model'; + /** * Class for the TableOfContentsDrilldownMenuComponent component. @@ -19,8 +22,20 @@ import { TableOfContentsService } from '../../app/services/toc/table-of-contents }) export class TableOfContentsDrilldownMenuComponent { root: TableOfContentsCategory[] | any; - menuStack: any[]; + visibleMenuStack = []; + menuStack = []; + thematicMenuStack = []; + chronologicalMenuStack = []; + chronologicalTitleStack = []; + alphabeticalMenuStack: any[]; + + chronologicalOrderActive: boolean; + thematicOrderActive = true; + alphabethicOrderActive: boolean; + titleStack: string[]; + thematicTitleStack: any[]; + alphabeticalTitleStack: any[]; errorMessage: string; currentItem: GeneralTocItem; collectionId: string; @@ -28,7 +43,11 @@ export class TableOfContentsDrilldownMenuComponent { titleText: string; introText: string; coverSelected: boolean; - titleSelected: boolean; + introductionSelected: boolean; + + sortableLetters = []; + letterView = false; + visibleTitleStack = []; constructor( private events: Events, @@ -36,14 +55,19 @@ export class TableOfContentsDrilldownMenuComponent { private app: App, public platform: Platform, protected storage: Storage, - public translate: TranslateService + public translate: TranslateService, + private config: ConfigService, ) { // this.open = this.action === 'open' ? true : false; this.registerEventListeners(); const nav = this.app.getActiveNavs(); this.getTOCItem(); this.coverSelected = false; - this.titleSelected = false; + this.introductionSelected = false; + } + + ionViewWillEnter() { + this.registerEventListeners(); } getTOCItem() { @@ -56,111 +80,198 @@ export class TableOfContentsDrilldownMenuComponent { }); } - registerEventListeners() { - this.events.subscribe('tableOfContents:loaded', (data) => { - this.getTOCItem(); - this.root = data.tocItems.children; - this.menuStack = []; + setActiveSortingType(e) { + const thematic = e.target.id === 'thematic' || e.target.parentElement.parentElement.id === 'thematic'; + const alphabetic = e.target.id === 'alphabetical' || e.target.parentElement.parentElement.id === 'alphabetical'; + const chronological = e.target.id === 'chronological' || e.target.parentElement.parentElement.id === 'chronological'; + + if (thematic) { + this.alphabethicOrderActive = false; + this.chronologicalOrderActive = false; + this.thematicOrderActive = true; + } else if (alphabetic) { + this.alphabethicOrderActive = true; + this.chronologicalOrderActive = false; + this.thematicOrderActive = false; + } else if (chronological) { + this.alphabethicOrderActive = false; + this.chronologicalOrderActive = true; + this.thematicOrderActive = false; + } + } - if ( data.tocItems.children !== undefined ) { - this.menuStack.push(data.tocItems.children); - } else { - this.menuStack.push(data.tocItems); - } + constructAlphabeticalTOC(data) { + this.alphabeticalMenuStack = []; + this.alphabeticalTitleStack = []; + const list = data.tocItems.children; - this.collectionId = data.tocItems.collectionId; - this.collectionName = data.tocItems.text; - - this.titleStack = []; - this.titleStack.push(data.tocItems.text || ''); - - this.titleText = ''; - this.introText = ''; - - let pushToMenu = true; - for (let menuItemIndex = 0; menuItemIndex <= (this.menuStack[0].length - 1); menuItemIndex++) { - const menuItem = this.menuStack[0][menuItemIndex]; - if ( menuItem.children !== undefined ) { - for (let menuSubitemIndex = 0; menuSubitemIndex <= (menuItem.children.length - 1); menuSubitemIndex++) { - const menuSubitem = menuItem.children[menuSubitemIndex]; - if ( this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children !== undefined ) { - for (let menuSubSubitemIndex = 0; - menuSubSubitemIndex <= (this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children.length - 1); - menuSubSubitemIndex++) { - const menuSubSubitem = menuSubitem.children[menuSubSubitemIndex]; - if ( menuSubSubitem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { - this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children[menuSubSubitemIndex].selected = true; - if ( pushToMenu ) { - this.menuStack.push(this.menuStack[0][menuItemIndex].children); - this.menuStack.push(this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children); - this.titleStack.push(this.menuStack[0][menuItemIndex].children[menuSubitemIndex].text); - pushToMenu = false; - } - } - } - } else { - if ( menuSubitem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { - this.menuStack[0][menuItemIndex].children[menuSubitemIndex].selected = true; + for (const child of list) { + if (child.date && child.type !== 'section_title') { + this.alphabeticalMenuStack.push(child); + } + } + + this.alphabeticalMenuStack.sort((a, b) => + (a.text.toUpperCase() < b.text.toUpperCase()) ? -1 : (a.text.toUpperCase() > b.text.toUpperCase()) ? 1 : 0); + } + + constructChronologialTOC(data) { + this.chronologicalMenuStack = []; + this.chronologicalTitleStack = []; + const list = data.tocItems.children; + + for (const child of list) { + if (child.date && child.type !== 'section_title') { + this.chronologicalMenuStack.push(child); + } + } + + this.chronologicalMenuStack.sort((a, b) => (a.date < b.date) ? -1 : (a.date > b.date) ? 1 : 0); + + } + + flattenList(data) { + const list = [data]; + if (!data.children) { + return list; + } + + for (const child of data.children) { + list.concat(this.flattenList(child)); + } + return list; + } + + constructToc(data) { + this.getTOCItem(); + this.root = data.tocItems.children; + this.menuStack = []; + + if ( data.tocItems.children !== undefined ) { + this.menuStack.push(data.tocItems.children); + } else { + this.menuStack.push(data.tocItems); + } + + try { + this.sortableLetters = this.config.getSettings('settings.sortableLetters'); + } catch (e) { + this.sortableLetters = null; + console.log(e); + } + + this.collectionId = data.tocItems.collectionId; + this.collectionName = data.tocItems.text; + + this.titleStack = []; + this.titleStack.push(data.tocItems.text || ''); + + this.titleText = ''; + this.introText = ''; + + let pushToMenu = true; + for (let menuItemIndex = 0; menuItemIndex <= (this.menuStack[0].length - 1); menuItemIndex++) { + const menuItem = this.menuStack[0][menuItemIndex]; + if ( menuItem.children !== undefined ) { + for (let menuSubitemIndex = 0; menuSubitemIndex <= (menuItem.children.length - 1); menuSubitemIndex++) { + const menuSubitem = menuItem.children[menuSubitemIndex]; + if ( this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children !== undefined ) { + for (let menuSubSubitemIndex = 0; + menuSubSubitemIndex <= (this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children.length - 1); + menuSubSubitemIndex++) { + const menuSubSubitem = menuSubitem.children[menuSubSubitemIndex]; + if ( menuSubSubitem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { + this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children[menuSubSubitemIndex].selected = true; if ( pushToMenu ) { this.menuStack.push(this.menuStack[0][menuItemIndex].children); - this.titleStack.push(this.menuStack[0][menuItemIndex].text); + this.menuStack.push(this.menuStack[0][menuItemIndex].children[menuSubitemIndex].children); + this.titleStack.push(this.menuStack[0][menuItemIndex].children[menuSubitemIndex].text); pushToMenu = false; } } } - } - } else { - if ( menuItem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { - this.menuStack[0][menuItemIndex].selected = true; - if ( pushToMenu ) { - this.menuStack.push(this.menuStack[0]); - this.titleStack.push(this.menuStack[0][menuItemIndex].text); - pushToMenu = false; + } else { + if ( menuSubitem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { + this.menuStack[0][menuItemIndex].children[menuSubitemIndex].selected = true; + if ( pushToMenu ) { + this.menuStack.push(this.menuStack[0][menuItemIndex].children); + this.titleStack.push(this.menuStack[0][menuItemIndex].text); + pushToMenu = false; + } } } } + } else { + if ( menuItem.itemId === data.tocItems.selectedCollId + '_' + data.tocItems.selectedPubId) { + this.menuStack[0][menuItemIndex].selected = true; + if ( pushToMenu ) { + this.menuStack.push(this.menuStack[0]); + this.titleStack.push(this.menuStack[0][menuItemIndex].text); + pushToMenu = false; + } + } } + } + + if ( data.tocItems.coverSelected !== undefined ) { + this.coverSelected = true; + } else { + this.coverSelected = false; + } + + if ( data.tocItems.introductionSelected !== undefined ) { + this.introductionSelected = true; + } else { + this.introductionSelected = false; + } + + this.translate.get('Read.TitlePage.Title').subscribe( + retData => { + this.titleText = retData; + }, error => { - if ( data.tocItems.coverSelected !== undefined ) { - this.coverSelected = true; - } else { - this.coverSelected = false; } + ); + this.translate.get('Read.Introduction.Title').subscribe( + retData => { + this.introText = retData; + }, error => { - if ( data.tocItems.titleSelected !== undefined ) { - this.titleSelected = true; - } else { - this.titleSelected = false; } + ); + }; - this.translate.get('Read.TitlePage.Title').subscribe( - retData => { - this.titleText = retData; - }, error => { - } - ); - this.translate.get('Read.Introduction.Title').subscribe( - retData => { - this.introText = retData; - }, error => { + registerEventListeners() { + this.events.subscribe('tableOfContents:loaded', (data) => { + console.log('tableOfContents:loaded in table-of-contents-drilldown.ts'); - } - ); + this.constructToc(data); + this.constructAlphabeticalTOC(data); + this.constructChronologialTOC(data); + + + this.visibleMenuStack = this.menuStack; }); } + ngOnDestroy() { + this.events.unsubscribe('tableOfContents:loaded'); + } drillDown(item) { - this.menuStack.push(item.children); - this.titleStack.push(item.text); + this.visibleMenuStack.push(item.children); + this.visibleTitleStack.push(item.text); } unDrill() { - if ( this.menuStack.length === 2 && this.menuStack[0] === this.menuStack[1] ) { + if ( this.visibleMenuStack.length === 2 && this.visibleMenuStack[0] === this.visibleMenuStack[1] ) { this.exit(); } - this.menuStack.pop(); - this.titleStack.pop(); + // document.getElementById('contentMenu').classList.add('menu-enabled'); + // document.getElementById('tableOfContentsMenu').classList.remove('menu-enabled'); + + this.visibleMenuStack.pop(); + this.visibleTitleStack.pop(); } open(item, type?, html?) { @@ -171,19 +282,19 @@ export class TableOfContentsDrilldownMenuComponent { const nav = this.app.getActiveNavs(); this.coverSelected = false; - this.titleSelected = false; + this.introductionSelected = false; - for (let menuItemIndex = 0; menuItemIndex < this.menuStack.length; menuItemIndex++) { - const menuItem = this.menuStack[menuItemIndex]; + for (let menuItemIndex = 0; menuItemIndex < this.visibleMenuStack.length; menuItemIndex++) { + const menuItem = this.visibleMenuStack[menuItemIndex]; for (let menuSubitemIndex = 0; menuSubitemIndex < menuItem.length; menuSubitemIndex++) { const menuSubitem = menuItem[menuSubitemIndex]; - this.menuStack[menuItemIndex].selected = false; + this.visibleMenuStack[menuItemIndex].selected = false; if ( menuSubitem.itemId === item.itemId) { - this.menuStack[menuItemIndex].selected = true; - this.menuStack[menuItemIndex][menuSubitemIndex].selected = true; + this.visibleMenuStack[menuItemIndex].selected = true; + this.visibleMenuStack[menuItemIndex][menuSubitemIndex].selected = true; } else { - this.menuStack[menuItemIndex].selected = false; - this.menuStack[menuItemIndex][menuSubitemIndex].selected = false; + this.visibleMenuStack[menuItemIndex].selected = false; + this.visibleMenuStack[menuItemIndex][menuSubitemIndex].selected = false; } } } @@ -201,6 +312,9 @@ export class TableOfContentsDrilldownMenuComponent { const parts = item.itemId.split('_'); params['collectionID'] = parts[0]; params['publicationID'] = parts[1]; + if ( parts[2] !== undefined ) { + params['chapterID'] = parts[2]; + } } if ( this.currentItem['facsimilePage'] ) { @@ -234,7 +348,7 @@ export class TableOfContentsDrilldownMenuComponent { } else { this.events.publish('title-logo:show', false); } - + console.log('Opening read from TableOfContentsDrilldownMenuComponent.open()'); nav[0].setRoot('read', params); try { @@ -260,9 +374,9 @@ export class TableOfContentsDrilldownMenuComponent { params['firstItem'] = '1'; const nav = this.app.getActiveNavs(); if (this.platform.is('mobile')) { - nav[0].push('cover', params); + nav[0].push('title-page', params); } else { - nav[0].setRoot('cover', params); + nav[0].setRoot('title-page', params); } } @@ -275,11 +389,14 @@ export class TableOfContentsDrilldownMenuComponent { } private exit() { + this.visibleMenuStack = []; + this.visibleTitleStack = []; this.menuStack = []; - this.titleStack = []; this.collectionId = null; this.collectionName = null; const nav = this.app.getActiveNavs(); nav[0].setRoot('EditionsPage', [], {animate: false, direction: 'back', animation: 'ios-transition'}); + this.events.publish('exitedTo', 'EditionsPage'); } } + diff --git a/src/components/text-changer/text-changer.html b/src/components/text-changer/text-changer.html index 4e7f6b14..86c93773 100644 --- a/src/components/text-changer/text-changer.html +++ b/src/components/text-changer/text-changer.html @@ -1,7 +1,15 @@ - - - - - - - \ No newline at end of file +
+
+ +
+

{{currentItemTitle}}

+
+ +
+
diff --git a/src/components/text-changer/text-changer.scss b/src/components/text-changer/text-changer.scss index 46120212..d3f18a69 100644 --- a/src/components/text-changer/text-changer.scss +++ b/src/components/text-changer/text-changer.scss @@ -1,5 +1,97 @@ text-changer { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + flex-grow: 2; .goLeft{ left: 0; } + .forwardBackward { + display: flex; + grid-template-columns: [first] 30% auto [last] 30%; + margin: auto; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + .forwardBackward__currentpage-name { + font-size: 15px!important; + margin: 0 auto; + font-weight: bold; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .forwardBackward__currentpage-name p{ + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .forwardBackward__previouspage-name, + .forwardBackward__nextpage-name { + vertical-align: super; + margin: 0 0.5rem; + &:hover { + text-shadow: 1px 0 0 currentColor; + } + } + .forwardButton, + .backwardButton { + display: flex; + align-items: center; + hyphens: auto; + background: transparent; + overflow: hidden; + &:hover .forwardBackward__previouspage-name, + &:hover .forwardBackward__nextpage-name { + text-shadow: 1px 0 0 currentColor; + } + .forwardBackward__nextpage-name, + .forwardBackward__previouspage-name { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + ion-icon, + .forwardBackward__currentpage-name, + .forwardBackward__nextpage-name, + .forwardBackward__previouspage-name { + font-size: 15px!important; + } + } + .forwardButtonDiv { + justify-content: flex-end; + } + .backwardButtonDiv { + } + } + @media screen and (max-width: 800px) { + .forwardBackward { + max-width: 100%; + padding: 0; + &__currentpage-name { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 70%; + } + } + } +} + +.text-changer-mobile-view { + .forwardBackward { + position: fixed; + max-width: 100vw; + width: 100vw; + z-index: 9999; + background-color: rgba(99,99,99, 0.9); + bottom: 0; + left: 0; + padding: 10px 1em; + transform: none; + height: 64px; + } } diff --git a/src/components/text-changer/text-changer.ts b/src/components/text-changer/text-changer.ts index 11b547cb..9c753a24 100644 --- a/src/components/text-changer/text-changer.ts +++ b/src/components/text-changer/text-changer.ts @@ -1,6 +1,7 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, ChangeDetectorRef } from '@angular/core'; import { Events, App, NavParams } from 'ionic-angular'; import { Storage } from '@ionic/storage'; +import { UserSettingsService } from '../../app/services/settings/user-settings.service'; /** * Generated class for the TextChangerComponent component. @@ -18,15 +19,26 @@ export class TextChangerComponent { @Input() recentlyOpenViews?: any; prevItem: any; nextItem: any; + lastNext: any; + lastPrev: any; + prevItemTitle: string; + nextItemTitle: string; + lastItem: boolean; + currentItemTitle: string; displayNext: Boolean = true; displayPrev: Boolean = true; + flattened: any; + currentToc: any; + constructor( public events: Events, public storage: Storage, public app: App, - public params: NavParams + public params: NavParams, + private userSettingsService: UserSettingsService, + private cf: ChangeDetectorRef ) { this.next(true).then(function(val) { this.displayNext = val; @@ -34,21 +46,45 @@ export class TextChangerComponent { this.previous(true).then(function(val) { this.displayPrev = val; }.bind(this)) + this.flattened = []; + this.getTocItemId(); } ngOnInit() { + this.setupData(); } - async previous(test?: boolean) { - if ( this.legacyId === undefined ) { - this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID'); + setupData() { + try { + const c_id = this.legacyId.split('_')[0]; + const toc = this.storage.get('toc_' + c_id); + toc.then(val => { + this.currentToc = val; + if (val && val.children) { + for (let i = 0; i < val.children.length; i++) { + if (val.children[i].itemId.split('_')[1] === c_id) { + this.currentItemTitle = val.children[i].text; + this.storage.set('currentTOCItemTitle', this.currentItemTitle); + this.nextItemTitle = String(val.children[i + 1].text); + this.prevItemTitle = String(val.children[i - 1].text); + } + } + } + }).catch(err => console.error(err)); + } catch ( e ) { + } + } + + async previous(test?: boolean) { + this.getTocItemId(); const c_id = this.legacyId.split('_')[0]; await this.storage.get('toc_' + c_id).then((toc) => { - this.findItem(toc, 'prev'); + this.findNext(toc); }); if (this.prevItem !== undefined && test !== true) { + this.storage.set('currentTOCItem', this.prevItem); await this.open(this.prevItem); } else if (test && this.prevItem !== undefined) { return true; @@ -58,14 +94,13 @@ export class TextChangerComponent { } async next(test?: boolean) { - if ( this.legacyId === undefined ) { - this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID'); - } + this.getTocItemId(); const c_id = this.legacyId.split('_')[0]; await this.storage.get('toc_' + c_id).then((toc) => { - this.findItem(toc, 'next'); + this.findNext(toc); }); if (this.nextItem !== undefined && test !== true) { + this.storage.set('currentTOCItem', this.nextItem); await this.open(this.nextItem); } else if (test && this.nextItem !== undefined) { return true; @@ -74,68 +109,98 @@ export class TextChangerComponent { } } - findItem(toc, type?: string) { - if (!toc) { - return; + getTocItemId() { + if ( this.legacyId === undefined ) { + this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID') ; } - if (!toc.children && toc instanceof Array) { - for (let i = 0; i < toc.length; i ++) { - if (toc[i].itemId && toc[i].itemId === this.legacyId) { - if (type === 'next' && toc[i + 1]) { - if (toc[i + 1].type === 'subtitle') { - i = i + 1; - } - if (toc[i + 1] === undefined || i + 1 === toc.length) { - if ( (i + 1) === toc.length ) { - this.nextItem = null; - break; - } - } else { - this.nextItem = toc[i + 1]; - break; - } - } else if (type === 'prev' && toc[i - 1]) { - if (toc[i - 1].type === 'subtitle') { - i = i - 1; - } - if (toc[i - 1] === undefined || i === 0) { - if ( i === 0 ) { - this.prevItem = null; - break; - } - } else { - this.prevItem = toc[i - 1]; - break; - } - } - } + if ( this.params.get('chapterID') !== undefined && + String(this.legacyId).indexOf(this.params.get('chapterID')) === -1 && + String(this.params.get('chapterID')).indexOf('ch') >= 0 ) { + this.legacyId += '_' + this.params.get('chapterID'); + } + + if ( this.params.get('tocLinkId') !== undefined ) { + this.legacyId = this.params.get('tocLinkId'); + } + } + + findNext(toc) { + this.getTocItemId(); + // flatten the toc structure + if ( this.flattened.length === 0 ) { + this.flatten(toc); + } + // get the next id + let currentId = 0; + for (let i = 0; i < this.flattened.length; i ++) { + if ( this.flattened[i].itemId === this.legacyId ) { + currentId = i; + break; } - } else if (toc.children) { - const childs = toc.children; - for (let j = 0; j < childs.length; j ++) { - if (childs[j] && childs[j].itemId && childs[j].itemId === this.legacyId) { + } + let nextId, prevId = 0; + // last item + if ((currentId + 1) === this.flattened.length) { + nextId = 0; + } else { + nextId = currentId + 1; + } + + if (currentId === 0) { + prevId = this.flattened.length - 1; + } else { + prevId = currentId - 1; + } + + this.nextItem = this.flattened[nextId]; + this.nextItemTitle = String(this.nextItem.text); + this.prevItem = this.flattened[prevId]; + this.prevItemTitle = String(this.prevItem.text); + this.currentItemTitle = String(this.flattened[currentId].text); + this.storage.set('currentTOCItemTitle', this.currentItemTitle); + } + + flatten(toc) { + if ( toc.children ) { + for (let i = 0, count = toc.children.length; i < count; i++) { + if ( toc.children[i].itemId !== undefined && toc.children[i].itemId !== '') { + this.flattened.push(toc.children[i]); } - if (childs[j] && childs[j].children) { - this.findItem(childs[j].children, type); + this.flatten(toc.children[i]); + } + } + } + + findPrevTitle(toc, currentIndex, prevChild?) { + if ( currentIndex === 0 ) { + this.findPrevTitle(prevChild, prevChild.length); + } + for ( let i = currentIndex; i > 0; i-- ) { + if ( toc[i - 1] !== undefined ) { + if ( toc[i - 1].title !== 'subtitle' && toc[i - 1].title !== 'section_title' ) { + return toc[i - 1]; } } } } open(item) { - const params = {tocItem: item, collection: {title: item.text}}; + const params = {tocItem: item, collection: {title: item.itemId}}; const nav = this.app.getActiveNavs(); params['tocLinkId'] = item.itemId; const parts = item.itemId.split('_'); params['collectionID'] = parts[0]; params['publicationID'] = parts[1]; + if ( parts[2] !== undefined ) { + params['chapterID'] = parts[2]; + } if (this.recentlyOpenViews !== undefined && this.recentlyOpenViews.length > 0) { params['recentlyOpenViews'] = this.recentlyOpenViews; } - + console.log('Opening read from TextChanged.open()'); nav[0].setRoot('read', params); } diff --git a/src/components/title-logo/title-logo.ts b/src/components/title-logo/title-logo.ts index 5a45cb47..fe658f0e 100644 --- a/src/components/title-logo/title-logo.ts +++ b/src/components/title-logo/title-logo.ts @@ -48,4 +48,8 @@ export class TitleLogoComponent { this.subtitle = subTitle; }); } + ngOnDestroy() { + this.events.unsubscribe('title-logo:setTitle'); + this.events.unsubscribe('title-logo:collectionTitle'); + } } diff --git a/src/components/toc-menu/toc-menu.ts b/src/components/toc-menu/toc-menu.ts index 2713c4ff..8eb3cd25 100644 --- a/src/components/toc-menu/toc-menu.ts +++ b/src/components/toc-menu/toc-menu.ts @@ -76,6 +76,7 @@ export class TocMenuComponent { params['legacyId'] = item.id; const nav = this.app.getActiveNavs(); + console.log('Opening read from TocMenu.openRead()'); nav[0].setRoot('read', params); } diff --git a/src/components/top-menu/top-menu.html b/src/components/top-menu/top-menu.html index 4c6c7da5..601cf7e2 100644 --- a/src/components/top-menu/top-menu.html +++ b/src/components/top-menu/top-menu.html @@ -21,8 +21,14 @@ - + - - - - - - - + + + + + + @@ -60,6 +64,9 @@ + @@ -72,8 +79,13 @@ - + + + + + + + + + + +

{{'Suggestions' | translate}}

+
+ + {{groupKey | translate}} + + +
+
+
+ + + + + + + +

{{'ElasticSearch.Years' | translate}}

+ +
+
+ +
+ + + + + {{facetGroupKey | translate}} + + + + + + + + + {{facet.key | translate}} ({{facet.doc_count}}) + + + {{facet.key_as_string || facet.key}} + ({{facet.doc_count}}) + + + + + + + + + +

No results

+
+
+
+
+
+
+
+
+ + + + +

+ {{'ElasticSearch.Found' | translate}} {{total}} +

+
+ +

+ {{'ElasticSearch.NoHits' | translate}} +

+
+ + + + + {{'ElasticSearch.SortBy' | translate}} + + {{'ElasticSearch.Relevance' | translate}} + {{'ElasticSearch.OldestFirst' | translate}} + {{'ElasticSearch.NewestFirst' | translate}} + + + +
+ + + + + + + +
+ + {{groupKey | translate}} + + +
+
+ + + + +
+ +
+
+ + + + +
+ + +
+ + +

+ {{query}}: {{hit.count[query][0][hit.id]['term_freq']}} +

+

+ {{query}}: {{'ElasticSearch.NoWordHits' | translate}} +

+
+
+

{{getHeading(hit.source)}}

+

{{hit.type | translate}}, {{getSubHeading(hit.source)}}

+

+
+
+
+
+
+ + + + + + +
+
+
+ + diff --git a/src/pages/elastic-search/elastic-search.module.ts b/src/pages/elastic-search/elastic-search.module.ts new file mode 100644 index 00000000..929bd863 --- /dev/null +++ b/src/pages/elastic-search/elastic-search.module.ts @@ -0,0 +1,51 @@ +import { OccurrencesPageModule } from '../occurrences/occurrences.module'; +import { FilterPageModule } from '../filter/filter.module'; +import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { ElasticSearchPage } from './elastic-search'; +import { TableOfContentsModule } from '../../components/table-of-contents/table-of-contents.module'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { ComponentsModule } from '../../components/components.module'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { PipesModule } from '../../pipes/pipes.module'; +import { FilterPage } from '../filter/filter'; +import { OccurrenceService } from '../../app/services/occurrence/occurence.service'; +import { OccurrencesPage } from '../occurrences/occurrences'; +import { ElasticSearchService } from '../../app/services/elastic-search/elastic-search.service'; + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + ElasticSearchPage + ], + imports: [ + IonicPageModule.forChild(ElasticSearchPage), + TableOfContentsModule, + PipesModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }), + ComponentsModule, + FilterPageModule, + OccurrencesPageModule + ], + providers: [ + ElasticSearchService, + OccurrenceService + ], + entryComponents: [ + ElasticSearchPage + ] +}) +export class ElasticSearchPageModule {} diff --git a/src/pages/elastic-search/elastic-search.scss b/src/pages/elastic-search/elastic-search.scss new file mode 100644 index 00000000..800b4739 --- /dev/null +++ b/src/pages/elastic-search/elastic-search.scss @@ -0,0 +1,292 @@ +page-elastic-search { + .word_count span{ + display: inline-block; + padding-left: 1rem; + } + + .fixed-content, + .scroll-content { + margin-top: 40px !important; + } + + ion-icon.settings-icon.icon { + font-size: 1.8em; + } + + ion-item.hitItem:hover{ + background-color: rgba(color($colors, primary), 0.3) !important; + } + + ul { + list-style-type: none; + } + + .filter-button { + font-size: 2.8rem; + } + + .searchbar { + color: white; + } + + .info-toolbar { + width: calc(100% - 27%); + position: absolute; + right: 0; + } + + .hits-column { + width: calc(100% - 27%); + height: 100%; + position: absolute; + right: 0; + top: 75px; + } + + .infinite-scroll-wrapper { + width: calc(100% - 27%); + position: absolute; + right: 0; + bottom: 0; + } + + .date-histogram-wrapper { + border: 0.5px solid rgba(190, 183, 155, 0.4); + border-radius: 3px; + margin: 20px 0; + padding: 10px; + + .years-heading { + margin-bottom: -14px; + } + } + + .sort-by-wrapper { + + .input-wrapper { + justify-content: flex-end; + } + + .sort-by-label { + flex: 0; + } + + .sort-by-select { + width: 300px; + } + } + + .hits-heading { + text-decoration: underline; + } + + .segment-md .segment-button { + font-size: 1rem; + text-transform: none; + } + + .searchStatus { + color: color($colors, grey); + } + + ion-item.item.item-block.item-md { + .item-inner { + border-bottom: none; + } + } + + ion-item.item.item-block.item-md { + cursor: pointer; + + .itemHeading { + padding-left: 20px; + font-weight: bold; + color: color($colors, blackish); + } + + .highlight { + color: color($colors, blackish); + + em { + background-color: color($colors, primary); + color: white; + padding: 0.2rem 6px; + line-height: 2.4rem; + font-style: normal; + } + } + + // List of facets + ion-list.facets { + margin: 0 0 0 20px; + + ion-item.item.item-block.item-md.facet { + min-height: 1rem; + + ion-checkbox.checkbox-md { + margin: 5px; + } + } + } + } + + .searchContainer { + display: flex; + align-items: stretch; + } + + ion-icon.rotate.open { + transform: rotate(90deg); + transition: transform ease-in-out .15s; + } + + ion-icon.closed { + transition: transform ease-in-out .15s; + } + + ion-icon[name="refresh"], + ion-icon[name="close"] { + padding-bottom: 0 !important; + } + + .selectedFacetGroups { + .toolbar-background { + background-color: transparent; + } + + .toolbar-content { + margin: 25px 0; + } + + .toolbar-title { + color: #000; + } + + .selectedFacetButton { + font-size: 0.9rem !important; + height: 28px !important; + + ion-icon { + font-size: 1.5em !important; + } + } + } + + .facet-group-wrapper { + .facet-item { + margin: 5px 0; + padding-bottom: 1rem; + + &.Type, + &.Genre, + &.Collection { + border: 0.5px solid rgba(190, 183, 155, 0.4); + border-radius: 3px; + } + } + + .accordion-item { + margin: 0 !important; + + .facet { + padding: 0; + + ion-checkbox { + margin-right: 15px !important; + } + } + + ion-label { + font-weight: 600; + } + + .accordion-arrow { + font-size: 1.5em; + } + + .show-more-button { + font-size: .8rem !important; + height: 2rem !important; + letter-spacing: 1px; + } + } + } + + .hits-heading { + padding-left: 32px; + } + + .hits-scroll-content { + overflow: auto; + margin-top: 0; + height: 100% !important; + + .scroll-content::-webkit-scrollbar { + display: none !important; + } + + .scroll-content { + margin-top: 0 !important; + } + } + + .searchbar-wrapper { + position: relative; + margin: 10px 0; + + .searchInput { + resize: none; + height: 32px; + text-indent: 25px; + line-height: 1.3rem; + width: 100%; + border-radius: 2px; + padding: 6px 8px; + box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px; + border: none !important; + } + + .removableSearchInput { + width: calc(100% - 25px); + } + + ion-icon { + position: absolute; + } + + ion-icon.searchIcon { + font-size: 1.2rem !important; + top: 7px; + left: 14px; + } + + ion-icon.removeIcon { + cursor: pointer; + top: 6px; + right: 0; + font-size: 1.4rem !important; + } + } + + .add-search-bar-wrapper { + button { + height: 32px !important; + padding: 0 16px; + margin: auto 0; + font-size: .8rem !important; + letter-spacing: 1px; + } + } +} + +.alert-title { + font-size: 1rem !important; +} + +.alert-md .alert-tappable { + min-height: 3.4rem; +} + +.alert-radio-group { + padding: 16px; +} diff --git a/src/pages/elastic-search/elastic-search.ts b/src/pages/elastic-search/elastic-search.ts new file mode 100644 index 00000000..30322354 --- /dev/null +++ b/src/pages/elastic-search/elastic-search.ts @@ -0,0 +1,726 @@ +import { Component, ViewChild, ChangeDetectorRef } from '@angular/core' +// tslint:disable-next-line:max-line-length +import { IonicPage, NavController, NavParams, ModalController, App, Platform, + LoadingController, ToastController, Content, Events, ViewController } from 'ionic-angular' +import get from 'lodash/get' +import debounce from 'lodash/debounce' +import size from 'lodash/size' + +import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service' +import { LanguageService } from '../../app/services/languages/language.service' +import { ConfigService } from '@ngx-config/core' +import { TextService } from '../../app/services/texts/text.service' +import { SingleOccurrence } from '../../app/models/single-occurrence.model' +import { Storage } from '@ionic/storage' +import { UserSettingsService } from '../../app/services/settings/user-settings.service' +import { ElasticSearchService } from '../../app/services/elastic-search/elastic-search.service' +import { noUndefined } from '@angular/compiler/src/util' + +/* + +Specs: + +- To the left (probably): all the filters, so the user can decide what filters to use before +actually performing the search, in order to avoid getting results not relevant. + +- Since the filters are always visible, they could simply be updated with the number of results +in the different categories after the search is finished? + +- Results are shown in different tabs according to the following categories: + texts by the author ZT (reading texts, variants, manuscripts); + texts by editors (introductions, comments, title pages, other info material on the site); + results from the indexes (persons, places, literary works (results both from the name fields and the description fields)). + +- Filters include: + genre -> edition; + text type (reading text, variant, manuscript, comment, introduction, index); + time period -> decade -> year -> month -> date; + sender - receiver (for letters) + + +Use cases: + +The user might want to look at the word 'snömos', but only in prose and reading texts (to find out how it was used by the author in the +19th century). From the results she sees it occurs throughout the decades 1840-1880, so she can take a look directly at the one from 1881. +The user can then decide to look at only comments containing (i.e. explaining) this word; there are 5 of them (and they are all different). + +Another user searches for the name Ulla as free text, only in prose, and gets 2 results (one person). +Ulla as an index search in prose returns 3 different persons, occurring in total 8 times, because they are mentioned +in other ways than by their first name in the texts, but connected to index posts containing this first name. + +In general, the user of the advanced search might want to use some filters from the start, or at least later choose between the +results according to their different categorization, so it’s important to always keep an overview of what categories the results belong to. + +*/ + +interface SearchOptions { + done?: Function + initialSearch?: boolean +} + +/** + * Elastic search page. + */ +@IonicPage({ + name: 'elastic-search', + segment: 'elastic-search/:query', + defaultHistory: ['HomePage'] +}) +@Component({ + selector: 'page-elastic-search', + templateUrl: 'elastic-search.html' +}) +export class ElasticSearchPage { + + @ViewChild(Content) content: Content + @ViewChild('myInput') myInput; + + // Helper to loop objects + objectKeys = Object.keys + objectValues = Object.values + + loading = false + infiniteLoading = false + showFilter = true + queries: string[] = [''] + cleanQueries: string[] = [''] + hits: object[] = [] + termData: object[] = []; + hitsPerPage = 20 + aggregations: object = {} + facetGroups: FacetGroups = {} + selectedFacetGroups: FacetGroups = {} + suggestedFacetGroups: FacetGroups = {} + + showAllFacets = false; + showAllFor = {}; + + // -1 when there a search hasn't returned anything yet. + total = -1 + from = 0 + sort = '' + + range: TimeRange + + groupsOpenByDefault: any; + + debouncedSearch = debounce(this.search, 500) + + constructor( + public navCtrl: NavController, + public navParams: NavParams, + public semanticDataService: SemanticDataService, + protected langService: LanguageService, + protected config: ConfigService, + public modalCtrl: ModalController, + private app: App, + private platform: Platform, + protected textService: TextService, + public loadingCtrl: LoadingController, + public elastic: ElasticSearchService, + protected storage: Storage, + private toastCtrl: ToastController, + private userSettingsService: UserSettingsService, + private events: Events, + private cf: ChangeDetectorRef + ) { + console.log('constructing elastic search'); + + try { + this.hitsPerPage = this.config.getSettings('ElasticSearch.hitsPerPage') + } catch (e) { + console.error('Failed to load Elastic Search Page. Configuration error.', e) + } + try { + this.groupsOpenByDefault = this.config.getSettings('ElasticSearch.groupOpenByDefault') + } catch (e) { + console.error('Failed to load set facet groups open by default. Configuration error.', e) + } + } + + private getParamsData() { + try { + const query = this.navParams.get('query') + if (query !== ':query') { + this.queries[0] = query + } + } catch (e) { + console.log('Problems parsing query parameters...'); + } + } + + ionViewDidLoad() { + this.search({initialSearch: true}) + // Open type by default + setTimeout(() => { + const facetGroups = Object.keys(this.facetGroups); + facetGroups.forEach(facetGroup => { + const openGroup = facetGroup.toLowerCase(); + switch (openGroup) { + case 'type': + if (this.groupsOpenByDefault.type) { + const facetListType = document.querySelector('.facetList-' + facetGroup); + try { + facetListType.style.height = '100%'; + const facetArrowType = document.querySelector('#arrow-1'); + facetArrowType.classList.add('open', 'rotate'); + } catch ( e ) { + + } + } + break; + case 'genre': + if (this.groupsOpenByDefault.genre) { + const facetListGenre = document.querySelector('.facetList-' + facetGroup); + try { + facetListGenre.style.height = '100%'; + const facetArrowGenre = document.querySelector('#arrow-2'); + facetArrowGenre.classList.add('open', 'rotate'); + } catch ( e ) { + + } + } + break; + case 'collection': + if (this.groupsOpenByDefault.collection) { + const facetListCollection = document.querySelector('.facetList-' + facetGroup); + try { + facetListCollection.style.height = '100%'; + const facetArrowCollection = document.querySelector('#arrow-3'); + facetArrowCollection.classList.add('open', 'rotate'); + } catch ( e ) { + + } + } + break; + default: + const facetListRest = document.querySelector('.facetList-' + facetGroup); + try { + facetListRest.style.setProperty('height', '0px'); + const facetArrowRest = document.querySelector('#arrow-' + facetGroup); + facetArrowRest.classList.add('closed', 'rotate'); + } catch (e) { + } + break; + } + }) + }, 300); + } + + ionViewDidEnter() { + try { + (window).ga('set', 'page', 'Elastic Search') + (window).ga('send', 'pageview') + } catch ( e ) { + + } + } + + ionViewWillLeave() { + this.events.publish('ionViewWillLeave', this.constructor.name) + } + + ionViewWillEnter() { + console.log('will enter elastic search'); + this.events.publish('ionViewWillEnter', this.constructor.name) + this.events.publish('tableOfContents:unSelectSelectedTocItem', true) + this.getParamsData(); + } + + open(hit) { + this.events.publish('searchHitOpened', hit) + const params = { tocItem: null, fetch: true, collection: { title: hit.source.TitleIndexed } }; + const path = hit.source.path; + const filename = path.split('/').pop(); + + // 199_18434_var_6251.xml This should preferrably be implemented via elastic data instead of path + const collection_id = filename.split('_').shift(); // 199 + const var_ms_id = filename.replace('.xml', '').split('_').pop(); // 6251 + + params['tocLinkId'] = collection_id + '_' + hit.source.publication_id; + params['collectionID'] = collection_id; + params['publicationID'] = hit.source.publication_id; + params['chapterID'] = 'nochapter'; + + params['facs_id'] = 'not'; + params['facs_nr'] = 'infinite'; + params['song_id'] = 'nosong'; + params['search_title'] = this.queries[0]; + params['matches'] = this.queries; + params['views'] = []; + // : facs_id / : facs_nr / : song_id / : search_title / : urlviews + // not / infinite / nosong / searchtitle / established & variations & facsimiles + + + switch (hit.source.xml_type) { + case 'est': { + params['urlviews'] = 'established'; + params['views'].push({type: 'established'}) + break; + } + case 'ms': { + params['urlviews'] = 'manuscripts'; + params['views'].push({type: 'manuscripts', id: var_ms_id}); + break; + } + case 'com': { + params['urlviews'] = 'comments'; + params['views'].push({type: 'comments'}) + break; + } + case 'var': { + params['urlviews'] = 'variations'; + params['views'].push({type: 'variations', id: var_ms_id}); + + break; + } + case 'inl': { + params['urlviews'] = 'introduction'; + params['views'].push({type: 'introduction', id: var_ms_id}); + + break; + } + case 'tit': { + params['urlviews'] = 'title'; + params['views'].push({type: 'title', id: var_ms_id}); + + break; + } + default: { + params['urlviews'] = 'established'; + params['views'].push({type: 'established'}) + // statements; + break; + } + } + if (hit.source.xml_type !== 'tit') { + params['selectedItemInAccordion'] = false; + this.app.getRootNav().push('read', params); + } else { + this.app.getRootNav().push('title-page', params); + } + } + + /** + * https://stackoverflow.com/questions/46991497/how-properly-bind-an-array-with-ngmodel-in-angular-4 + */ + trackByIdx(index: number): number { + return index + } + + /** + * Triggers a new search and clears selected facets. + */ + onQueryChange() { + this.autoExpandSearchfields() + this.reset() + this.loading = true + this.debouncedSearch() + this.cf.detectChanges() + } + + /** + * Triggers a new search with selected facets. + */ + onFacetsChanged() { + this.cf.detectChanges() + this.reset() + this.search() + } + + /** + * Triggers a new search with selected years. + */ + onRangeChange(from: number, to: number) { + if (from && to) { + // Certain date range + this.range = {from, to} + + this.cf.detectChanges() + this.reset() + this.search() + + } else if (!from && !to) { + // All time + this.range = null + this.cf.detectChanges() + this.reset() + this.search() + + } else { + // Only one year selected, so do nothing + this.range = null + } + } + + /** + * Sorting changed so trigger new query. + */ + onSortByChanged() { + this.reset() + this.search() + } + + /** + * Resets search results. + */ + reset() { + this.hits = [] + this.from = 0 + this.total = -1 + this.suggestedFacetGroups = {} + } + + /** + * Immediately execute a search. + * Use debouncedSearch to wait for additional key presses when use types. + */ + private search({ done, initialSearch }: SearchOptions = {}) { + console.log(`search from ${this.from} to ${this.from + this.hitsPerPage}`) + + this.loading = true + + // Fetch hits + this.elastic.executeSearchQuery({ + queries: this.queries, + highlight: { + fields: { + textDataIndexed: { number_of_fragments: 2 }, + }, + }, + from: this.from, + size: initialSearch ? 0 : this.hitsPerPage, + facetGroups: this.facetGroups, + range: this.range, + sort: this.parseSortForQuery(), + }) + .subscribe((data: any) => { + this.loading = false + this.total = data.hits.total.value + + // Append new hits to this.hits array. + Array.prototype.push.apply(this.hits, data.hits.hits.map((hit: any) => ({ + type: hit._source.xml_type, + source: hit._source, + highlight: hit.highlight, + id: hit._id + }))) + + this.cleanQueries = []; + if (this.queries.length > 0 && this.queries[0] !== undefined && this.queries[0].length > 0 ) { + this.queries.forEach(term => { + this.cleanQueries.push(term.toLowerCase().replace(/[^a-zA-ZåäöÅÄÖ[0-9]+/g, '')); + this.analyticsEvent('term', term); + }); + for (const item in data.hits.hits) { + this.elastic.executeTermQuery(this.cleanQueries, [data.hits.hits[item]['_id']]) + .subscribe((termData: any) => { + this.termData = termData; + const elementsIndex = this.hits.findIndex(element => element['id'] === data.hits.hits[item]['_id'] ) + this.hits[elementsIndex] = {...this.hits[elementsIndex], count: termData} + }) + } + } + + if (done) { + done() + } + }) + + // Fetch aggregation data for facets. + this.elastic.executeAggregationQuery({ + queries: this.queries, + facetGroups: this.facetGroups, + range: this.range, + }) + .subscribe((data: any) => { + console.log('aggregation data', data) + + this.populateFacets(data.aggregations) + }) + + // Fetch suggestions + // TODO: Currently only works with the first search field. + if (this.queries[0] && this.queries[0].length > 3) { + this.elastic.executeSuggestionsQuery({ + query: this.queries[0], + }) + .subscribe((data: any) => { + console.log('suggestions data', data) + this.populateSuggestions(data.aggregations) + }) + } + } + + private parseSortForQuery() { + if (!this.sort) { + return + } + + const [key, direction] = this.sort.split('.') + return [{ [key]: direction }] + } + + hasMore() { + return this.total > this.from + this.hitsPerPage + } + + analyticsEvent(type, term) { + try { + (window).ga('send', 'event', { + eventCategory: 'Search', + eventLabel: 'ElasticSearch - ' + type, + eventAction: String(term), + eventValue: 10 + }); + } catch ( e ) { + } + } + + /** + * TODO: Make infinite scroll should work with the super long facets column. + * Current workaround for this is to increate hitsPerPage to 20. + */ + loadMore(e) { + this.infiniteLoading = true + this.from += this.hitsPerPage + + // Search and let ion-infinite-scroll know that it can re-enable itself. + this.search({ + done: () => { + this.infiniteLoading = false + e.complete() + }, + }) + } + + canShowHits() { + return (!this.loading || this.infiniteLoading) && (this.queries[0] || this.range || this.hasSelectedFacets()) + } + + hasSelectedFacets() { + return Object.values(this.facetGroups).some(facets => Object.values(facets).some(facet => facet.selected)) + } + + hasSelectedFacetsByGroup(groupKey: string) { + return size(this.selectedFacetGroups[groupKey]) > 0 + } + + hasSelectedNormalFacets() { + return Object.keys(this.facetGroups).some(facetGroupKey => + facetGroupKey !== 'Type' && facetGroupKey !== 'Years' && Object.values(this.facetGroups[facetGroupKey]).some(facet => facet.selected) + ) + } + + hasFacets(facetGroupKey: string) { + return size(this.facetGroups[facetGroupKey]) > 0 + } + + hasSuggestedFacetsByGroup(groupKey: string) { + return size(this.suggestedFacetGroups[groupKey]) > 0 + } + + hasSuggestedFacets() { + return Object.values(this.suggestedFacetGroups).some(facets => size(facets) > 0) + } + + getFacets(facetGroupKey: string): Facet[] { + const facets = this.facetGroups[facetGroupKey] + return facets ? Object.values(facets) : [] + } + + /** + * Toggles facet on/off. Note that the selected state is controlled by the ion-checkbox + * so it should not be modified here. + */ + updateFacet(facetGroupKey: string, facet: Facet) { + const facets = this.facetGroups[facetGroupKey] || {} + facets[facet.key] = facet + this.facetGroups[facetGroupKey] = facets + + this.updateSelectedFacets(facetGroupKey, facet) + + this.onFacetsChanged() + this.analyticsEvent('facet', String(facet.key)); + } + + selectSuggestedFacet(facetGroupKey: string, facet: Facet) { + this.suggestedFacetGroups = {} + this.queries = [''] + + facet.selected = true + this.updateFacet(facetGroupKey, facet) + } + + unselectFacet(facetGroupKey: string, facet: Facet) { + facet.selected = false + this.updateFacet(facetGroupKey, facet) + } + + private updateSelectedFacets(facetGroupKey: string, facet: Facet) { + const facetGroup = this.selectedFacetGroups[facetGroupKey] || {} + + // Set or delete facet from selected facets + if (facet.selected) { + facetGroup[facet.key] = facet + } else { + delete facetGroup[facet.key] + } + + // Set or delete facet group from selected facet groups + if (size(facetGroup) === 0) { + delete this.selectedFacetGroups[facetGroupKey] + } else { + this.selectedFacetGroups[facetGroupKey] = facetGroup + } + } + + /** + * Populate facets data using the search results aggregation data. + */ + private populateFacets(aggregations: AggregationsData) { + // Get aggregation keys that are ordered in config.json. + this.elastic.getAggregationKeys().forEach(facetGroupKey => { + const newFacets = this.convertAggregationsToFacets(aggregations[facetGroupKey]) + if (this.facetGroups[facetGroupKey]) { + Object.entries(this.facetGroups[facetGroupKey]).forEach(([facetKey, existingFacet]: [string, any]) => { + const newFacet = newFacets[facetKey] + if (newFacet) { + existingFacet.doc_count = newFacet.doc_count + } else if (this.hasSelectedFacetsByGroup(facetGroupKey)) { + // Unselected facets aren't updating because the terms bool.filter in the query + // prevents unselected aggregations from appearing in the results. + // TODO: Fix this by separating search and aggregation query. + } else { + delete this.facetGroups[facetGroupKey][facetKey]; + // existingFacet.doc_count = 0 + } + }) + Object.entries(newFacets).forEach(([facetKey, existingFacet]: [string, any]) => { + if ( this.facetGroups[facetGroupKey][facetKey] === undefined ) { + this.facetGroups[facetGroupKey][facetKey] = existingFacet; + } + }); + } else { + this.facetGroups[facetGroupKey] = newFacets + } + }) + } + + /** + * Populate suggestions data using the search results aggregation data. + */ + private populateSuggestions(aggregations: AggregationsData) { + Object.entries(aggregations).forEach(([aggregationKey, value]: [string, any]) => { + this.suggestedFacetGroups[aggregationKey] = this.convertAggregationsToFacets(value) + }) + } + + /** + * Convert aggregation data to facets data. + */ + private convertAggregationsToFacets(aggregation: AggregationData): Facets { + const facets = {} + // Get buckets from either unfiltered or filtered aggregation. + const buckets = aggregation.buckets || aggregation.filtered.buckets + + buckets.forEach((facet: Facet) => { + facets[facet.key] = facet + }) + return facets + } + + getPublicationName(source: any) { + return get(source, 'publication_data[0].pubname') + } + + getTitle(source: any) { + return (source.TitleIndexed || source.name || '').trim() + } + + getGenre(source: any) { + return get(source, 'publication_data[0].genre', source.collection_name) + } + + private formatISO8601DateToLocale(date: string) { + return date && new Date(date).toLocaleDateString('fi-FI') + } + + private getDate(source: any) { + return get(source, 'publication_data[0].original_publication_date', this.formatISO8601DateToLocale(source.orig_date_certain)) + } + + private filterEmpty(array: any[]) { + return array.filter(str => str).join(', ') + } + + getHeading(source: any) { + switch (source.type) { + case 'brev': + return this.filterEmpty([this.getTitle(source), this.getPublicationName(source)]) + + default: + return this.filterEmpty([this.getTitle(source), this.getPublicationName(source)]) + } + } + + getSubHeading(source) { + return this.filterEmpty([ + this.getGenre(source), + source.type !== 'brev' && this.getDate(source) + ]) + } + + getEllipsisString(str: string, max = 15) { + if (!str || str.length <= max) { + return str + } else { + return str.substr(0, max) + '...' + } + } + + openAccordion(e, group) { + const facet = document.getElementById('facetList-' + group); + const arrow = document.getElementById('arrow-' + group); + + arrow.classList.toggle('rotate'); + + if (arrow.classList.contains('open')) { + facet.style.height = '0px'; + arrow.classList.add('closed'); + arrow.classList.remove('open'); + } else { + facet.style.height = '100%'; + arrow.classList.add('open'); + arrow.classList.remove('closed'); + } + this.cf.detectChanges(); + } + + addSearchField() { + this.queries.push('') + } + + removeSearchField(i) { + this.queries.splice(i, 1) + } + + autoExpandSearchfields() { + const inputs: NodeListOf = document.querySelectorAll('.searchInput') + + for (let i = 0; i < inputs.length; i++) { + const borderTop = measure(inputs[i], 'border-top-width') + const borderBottom = measure(inputs[i], 'border-bottom-width') + + inputs[i].style.height = '' + inputs[i].style.height = borderTop + inputs[i].scrollHeight + borderBottom + 'px' + } + + function measure(elem: Element, property) { + return parseInt( + window.getComputedStyle(elem, null) + .getPropertyValue(property) + .replace(/px$/, '')) + } + } +} diff --git a/src/pages/facsimile-zoom/facsimile-zoom.html b/src/pages/facsimile-zoom/facsimile-zoom.html index 830a795e..7295c39b 100644 --- a/src/pages/facsimile-zoom/facsimile-zoom.html +++ b/src/pages/facsimile-zoom/facsimile-zoom.html @@ -1,18 +1,21 @@ - +
- - +
+ *ngIf="!userSettingsService.isMobile() && !userSettingsService.isTablet() && images.length > 1"> {{"Read.Facsimiles.Page" | translate}} ({{"Read.Facsimiles.Of" | translate}} {{(images)?images.length:0}}): @@ -52,22 +55,44 @@
+ [src]="images[activeImage]" + class="facsimile-zoom-image" + [ngStyle]="{'transform':'scale('+zoom+') rotate('+angle+'deg) translate3d(' + prevX + 'px, ' + prevY + 'px, 0px)'}" + (wheel)="onMouseWheel($event)" + (pan)="handlePanEvent($event)" + (mouseup)="onMouseUp($event)" + (mousedown)="$event.preventDefault();" + draggable="true" + > - + [src]="backSide(images[activeImage])" + class="facsimile-zoom-image" + [ngStyle]="{'transform':'scale('+zoom+') rotate('+angle+'deg) translate3d(' + prevX + 'px, ' + prevY + 'px, 0px)'}" + (wheel)="onMouseWheel($event)" + (pan)="handlePanEvent($event)" + (mouseup)="onMouseUp($event)" + (mousedown)="$event.preventDefault();" + draggable="true" + > +
- +
- +
@@ -76,4 +101,4 @@
- \ No newline at end of file + diff --git a/src/pages/facsimile-zoom/facsimile-zoom.scss b/src/pages/facsimile-zoom/facsimile-zoom.scss index fec9bcc9..616c2087 100644 --- a/src/pages/facsimile-zoom/facsimile-zoom.scss +++ b/src/pages/facsimile-zoom/facsimile-zoom.scss @@ -1,7 +1,7 @@ -page-facsimile-zoom { +page-facsimile-zoom { .facsimile-zoom-image{ position: relative !important; - transform-origin: center top; + transform-origin: center center; top: 5em; } /*ion-fab{ @@ -29,8 +29,8 @@ page-facsimile-zoom { } .scroll{ - height:100%; - width:100%; + height:100%; + width:100%; position: relative; } @@ -90,13 +90,13 @@ page-facsimile-zoom { width: 40%; } .div_arrow_left{ - + } .div_plus_minus{ - + } .div_arrow_right{ - + } background-color: rgba(2,2,2,0.5); @@ -129,7 +129,8 @@ page-facsimile-zoom { } .facsimile-zoom-image { max-width: 100%; - max-height: 100vh; + // 72px is the height of the toolbar + max-height: calc(100vh - 72px); margin: auto; } @@ -180,4 +181,11 @@ page-facsimile-zoom { flex-direction: row; padding-bottom: 1rem; } -} \ No newline at end of file + + img { + cursor: move; + // https://github.com/hammerjs/hammer.js/issues/1050#issuecomment-267595556 + // Need to use !important because something is trying to override the value. + touch-action: none !important; + } +} diff --git a/src/pages/facsimile-zoom/facsimile-zoom.ts b/src/pages/facsimile-zoom/facsimile-zoom.ts index 349450f6..53640da0 100644 --- a/src/pages/facsimile-zoom/facsimile-zoom.ts +++ b/src/pages/facsimile-zoom/facsimile-zoom.ts @@ -18,15 +18,21 @@ import { UserSettingsService } from '../../app/services/settings/user-settings.s }) export class FacsimileZoomModalPage { - images: any; + images: any[] = []; backsides: any; descriptions: any; activeImage: any; zoom = 1.0; + angle = 0; + latestDeltaX = 0 + latestDeltaY = 0 + prevX = 0 + prevY = 0 facsUrl = ''; facsimilePagesInfinite = false; backside = false; + facsSize: number; facsPage: any; facsNumber = 0; manualPageNumber = 1; @@ -41,6 +47,13 @@ export class FacsimileZoomModalPage { this.backsides = []; } + rotate() { + this.angle += 90; + if ( this.angle >= 360 ) { + this.angle = 0; + } + } + cancel() { this.viewCtrl.dismiss(this.viewCtrl); } @@ -53,6 +66,8 @@ export class FacsimileZoomModalPage { } ionViewWillLoad() { + this.facsSize = this.navParams.get('facsSize'); + if (this.navParams.get('facsimilePagesInfinite')) { this.facsimilePagesInfinite = true; this.facsUrl = this.navParams.get('facsUrl'); @@ -66,8 +81,10 @@ export class FacsimileZoomModalPage { this.descriptions = []; } this.activeImage = this.navParams.get('activeImage'); + this.manualPageNumber = this.activeImage this.doAnalytics(String(this.images[this.activeImage])); } + try { this.backsides = this.navParams.get('backsides'); if ( this.backsides === undefined ) { @@ -176,15 +193,58 @@ export class FacsimileZoomModalPage { this.doAnalytics(String(this.images[this.activeImage])); } - handleSwipeEvent(event) { - if ( event.direction === 2 ) { - this.next(); - } else if ( event.direction === 4 ) { - this.previous(); + backSide(url) { + return url.replace('.jpg', 'B.jpg'); + } + + handlePanEvent(event) { + const img = event.target; + // Store latest zoom adjusted delta. + // NOTE: img must have touch-action: none !important; + // otherwise deltaX and deltaY will give wrong values on mobile. + this.latestDeltaX = event.deltaX / this.zoom + this.latestDeltaY = event.deltaY / this.zoom + + // Get current position from last position and delta. + let x = this.prevX + this.latestDeltaX + let y = this.prevY + this.latestDeltaY + + if (this.angle === 90) { + const tmp = x; + x = y; + y = tmp; + y = y * -1; + } else if (this.angle === 180) { + y = y * -1; + x = x * -1; + } else if (this.angle === 270) { + const tmp = x; + x = y; + y = tmp; + x = x * -1; + } + + if (img !== null) { + img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + x + 'px, ' + y + 'px, 0px)'; } } - backSide(url) { - return url.replace('.jpg', 'B.jpg'); + onMouseUp(e) { + // Update the previous position on desktop by adding the latest delta. + this.prevX += this.latestDeltaX + this.prevY += this.latestDeltaY + } + + onMouseWheel(e) { + const img = e.target; + if (e.deltaY > 0) { + this.zoomIn(); + img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.prevX + 'px, ' + + this.prevY + 'px, 0px)'; + } else { + this.zoomOut(); + img.style.transform = 'rotate(' + this.angle + 'deg) scale(' + this.zoom + ') translate3d(' + this.prevX + 'px, ' + + this.prevY + 'px, 0px)'; + } } } diff --git a/src/pages/filter/filter.html b/src/pages/filter/filter.html index 569bb890..537a58d7 100644 --- a/src/pages/filter/filter.html +++ b/src/pages/filter/filter.html @@ -11,7 +11,7 @@ --> - + - + + +

{{"Occurrences.NoInfoFound" | translate}}

- {{occupation}} - + {{occupation}} - {{title}} ({{date_born}}–{{date_deceased}}) ({{"Occurrences.latitude" | translate}} {{latitude}}, @@ -32,7 +34,7 @@

- + @@ -67,7 +69,7 @@

{{place_of_birth}} - + {{"Occurrences.type" | translate}} @@ -83,12 +85,54 @@

{{source}} - + + + {{"Occurrences.publisher" | translate}} + + + {{publisher}} + + + + + {{"Occurrences.published_year" | translate}} + + + {{published_year}} + + + + + {{"Occurrences.isbn" | translate}} + + + {{isbn}} + + + + + {{"Occurrences.authors" | translate}} + + + +
{{author.full_name}}
+
+
+
+ + + {{"Occurrences.journal" | translate}} + + + {{journal}} + + + {{"Occurrences.description" | translate}} - +
{{description}}
@@ -100,7 +144,7 @@

-
{{i+1}}.
+
{{i+1}}.
{{article.description}}
{{"Occurrences.download" | translate}}
@@ -115,24 +159,46 @@

- - {{text.collectionName}} - {{text.displayName}} - -
- {{"Occurrences.Song" | translate}}: {{text.description}} -
-
- Facsimile page: {{text.publication_facsimile_page}} -
-
- - - - - - -
+ {{"Occurrences.Title" | translate}} + + + + + + {{collection.name}} + + + + + + + +

{{publication.occurrences[0].displayName}} ({{publication.occurrences.length}} {{"Occurrences.OccurrencesSmall" | translate}})

+

{{publication.occurrences[0].displayName}}

+ +
+ {{"Occurrences.Facsimile" | translate}}: {{publication.occurrences[0].publication_facsimile_page}} +
+
+ +
+ {{"Occurrences.Song" | translate}}: {{publication.occurrences[0].description}} +
+
+ + + + + + +
+
+
+
+ +
 
+
+
diff --git a/src/pages/occurrences/occurrences.scss b/src/pages/occurrences/occurrences.scss index d43ffd4c..1e7d9f48 100644 --- a/src/pages/occurrences/occurrences.scss +++ b/src/pages/occurrences/occurrences.scss @@ -3,6 +3,20 @@ page-occurrences { background-color: white; } + + + .grid{ + padding: 0%; + } + + .grid ion-col{ + padding-left: 0%; + } + + .description { + padding-left: 0.3rem; + } + .close-button { font-size: 5.2rem; margin-right: 1.2rem; @@ -29,7 +43,7 @@ page-occurrences { margin-left: 1.2rem; div{ font-size: 1.4rem !important; - } + } } .collectionName{ @@ -49,7 +63,7 @@ page-occurrences { .map_hidden{ display: none; } - + .map_icon{ position: absolute; right: 0.3em; @@ -79,12 +93,12 @@ page-occurrences { .media_image_wrapper img{ width: auto !important; } - + .media_description { margin-top: 1rem; margin-bottom: 2rem; } - + .toolbar-title{ text-align: left; } @@ -102,4 +116,37 @@ page-occurrences { .article_link{ margin-bottom: 1rem; } -} + .forward-arrow-span{ + float: right; + } + .forward-arrow-span ion-icon{ + font-size: 1.4rem; + } + .collection_name_label ion-label{ + white-space: normal; + padding-top: 0.8rem; + } + .publication_name_label ion-label{ + white-space: normal; + margin-top: middle; + } + .occurrences_list ion-label{ + margin: auto; + } + .occurrences_list_group { + // padding-bottom: 2rem; + } + .list_of_occurrences_item ion-label{ + // margin-top: 0px; + } + + .description_header{ + + } + .row{ + border-bottom: color($colors, secondary) 1px solid; + } + ion-label{ + color: $text-color; + } + } diff --git a/src/pages/occurrences/occurrences.ts b/src/pages/occurrences/occurrences.ts index ad0b0def..0a6ffbb3 100644 --- a/src/pages/occurrences/occurrences.ts +++ b/src/pages/occurrences/occurrences.ts @@ -9,6 +9,7 @@ import { Occurrence, OccurrenceType, OccurrenceResult } from '../../app/models/o import { SingleOccurrence } from '../../app/models/single-occurrence.model'; import { TranslateService } from '@ngx-translate/core'; import leaflet from 'leaflet'; +import { BootstrapOptions } from '@angular/core/src/application_ref'; /** * Generated class for the OccurrencesPage page. @@ -31,6 +32,7 @@ export class OccurrencesPage { title: string; occurrenceResult: OccurrenceResult; texts: any[] = []; + groupedTexts: any[] = []; longitude: Number = null; latitude: Number = null; city: string = null; @@ -43,9 +45,19 @@ export class OccurrencesPage { country: string = null; date_born: string = null; date_deceased: string = null; + publisher: string = null; + published_year: string = null; + journal: string = null; + isbn: string = null; + authors: Array = []; filterToggle: Boolean = true; singleOccurrenceType: string = null; galleryOccurrenceData: any = []; + hideTypeAndDescription = false; + isLoading: Boolean = true; + infoLoading: Boolean = true; + showPublishedStatus: Number = 2; + noData: Boolean = false; objectType = ''; @@ -70,8 +82,17 @@ export class OccurrencesPage { public viewCtrl: ViewController, private events: Events ) { - this.occurrenceResult = navParams.get('occurrenceResult'); - this.title = this.occurrenceResult.name; + this.occurrenceResult = this.navParams.get('occurrenceResult'); + if ( this.occurrenceResult !== undefined ) { + this.init(); + } else if ( this.navParams.get('type') && this.navParams.get('id') ) { + this.getObjectData(this.navParams.get('type'), this.navParams.get('id')); + } + } + + init() { + this.groupedTexts = []; + this.title = (this.occurrenceResult.name === undefined) ? this.occurrenceResult['full_name'] : this.occurrenceResult.name; this.longitude = (Number(this.occurrenceResult.longitude) !== 0 ) ? Number(this.occurrenceResult.longitude) : null; this.latitude = (Number(this.occurrenceResult.latitude) !== 0 ) ? Number(this.occurrenceResult.latitude) : null; this.city = this.occurrenceResult.city; @@ -82,10 +103,31 @@ export class OccurrencesPage { this.source = this.occurrenceResult.source; this.description = this.occurrenceResult.description; this.country = this.occurrenceResult.country; + + this.publisher = this.occurrenceResult.publisher; + this.published_year = this.occurrenceResult.published_year; + this.journal = this.occurrenceResult.journal; + this.isbn = this.occurrenceResult.isbn; + this.authors = this.occurrenceResult.author_data; + + if ( this.authors[0] === undefined || this.authors[0]['id'] === undefined ) { + this.authors = []; + } + this.date_born = (this.occurrenceResult.date_born !== undefined && this.occurrenceResult.date_born !== null) ? - String(this.occurrenceResult.date_born).split('-')[0] : null; + String(this.occurrenceResult.date_born).split('-')[0].replace(/^0+/, '') : null; this.date_deceased = (this.occurrenceResult.date_deceased !== undefined && this.occurrenceResult.date_deceased !== null) ? - String(this.occurrenceResult.date_deceased).split('-')[0] : null; + String(this.occurrenceResult.date_deceased).split('-')[0].replace(/^0+/, '') : null; + + let bcTranslation = 'BC'; + this.translate.get('BC').subscribe( + translation => { + bcTranslation = translation; + }, error => { } + ); + if ( this.date_deceased !== null ) { + this.date_deceased = this.date_deceased + '' + ((String(this.occurrenceResult.date_deceased).includes('BC')) ? ' ' + bcTranslation : ''); + } try { this.singleOccurrenceType = this.config.getSettings('SingleOccurrenceType'); @@ -93,11 +135,29 @@ export class OccurrencesPage { this.singleOccurrenceType = null; } + try { + this.hideTypeAndDescription = this.config.getSettings('Occurrences.HideTypeAndDescription'); + } catch (e) { + this.hideTypeAndDescription = false; + } + + try { + this.showPublishedStatus = this.config.getSettings('Occurrences.ShowPublishedStatus'); + } catch (e) { + this.showPublishedStatus = 2; + } + + this.setObjectType(); + this.getOccurrenceTexts(this.occurrenceResult); + this.getMediaData(); + this.getArticleData(); + this.getGalleryOccurrences(); + try { try { (window).ga('send', 'event', { eventCategory: 'Occurrence', - eventLabel: this.navParams.get('objectType'), + eventLabel: this.objectType, eventAction: String(this.title), eventValue: 10 }); @@ -106,12 +166,6 @@ export class OccurrencesPage { } catch ( e ) { } - - this.getOccurrenceTexts(navParams.get('occurrenceResult')); - this.setObjectType(); - this.getMediaData(); - this.getArticleData(); - this.getGalleryOccurrences(); } ionViewWillLeave() { @@ -127,6 +181,29 @@ export class OccurrencesPage { } } + getObjectData(type, id) { + this.infoLoading = true; + this.semanticDataService.getSingleObjectElastic(type, id).subscribe( + data => { + this.infoLoading = false; + this.objectType = type; + const personsTmp = []; + if ( data.hits.hits.length <= 0 ) { + this.noData = true; + } else { + this.occurrenceResult = data.hits.hits[0]['_source']; + } + if ( type === 'work' ) { + this.occurrenceResult.id = this.occurrenceResult['man_id']; + this.occurrenceResult.description = this.occurrenceResult['reference']; + this.occurrenceResult.name = this.occurrenceResult['title']; + } + this.init(); + }, + err => {console.error(err); this.infoLoading = false; } + ); + } + getMediaData() { if (!this.objectType.length) { return; @@ -234,10 +311,15 @@ export class OccurrencesPage { getOccurrenceTexts(occurrenceResult) { this.texts = []; - - const occurrences: Occurrence[] = occurrenceResult.occurrences; - for (const occurence of occurrences) { - this.getOccurrence(occurence); + this.groupedTexts = []; + let occurrences: Occurrence[] = []; + if ( occurrenceResult.occurrences !== undefined ) { + occurrences = occurrenceResult.occurrences; + for (const occurence of occurrences) { + this.getOccurrence(occurence); + } + } else { + this.getOccurrences(occurrenceResult.id); } } @@ -300,6 +382,10 @@ export class OccurrencesPage { } } + objectKeys(obj) { + return Object.keys(obj); +} + openGallery(data) { let type = this.objectType; if ( type === 'places' ) { @@ -343,7 +429,7 @@ export class OccurrencesPage { const newOccurrence = new SingleOccurrence(); let fileName = occurrence.original_filename; - if ( occurrence.original_filename === null ) { + if ( occurrence.original_filename === undefined || occurrence.original_filename === null ) { fileName = occurrence.collection_id + '_' + occurrence['publication_id'] + '.xml'; } @@ -356,9 +442,44 @@ export class OccurrencesPage { occurrence.collection_id + '_' + occurrence.publication_id : newOccurrence.linkID.split('_' + type)[0]; newOccurrence.collectionName = occurrence.collection_name; newOccurrence.displayName = (occurrence.publication_name !== null) ? occurrence.publication_name : occurrence.collection_name; + this.setOccurrenceTree(newOccurrence, occurrence); + this.texts.push(newOccurrence); } + setOccurrenceTree(newOccurrence, occurrence) { + let foundCollection = false; + for ( let i = 0; i < this.groupedTexts.length; i++ ) { + if ( this.groupedTexts[i].collection_id === occurrence.collection_id) { + foundCollection = true; + let foundPublication = false; + for ( let j = 0; j < this.groupedTexts[i].publications.length; j++ ) { + if ( this.groupedTexts[i].publications[j].publication_id === occurrence.publication_id) { + this.groupedTexts[i].publications[j].occurrences.push(newOccurrence); + foundPublication = true; + break; + } + } + if ( !foundPublication && occurrence.publication_published >= this.showPublishedStatus ) { + const item = {publication_id: occurrence.publication_id, name: occurrence.publication_name, occurrences: [newOccurrence]}; + this.groupedTexts[i].publications.push(item); + } + break; + } + } + + if ( !foundCollection ) { + if ( occurrence.collection_name === undefined ) { + occurrence.collection_name = occurrence.publication_collection_name; + } + if ( occurrence.publication_published >= this.showPublishedStatus ) { + const item = {collection_id: occurrence.collection_id, name: occurrence.collection_name, hidden: true, + publications: [{publication_id: occurrence.publication_id, name: occurrence.publication_name, occurrences: [newOccurrence]}]}; + this.groupedTexts.push(item); + } + } + } + setFacsimileOccurrence(occurrence: Occurrence, type: string) { const newOccurrence = new SingleOccurrence(); newOccurrence.collectionID = occurrence.collection_id + '_' + occurrence.publication_id; @@ -367,9 +488,89 @@ export class OccurrencesPage { newOccurrence.collectionName = occurrence.collection_name newOccurrence.facsimilePage = occurrence.publication_facsimile_page newOccurrence.displayName = (occurrence.publication_name !== null ) ? occurrence.publication_name : occurrence.collection_name; + this.setOccurrenceTree(newOccurrence, occurrence); this.texts.push(newOccurrence); } + getOccurrences(id) { + this.isLoading = true; + if ( this.objectType === 'work' ) { + this.objectType = 'work_manifestation'; + } + this.semanticDataService.getOccurrences(this.objectType, id).subscribe( + occ => { + this.groupedTexts = []; + this.infoLoading = false; + // Sort alphabetically + const addedTOCs: Array = []; + occ.forEach(item => { + if ( item.occurrences !== undefined ) { + for (const occurence of item.occurrences) { + this.getOccurrence(occurence); + } + if ( item.occurrences[0] !== undefined && + addedTOCs.includes(item.occurrences[0]['collection_id']) === false ) { + this.getPublicationTOCName(item.occurrences[0], this.groupedTexts); + addedTOCs.push(item.occurrences[0]['collection_id']); + } + } + }); + this.isLoading = false; + this.infoLoading = false; + }, + err => { + this.isLoading = false; + this.infoLoading = false; + }, + () => console.log('Fetched tags...') + ); + } + + getPublicationTOCName(occ_data, all_data) { + const itemId = occ_data['collection_id'] + '_' + occ_data['publication_id']; + this.semanticDataService.getPublicationTOC(occ_data['collection_id']).subscribe( + toc_data => { + this.updatePublicationNames(toc_data, all_data, itemId); + }, + error => { + } + ); + } + + public updatePublicationNames(tocData, allData, itemId) { + tocData.forEach( item => { + allData.forEach(data => { + data['publications'].forEach(pub => { + const id = data['collection_id'] + '_' + pub['publication_id']; + if ( id === item['itemId'] ) { + pub.occurrences[0].displayName = item['text']; + pub['name'] = item['text']; + } + }); + }); + }); + } + + sortList(arrayToSort, fieldToSortOn) { + arrayToSort.sort(function(a, b) { + if (a[fieldToSortOn].charCodeAt(0) < b[fieldToSortOn].charCodeAt(0)) { return -1; } + if (a[fieldToSortOn].charCodeAt(0) > b[fieldToSortOn].charCodeAt(0)) { return 1; } + return 0; + }); + } + + toggleList(id) { + for ( let i = 0; i < this.groupedTexts.length; i++ ) { + if ( id === this.groupedTexts[i]['collection_id'] ) { + if (this.groupedTexts[i].hidden === true) { + this.groupedTexts[i].hidden = false; + } else { + this.groupedTexts[i].hidden = true; + } + } + } + } + cancel() { this.viewCtrl.dismiss(); } diff --git a/src/pages/pdf/pdf.ts b/src/pages/pdf/pdf.ts index d54d07c7..42720100 100644 --- a/src/pages/pdf/pdf.ts +++ b/src/pages/pdf/pdf.ts @@ -55,6 +55,10 @@ export class PdfPage { this.events.publish('pdfview:open', {'isOpen': true}); } + ngOnDestroy() { + this.events.unsubscribe('open:pdf'); + } + ionViewWillLeave() { this.events.publish('ionViewWillLeave', this.constructor.name); this.loading.dismiss(); diff --git a/src/pages/person-search/person-search.html b/src/pages/person-search/person-search.html index 7f6c4793..e29b2d42 100644 --- a/src/pages/person-search/person-search.html +++ b/src/pages/person-search/person-search.html @@ -60,15 +60,14 @@
- {{p.firstOfItsKind}} + {{p.firstOfItsKind.toLocaleUpperCase()}} -

{{p.last_name}}, {{p.first_name}}

-

{{p.name}}

+

{{p.full_name}} ({{(p.date_born)?p.date_born.split('-')[0]:p.date_born}}–{{(p.date_deceased)?p.date_deceased.split('-')[0]:p.date_deceased}}{{(p.date_born !== null && p.date_born.includes('BC'))?(' '+('BC'|translate)):''}})

- + diff --git a/src/pages/person-search/person-search.ts b/src/pages/person-search/person-search.ts index 5bc11a1f..fdef6517 100644 --- a/src/pages/person-search/person-search.ts +++ b/src/pages/person-search/person-search.ts @@ -13,6 +13,7 @@ import { SingleOccurrence } from '../../app/models/single-occurrence.model'; import { Storage } from '@ionic/storage'; import { UserSettingsService } from '../../app/services/settings/user-settings.service'; import { OccurrencesPage } from '../occurrences/occurrences'; +import { TranslateService } from '@ngx-translate/core'; /** * Generated class for the PersonSearchPage page. @@ -27,7 +28,7 @@ import { OccurrencesPage } from '../occurrences/occurrences'; @IonicPage({ name: 'person-search', - segment: 'search/:type/:subtype', + segment: 'person-search/:type/:subtype', defaultHistory: ['HomePage'] }) @Component({ @@ -48,7 +49,7 @@ export class PersonSearchPage { personsCopy: any[] = []; searchText: string; texts: SingleOccurrence[] = []; - infiniteScrollNumber = 30; + infiniteScrollNumber = 200; personTitle: string; selectedLinkID: string; @@ -56,6 +57,9 @@ export class PersonSearchPage { showFilter = true; type: any; subType: any; + from = 0; + + filters: any[] = []; objectType = 'subject'; @@ -66,6 +70,7 @@ export class PersonSearchPage { personsKey = 'person-search'; personSearchTypes = []; + filterYear: number; constructor(public navCtrl: NavController, public navParams: NavParams, @@ -79,12 +84,14 @@ export class PersonSearchPage { public loadingCtrl: LoadingController, public occurrenceService: OccurrenceService, protected storage: Storage, + public translate: TranslateService, private toastCtrl: ToastController, private userSettingsService: UserSettingsService, private events: Events, private cf: ChangeDetectorRef ) { const type = this.navParams.get('type') || null; + this.filterYear = null; this.langService.getLanguage().subscribe((lang) => { this.appName = this.config.getSettings('app.name.' + lang); try { @@ -104,6 +111,10 @@ export class PersonSearchPage { this.type = this.navParams.get('type'); this.subType = this.navParams.get('subtype'); + if ( String(this.subType).includes('subtype') ) { + this.subType = null; + } + if (this.subType) { this.personsKey += `-${this.subType}`; } @@ -124,8 +135,6 @@ export class PersonSearchPage { selectMusicAccordionItem() { const appHasMusicAccordion = this.appHasMusicAccordionConfig(); - this.subType = this.navParams.get('subtype') || null; - if (!appHasMusicAccordion || !this.subType.length) { return; } @@ -160,25 +169,14 @@ export class PersonSearchPage { this.selectMusicAccordionItem(); this.setData(); } + sortByLetter(letter) { - const list = []; - try { - for (const p of this.allData) { - if (p.sortBy && p.sortBy.charCodeAt(0) === String(letter).toLowerCase().charCodeAt(0)) { - list.push(p); - } else { - const combining = /[\u0300-\u036F]/g; - const tmpChar = p.sortBy.normalize('NFKD').replace(combining, '').replace(',', ''); - if ( tmpChar.charCodeAt(0) === String(letter).toLowerCase().charCodeAt(0) ) { - list.push(p); - } - } - } - } catch ( e ) { - this.persons = this.allData; - } - this.persons = list; + this.searchText = letter; + this.persons = []; + this.cf.detectChanges(); + this.getPersons(); } + setData() { this.storage.get(this.personsKey).then((persons) => { if (persons) { @@ -189,24 +187,29 @@ export class PersonSearchPage { } }); } + getPersons() { this.showLoading = true; - this.semanticDataService.getSubjectOccurrences().subscribe( + this.semanticDataService.getSubjectsElastic(this.from, this.searchText, this.filters).subscribe( persons => { const personsTmp = []; + persons = persons.hits.hits; persons.forEach(element => { + element = element['_source']; const sortBy = []; - if ( element['last_name'] != null ) { - sortBy.push(String(element['last_name']).toLowerCase().trim().replace(' ', '').replace('ʽ', '')); + let sortByName = String(element['full_name']).toLowerCase().replace('ʽ', ''); + sortByName = sortByName.replace('de ', ''); + sortByName = sortByName.replace('von ', ''); + sortByName = sortByName.replace('van ', ''); + sortByName = sortByName.replace('af ', ''); + sortByName = sortByName.trim(); + if ( element['date_deceased'] !== null ) { + element['date_deceased'] = String(element['date_deceased']).replace(/^0+/, ''); } - if ( element['first_name'] != null ) { - sortBy.push(String(element['first_name']).toLowerCase().trim().replace(' ', '').replace('ʽ', '')); + if ( element['date_born'] !== null ) { + element['date_born'] = String(element['date_born']).replace(/^0+/, ''); } - - if ( element['last_name'] == null && element['first_name'] == null ) { - sortBy.push(String(element['name']).toLowerCase().trim().replace(' ', '').replace('ʽ', '')); - } - + sortBy.push(sortByName); element['sortBy'] = sortBy.join(); const ltr = element['sortBy'].charAt(0); if (ltr.length === 1 && ltr.match(/[a-zåäö]/i)) { @@ -216,29 +219,28 @@ export class PersonSearchPage { } if ( this.subType !== '' && this.subType !== null && element['object_type'] !== this.subType ) { } else { - personsTmp.push(element); + let found = false; + this.persons.forEach(pers => { + if ( pers.id === element['id'] ) { + found = true; + } + }); + if ( !found ) { + personsTmp.push(element); + this.persons.push(element); + } } }); - this.allData = personsTmp; - this.cacheData = personsTmp; + this.allData = this.persons; + this.cacheData = this.persons; this.showLoading = false; - this.sortListAlphabeticallyAndGroup(this.allData); - - for (let i = 0; i < this.infiniteScrollNumber; i++) { - if (i === personsTmp.length) { - break; - } else { - this.persons.push(personsTmp[this.count]); - this.personsCopy.push(personsTmp[this.count]); - this.count++; - } - } + this.sortListAlphabeticallyAndGroup(this.persons); }, - err => {console.error(err); this.showLoading = false; }, - () => console.log(this.persons) + err => {console.error(err); this.showLoading = false; } ); } + async download() { this.cacheItem = !this.cacheItem; @@ -248,14 +250,17 @@ export class PersonSearchPage { this.removeFromCache(this.personsKey); } } + async storeCacheText(id: string, text: any) { await this.storage.set(id, text); await this.addedToCacheToast(id); } + async removeFromCache(id: string) { await this.storage.remove(id); await this.removedFromCacheToast(id); } + getCacheText(id: string) { this.storage.get(id).then((persons) => { this.allData = persons; @@ -269,6 +274,7 @@ export class PersonSearchPage { } }); } + async addedToCacheToast(id: string) { let status = ''; @@ -292,6 +298,7 @@ export class PersonSearchPage { await toast.present(); } + async removedFromCacheToast(id: string) { let status = ''; @@ -315,6 +322,7 @@ export class PersonSearchPage { await toast.present(); } + sortListAlphabeticallyAndGroup(listOfPersons: any[]) { const persons = listOfPersons; @@ -329,6 +337,7 @@ export class PersonSearchPage { } return persons; } + groupPersonsAlphabetically(persons) { // Checks when first character changes in order to divide names into alphabetical groups for (let i = 0; i < persons.length ; i++) { @@ -361,10 +370,11 @@ export class PersonSearchPage { filterModal.onDidDismiss(filters => { if (filters) { - if (filters['isEmpty']) { + this.persons = []; + this.allData = []; + this.filters = filters; + if (filters['isEmpty'] || filters['isEmpty'] === undefined) { console.log('filters are empty') - this.persons = []; - this.allData = []; this.count = 0; this.getPersons(); } else { @@ -381,12 +391,7 @@ export class PersonSearchPage { } } if (filters.filterPersonTypes) { - const filterSelected = filters.filterPersonTypes.some(function(el) { - return el.selected === true; - }); - if (filterSelected) { - this.getSubjectsOccurrenceBySubjectType(filters.filterPersonTypes); - } + this.getPersons(); } } } @@ -395,29 +400,8 @@ export class PersonSearchPage { } filterByYear(year: number) { - this.count = 0; - const newData = []; - - for (const p of this.allData) { - if (p.date_born && p.date_deceased) { - if (Number(p.date_born.match(/\S+/g)[3]) < year && Number(p.date_deceased.match(/\S+/g)[3]) > year) { - newData.push(p); - } - } - } - - this.persons = []; - this.allData = newData; - - for (let k = 0; k < this.infiniteScrollNumber; k++) { - if (k === this.allData.length) { - break; - } else { - this.persons.push(this.allData[this.count]); - this.personsCopy.push(this.allData[this.count]); - this.count++ - } - } + this.filterYear = year; + this.getPersons(); } getSubjectsOccurrencesByCollection(filterCollections, callback) { @@ -496,12 +480,15 @@ export class PersonSearchPage { if ( !terms || terms === '' ) { this.persons = this.personsCopy; } else if (terms != null) { - const oldPersons = this.persons; + this.from = 0; + this.getPersons(); this.persons = []; terms = String(terms).toLowerCase().replace(' ', ''); for (const person of this.allData) { - const sortBy = String(person.first_name + '' + person.last_name).toLowerCase().replace(' ', '').replace('ʽ', ''); - const sortByReverse = String(person.last_name + '' + person.first_name).toLowerCase().replace(' ', '').replace('ʽ', ''); + let sortBy = String(person.full_name).toLowerCase().replace(' ', '').replace('ʽ', ''); + sortBy = sortBy.replace('de', '').replace('von', '').replace('van', '').replace('af', ''); + let sortByReverse = String(person.full_name).toLowerCase().replace(' ', '').replace('ʽ', ''); + sortByReverse = sortByReverse.replace('de', '').replace('von', '').replace('van', '').replace('af', ''); if (sortBy) { if (sortBy.includes(terms) || sortByReverse.includes(terms)) { const inList = this.persons.some(function(p) { @@ -518,13 +505,8 @@ export class PersonSearchPage { } doInfinite(infiniteScroll) { - for (let i = 0; i < this.infiniteScrollNumber; i++) { - if ( this.allData !== undefined ) { - this.persons.push(this.allData[this.count]); - this.personsCopy.push(this.allData[this.count]); - this.count++ - } - } + this.from += this.infiniteScrollNumber; + this.getPersons(); infiniteScroll.complete(); } diff --git a/src/pages/place-search/place-search.html b/src/pages/place-search/place-search.html index 6c475ece..2e0f5de5 100644 --- a/src/pages/place-search/place-search.html +++ b/src/pages/place-search/place-search.html @@ -46,7 +46,7 @@ - + @@ -62,14 +62,14 @@
- {{p.firstOfItsKind}} + {{p.firstOfItsKind.toLocaleUpperCase()}}

{{p.name}}

- + diff --git a/src/pages/place-search/place-search.ts b/src/pages/place-search/place-search.ts index 6f28712c..396f3085 100644 --- a/src/pages/place-search/place-search.ts +++ b/src/pages/place-search/place-search.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ChangeDetectorRef } from '@angular/core'; import { IonicPage, NavController, NavParams, App, Platform, ToastController, ModalController, Content, Events, ViewController } from 'ionic-angular'; import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service'; @@ -52,6 +52,9 @@ export class PlaceSearchPage { showLoading = false; showFilter = true; + from = 0; + infiniteScrollNumber = 30; + selectedLinkID: string; objectType = 'location'; @@ -74,7 +77,8 @@ export class PlaceSearchPage { private toastCtrl: ToastController, public modalCtrl: ModalController, public viewCtrl: ViewController, - private userSettingsService: UserSettingsService + private userSettingsService: UserSettingsService, + private cf: ChangeDetectorRef ) { this.langService.getLanguage().subscribe((lang) => { this.appName = this.config.getSettings('app.name.' + lang); @@ -103,16 +107,16 @@ export class PlaceSearchPage { (window).ga('send', 'pageview'); } - getPlaces() { + async getPlaces() { this.showLoading = true; - this.semanticDataService.getLocationOccurrences().subscribe( + this.semanticDataService.getLocationElastic(this.from, this.searchText).subscribe( places => { - this.allData = places; - this.cacheData = places; + places = places.hits.hits; this.showLoading = false; const placesTmp = []; places.forEach(element => { + element = element['_source']; element['sortBy'] = String(element['name']).toLowerCase().trim().replace('ʽ', ''); const ltr = element['sortBy'].charAt(0); const mt = ltr.match(/[a-zåäö]/i); @@ -122,22 +126,21 @@ export class PlaceSearchPage { const combining = /[\u0300-\u036F]/g; element['sortBy'] = element['sortBy'].normalize('NFKD').replace(combining, '').replace(',', ''); } - placesTmp.push(element); + let found = false; + this.places.forEach(place => { + if ( place.id === element['id'] ) { + found = true; + } + }); + if ( !found ) { + placesTmp.push(element); + this.places.push(element); + } }); - this.allData = placesTmp; - this.cacheData = placesTmp; + this.allData = this.places; + this.cacheData = this.places; this.sortListAlphabeticallyAndGroup(this.allData); - - for (let i = 0; i < 30; i++) { - if (i === places.length) { - break; - } else { - this.places.push(placesTmp[this.count]); - this.placesCopy.push(placesTmp[this.count]); - this.count++ - } - } }, err => {console.error(err); this.showLoading = false; } ); @@ -155,6 +158,12 @@ export class PlaceSearchPage { } } + onChanged(obj) { + this.cf.detectChanges(); + console.log('segment changed') + this.filter(obj); + } + showAll() { this.places = []; this.allData = []; @@ -201,6 +210,8 @@ export class PlaceSearchPage { } sortByLetter(letter: any) { + this.searchText = letter; + this.getPlaces(); const list = []; try { for (const p of this.allData) { @@ -221,37 +232,38 @@ export class PlaceSearchPage { } filter(terms) { - if (!terms) { - this.places = this.placesCopy; - } else if (terms != null) { - this.places = []; - terms = terms.toLocaleLowerCase(); - for (const place of this.allData) { - if (place.sortBy) { - const title = String(place.name).toLowerCase().replace(' ', '').replace('ʽ', ''); - if (title.includes(terms)) { - const inList = this.places.some(function(p) { - return p.sortBy === place.sortBy - }); - if (!inList) { - this.places.push(place); + if ( terms._value ) { + terms = terms._value; + } + if (terms != null) { + this.from = 0; + this.getPlaces().then(() => { + const oldPersons = this.places; + this.places = []; + terms = terms.toLocaleLowerCase(); + for (const place of this.allData) { + if (place.sortBy) { + const title = String(place.name).toLowerCase().replace(' ', '').replace('ʽ', ''); + if (title.includes(terms) ) { + const inList = this.places.some(function(p) { + return p.sortBy === place.sortBy + }); + if (!inList && title.charAt(0) === String(terms).toLowerCase().charAt(0)) { + this.places.push(place); + } } } } - } + }); + } else { this.places = this.placesCopy; } } - doInfinite(infiniteScroll: any) { - for (let i = 0; i < 30; i++) { - if ( this.allData !== undefined ) { - this.places.push(this.allData[this.count]); - this.placesCopy.push(this.allData[this.count]); - this.count++ - } - } + doInfinite(infiniteScroll) { + this.from += this.infiniteScrollNumber; + this.getPlaces(); infiniteScroll.complete(); } @@ -440,6 +452,7 @@ export class PlaceSearchPage { if (this.platform.is('mobile')) { nav[0].push('read', params); } else { + console.log('Opening read from PlaceSearch.openText()'); nav[0].setRoot('read', params); } } diff --git a/src/pages/read-popover/read-popover.html b/src/pages/read-popover/read-popover.html index d523e978..82ede163 100644 --- a/src/pages/read-popover/read-popover.html +++ b/src/pages/read-popover/read-popover.html @@ -1,6 +1,7 @@ - - {{'Read.Popover.Appearance' | translate}} + +
{{'Read.Popover.Appearance' | translate}}
+
@@ -35,6 +36,11 @@ + + {{'Read.Popover.WorkInfo' | translate}} + + + {{'Read.Popover.Abbreviations' | translate}} diff --git a/src/pages/read-popover/read-popover.module.ts b/src/pages/read-popover/read-popover.module.ts index d1203a28..252fafa5 100644 --- a/src/pages/read-popover/read-popover.module.ts +++ b/src/pages/read-popover/read-popover.module.ts @@ -3,7 +3,7 @@ import { IonicPageModule } from 'ionic-angular'; import { ReadPopoverPage } from './read-popover'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { ReadPopoverService } from '../../app/services/settings/read-popover.service'; +import { ReadPopoverService, Fontsize } from '../../app/services/settings/read-popover.service'; import { HttpClient } from '@angular/common/http'; export function createTranslateLoader(http: HttpClient): TranslateLoader { diff --git a/src/pages/read-popover/read-popover.scss b/src/pages/read-popover/read-popover.scss index 94045139..779aa185 100644 --- a/src/pages/read-popover/read-popover.scss +++ b/src/pages/read-popover/read-popover.scss @@ -23,6 +23,20 @@ read-popover-page { min-height: 1rem !important; } + .read-popover-header ion-label { + display: flex; + justify-content: space-between; + margin-top: 2px; + } + + .close{ + font-size: 1rem; + } + + .appearence{ + margin-top: 0.8rem; + } + .asterix{ height: 0.8em; padding-right: 0.1em; @@ -32,6 +46,10 @@ read-popover-page { display: inline; background-color: color($colors, person); } + .show_workInfo{ + display: inline; + background-color: color($colors, work); + } .show_placeInfo{ display: inline; background-color: color($colors, location); @@ -47,5 +65,5 @@ read-popover-page { } .popover-content{ - overflow: hidden !important; + overflow-x: hidden !important; } diff --git a/src/pages/read-popover/read-popover.ts b/src/pages/read-popover/read-popover.ts index f314d68f..05f45aef 100644 --- a/src/pages/read-popover/read-popover.ts +++ b/src/pages/read-popover/read-popover.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { TranslateModule, TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { ViewController, Events } from 'ionic-angular'; +import { ViewController, Events, NavParams } from 'ionic-angular'; import { ConfigService } from '@ngx-config/core'; import { ReadPopoverService, Fontsize } from '../../app/services/settings/read-popover.service'; @@ -22,6 +22,7 @@ export class ReadPopoverPage { 'comments': boolean, 'personInfo': boolean, 'placeInfo': boolean, + 'workInfo': boolean, 'changes': boolean, 'abbreviations': boolean, 'pageNumbering': boolean, @@ -33,6 +34,7 @@ export class ReadPopoverPage { 'comments': false, 'personInfo': false, 'placeInfo': false, + 'workInfo': false, 'changes': false, 'abbreviations': false, 'pageNumbering': false, @@ -47,9 +49,16 @@ export class ReadPopoverPage { private config: ConfigService, public readPopoverService: ReadPopoverService, public translate: TranslateService, - private events: Events + private events: Events, + public params: NavParams ) { + const toggles = this.params.get('toggles'); this.readToggles = this.config.getSettings('settings.readToggles'); + + if ( toggles !== undefined ) { + this.readToggles = toggles; + } + this.show = readPopoverService.show; this.fontsize = readPopoverService.fontsize; } @@ -70,6 +79,7 @@ export class ReadPopoverPage { this.show.comments = true; this.show.personInfo = true; this.show.placeInfo = true; + this.show.workInfo = true; this.show.changes = true; this.show.abbreviations = true; this.show.pageNumbering = true; @@ -79,6 +89,7 @@ export class ReadPopoverPage { this.show.comments = false; this.show.personInfo = false; this.show.placeInfo = false; + this.show.workInfo = false; this.show.changes = false; this.show.abbreviations = false; this.show.pageNumbering = false; @@ -88,6 +99,7 @@ export class ReadPopoverPage { this.toggleComments(); this.togglePersonInfo(); this.togglePlaceInfo(); + this.toggleWorkInfo(); this.toggleChanges(); this.toggleAbbreviations(); this.togglePageNumbering(); @@ -97,48 +109,94 @@ export class ReadPopoverPage { toggleComments() { this.readPopoverService.show.comments = this.show.comments; + this.doAnalytics('toggleComments - ' + this.show.comments); } togglePersonInfo() { this.readPopoverService.show.personInfo = this.show.personInfo; + this.doAnalytics('togglePersonInfo - ' + this.show.personInfo); } togglePlaceInfo() { this.readPopoverService.show.placeInfo = this.show.placeInfo; + this.doAnalytics('togglePlaceInfo - ' + this.show.placeInfo); + } + + toggleWorkInfo() { + this.readPopoverService.show.workInfo = this.show.workInfo; + this.doAnalytics('toggleWorkInfo - ' + this.show.workInfo); } toggleChanges() { this.readPopoverService.show.changes = this.show.changes; + this.doAnalytics('toggleChanges - ' + this.show.changes); } toggleAbbreviations() { this.readPopoverService.show.abbreviations = this.show.abbreviations; + this.doAnalytics('toggleAbbreviations - ' + this.show.abbreviations); } togglePageNumbering() { this.readPopoverService.show.pageNumbering = this.show.pageNumbering; + this.doAnalytics('togglePageNumbering - ' + this.show.pageNumbering); } togglePageBreakOriginal() { this.readPopoverService.show.pageBreakOriginal = this.show.pageBreakOriginal; + this.doAnalytics('togglePageBreakOriginal - ' + this.show.pageBreakOriginal); } togglePageBreakEdition() { this.readPopoverService.show.pageBreakEdition = this.show.pageBreakEdition; + this.doAnalytics('togglePageBreakEdition - ' + this.show.pageBreakEdition); } decreaseFontSize() { - this.fontsize = Fontsize.small; - this.readPopoverService.fontsize = this.fontsize; + try { + this.fontsize = Fontsize.small; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('decreaseFontSize - ' + this.fontsize); + } catch ( e ) { + this.fontsize = 0; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('decreaseFontSize - ' + this.fontsize); + } } increaseFontMeduimSize() { - this.fontsize = Fontsize.medium; - this.readPopoverService.fontsize = this.fontsize; + try { + this.fontsize = Fontsize.medium; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('increaseFontMeduimSize - ' + this.fontsize); + } catch ( e ) { + this.fontsize = 1; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('increaseFontMeduimSize - ' + this.fontsize); + } } increaseFontSize() { - this.fontsize = Fontsize.large; - this.readPopoverService.fontsize = this.fontsize; + try { + this.fontsize = Fontsize.large; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('increaseFontSize - ' + this.fontsize); + } catch ( e ) { + this.fontsize = 2; + this.readPopoverService.fontsize = this.fontsize; + this.doAnalytics('increaseFontSize - ' + this.fontsize); + } + } + + doAnalytics(type) { + try { + (window).ga('send', 'event', { + eventCategory: 'Read-Settings', + eventLabel: 'Read-Settings - ' + type, + eventAction: type, + eventValue: 10 + }); + } catch ( e ) { + } } } diff --git a/src/pages/read/read.html b/src/pages/read/read.html index 00eee158..3de7337b 100644 --- a/src/pages/read/read.html +++ b/src/pages/read/read.html @@ -1,31 +1,38 @@ - - - - -
- - -
-
- - -
- -
-
-
-
+ + + + + + + +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+
+
- -
- + + +
- -
- +
+ + {{ view.type | translate }} + - + +
@@ -92,6 +103,9 @@
+
+ +
@@ -99,117 +113,86 @@
- + - + +
- - - - - - - +
- +
-
+
{{ "Read.Established.Title" | translate }} - - {{ "Read.Manuscripts.Title" | translate }} - - - {{ "Read.Comments.Title" | translate }} - - - {{ "Read.Variations.Title" | translate }} - {{ "Read.Facsimiles.Title" | translate }} - - {{ "Read.Introduction.Title" | translate }} -
-
-
- -
-
- -
-
- -
-
+
-
- -
- -
-
- -
-
- -
-
-
+
+ diff --git a/src/pages/read/read.scss b/src/pages/read/read.scss index 5701d3dc..e6a27df6 100644 --- a/src/pages/read/read.scss +++ b/src/pages/read/read.scss @@ -1,5 +1,65 @@ page-read { + .work_title{ + font-style: italic; + } + + .back-to-search { + position: absolute; + } + + .notePosition, + .noteLemma { + font-weight: 700; + } + + .notePosition { + margin-right: 5px; + } + + .read-fab-menu { + bottom: 50px; + } + + .toolbar-content { + display: grid; + grid-template-columns: auto max-content; + + ion-buttons { + order: 0; + } + .bar-buttons:last-child { + // margin-left: -118px; + } + } + @media screen and (max-width: 800px) { + .secondary .toolbar-content { + display: flex; + justify-content: flex-end; + + .bar-buttons:last-child { + margin-left: 0; + flex-grow: 1; + min-width: 30%; + } + } + } + + .toolTip{ + z-index: 1000; + position: absolute; + background: #f1f1f1; + border: 1px solid #222; + box-shadow: rgba(0, 0, 0, 0.3) 0 2px 10px; + padding: 0.5rem; + white-space: normal; + max-width: 400px; + text-align: left; + p { + line-height: 22px; + } + } + .segment-header{ font-size: 1rem !important; padding-left: 1rem; @@ -15,7 +75,7 @@ page-read { font-size: 1rem !important; min-width: max-content; } - + .segment-activated{ background-color: color($colors, primary) !important; color: color($colors, white) !important; @@ -35,7 +95,14 @@ page-read { ion-icon.settings-icon.icon { font-size: 1.8em; - } + } + + .settings-icon, + .facebook-icon, + .twitter-icon, + .mail-icon { + cursor: pointer; + } button.close-button.fab.fab-md { position: absolute; @@ -56,10 +123,16 @@ page-read { } .read-columns { + margin-bottom: 0.5rem; -webkit-overflow-scrolling: touch; overflow-x: scroll; overflow-y: hidden; - white-space: nowrap; + white-space: nowrap; + &__title { + background: #E4DCCB!important; + font-size: 0.8rem!important; + font-weight: bold!important; + } &.single-column .read-column { max-width: 50%; @@ -96,7 +169,7 @@ page-read { .tooltip { display: none; } - + .tooltiptrigger{ cursor: pointer; } @@ -122,7 +195,8 @@ page-read { } .forwardBackward { - vertical-align:bottom; + height: 100%; + align-items: center; padding-left: 1em; padding-right: 1em; } @@ -141,14 +215,6 @@ page-read { padding-left: 50px !important; } - .forwardButtonDiv{ - float:right; - } - - .backwardButtonDiv{ - float:left; - } - ion-slides { .card { background-color: #fff; @@ -161,19 +227,19 @@ page-read { } } button[ion-fab] { - overflow: visible; - position: relative; - contain: layout; + overflow: visible; + position: relative; + contain: layout; - ion-label { - position: absolute; - top: -4px; + ion-label { + position: absolute; + top: -4px; right: 40px; padding-right: 5px; - - color: rgba(0,0,0,0.7); + color: #f1f1f1; line-height: 24px; - transition: right 0.5s; + transition: right 0.5s; + font-weight: bold; } &:hover ion-label { right: 35px; @@ -193,7 +259,7 @@ page-read { right: -10px !important; top: -15px; } - + .occurrenceButton{ float: left; z-index: 999; @@ -223,11 +289,37 @@ page-read { font-size: 1.0rem; color: #78756f; } - + .collections{ font-size: 1.0rem; color: #78756f; + } + .read-from-beginning-button { + margin-bottom: 74px; + } + .secondary-toolbar-icons-wrapper { + ion-icon { + font-size: 1.6rem!important; + padding: 0.5rem; + cursor: pointer; + } + } + @media screen and (max-width: 800px) { + .text-changer-desktop-view{ + max-width: 80%; + } + .forwardBackward__previouspage-name{ + display: none; + } + .forwardBackward__nextpage-name{ + display: none; + } +} + + .text-changer-mobile-view{ + } + } .md, @@ -236,5 +328,41 @@ page-read { .swiper-slide .swiper-slide-active{ width: 240px;//your width here border: 1px solid black; - } - } + } +} + +.no-scroll { + padding-top: 40px; + height: 100%; + + .scroll-content { + overflow: hidden; + } +} + +ion-fab .fab-list-active .fab-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, 0.60); + z-index: -1; +} +@media(max-width: 800px) { + ion-backdrop { + visibility: visible !important; + z-index: -2; + } + page-read .scroll-content { + padding: 0!important; + } + .modal-wrapper { + height: 70%; + margin: 25px auto; + .scroll-content { + padding: 16px; + } + } +} + diff --git a/src/pages/read/read.ts b/src/pages/read/read.ts index 574d2976..94e0bc33 100644 --- a/src/pages/read/read.ts +++ b/src/pages/read/read.ts @@ -9,6 +9,7 @@ import { TranslateModule, LangChangeEvent, TranslateService, TranslatePipe } fro import { ConfigService } from '@ngx-config/core'; import { ReadPopoverPage } from '../read-popover/read-popover'; +import { SharePopoverPage } from '../share-popover/share-popover'; import { CommentModalPage } from '../comment-modal/comment-modal'; import { SemanticDataModalPage } from '../semantic-data-modal/semantic-data-modal'; @@ -35,6 +36,8 @@ import { ReferenceDataModalPage } from '../reference-data-modal/reference-data-m import { OccurrencesPage } from '../occurrences/occurrences'; import { OccurrenceResult } from '../../app/models/occurrence.model'; import { SearchAppPage } from '../search-app/search-app'; +import { SocialSharing } from '@ionic-native/social-sharing'; +import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service'; /** * A page used for reading publications. @@ -51,7 +54,7 @@ enum TextType { @IonicPage({ name: 'read', - segment: 'publication/:collectionID/text/:publicationID/:facs_id/:facs_nr/:song_id/:search_title/:urlviews' + segment: 'publication/:collectionID/text/:publicationID/:chapterID/:facs_id/:facs_nr/:song_id/:search_title/:urlviews' }) @Component({ selector: 'page-read', @@ -63,6 +66,8 @@ export class ReadPage /*implements OnDestroy*/ { @ViewChild('readColumn') readColumn: ElementRef; @ViewChild('scrollBar') scrollBar: ElementRef; @ViewChild(Navbar) navBar: Navbar; + @ViewChild('fab') fabList: FabContainer; + @ViewChild('settingsIconElement') settingsIconElement: ElementRef; listenFunc: Function; textType: TextType = TextType.ReadText; @@ -73,13 +78,21 @@ export class ReadPage /*implements OnDestroy*/ { appName: string; tocRoot: TableOfContentsCategory[]; popover: ReadPopoverPage; - tooltipContent: any; + sharePopover: SharePopoverPage; subTitle: string; cacheItem = false; collectionTitle: string; hasOccurrenceResults = false; showOccurrencesModal = false; searchResult: string; + showToolTip: boolean; + toolTipPosition: object; + toolTipText: string; + + maxSingleWindowWidth: Number; + + prevItem: any; + nextItem: any; divWidth = '100px'; @@ -114,13 +127,6 @@ export class ReadPage /*implements OnDestroy*/ { pager: false }; - showSocial = { - facebook: true, - email: true, - twitter: true, - instagram: false - }; - show = 'established'; // Mobile tabs availableViewModes = [ @@ -130,11 +136,38 @@ export class ReadPage /*implements OnDestroy*/ { 'established', 'facsimiles', 'introduction', - 'songexample' + 'songexample', + 'illustrations' ]; appUsesAccordionToc = false; + tooltips = { + 'persons': {}, + 'comments': {}, + 'works': {}, + 'places': {}, + 'abbreviations': {}, + 'footnotes': {} + }; + + nativeEmail() { + // Check if sharing via email is supported + this.socialSharing.canShareViaEmail().then(() => { + console.log('Sharing via email is possible'); + + // Share via email + this.socialSharing.shareViaEmail('Body', 'Subject', ['recipient@example.org']).then(() => { + // Success! + }).catch(() => { + // Error! + console.log('Email error') + }); + }).catch(() => { + console.log('Sharing via email is not possible'); + }); + } + constructor(private app: App, public viewCtrl: ViewController, public navCtrl: NavController, @@ -157,8 +190,10 @@ export class ReadPage /*implements OnDestroy*/ { private events: Events, private platform: Platform, private storage: Storage, + public semanticDataService: SemanticDataService, private userSettingsService: UserSettingsService, - public publicationCacheService: PublicationCacheService + public publicationCacheService: PublicationCacheService, + private socialSharing: SocialSharing ) { this.isCached(); this.searchResult = null; @@ -180,6 +215,8 @@ export class ReadPage /*implements OnDestroy*/ { let link = null; + this.maxSingleWindowWidth = 95; + this.matches = []; this.availableViewModes = []; @@ -215,6 +252,10 @@ export class ReadPage /*implements OnDestroy*/ { this.show = this.config.getSettings('defaults.ReadModeView'); + if ( this.show === 'facsimiles' ) { + this.maxSingleWindowWidth = 35; + } + this.setDefaultViews(); const title = global.getSubtitle(); @@ -230,7 +271,8 @@ export class ReadPage /*implements OnDestroy*/ { this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID'); this.establishedText.link = this.params.get('collectionID') + '_' + this.params.get('publicationID'); - if (this.params.get('chapterID') !== undefined) { + if (this.params.get('chapterID') !== undefined && this.params.get('chapterID') !== 'nochapter' && + this.params.get('chapterID') !== ':chapterID' && this.params.get('chapterID') !== 'chapterID') { this.establishedText.link += '_' + this.params.get('chapterID'); } @@ -286,16 +328,19 @@ export class ReadPage /*implements OnDestroy*/ { }); } - this.events.subscribe('show:view', (view, id) => { + this.events.subscribe('show:view', (view, id, chapter) => { // user and time are the same arguments passed in `events.publish(user, time)` - console.log('Welcome', view, 'at', id); + console.log('Welcome', view, 'at', id, 'chapter', chapter); this.openNewExternalView(view, id); }); this.getAdditionalParams(); } - + ngOnDestroy() { + this.events.unsubscribe('show:view'); + } ionViewDidEnter() { + this.events.publish('help:continue'); (window).ga('set', 'page', 'Read'); (window).ga('send', 'pageview'); } @@ -364,10 +409,75 @@ export class ReadPage /*implements OnDestroy*/ { * @param searchTocItem */ getTocRoot(id: string, searchTocItem?: boolean) { + + this.storage.get('toc_' + id).then((tocItemsC) => { + if (tocItemsC) { + console.log('get toc root from cache... --- --- in read'); + tocItemsC.selectedCollId = null; + tocItemsC.selectedPubId = null; + if (this.params.get('collectionID') && this.params.get('publicationID')) { + tocItemsC.selectedCollId = this.params.get('collectionID'); + tocItemsC.selectedPubId = this.params.get('publicationID'); + } + + if ( this.params.get('chapterID') ) { + tocItemsC.selectedChapterId = this.params.get('chapterID'); + } + + const tocLoadedParams = { tocItems: tocItemsC }; + if (searchTocItem && this.appUsesAccordionToc) { + tocLoadedParams['searchTocItem'] = true; + tocLoadedParams['collectionID'] = this.params.get('collectionID'); + tocLoadedParams['publicationID'] = this.params.get('publicationID'); + tocLoadedParams['chapterID'] = this.params.get('chapterID'); + tocLoadedParams['itemId'] = this.params.get('collectionID') + '_' + this.params.get('publicationID'); + + if (this.search_title) { + tocLoadedParams['search_title'] = this.search_title; + } + } + this.events.publish('tableOfContents:loaded', tocLoadedParams); + console.log('toc from cache - read'); + } else { + this.tocService.getTableOfContents(id) + .subscribe( + tocItems => { + console.log('get toc root... --- --- in read'); + tocItems.selectedCollId = null; + tocItems.selectedPubId = null; + if (this.params.get('collectionID') && this.params.get('publicationID')) { + tocItems.selectedCollId = this.params.get('collectionID'); + tocItems.selectedPubId = this.params.get('publicationID'); + } + + if ( this.params.get('chapterID') ) { + tocItems.selectedChapterId = this.params.get('chapterID'); + } + + const tocLoadedParams = { tocItems: tocItems }; + + if (searchTocItem && this.appUsesAccordionToc) { + tocLoadedParams['searchTocItem'] = true; + tocLoadedParams['collectionID'] = this.params.get('collectionID'); + tocLoadedParams['publicationID'] = this.params.get('publicationID'); + tocLoadedParams['chapterID'] = this.params.get('chapterID'); + + if (this.search_title) { + tocLoadedParams['search_title'] = this.search_title; + } + } + this.events.publish('tableOfContents:loaded', tocLoadedParams); + this.storage.set('toc_' + id, tocItems); + }, + error => { this.errorMessage = error }); + } + }); + this.tocService.getTableOfContents(id) .subscribe( tocItems => { console.log('get toc root... --- --- in read'); + this.storage.set('currentTOCItem', tocItems.children[0][0]); tocItems.selectedCollId = null; tocItems.selectedPubId = null; if (this.params.get('collectionID') && this.params.get('publicationID')) { @@ -391,31 +501,6 @@ export class ReadPage /*implements OnDestroy*/ { error => { this.errorMessage = error }); } - private scrollToElement(element: string) { - try { - element = element.replace(/#/g, ''); - const elementStart = 'start' + element.replace(/en/g, ''); - // scroll to element - if (this.elementRef.nativeElement.querySelector('.' + element) != null) { - const scrollTarget = this.elementRef.nativeElement.querySelector('.' + element); - const yOffset = scrollTarget.offsetTop; - if (scrollTarget.parentElement.parentElement !== null) { - scrollTarget.parentElement.parentElement.scrollIntoView(true); - } - } - // show start arrow - if (this.elementRef.nativeElement.querySelector('.anchor_lemma[data-id="' + elementStart + '"]') != null) { - const targetArrow = this.elementRef.nativeElement.querySelector('.anchor_lemma[data-id="' + elementStart + '"]'); - targetArrow.style.display = 'initial'; - setTimeout(() => { targetArrow.style.display = 'none' }, 3000); - } - } catch (e) { - console.log(element); - console.log(document.getElementById(element)); - console.log(e); - } - } - setTocCache() { const id = this.params.get('collectionID'); this.tocService.getTableOfContents(id) @@ -459,7 +544,7 @@ export class ReadPage /*implements OnDestroy*/ { showAllViews() { this.availableViewModes.forEach(function (viewmode) { const viewTypesShown = this.getViewTypesShown(); - if ( viewmode !== 'showAll' && this.viewModeShouldBeShown(viewmode) && viewTypesShown.indexOf(viewmode) === -1 ) { + if (viewmode !== 'showAll' && this.viewModeShouldBeShown(viewmode) && viewTypesShown.indexOf(viewmode) === -1) { this.show = viewmode; this.addView(viewmode); } @@ -481,6 +566,8 @@ export class ReadPage /*implements OnDestroy*/ { return false; } else if (viewmode === 'songexample' && !this.displayToggles['songexample']) { return false; + } else if (viewmode === 'illustrations' && !this.displayToggles['illustrations']) { + return false; } return true; @@ -502,7 +589,7 @@ export class ReadPage /*implements OnDestroy*/ { } catch (e) { console.log(e); } - const views = urlViews.split('&'); + const views = (urlViews + '').split('&'); if (this.params.get('views') !== undefined) { this.setViewsFromSearchResults(); } else { @@ -541,8 +628,10 @@ export class ReadPage /*implements OnDestroy*/ { if (viewmodes && viewmodes !== undefined) { const hasExpired = viewmodes.expires < now; - if (viewmodes !== undefined && viewmodes.views.length > 0 && !hasExpired) { + if (viewmodes !== undefined && viewmodes.views.length > 0 && !hasExpired && this.viewsExistInAvailableViewModes(viewmodes.views)) { this.setViews(viewmodes.views); + } else { + this.setConfigDefaultReadModeViews(); } } else { this.setConfigDefaultReadModeViews(); @@ -553,24 +642,25 @@ export class ReadPage /*implements OnDestroy*/ { setViewsFromSearchResults() { for (const v of this.params.get('views')) { if (v.type) { + // console.log(`Aading view ${v.type}, ${v.id}`); this.addView(v.type, v.id); } - if (v.type === 'manuscripts') { + if (v.type === 'manuscripts' || v.type === 'ms') { this.show = 'manuscripts'; this.typeVersion = v.id; - } else if (v.type === 'variation') { + } else if (v.type === 'variation' || v.type === 'var') { this.show = 'variations'; this.typeVersion = v.id; - } else if ((v.type === 'comments')) { + } else if ((v.type === 'comments' || v.type === 'com')) { this.show = 'comments'; - } else if (v.type === 'established') { + } else if (v.type === 'established' || v.type === 'est') { this.show = 'established'; - } else if (v.type === 'facsimiles') { + } else if (v.type === 'facsimiles' || v.type === 'facs') { this.show = 'facsimiles'; } else if (v.type === 'song-example') { this.show = 'song-example'; - } else if (v.type === 'introduction') { + } else if (v.type === 'introduction' || v.type === 'int') { this.show = 'introduction'; } } @@ -626,6 +716,12 @@ export class ReadPage /*implements OnDestroy*/ { song_id = 'nosong'; } + let chapter_id = 'nochapter'; + if (this.params.get('chapterID') !== undefined && this.params.get('chapterID') !== 'nochapter' && + this.params.get('chapterID') !== ':chapterID' && this.params.get('chapterID') !== 'chapterID') { + chapter_id = this.params.get('chapterID'); + } + if (this.params.get('search_title') !== undefined && this.params.get('search_title') !== ':song_id' && this.params.get('search_title') !== 'searchtitle') { @@ -636,16 +732,19 @@ export class ReadPage /*implements OnDestroy*/ { const colID = this.params.get('collectionID'); const pubID = this.params.get('publicationID'); - const url = `#/publication/${colID}/text/${pubID}/${facs_id}/${facs_nr}/${song_id}/${search_title}/`; + const url = `#/publication/${colID}/text/${pubID}/${chapter_id}/${facs_id}/${facs_nr}/${song_id}/${search_title}/`; const viewModes = this.getViewTypesShown(); - window.history.replaceState('', '', url.concat(viewModes.join('&'))); + // this causes problems with back, thus this check. + if (!this.navCtrl.canGoBack() ) { + window.history.replaceState('', '', url.concat(viewModes.join('&'))); + } } viewsExistInAvailableViewModes(viewmodes) { viewmodes.forEach(function (viewmode) { - if (!(this.availableViewModes.indexOf(viewmode) > -1)) { + if ( this.availableViewModes.indexOf(viewmode) === -1 ) { return false; } }.bind(this)); @@ -771,6 +870,14 @@ export class ReadPage /*implements OnDestroy*/ { console.log(e); } }.bind(this), 1000); + this.renderer.listen(this.elementRef.nativeElement, 'mouseover', (event) => { + if ((event.target.parentNode.classList.contains('tooltiptrigger') || event.target.classList.contains('tooltiptrigger')) && + this.readPopoverService.show.changes) { + if (event.target !== undefined) { + this.showChangesTooltip(event); + } + } + }); } scrollToTOC(element: HTMLElement) { @@ -783,40 +890,154 @@ export class ReadPage /*implements OnDestroy*/ { } } + private getEventTarget(event) { + let eventTarget: any = document.createElement('div'); + eventTarget['classList'] = []; + + if ( event.target.getAttribute('data-id') ) { + return event.target; + } + + if (event['target']['parentNode'] !== undefined && event['target']['parentNode']['classList'].contains('tooltiptrigger')) { + eventTarget = event['target']['parentNode']; + } else if (event.target !== undefined && event['target']['classList'].contains('tooltiptrigger')) { + eventTarget = event.target; + } else if (event.target !== undefined && eventTarget['classList'].contains('anchor')) { + eventTarget = event.target; + } + return eventTarget; + } + private setUpTextListeners() { // We must do it like this since we want to trigger an event on a dynamically loaded innerhtml. const nElement: any = this.elementRef.nativeElement; - this.listenFunc = this.renderer.listen(nElement, 'click', (event) => { - let eventTarget: any = document.createElement('div'); - eventTarget['classList'] = []; - if (event['target']['parentNode'] !== undefined && event['target']['parentNode']['classList'].contains('tooltiptrigger')) { - eventTarget = event['target']['parentNode']; - } else if ( event.target !== undefined && event['target']['classList'].contains('tooltiptrigger') ) { - eventTarget = event.target; - } else if ( event.target !== undefined && eventTarget['classList'].contains('anchor') ) { - eventTarget = event.target; + this.listenFunc = this.renderer.listen(nElement, 'click', (event) => { + const eventTarget = this.getEventTarget(event); + + if (eventTarget['classList'].contains('tooltiptrigger')) { + if (eventTarget.hasAttribute('data-id')) { + if (eventTarget['classList'].contains('person') && this.readPopoverService.show.personInfo) { + this.showPersonModal(eventTarget.getAttribute('data-id')); + } else if (eventTarget['classList'].contains('placeName') && this.readPopoverService.show.placeInfo) { + this.showPlaceModal(eventTarget.getAttribute('data-id')); + } else if (eventTarget['classList'].contains('title') && this.readPopoverService.show.workInfo) { + this.showWorkModal(eventTarget.getAttribute('data-id')); + } else if (eventTarget['classList'].contains('comment') && this.readPopoverService.show.comments) { + this.showCommentModal(eventTarget.getAttribute('data-id')); + } else if (eventTarget['classList'].contains('ttVariant') && this.readPopoverService.show.comments) { + this.showCommentModal(eventTarget.getAttribute('data-id')); + } + } else { + + } + } else if (eventTarget['classList'].contains('anchor')) { + if (eventTarget.hasAttribute('href')) { + this.scrollToElement(eventTarget.getAttribute('href')); + } + } + if (event.target.classList.contains('variantScrollTarget') && this.readPopoverService.show.comments) { + if (event.target !== undefined) { + event.target.style.fontWeight = 'bold'; + this.showVariationTooltip(event); + this.scrollToElement(event.target); + } + setTimeout(function () { + if (event.target !== undefined) { + event.target.style.fontWeight = 'normal'; + } + }, 1000); + } + if (event.target.classList.contains('tooltiptrigger') && this.readPopoverService.show.comments) { + if (event.target !== undefined) { + event.target.style.fontWeight = 'bold'; } + setTimeout(function () { + if (event.target !== undefined) { + event.target.style.fontWeight = 'normal'; + } + }, 1000); + } + }).bind(this); - if ( eventTarget['classList'].contains('tooltiptrigger')) { - if (eventTarget.hasAttribute('data-id')) { - if (eventTarget['classList'].contains('person') && this.readPopoverService.show.personInfo) { - this.showPersonModal(eventTarget.getAttribute('data-id')); - } else if (eventTarget['classList'].contains('placeName') && this.readPopoverService.show.placeInfo) { - this.showPlaceModal(eventTarget.getAttribute('data-id')); - } else if (eventTarget['classList'].contains('comment') && this.readPopoverService.show.comments) { - this.showCommentModal(eventTarget.getAttribute('data-id')); - } else if (eventTarget['classList'].contains('ttVariant') && this.readPopoverService.show.comments) { - this.showCommentModal(eventTarget.getAttribute('data-id')); - } - } else { + this.renderer.listen(nElement, 'mousewheel', (event) => { + this.showToolTip = false; + }).bind(this) + let toolTipsSettings; + try { + toolTipsSettings = this.config.getSettings('settings.toolTips'); + } catch (e) { + console.error(e); + } + this.renderer.listen(nElement, 'mouseover', (event) => { + const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + const sidePaneIsOpen = document.querySelector('ion-split-pane').classList.contains('split-pane-visible'); + + const eventTarget = this.getEventTarget(event); + const elem = event.target; + if (eventTarget['classList'].contains('tooltiptrigger')) { + const x = ((elem.getBoundingClientRect().x + vw) - vw) + (elem.offsetWidth + 10); + const y = ((elem.getBoundingClientRect().y + vh) - vh) - 108; + if (sidePaneIsOpen) { + this.toolTipPosition = { + top: y + 'px', + left: (x - 269) + 'px' + }; + } else { + this.toolTipPosition = { + top: y + 'px', + left: x + 'px' + }; + } + if (eventTarget['classList'].contains('ttVariant') && this.readPopoverService.show.comments) { + if (event.target !== undefined) { + this.showVariationTooltip(event); } - } else if ( eventTarget['classList'].contains('anchor')) { - if (eventTarget.hasAttribute('href')) { - this.scrollToElement(eventTarget.getAttribute('href')); + } + if (eventTarget.hasAttribute('data-id')) { + if (toolTipsSettings.personInfo && eventTarget['classList'].contains('person') && this.readPopoverService.show.personInfo) { + this.showToolTip = true; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + this.showPersonTooltip(eventTarget.getAttribute('data-id'), event); + } else if (toolTipsSettings.placeInfo + && eventTarget['classList'].contains('placeName') + && this.readPopoverService.show.placeInfo) { + this.showToolTip = true; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + this.showPlaceTooltip(eventTarget.getAttribute('data-id'), event); + } else if (toolTipsSettings.workInfo + && eventTarget['classList'].contains('title') + && this.readPopoverService.show.workInfo) { + this.showToolTip = true; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + this.showWorkTooltip(eventTarget.getAttribute('data-id'), event); + } else if (toolTipsSettings.comments && eventTarget['classList'].contains('comment') && this.readPopoverService.show.comments) { + this.showToolTip = true; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + this.showCommentTooltip(eventTarget.getAttribute('data-id'), event); + } else if (toolTipsSettings.footNotes + && eventTarget['classList'].contains('ttFoot')) { + this.showToolTip = true; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + this.showFootnoteTooltip(eventTarget.getAttribute('data-id'), event); } + } else { + + } + } else if (eventTarget['classList'].contains('anchor')) { + if (eventTarget.hasAttribute('href')) { + this.scrollToElement(eventTarget.getAttribute('href')); } - }).bind(this); + } + }).bind(this); + + } public get isIntroduction() { return this.textType === TextType.Introduction; @@ -834,6 +1055,28 @@ export class ReadPage /*implements OnDestroy*/ { this.getIntroduction(this.params.get('collectionID'), this.translate.currentLang); } + hideToolTip() { + window['reload_timer'] = setTimeout(() => { + this.showToolTip = false; + }, 4000); + } + + showFootnoteTooltip(id: string, origin: any) { + const target = document.getElementsByClassName('ttFixed'); + let foundElem: any = ''; + for (let i = 0; i < target.length; i++) { + const elt = target[i] as HTMLElement; + if ( elt.getAttribute('data-id') === id ) { + foundElem = this.sanitizer.bypassSecurityTrustHtml(elt.innerHTML); + break; + } + } + this.setToolTipText(foundElem); + this.tooltips.footnotes[id] = foundElem; + return foundElem; + } + + private showText() { this.textType = TextType.ReadText; @@ -880,7 +1123,7 @@ export class ReadPage /*implements OnDestroy*/ { this.events.publish('ionViewWillEnter', this.constructor.name); this.events.publish('musicAccordion:reset', true); - if (this.userSettingsService.isMobile() || this.userSettingsService.isTablet()) { + if (this.userSettingsService.isMobile()) { this.viewCtrl.showBackButton(true); } else { this.viewCtrl.showBackButton(false); @@ -905,6 +1148,9 @@ export class ReadPage /*implements OnDestroy*/ { }); } + back() { + this.viewCtrl.dismiss(); + } setText(id, data) { @@ -933,7 +1179,7 @@ export class ReadPage /*implements OnDestroy*/ { this.textService.getIntroduction(id, lang).subscribe( res => { // in order to get id attributes for tooltips - console.log('recieved introduction,..,', res.content); + // console.log('recieved introduction,..,', res.content); this.establishedText.content = this.sanitizer.bypassSecurityTrustHtml( res.content.replace(/images\//g, 'assets/images/') .replace(/\.png/g, '.svg') @@ -943,64 +1189,171 @@ export class ReadPage /*implements OnDestroy*/ { ); } - showPersonTooltip(id: string) { + showPersonTooltip(id: string, origin: any) { + if (this.tooltips.persons[id]) { + this.setToolTipText(this.tooltips.persons[id]); + return; + } + this.tooltipService.getPersonTooltip(id).subscribe( tooltip => { - this.showTooltip(tooltip.description); + let text = ''; + if ( tooltip.date_born !== null || tooltip.date_deceased !== null ) { + const date_born = String(tooltip.date_born).split('-')[0].replace(/^0+/, ''); + const date_deceased = String(tooltip.date_deceased).split('-')[0].replace(/^0+/, ''); + let bcTranslation = 'BC'; + this.translate.get('BC').subscribe( + translation => { + bcTranslation = translation; + }, error => { } + ); + const bcIndicator = (String(tooltip.date_deceased).includes('BC')) ? ' ' + bcTranslation : ''; + text = '' + tooltip.name + ' (' + date_born + '–' + date_deceased + '' + bcIndicator + ')'; + } else { + text = '' + tooltip.name + ''; + } + + if ( tooltip.description !== null ) { + text += ', ' + tooltip.description + } + + this.setToolTipText(text); + this.tooltips.persons[id] = text; }, error => { - this.showTooltip('Could not get person information'); + let noInfoFound = 'Could not get person information'; + this.translate.get('Occurrences.NoInfoFound').subscribe( + translation => { + noInfoFound = translation; + }, err => { } + ); + this.setToolTipText(noInfoFound); } ); } - showPlaceTooltip(id: string) { + showVariationTooltip(origin: any) { + if (origin.target.nextSibling.className !== undefined && String(origin.target.nextSibling.className).includes('tooltip')) { + this.showToolTip = true; + this.toolTipText = origin.target.nextSibling.textContent; + clearTimeout(window['reload_timer']); + this.hideToolTip(); + } + } + + + showChangesTooltip(origin: any) { + let elem = []; + if (origin.target.nextSibling !== null && origin.target.nextSibling !== undefined && + !String(origin.target.nextSibling.className).includes('tooltiptrigger')) { + elem = origin.target; + } else if (origin.target.parentNode.nextSibling !== null && origin.target.parentNode.nextSibling !== undefined) { + elem = origin.target.parentNode; + } + if (elem['nextSibling'] !== null && elem['nextSibling'] !== undefined) { + if (elem['nextSibling'].className !== undefined && String(elem['nextSibling'].className).includes('tooltip')) { + this.showToolTip = true; + this.toolTipText = elem['nextSibling'].textContent; + } + } + } + + showPlaceTooltip(id: string, origin: any) { + if (this.tooltips.places[id]) { + this.setToolTipText(this.tooltips.places[id]); + return; + } + this.tooltipService.getPlaceTooltip(id).subscribe( tooltip => { - this.showTooltip(tooltip.description); + this.setToolTipText((tooltip.description) ? tooltip.name + ', ' + tooltip.description : tooltip.name); + this.tooltips.places[id] = (tooltip.description) ? tooltip.name + ', ' + tooltip.description : tooltip.name; + }, + error => { + let noInfoFound = 'Could not get place information'; + this.translate.get('Occurrences.NoInfoFound').subscribe( + translation => { + noInfoFound = translation; + }, err => { } + ); + this.setToolTipText(noInfoFound); + } + ); + } + + showWorkTooltip(id: string, origin: any) { + if (this.tooltips.works[id]) { + this.setToolTipText(this.tooltips.works[id]); + return; + } + this.semanticDataService.getSingleObjectElastic('work', id).subscribe( + tooltip => { + tooltip = tooltip.hits.hits[0]['_source']; + const description = '' + tooltip.title + '
' + tooltip.reference; + this.setToolTipText(description); + this.tooltips.works[id] = description; }, error => { - this.showTooltip('Could not get place information'); + let noInfoFound = 'Could not get work information'; + this.translate.get('Occurrences.NoInfoFound').subscribe( + translation => { + noInfoFound = translation; + }, err => { } + ); + this.setToolTipText(noInfoFound); } ); } + showCommentTooltip(id: string, origin: any) { + if (this.tooltips.comments[id]) { + this.setToolTipText(this.tooltips.comments[id]); + return; + } + + id = this.establishedText.link + ';' + id; + this.tooltipService.getCommentTooltip(id).subscribe( + tooltip => { + this.setToolTipText(tooltip.description); + this.tooltips.comments[id] = tooltip.description + }, + error => { + this.setToolTipText('Could not get comment'); + } + ); + } + + + + setToolTipText(text: string) { + this.toolTipText = text; + } + showCommentModal(id: string) { id = id.replace('end', 'en'); id = this.establishedText.link + ';' + id; - const modal = this.modalCtrl.create(CommentModalPage, { id: id, title: this.texts.CommentsFor + ' ' + this.establishedText.title }); + const modal = this.modalCtrl.create( + CommentModalPage, + { id: id, title: this.texts.CommentsFor + ' ' + this.establishedText.title }, + { showBackdrop: true }); modal.present(); - } showPersonModal(id: string) { - const modal = this.modalCtrl.create(SemanticDataModalPage, { id: id, type: 'person' }); + // const modal = this.modalCtrl.create(SemanticDataModalPage, { id: id, type: 'person' }); + // modal.present(); + const modal = this.modalCtrl.create(OccurrencesPage, { id: id, type: 'subject' }); modal.present(); } showPlaceModal(id: string) { - const modal = this.modalCtrl.create(SemanticDataModalPage, { id: id, type: 'place' }); + const modal = this.modalCtrl.create(OccurrencesPage, { id: id, type: 'location' }); modal.present(); } - - - - - showCommentTooltip(id: string) { - - id = this.establishedText.link + ';' + id; - this.tooltipService.getCommentTooltip(id).subscribe( - tooltip => { - this.showTooltip(tooltip.description); - }, - error => { - this.showTooltip('Could not get comment'); - } - ); - } - - showTooltip(text: string) { + showWorkModal(id: string) { + const modal = this.modalCtrl.create(OccurrencesPage, { id: id, type: 'work' }); + modal.present(); } showPopover(myEvent) { @@ -1010,6 +1363,13 @@ export class ReadPage /*implements OnDestroy*/ { }); } + showSharePopover(myEvent) { + const popover = this.popoverCtrl.create(SharePopoverPage, {}, { cssClass: 'share-popover' }); + popover.present({ + ev: myEvent + }); + } + presentDownloadActionSheet() { const actionSheet = this.actionSheetCtrl.create({ title: 'Ladda ner digital version', @@ -1043,19 +1403,19 @@ export class ReadPage /*implements OnDestroy*/ { } openNewView(event: any) { - if (event.viewType === 'facsimile') { + if (event.viewType === 'facsimiles') { this.addView(event.viewType, event.id); } else if (event.viewType === 'manuscriptFacsimile') { - this.addView('facsimile', event.id); + this.addView('facsimiles', event.id); } else if (event.viewType === 'facsimileManuscript') { - this.addView('manuscript', event.id); + this.addView('manuscripts', event.id); } else { this.addView(event.viewType, event.id); } } addView(type: string, id?: string, fab?: FabContainer, external?: boolean) { - if ( external === true ) { + if (external === true) { this.external = id; } else { this.external = null; @@ -1070,7 +1430,8 @@ export class ReadPage /*implements OnDestroy*/ { manuscripts: { show: (type === 'manuscripts'), id: id }, variations: { show: (type === 'variations'), id: id }, introduction: { show: (type === 'introduction'), id: id }, - songexample: { show: (type === 'songexample'), id: id } + songexample: { show: (type === 'songexample'), id: id }, + illustrations: { show: (type === 'illustrations'), id: id } }); this.updateURL(); @@ -1080,9 +1441,9 @@ export class ReadPage /*implements OnDestroy*/ { } // Always open two variations if no variation is yet open - if (type === 'variations' && this.hasKey('variations', this.views) === false) { - this.addView('variations'); - } + // if (type === 'variations' && this.hasKey('variations', this.views) === false) { + // this.addView('variations'); + // } } } @@ -1133,53 +1494,145 @@ export class ReadPage /*implements OnDestroy*/ { } } - firstPage() { - this.tocService.getFirst(this.params.get('collectionID')).subscribe( - first => { - this.openAnother(first[0], 'forward'); - }, - error => { this.errorMessage = error } - ); + swipePrevNext(myEvent) { + if (myEvent.direction !== undefined) { + if (myEvent.direction === 2) { + this.next(); + } else if (myEvent.direction === 4) { + this.previous(); + } + } } - nextPage() { - if (this.prevnext !== undefined) { - this.openAnother(this.prevnext.next, 'forward'); + async previous(test?: boolean) { + if (this.legacyId === undefined) { + this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID'); + } + const c_id = this.legacyId.split('_')[0]; + await this.storage.get('toc_' + c_id).then((toc) => { + this.findTocItem(toc, 'prev'); + }); + + if (this.prevItem !== undefined && test !== true) { + await this.open(this.prevItem); + } else if (test && this.prevItem !== undefined) { + return true; + } else if (test && this.prevItem === undefined) { + return false; } } - prevPage() { - if (this.prevnext !== undefined) { - this.openAnother(this.prevnext.prev, 'back'); + async next(test?: boolean) { + if (this.legacyId === undefined) { + this.legacyId = this.params.get('collectionID') + '_' + this.params.get('publicationID'); + } + const c_id = this.legacyId.split('_')[0]; + await this.storage.get('toc_' + c_id).then((toc) => { + this.findTocItem(toc, 'next'); + }); + if (this.nextItem !== undefined && test !== true) { + await this.open(this.nextItem); + } else if (test && this.nextItem !== undefined) { + return true; + } else if (test && this.nextItem === undefined) { + return false; } } - swipePrevNext(myEvent) { - if (myEvent['offsetDirection'] !== undefined) { - if (myEvent['offsetDirection'] === 2) { - this.nextPage(); - } else if (myEvent['offsetDirection'] === 4) { - this.prevPage(); + findTocItem(toc, type?: string) { + if (!toc) { + return; + } + + if (!toc.children && toc instanceof Array) { + for (let i = 0; i < toc.length; i++) { + if (toc[i].itemId && toc[i].itemId === this.legacyId) { + if (type === 'next' && toc[i + 1]) { + if (toc[i + 1].type === 'subtitle') { + i = i + 1; + } + if (toc[i + 1] === undefined || i + 1 === toc.length) { + if ((i + 1) === toc.length) { + this.nextItem = null; + break; + } + } else { + this.nextItem = toc[i + 1]; + break; + } + } else if (type === 'prev' && toc[i - 1]) { + if (toc[i - 1].type === 'subtitle') { + i = i - 1; + } + if (toc[i - 1] === undefined || i === 0) { + if (i === 0) { + this.prevItem = null; + break; + } + } else { + this.prevItem = toc[i - 1]; + break; + } + } + } + } + } else if (toc.children) { + const childs = toc.children; + for (let j = 0; j < childs.length; j++) { + if (childs[j] && childs[j].itemId && childs[j].itemId === this.legacyId) { + + if (childs[j + 1]) { + if (childs[j + 1].itemId === '') { + this.nextItem = childs[j + 2]; + } else { + this.nextItem = childs[j + 1]; + } + } + + if (childs[j - 1].itemId === '') { + this.prevItem = childs[j - 2]; + } else { + this.prevItem = childs[j - 1]; + } + } + if (childs[j] && childs[j].children) { + this.findTocItem(childs[j].children, type); + } } } } - openAnother(tocItem: any, direction = 'forward') { - const params = { root: this.tocRoot, tocItem: tocItem, fetch: false, collection: { title: tocItem.title } }; - params['collectionID'] = tocItem.collection_id; - params['publicationID'] = tocItem.link_id; - + open(item) { + const params = { tocItem: item, collection: { title: item.itemId } }; + this.storage.set('currentTOCItem', item); const nav = this.app.getActiveNavs(); - nav[0].push('read', params, { animate: true, direction: direction, animation: 'ios-transition' }).then(() => { - // This is so that we can always hace only one text in the stack, so that - // when we press the back button i nav menu, we go to the table of contents - // instead of the previous text we read. - // this allows us to go to previous/next texts with the custom arrows - const index = nav[0].getActive().index; - nav[0].remove(index - 1); // we remove the last text we read from the stack. - // so that "back" is always the table of contents. - }); + params['tocLinkId'] = item.itemId; + const parts = item.itemId.split('_'); + params['collectionID'] = parts[0]; + params['publicationID'] = parts[1]; + + // if (this.recentlyOpenViews !== undefined && this.recentlyOpenViews.length > 0) { + // params['recentlyOpenViews'] = this.recentlyOpenViews; + // } + + console.log('Opening read from ReadPage.open()'); + nav[0].setRoot('read', params); + } + + private scrollToElement(element: HTMLElement) { + element.scrollIntoView(); + this.showToolTip = false; + try { + const elems: NodeListOf = document.querySelectorAll('span'); + for (let i = 0; i < elems.length; i++) { + if (elems[i].id === element.id) { + elems[i].scrollIntoView(); + } + } + } catch (e) { + + } } keyPress(event) { @@ -1238,4 +1691,28 @@ export class ReadPage /*implements OnDestroy*/ { return includePrevNext ? returnData : returnData.item; } } + + + firstPage() { + const c_id = this.legacyId.split('_')[0]; + const toc = this.storage.get('toc_' + c_id) + let firstItemOfCollection; + toc.then(val => { + if (val.children) { + firstItemOfCollection = val.children[1]; + // console.log(firstItemOfCollection); + + const params = {tocItem: firstItemOfCollection, collection: {title: firstItemOfCollection.itemId}}; + const nav = this.app.getActiveNavs(); + + params['tocLinkId'] = firstItemOfCollection.itemId; + const parts = firstItemOfCollection.itemId.split('_'); + params['collectionID'] = parts[0]; + params['publicationID'] = parts[1]; + + console.log('Opening read from ReadPage.firstPage()'); + nav[0].setRoot('read', params); + } + }).catch(err => console.error(err)); + } } diff --git a/src/pages/reference-data-modal/reference-data-modal.html b/src/pages/reference-data-modal/reference-data-modal.html index 90b63c6d..f0f1c7aa 100644 --- a/src/pages/reference-data-modal/reference-data-modal.html +++ b/src/pages/reference-data-modal/reference-data-modal.html @@ -21,10 +21,10 @@ -
...
+
...
-
...
-
+
...
+ diff --git a/src/pages/reference-data-modal/reference-data-modal.scss b/src/pages/reference-data-modal/reference-data-modal.scss index f90556bc..88a09719 100644 --- a/src/pages/reference-data-modal/reference-data-modal.scss +++ b/src/pages/reference-data-modal/reference-data-modal.scss @@ -7,5 +7,8 @@ page-reference-data-modal { .header{ font-size: 1.5rem; font-weight: 400; - } + } + .urn{ + white-space: normal; + } } diff --git a/src/pages/reference-data-modal/reference-data-modal.ts b/src/pages/reference-data-modal/reference-data-modal.ts index 41c9b7e4..9a9989e6 100644 --- a/src/pages/reference-data-modal/reference-data-modal.ts +++ b/src/pages/reference-data-modal/reference-data-modal.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { NavController, ViewController, NavParams, Events } from 'ionic-angular'; import { DomSanitizer } from '@angular/platform-browser'; import { ReferenceDataService } from '../../app/services/reference-data/reference-data.service'; +import { Storage } from '@ionic/storage'; /* Generated class for the ReferenceDataModal page. @@ -21,6 +22,7 @@ export class ReferenceDataModalPage { constructor( public navCtrl: NavController, public viewCtrl: ViewController, params: NavParams, + private storage: Storage, private sanitizer: DomSanitizer, private referenceDataService: ReferenceDataService, private events: Events @@ -44,6 +46,9 @@ export class ReferenceDataModalPage { if ( idParts[4] !== undefined ) { relevantParts += '/' + idParts[4]; } + if ( idParts[5] !== undefined && idParts[5] !== 'nochapter' && idParts[5] !== 'not') { + relevantParts += '/' + idParts[5]; + } this.getReferenceData(relevantParts); } @@ -58,7 +63,19 @@ export class ReferenceDataModalPage { this.referenceDataService.getReferenceData(id).subscribe( data => { this.referenceData = data; - }, + if ( String(data).length === 0 && id.includes('/') ) { + const newId = id.slice(0, id.lastIndexOf('/')); + if ( newId.length > 0 ) { + this.getReferenceData(newId); + } + } + this.storage.get('currentTOCItemTitle').then((currentTOCItemTitle) => { + if ( currentTOCItemTitle !== '' && currentTOCItemTitle !== undefined && this.referenceData['reference_text'] ) { + this.referenceData['reference_text'] = + String(this.referenceData['reference_text']).replace('[title]', currentTOCItemTitle) + } + }); + }, error => { this.referenceData = 'Unable to get referenceData'; } @@ -67,5 +84,6 @@ export class ReferenceDataModalPage { dismiss() { this.viewCtrl.dismiss(); + this.events.publish('share:dismiss'); } } diff --git a/src/pages/search-app/search-app.ts b/src/pages/search-app/search-app.ts index 3cf4fe21..0f6fdd95 100644 --- a/src/pages/search-app/search-app.ts +++ b/src/pages/search-app/search-app.ts @@ -36,6 +36,10 @@ export class SearchAppPage { onInput(event) { } + ngOnDestroy() { + this.events.unsubscribe('searchModal:closed'); + } + ionViewDidEnter() { (window).ga('set', 'page', 'Search'); (window).ga('send', 'pageview'); diff --git a/src/pages/semantic-data-modal/semantic-data-modal.html b/src/pages/semantic-data-modal/semantic-data-modal.html index 72a707ef..e3b6b573 100644 --- a/src/pages/semantic-data-modal/semantic-data-modal.html +++ b/src/pages/semantic-data-modal/semantic-data-modal.html @@ -44,5 +44,27 @@ {{"Occurrences.no_occurrences" | translate}}
+ +
+ + {{occurrence['name']}} +

 

+
+
+
+ {{"Occurrences.no_occurrences" | translate}} +
+
+ +
+ + {{occurrence['occurrences'][0].description}} +

{{occurrence['occurrences'][0].publication_collection_name}} - {{occurrence['occurrences'][0].publication_name}}

+
+
+
+ {{"Occurrences.no_occurrences" | translate}} +
+
diff --git a/src/pages/semantic-data-modal/semantic-data-modal.ts b/src/pages/semantic-data-modal/semantic-data-modal.ts index fdd60e00..5560acc5 100644 --- a/src/pages/semantic-data-modal/semantic-data-modal.ts +++ b/src/pages/semantic-data-modal/semantic-data-modal.ts @@ -4,6 +4,7 @@ import { NavController, ViewController, NavParams, App, Platform } from 'ionic-a import { DomSanitizer } from '@angular/platform-browser'; import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service'; import { TranslateService } from '@ngx-translate/core'; +import { Occurrence } from '../../app/models/occurrence.model'; /* Generated class for the SemanticDataModal page. @@ -27,6 +28,7 @@ export class SemanticDataModalPage { public tagOccurrences: Array; public workOccurrences: Array; public type: string; + public publicationTOCNames: Array; constructor( public navCtrl: NavController, public viewCtrl: ViewController, @@ -41,6 +43,7 @@ export class SemanticDataModalPage { ) { let id = params.get('id'); + this.publicationTOCNames = []; this.legacyPrefix = ''; try { @@ -73,13 +76,13 @@ export class SemanticDataModalPage { } if (this.type === 'tag') { - this.getPerson(id); + this.getTag(id); this.getTagOccurrencesById(id); } if (this.type === 'work') { - this.getPerson(id); - this.getWorkOccurrencesById(id); + this.getWork(params.get('id')); + this.getWorkOccurrencesById(params.get('id')); } } @@ -87,6 +90,54 @@ export class SemanticDataModalPage { } + getTag(id: string) { + this.semanticDataService.getTag(id).subscribe( + data => { + // in order to get id attributes for tooltips + this.title = data.name; + this.description = data.description; + this.semanticData = this.sanitizer.bypassSecurityTrustHtml( + String(data).replace(/images\//g, 'assets/images/') + .replace(/\.png/g, '.svg') + ); + + }, + error => { + this.semanticData = 'Unable to get semanticData'; + } + ); + } + + getPublicationTOCName(occ_data, all_data) { + const itemId = occ_data['occurrences'][0]['collection_id'] + '_' + occ_data['occurrences'][0]['publication_id']; + this.semanticDataService.getPublicationTOC(occ_data['occurrences'][0]['collection_id']).subscribe( + toc_data => { + this.updatePublicationNames(toc_data, all_data, itemId); + }, + error => { + this.semanticData = 'Unable to get semanticData'; + } + ); + } + + getWork(id: string) { + this.semanticDataService.getWork(id).subscribe( + data => { + // in order to get id attributes for tooltips + this.title = data.title; + this.description = data.description; + this.semanticData = this.sanitizer.bypassSecurityTrustHtml( + String(data).replace(/images\//g, 'assets/images/') + .replace(/\.png/g, '.svg') + ); + + }, + error => { + this.semanticData = 'Unable to get semanticData'; + } + ); + } + getPlace(id: string) { this.semanticDataService.getPlace(id).subscribe( data => { @@ -138,6 +189,14 @@ export class SemanticDataModalPage { this.semanticDataService.getSubjectOccurrencesById(id).subscribe( data => { this.subjectOccurrences = data; + const addedTOCs: Array = []; + this.subjectOccurrences.forEach(element => { + if ( element['occurrences'][0]['collection_id'] !== undefined && + addedTOCs.includes(element['occurrences'][0]['collection_id']) === false ) { + this.getPublicationTOCName(element, this.subjectOccurrences); + addedTOCs.push(element['occurrences'][0]['collection_id']); + } + }); }, error => { this.semanticData = 'Unable to get semanticData'; @@ -145,10 +204,31 @@ export class SemanticDataModalPage { ); } + public updatePublicationNames(tocData, allData, itemId) { + tocData.forEach( item => { + allData.forEach(data => { + data['occurrences'].forEach(occ => { + const id = occ['collection_id'] + '_' + occ['publication_id']; + if ( id === item['itemId'] ) { + occ['publication_name'] = item['text']; + } + }); + }); + }); + } + public getLocationOccurrencesById(id: string) { this.semanticDataService.getLocationOccurrencesById(id).subscribe( data => { this.locationOccurrences = data; + const addedTOCs: Array = []; + this.locationOccurrences.forEach(element => { + if ( element['occurrences'][0]['collection_id'] !== undefined && + addedTOCs.includes(element['occurrences'][0]['collection_id']) === false ) { + this.getPublicationTOCName(element, this.locationOccurrences); + addedTOCs.push(element['occurrences'][0]['collection_id']); + } + }); }, error => { this.semanticData = 'Unable to get semanticData'; @@ -160,6 +240,14 @@ export class SemanticDataModalPage { this.semanticDataService.getTagOccurrencesById(id).subscribe( data => { this.tagOccurrences = data; + const addedTOCs: Array = []; + this.tagOccurrences.forEach(element => { + if ( element['occurrences'][0]['collection_id'] !== undefined && + addedTOCs.includes(element['occurrences'][0]['collection_id']) === false ) { + this.getPublicationTOCName(element, this.tagOccurrences); + addedTOCs.push(element['occurrences'][0]['collection_id']); + } + }); }, error => { this.semanticData = 'Unable to get semanticData'; @@ -171,6 +259,14 @@ export class SemanticDataModalPage { this.semanticDataService.getWorkOccurrencesById(id).subscribe( data => { this.workOccurrences = data; + const addedTOCs: Array = []; + this.workOccurrences.forEach(element => { + if ( element['occurrences'][0]['collection_id'] !== undefined && + addedTOCs.includes(element['occurrences'][0]['collection_id']) === false ) { + this.getPublicationTOCName(element, this.workOccurrences); + addedTOCs.push(element['occurrences'][0]['collection_id']); + } + }); }, error => { this.semanticData = 'Unable to get semanticData'; @@ -195,31 +291,29 @@ export class SemanticDataModalPage { openText(text: any) { const params = {}; - const nav = this.app.getActiveNavs(); - const col_id = text.collection_id; - const pub_id = text.publication_id; + const col_id = (text.collection_id !== undefined ) ? text.collection_id : text.publication_collection_id; + const pub_id = (text.publication_id !== undefined) ? text.publication_id : text.id; let text_type: string; - if (text.publication_facsimile_id !== null) { + if (text.publication_facsimile_id !== undefined && text.publication_facsimile_id !== null) { text_type = 'facsimiles'; - } else if (text.publication_comment_id !== null) { + } else if (text.publication_comment_id !== undefined && text.publication_comment_id !== null) { text_type = 'comments'; - } else if (text.publication_version_id !== null) { + } else if (text.publication_version_id !== undefined && text.publication_version_id !== null) { text_type = 'variations' - } else if (text.publication_manuscript_id !== null) { + } else if (text.publication_manuscript_id !== undefined && text.publication_manuscript_id !== null) { text_type = 'manuscripts' } else { text_type = 'established'; } - params['tocLinkId'] = text.collection_id; + if (this.type === 'work') { + text_type = 'established'; + } + + params['tocLinkId'] = col_id; params['collectionID'] = col_id; params['publicationID'] = pub_id; - /*if ( text.facsimilePage ) { - params['facsimilePage'] = text.facsimile_page; - } else { - params['facsimilePage'] = null; - }*/ params['views'] = [ { @@ -234,13 +328,8 @@ export class SemanticDataModalPage { params['showOccurrencesModalOnRead'] = true; } - if (this.platform.is('mobile')) { this.viewCtrl.dismiss(); this.app.getRootNav().push('read', params); - } else { - this.viewCtrl.dismiss(); - this.app.getRootNav().push('read', params); - } } dismiss() { diff --git a/src/pages/share-popover/share-popover.html b/src/pages/share-popover/share-popover.html new file mode 100644 index 00000000..af3893c6 --- /dev/null +++ b/src/pages/share-popover/share-popover.html @@ -0,0 +1,31 @@ + + + + {{'Reference.title' | translate }} +
+
+ + +

{{'Reference.urn' | translate }}

+
+
+ + Dela + + +

Mail

+
+ + +

Facebook

+
+ + +

Twitter

+
+ + +

Instagram

+
+
+
diff --git a/src/pages/share-popover/share-popover.module.ts b/src/pages/share-popover/share-popover.module.ts new file mode 100644 index 00000000..a6e68ee2 --- /dev/null +++ b/src/pages/share-popover/share-popover.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { SharePopoverPage } from './share-popover'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + declarations: [ + SharePopoverPage, + ], + imports: [ + IonicPageModule.forChild(SharePopoverPage), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }) + ], +}) +export class SharePopoverPageModule {} diff --git a/src/pages/share-popover/share-popover.scss b/src/pages/share-popover/share-popover.scss new file mode 100644 index 00000000..aebff949 --- /dev/null +++ b/src/pages/share-popover/share-popover.scss @@ -0,0 +1,27 @@ +page-share-popover { + ion-list { + margin: 0 !important; + } + + ion-label { + overflow: visible; + } + + ion-item { + cursor: pointer; + + ion-icon { + display: flex; + align-items: center; + font-size: 1em !important; + } + + p { + font-weight: 700; + } + } + .close{ + font-size: 1rem; + float: right; + } +} diff --git a/src/pages/share-popover/share-popover.ts b/src/pages/share-popover/share-popover.ts new file mode 100644 index 00000000..d5086dfe --- /dev/null +++ b/src/pages/share-popover/share-popover.ts @@ -0,0 +1,84 @@ +import { Component } from '@angular/core'; +import { Events, IonicPage, ModalController, NavController, NavParams, ViewController } from 'ionic-angular'; +import { ReferenceDataModalPage } from '../../pages/reference-data-modal/reference-data-modal'; + +/** + * Generated class for the SharePopoverPage page. + * + * See https://ionicframework.com/docs/components/#navigation for more info on + * Ionic pages and navigation. + */ + +@IonicPage() +@Component({ + selector: 'page-share-popover', + templateUrl: 'share-popover.html', +}) +export class SharePopoverPage { + showSocial = { + facebook: true, + email: true, + twitter: true, + instagram: false + }; + constructor( + public navCtrl: NavController, + public navParams: NavParams, + public events: Events, + public viewCtrl: ViewController, + private modalController: ModalController) { + } + + ionViewDidLoad() { + this.doAnalytics('click'); + this.events.subscribe('share:dismiss', (page) => { + this.dismiss(); + }); + } + + shareURI() { + this.doAnalytics('URI'); + this.showReference(); + } + + shareFacebook() { + this.doAnalytics('FB'); + } + + shareTwitter() { + this.doAnalytics('Twitter'); + } + + shareInstagram() { + this.doAnalytics('Insta'); + } + + shareEmail() { + this.doAnalytics('Email'); + } + + doAnalytics(type) { + try { + (window).ga('send', 'event', { + eventCategory: 'Share-Popover', + eventLabel: 'Share-Popover', + eventAction: type, + eventValue: 10 + }); + } catch ( e ) { + } + } + + private showReference() { + // Get URL of Page and then the URI + const modal = this.modalController.create(ReferenceDataModalPage, {id: document.URL, type: 'reference'}); + modal.present(); + modal.onDidDismiss(data => { + // console.log('dismissed', data); + }); + } + + dismiss() { + this.viewCtrl.dismiss(); + } +} diff --git a/src/pages/single-edition-part/single-edition-part.ts b/src/pages/single-edition-part/single-edition-part.ts index 47d8cd6a..314040a9 100644 --- a/src/pages/single-edition-part/single-edition-part.ts +++ b/src/pages/single-edition-part/single-edition-part.ts @@ -13,7 +13,7 @@ import { TableOfContentsService } from '../../app/services/toc/table-of-contents @IonicPage({ name: 'single-edition-part', - segment: 'publication/:collectionID/table-of-contents/:id/' + segment: 'publication-part-toc/:collectionID/:id/' }) @Component({ selector: 'page-single-edition-part', diff --git a/src/pages/single-edition/single-edition.ts b/src/pages/single-edition/single-edition.ts index fa06c3bb..d350d7c3 100644 --- a/src/pages/single-edition/single-edition.ts +++ b/src/pages/single-edition/single-edition.ts @@ -7,6 +7,7 @@ import { global } from '../../app/global'; import { TranslateService, TranslatePipe } from '@ngx-translate/core'; import { ReadPopoverPage } from '../read-popover/read-popover'; import { DomSanitizer } from '@angular/platform-browser'; +import { Storage } from '@ionic/storage'; import { TextService } from '../../app/services/texts/text.service'; import { HtmlContentService } from '../../app/services/html/html-content.service'; import { LanguageService } from '../../app/services/languages/language.service'; @@ -26,7 +27,8 @@ import { PdfService } from '../../app/services/pdf/pdf.service'; @IonicPage({ name: 'single-edition', - segment: 'publication/:id/table-of-contents' + segment: 'publication-toc/:id', + priority: 'high' }) @Component({ selector: 'page-single-edition', @@ -51,6 +53,9 @@ export class SingleEditionPage { description: string; showPage = false; show: string; + hasTitle: boolean; + hasCover: boolean; + hasIntro: boolean; childrenPdfs = []; hasDigitalEditionListChildren = false; @@ -65,6 +70,7 @@ export class SingleEditionPage { protected htmlService: HtmlContentService, protected translate: TranslateService, protected app: App, + private storage: Storage, protected langService: LanguageService, protected events: Events, protected sanitizer: DomSanitizer, @@ -83,7 +89,25 @@ export class SingleEditionPage { this.show = this.config.getSettings('defaults.ReadModeView'); }); - if (this.collection !== undefined && this.collection.id !== undefined) { + try { + this.hasTitle = this.config.getSettings('HasTitle'); + } catch (e) { + this.hasTitle = false; + } + + try { + this.hasIntro = this.config.getSettings('HasIntro'); + } catch (e) { + this.hasIntro = false; + } + + try { + this.hasCover = this.config.getSettings('HasCover'); + } catch (e) { + this.hasCover = false; + } + + if (this.collection !== undefined && this.collection.id !== undefined && this.collection.id !== 'mediaCollections') { if (this.collection.title !== undefined) { global.setSubtitle(this.collection.title); } @@ -98,7 +122,7 @@ export class SingleEditionPage { } const collectionImages = this.config.getSettings('editionImages'); - if ( this.collection.id !== undefined ) { + if ( this.collection.id !== undefined && this.collection.id !== 'mediaCollections' ) { this.image = collectionImages[this.collection.id]; this.setCollectionTitle(); this.events.publish('title-logo:collectionTitle', this.subTitle); @@ -171,16 +195,18 @@ export class SingleEditionPage { } async setCollectionTitle() { - await this.textService.getCollection(this.params.get('id')).subscribe( - collection => { - this.subTitle = collection[0].name; - this.events.publish('title-logo:collectionTitle', collection[0].name); - }, - error => { - console.log('could not get collection title'); - }, - () => console.log(this.subTitle) - ); + if ( this.params.get('id') !== 'mediaCollections' ) { + await this.textService.getCollection(this.params.get('id')).subscribe( + collection => { + this.subTitle = collection[0].name; + this.events.publish('title-logo:collectionTitle', collection[0].name); + }, + error => { + console.log('could not get collection title'); + }, + () => console.log(this.subTitle) + ); + } } ionViewWillLeave() { @@ -190,9 +216,11 @@ export class SingleEditionPage { this.events.publish('ionViewWillEnter', this.constructor.name); this.events.publish('tableOfContents:unSelectSelectedTocItem', true); this.events.publish('musicAccordion:reset', true); - if (this.collection.id && this.collection.isDownloadOnly === false) { + if (this.collection.id && !this.collection.isDownloadOnly) { this.getTocRoot(this.collection.id); - this.maybeLoadTitlePage(this.collection.id); + this.maybeLoadIntroductionPage(this.collection.id); + } else { + console.log(this.collection.id, 'perhaps maybe'); } this.viewCtrl.setBackButtonText(''); console.log('single collection ion will enter...'); @@ -200,14 +228,40 @@ export class SingleEditionPage { } getTocRoot(id: string) { - this.tableOfContentsService.getTableOfContents(id) - .subscribe( - tocItems => { - this.tocItems = tocItems; - console.log('get toc root... --- --- in single edition'); - this.events.publish('tableOfContents:loaded', { tocItems: tocItems }); - }, - error => { this.errorMessage = error }); + this.storage.get('toc_' + id).then((tocItemsC) => { + if (tocItemsC) { + this.tocItems = tocItemsC; + console.log('get toc root... --- --- in single edition'); + const tocLoadedParams = { tocItems: tocItemsC }; + tocLoadedParams['collectionID'] = this.collection; + tocLoadedParams['searchTocItem'] = true; + this.events.publish('tableOfContents:loaded', tocLoadedParams); + console.log('toc from cache'); + } else { + if ( id !== 'mediaCollections' ) { + this.tableOfContentsService.getTableOfContents(id) + .subscribe( + tocItems => { + this.tocItems = tocItems; + console.log('get toc root... --- --- in single edition'); + const tocLoadedParams = { tocItems: tocItems }; + tocLoadedParams['collectionID'] = this.collection; + tocLoadedParams['searchTocItem'] = true; + this.events.publish('tableOfContents:loaded', tocLoadedParams); + this.storage.set('toc_' + id, tocItems); + }, + error => { this.errorMessage = error }); + } else { + this.tocItems = this.collection['accordionToc']['toc']; + const tocLoadedParams = { tocItems: this.tocItems }; + tocLoadedParams['collectionID'] = 'mediaCollections'; + tocLoadedParams['searchTocItem'] = true; + this.events.publish('tableOfContents:loaded', tocLoadedParams); + this.storage.set('toc_' + id, this.tocItems); + console.log('media'); + } + } + }); } getTableOfContents(id: string) { @@ -241,22 +295,23 @@ export class SingleEditionPage { params['collectionID'] = this.params.get('id') try { params['publicationID'] = String(this.tocItems['children'][0]['itemId']).split('_')[1]; + const nav = this.app.getActiveNavs(); + console.log('Opening read from SingleEdition.openFirstPage()'); + nav[0].setRoot('read', params); } catch (e) { - console.log(e); - params['publicationID'] = '1'; + this.maybeLoadIntroductionPage(params['collectionID']); } - - const nav = this.app.getActiveNavs(); - nav[0].setRoot('read', params); } - maybeLoadTitlePage(collectionID: string) { - if (this.platform.is('core') || this.platform.is('tablet')) { + maybeLoadIntroductionPage(collectionID: string) { const nav = this.app.getActiveNavs(); const params = { collection: this.collection, fetch: true, collectionID: this.collection.id }; - nav[0].setRoot('cover', params); - } else { - this.showPage = true; - } + if ( this.hasIntro ) { + nav[0].setRoot('introduction', params); + } else if ( this.hasCover ) { + nav[0].setRoot('cover-page', params); + } else if ( this.hasTitle ) { + nav[0].setRoot('title-page', params); + } } } diff --git a/src/pages/tag-search/tag-search.html b/src/pages/tag-search/tag-search.html index cafcf34e..20c5145a 100644 --- a/src/pages/tag-search/tag-search.html +++ b/src/pages/tag-search/tag-search.html @@ -44,7 +44,7 @@ - + @@ -56,7 +56,7 @@ - +
@@ -67,7 +67,7 @@
- + diff --git a/src/pages/tag-search/tag-search.ts b/src/pages/tag-search/tag-search.ts index 5ac8003c..52d22ded 100644 --- a/src/pages/tag-search/tag-search.ts +++ b/src/pages/tag-search/tag-search.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ChangeDetectorRef } from '@angular/core'; import { IonicPage, NavController, NavParams, App, Platform, ToastController, ModalController, Content, Events, ViewController } from 'ionic-angular'; import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service'; @@ -51,6 +51,9 @@ export class TagSearchPage { cacheItem = false; showLoading = false; showFilter = true; + from = 0; + infiniteScrollNumber = 30; + filters: any[] = []; selectedLinkID: string; @@ -74,7 +77,8 @@ export class TagSearchPage { public modalCtrl: ModalController, public viewCtrl: ViewController, private userSettingsService: UserSettingsService, - private events: Events + private events: Events, + private cf: ChangeDetectorRef ) { this.langService.getLanguage().subscribe((lang) => { this.appName = this.config.getSettings('app.name.' + lang); @@ -105,34 +109,52 @@ export class TagSearchPage { gettags() { this.showLoading = true; - this.semanticDataService.getTagOccurrences().subscribe( + this.semanticDataService.getTagElastic(this.from, this.searchText, this.filters).subscribe( tags => { - + const tagsTmp = []; + tags = tags.hits.hits; tags.forEach(element => { - element.name = String(element.name).toLocaleLowerCase() + element = element['_source']; + element.name = String(element.name) + element['sortBy'] = String(element.name).trim().replace('ʽ', '').toUpperCase(); + if ( element.name ) { + let found = false; + this.tags.forEach(tag => { + if ( tag.id === element['id'] ) { + found = true; + } + }); + if ( !found ) { + tagsTmp.push(element); + this.tags.push(element); + } + } + const ltr = element['sortBy'].charAt(0); + const mt = ltr.match(/[a-zåäö]/i); + if (ltr.length === 1 && ltr.match(/[a-zåäö]/i) !== null) { + // console.log(ltr); + } else { + const combining = /[\u0300-\u036F]/g; + element['sortBy'] = element['sortBy'].normalize('NFKD').replace(combining, '').replace(',', ''); + } }); - this.allData = tags; - this.cacheData = tags; + this.allData = this.tags; + this.cacheData = this.tags; this.showLoading = false; - this.sortListAlphabeticallyAndGroup(this.allData); - - for (let i = 0; i < 30; i++) { - if (i === tags.length) { - break; - } else { - this.tags.push(tags[this.count]); - this.tagsCopy.push(tags[this.count]); - this.count++ - } - } }, err => {console.error(err); this.showLoading = false; }, () => console.log(this.tags) ); } + onChanged(obj) { + this.cf.detectChanges(); + console.log('segment changed') + this.filter(obj); + } + loadMoretags() { for (let i = 0; i < 30; i++) { if (i === this.allData.length) { @@ -155,17 +177,10 @@ export class TagSearchPage { } sortByLetter(letter) { - const list = []; - try { - for (const p of this.allData) { - if (p.name && p.name.startsWith(letter)) { - list.push(p); - } - } - } catch ( e ) { - this.tags = this.allData; - } - this.tags = list; + this.searchText = letter; + this.tags = []; + this.cf.detectChanges(); + this.gettags(); } ionViewDidLeave() { @@ -209,11 +224,16 @@ export class TagSearchPage { } filter(terms) { + if ( terms._value ) { + terms = terms._value; + } if (!terms) { this.tags = this.tagsCopy; } else if (terms != null) { + this.from = 0; + this.gettags(); this.tags = []; - terms = terms.toLocaleLowerCase(); + terms = String(terms).toLowerCase().replace(' ', ''); for (const tag of this.allData) { if (tag.name) { const title = tag.name.toLocaleLowerCase(); @@ -233,13 +253,8 @@ export class TagSearchPage { } doInfinite(infiniteScroll) { - for (let i = 0; i < 30; i++) { - if ( this.allData !== undefined ) { - this.tags.push(this.allData[this.count]); - this.tagsCopy.push(this.allData[this.count]); - this.count++; - } - } + this.from += this.infiniteScrollNumber; + this.gettags(); infiniteScroll.complete(); } @@ -369,21 +384,21 @@ export class TagSearchPage { // Sort alphabetically data.sort(function(a, b) { - if (a.name < b.name) { return -1; } - if (a.name > b.name) { return 1; } + if (a.sortBy < b.sortBy) { return -1; } + if (a.sortBy > b.sortBy) { return 1; } return 0; }); // Check when first character changes in order to divide names into alphabetical groups for (let i = 0; i < data.length ; i++) { if (data[i] && data[i - 1]) { - if (data[i].name && data[i - 1].name) { - if (data[i].name.length > 1 && data[i - 1].name.length > 1) { - if (data[i].name.charAt(0) !== data[i - 1].name.charAt(0)) { - console.log(data[i].name.charAt(0) + ' != ' + data[i - 1].name.charAt(0)) - const ltr = data[i].name.charAt(0); + if (data[i].sortBy && data[i - 1].sortBy) { + if (data[i].sortBy.length > 1 && data[i - 1].sortBy.length > 1) { + if (data[i].sortBy.charAt(0) !== data[i - 1].sortBy.charAt(0)) { + console.log(data[i].sortBy.charAt(0) + ' != ' + data[i - 1].sortBy.charAt(0)) + const ltr = data[i].sortBy.charAt(0); if (ltr.length === 1 && ltr.match(/[a-z]/i)) { - data[i]['firstOfItsKind'] = data[i].name.charAt(0); + data[i]['firstOfItsKind'] = data[i].sortBy.charAt(0); } } } @@ -392,8 +407,8 @@ export class TagSearchPage { } for (let j = 0; j < data.length; j++) { - if (data[j].name.length > 1) { - data[j]['firstOfItsKind'] = data[j].name.charAt(0); + if (data[j].sortBy.length > 1) { + data[j]['firstOfItsKind'] = data[j].sortBy.charAt(0); break; } } @@ -436,10 +451,13 @@ export class TagSearchPage { } openFilterModal() { - const filterModal = this.modalCtrl.create(FilterPage, { searchType: 'tag-search' }); + const filterModal = this.modalCtrl.create(FilterPage, { searchType: 'tag-search', activeFilters: this.filters }); filterModal.onDidDismiss(filters => { if (filters) { + this.tags = []; + this.allData = []; + this.filters = filters; if (filters['isEmpty']) { console.log('filters are empty') this.tags = []; @@ -447,6 +465,11 @@ export class TagSearchPage { this.count = 0; this.gettags(); } + + if (filters.filterCategoryTypes) { + this.gettags(); + } + if (filters.filterCollections) { const filterSelected = filters.filterCollections.some(function(el) { return el.selected === true; diff --git a/src/pages/title/title.html b/src/pages/title/title.html new file mode 100644 index 00000000..b8501086 --- /dev/null +++ b/src/pages/title/title.html @@ -0,0 +1,15 @@ + +
+ +
+ + + +
+ +
+
+
+
+
+
diff --git a/src/pages/title/title.module.ts b/src/pages/title/title.module.ts new file mode 100644 index 00000000..1968ed22 --- /dev/null +++ b/src/pages/title/title.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TitlePage } from './title'; + +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { ComponentsModule } from '../../components/components.module'; +import { HttpClient } from '@angular/common/http'; +import { MarkdownModule } from 'angular2-markdown'; + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + declarations: [ + TitlePage, + ], + imports: [ + IonicPageModule.forChild(TitlePage), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }), + ComponentsModule, + MarkdownModule.forRoot(), + ], +}) +export class TitlePageModule {} diff --git a/src/pages/title/title.scss b/src/pages/title/title.scss new file mode 100644 index 00000000..e6121d52 --- /dev/null +++ b/src/pages/title/title.scss @@ -0,0 +1,3 @@ +page-title { + +} diff --git a/src/pages/title/title.ts b/src/pages/title/title.ts new file mode 100644 index 00000000..eb758d9a --- /dev/null +++ b/src/pages/title/title.ts @@ -0,0 +1,188 @@ +import { Component } from '@angular/core'; +import { IonicPage, NavController, NavParams, Events } from 'ionic-angular'; +import { DomSanitizer } from '@angular/platform-browser'; +import { Storage } from '@ionic/storage'; +import { LanguageService } from '../../app/services/languages/language.service'; +import { TextService } from '../../app/services/texts/text.service'; +import { UserSettingsService } from '../../app/services/settings/user-settings.service'; +import { TableOfContentsService } from '../../app/services/toc/table-of-contents.service'; +import { ConfigService } from '@ngx-config/core'; +import { MdContentService } from '../../app/services/md/md-content.service'; + +/** + * Generated class for the TitlePage page. + * + * See https://ionicframework.com/docs/components/#navigation for more info on + * Ionic pages and navigation. + */ + +@IonicPage({ + name: 'title-page', + segment: 'publication-title/:collectionID', + priority: 'high' +}) +@Component({ + selector: 'page-title', + templateUrl: 'title.html', +}) +export class TitlePage { + + errorMessage: any; + mdContent: string; + lang = 'sv'; + hasMDTitle = false; + hasDigitalEditionListChildren = false; + childrenPdfs = []; + protected id: string; + protected text: any; + protected collection: any; + titleSelected: boolean; + collectionID: any; + + constructor( + public navCtrl: NavController, + public navParams: NavParams, + private langService: LanguageService, + private textService: TextService, + private mdService: MdContentService, + protected sanitizer: DomSanitizer, + protected params: NavParams, + protected events: Events, + private storage: Storage, + private userSettingsService: UserSettingsService, + protected tableOfContentsService: TableOfContentsService, + public config: ConfigService, + public mdContentService: MdContentService + ) { + this.titleSelected = true; + this.id = this.params.get('collectionID'); + console.log(`Titlepage id is ${this.id}`); + + this.collection = this.params.get('collection'); + if ( this.params.get('publicationID') === undefined ) { + this.titleSelected = true; + } else { + this.titleSelected = false; + } + this.mdContent = ''; + try { + this.hasMDTitle = this.config.getSettings('ProjectStaticMarkdownTitleFolder'); + } catch (e) { + this.hasMDTitle = false; + } + + this.events.subscribe('language:change', () => { + this.langService.getLanguage().subscribe((lang) => { + this.lang = lang; + this.ionViewDidLoad(); + }); + }); + + this.checkIfCollectionHasChildrenPdfs(); + if (!isNaN(Number(this.id))) { + if (this.hasMDTitle) { + const folder = this.hasMDTitle; + this.getMdContent(`${this.lang}-${folder}-${this.id}`); + } + } + } + + ngOnDestroy() { + this.events.unsubscribe('language:change'); + } + + checkIfCollectionHasChildrenPdfs() { + this.collectionID = this.params.get('collectionID'); + let configChildrenPdfs = []; + + try { + configChildrenPdfs = this.config.getSettings(`collectionChildrenPdfs.${this.collectionID}`); + } catch (e) {} + + if (configChildrenPdfs.length) { + this.childrenPdfs = configChildrenPdfs; + this.hasDigitalEditionListChildren = true; + this.events.publish('CollectionWithChildrenPdfs:highlight', this.collectionID); + } + } + + ionViewWillLeave() { + this.events.publish('ionViewWillLeave', this.constructor.name); + } + ionViewWillEnter() { + this.events.publish('ionViewWillEnter', this.constructor.name); + this.events.publish('musicAccordion:reset', true); + this.events.publish('tableOfContents:unSelectSelectedTocItem', {'selected': 'title'}); + + this.events.publish('SelectedItemInMenu', { + menuID: this.params.get('collectionID'), + component: 'title-page' + }); + + } + + getMdContent(fileID: string) { + this.mdContentService.getMdContent(fileID) + .subscribe( + text => { this.mdContent = text.content; }, + error => {this.errorMessage = error} + ); + } + + getTocRoot(id: string) { + this.storage.get('toc_' + id).then((tocItemsC) => { + if (tocItemsC) { + tocItemsC.titleSelected = this.titleSelected; + this.events.publish('tableOfContents:loaded', {tocItems: tocItemsC, searchTocItem: true, collectionID: tocItemsC.collectionId, 'caller': 'title'}); + } else { + this.tableOfContentsService.getTableOfContents(id) + .subscribe( + tocItems => { + tocItems.titleSelected = this.titleSelected; + this.events.publish('tableOfContents:loaded', {tocItems: tocItems, searchTocItem: true, collectionID: tocItems.collectionId, 'caller': 'title'}); + this.storage.set('toc_' + id, tocItems); + }, + error => {this.errorMessage = error}); + } + }); + } + + ionViewDidLoad() { + this.getTocRoot(this.params.get('collectionID')); + this.events.publish('pageLoaded:title'); + if (!isNaN(Number(this.id))) { + if (!this.hasMDTitle) { + this.langService.getLanguage().subscribe(lang => { + this.textService.getTitlePage(this.id, lang).subscribe( + res => { + // in order to get id attributes for tooltips + this.text = this.sanitizer.bypassSecurityTrustHtml( + res.content.replace(/images\//g, 'assets/images/') + .replace(/\.png/g, '.svg') + ); + }, + error => { + this.errorMessage = error; + } + ); + }); + } else { + if (isNaN(Number(this.id))) { + this.langService.getLanguage().subscribe(lang => { + const fileID = lang + '-08'; + this.hasMDTitle = true; + this.mdService.getMdContent(fileID).subscribe( + res => { + // in order to get id attributes for tooltips + this.mdContent = res.content; + }, + error => { + this.errorMessage = error; + } + ); + }); + } + } + } + } +} diff --git a/src/pages/user-settings-popover/user-settings-popover.html b/src/pages/user-settings-popover/user-settings-popover.html index 503af08d..42f58bd0 100644 --- a/src/pages/user-settings-popover/user-settings-popover.html +++ b/src/pages/user-settings-popover/user-settings-popover.html @@ -1,37 +1,30 @@ - - {{"About.Language" | translate}} - - - {{lang}} - - + + {{"About.Language" | translate}} + + + {{lang}} + + - - View mode - - - Auto - - - - - Desktop - - - - - Mobile - - - - - Read focus - - - - - - + + View mode + + + Auto + + + + + Desktop + + + + + Mobile + + + + \ No newline at end of file diff --git a/src/pages/user-settings-popover/user-settings-popover.ts b/src/pages/user-settings-popover/user-settings-popover.ts index 83bae8ce..a0dde489 100644 --- a/src/pages/user-settings-popover/user-settings-popover.ts +++ b/src/pages/user-settings-popover/user-settings-popover.ts @@ -78,6 +78,4 @@ export class UserSettingsPopoverPage { togglePageNumbering() { // git this.readPopoverService.show.pageNumbering = this.show.pageNumbering; } - - } diff --git a/src/pages/work-search/work-search.html b/src/pages/work-search/work-search.html index a57e0afe..cd1a50a6 100644 --- a/src/pages/work-search/work-search.html +++ b/src/pages/work-search/work-search.html @@ -51,7 +51,7 @@ - + @@ -72,8 +72,8 @@ {{p.firstOfItsKind}} - -

{{p.name}}

+ + {{(author['last_name'] != '')?author['last_name'].trim():''}}, {{(author['first_name'] != '')?author['first_name'].trim():''}}{{(i < (p.author_data.length-1))?', ': ''}}, {{p['title']}} @@ -85,4 +85,4 @@
- \ No newline at end of file + diff --git a/src/pages/work-search/work-search.ts b/src/pages/work-search/work-search.ts index 7e72cd92..19ceafb4 100644 --- a/src/pages/work-search/work-search.ts +++ b/src/pages/work-search/work-search.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ChangeDetectorRef } from '@angular/core'; import { IonicPage, NavController, NavParams, App, Platform, ToastController, ModalController, Content, Events, ViewController } from 'ionic-angular'; import { SemanticDataService } from '../../app/services/semantic-data/semantic-data.service'; @@ -51,11 +51,14 @@ export class WorkSearchPage { cacheItem = false; showLoading = false; showFilter = true; + objectType = 'work'; selectedLinkID: string; // tslint:disable-next-line:max-line-length alphabet: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö']; + from = 0; + infiniteScrollNumber = 200; constructor(public navCtrl: NavController, public navParams: NavParams, @@ -71,7 +74,8 @@ export class WorkSearchPage { public viewCtrl: ViewController, public modalCtrl: ModalController, private userSettingsService: UserSettingsService, - private events: Events + private events: Events, + private cf: ChangeDetectorRef ) { this.langService.getLanguage().subscribe((lang) => { this.appName = this.config.getSettings('app.name.' + lang); @@ -109,36 +113,64 @@ export class WorkSearchPage { getworks() { this.showLoading = true; - this.semanticDataService.getWorkOccurrences().subscribe( + this.semanticDataService.getWorksElastic(this.from, this.searchText).subscribe( works => { + works = works.hits.hits; + this.showLoading = false; + const worksTmp = []; works.forEach(element => { - element.name = String(element.name).toLocaleLowerCase() - }); + element = element['_source']; - this.allData = works; - this.cacheData = works; - this.showLoading = false; + element['sortBy'] = String(element['title']).trim().replace('ʽ', ''); - this.sortListAlphabeticallyAndGroup(this.allData); + // remove any empty author_data + if ( element['author_data'][0]['id'] === undefined ) { + element['author_data'] = []; + } + + if ( element['author_data'].length > 0 ) { + element['sortBy'] = String(element['author_data'][0]['first_name']).trim().replace('ʽ', ''); + } + + // prefer sorting by last_name + if ( element['author_data'].length > 0 && String(element['author_data'][0]['last_name']).trim().length > 0 ) { + element['sortBy'] = String(element['author_data'][0]['last_name']).trim().replace('ʽ', ''); + } - for (let i = 0; i < 30; i++) { - if (i === works.length) { - break; + const ltr = element['sortBy'].charAt(0); + const mt = ltr.match(/[a-zåäö]/i); + if (ltr.length === 1 && ltr.match(/[a-zåäö]/i) !== null) { + // console.log(ltr); } else { - this.works.push(works[this.count]); - this.worksCopy.push(works[this.count]); - this.count++ + const combining = /[\u0300-\u036F]/g; + element['sortBy'] = element['sortBy'].normalize('NFKD').replace(combining, '').replace(',', ''); } - } + let found = false; + this.works.forEach(work => { + work.id = work.man_id; + work.description = work.reference; + work.name = work.title; + if ( work.id === element['id'] ) { + found = true; + } + }); + if ( !found ) { + worksTmp.push(element); + this.works.push(element); + } + }); + + this.allData = this.works; + this.cacheData = this.works; + this.sortListAlphabeticallyAndGroup(this.allData); }, - err => {console.error(err); this.showLoading = false; }, - () => console.log(this.works) + err => {console.error(err); this.showLoading = false; } ); } loadMoreworks() { - for (let i = 0; i < 30; i++) { + for (let i = 0; i < this.infiniteScrollNumber; i++) { if (i === this.allData.length) { break; } else { @@ -175,11 +207,16 @@ export class WorkSearchPage { } filter(terms) { + if ( terms._value ) { + terms = terms._value; + } if (!terms) { this.works = this.worksCopy; } else if (terms != null) { + this.from = 0; this.works = []; - terms = terms.toLocaleLowerCase(); + this.getworks(); + terms = String(terms).toLowerCase().replace(' ', ''); for (const work of this.allData) { if (work.name) { const title = work.name.toLocaleLowerCase(); @@ -194,24 +231,58 @@ export class WorkSearchPage { } } } else { - this.works = this.worksCopy; + this.worksCopy = this.worksCopy; } } + onChanged(obj) { + this.cf.detectChanges(); + this.filter(obj); + } + doInfinite(infiniteScroll) { - for (let i = 0; i < 30; i++) { - if ( this.allData !== undefined ) { - this.works.push(this.allData[this.count]); - this.worksCopy.push(this.allData[this.count]); - this.count++; - } - } + this.from += this.infiniteScrollNumber; + this.getworks(); infiniteScroll.complete(); } async openWork(occurrenceResult: OccurrenceResult) { - const occurrenceModal = this.modalCtrl.create(OccurrencesPage, { occurrenceResult: occurrenceResult }); - occurrenceModal.present(); + let showOccurrencesModalOnRead = false; + if (this.config.getSettings('showOccurencesModalOnReadPageAfterSearch.tagSearch')) { + showOccurrencesModalOnRead = true; + } + + let openOccurrencesAndInfoOnNewPage = false; + + try { + openOccurrencesAndInfoOnNewPage = this.config.getSettings('OpenOccurrencesAndInfoOnNewPage'); + } catch (e) { + openOccurrencesAndInfoOnNewPage = false; + } + + if (openOccurrencesAndInfoOnNewPage) { + const nav = this.app.getActiveNavs(); + + const params = { + id: occurrenceResult.id, + objectType: this.objectType + } + + if ((this.platform.is('mobile') || this.userSettingsService.isMobile()) && !this.userSettingsService.isDesktop()) { + nav[0].push('occurrences-result', params); + } else { + nav[0].setRoot('occurrences-result', params); + } + + } else { + const occurrenceModal = this.modalCtrl.create(OccurrencesPage, { + occurrenceResult: occurrenceResult, + showOccurrencesModalOnRead: showOccurrencesModalOnRead, + objectType: this.objectType + }); + + occurrenceModal.present(); + } } async download() { @@ -301,21 +372,21 @@ export class WorkSearchPage { // Sort alphabetically data.sort(function(a, b) { - if (a.name < b.name) { return -1; } - if (a.name > b.name) { return 1; } + if (a.sortBy < b.sortBy) { return -1; } + if (a.sortBy > b.sortBy) { return 1; } return 0; }); // Check when first character changes in order to divide names into alphabetical groups for (let i = 0; i < data.length ; i++) { if (data[i] && data[i - 1]) { - if (data[i].name && data[i - 1].name) { - if (data[i].name.length > 1 && data[i - 1].name.length > 1) { - if (data[i].name.charAt(0) !== data[i - 1].name.charAt(0)) { - console.log(data[i].name.charAt(0) + ' != ' + data[i - 1].name.charAt(0)) - const ltr = data[i].name.charAt(0); + if (data[i].sortBy && data[i - 1].sortBy) { + if (data[i].sortBy.length > 1 && data[i - 1].sortBy.length > 1) { + if (data[i].sortBy.charAt(0) !== data[i - 1].sortBy.charAt(0)) { + console.log(data[i].sortBy.charAt(0) + ' != ' + data[i - 1].sortBy.charAt(0)) + const ltr = data[i].sortBy.charAt(0); if (ltr.length === 1 && ltr.match(/[a-z]/i)) { - data[i]['firstOfItsKind'] = data[i].name.charAt(0); + data[i]['firstOfItsKind'] = data[i].sortBy.charAt(0); } } } @@ -324,8 +395,8 @@ export class WorkSearchPage { } for (let j = 0; j < data.length; j++) { - if (data[j].name.length > 1) { - data[j]['firstOfItsKind'] = data[j].name.charAt(0); + if (data[j].sortBy.length > 1) { + data[j]['firstOfItsKind'] = data[j].sortBy.charAt(0); break; } } @@ -340,16 +411,7 @@ export class WorkSearchPage { const pub_id = text.collectionID.split('_')[1]; let text_type: string; - if (text.textType === 'ms') { - text_type = 'manuscripts'; - } else if (text.textType === 'var') { - text_type = 'variations'; - } else if (text.textType === 'facs') { - text_type = 'facsimiles' - } else { - text_type = 'comments'; - } - + text_type = 'established'; params['tocLinkId'] = text.collectionID; params['collectionID'] = col_id; params['publicationID'] = pub_id; @@ -359,12 +421,7 @@ export class WorkSearchPage { id: text.linkID } ]; - - if (this.platform.is('mobile')) { this.app.getRootNav().push('read', params); - } else { - this.app.getRootNav().push('read', params); - } } openFilterModal() { diff --git a/src/pages/work-search/worksearch.scss b/src/pages/work-search/worksearch.scss index 4715918b..13151899 100644 --- a/src/pages/work-search/worksearch.scss +++ b/src/pages/work-search/worksearch.scss @@ -7,7 +7,7 @@ page-work-search { .scroll-content { margin-top: 40px !important; } - + .occurence { background-color: white; } @@ -52,7 +52,7 @@ page-work-search { .filter-button { font-size: 2.8rem; } - + .searchbar { color: white; } @@ -60,4 +60,14 @@ page-work-search { .loading{ float: right; } + .work_title{ + font-style: italic; + } + .author{ + color: color($colors, gray); + } + + div .item-inner{ + cursor: pointer; + } } diff --git a/src/theme/_project-variables.scss b/src/theme/_project-variables.scss index dc5bce16..7fb6669c 100644 --- a/src/theme/_project-variables.scss +++ b/src/theme/_project-variables.scss @@ -42,7 +42,10 @@ location: #62c3e1, tag: #0fa14c, abbreviations: #D3DD9F, - changes: #c0c0c0 + changes: #c0c0c0, + work: #e6572c, + blackish: #363636, + grey: #666666 ); @@ -181,10 +184,14 @@ p{ font-size: 1.1rem; } -.popover_settings .popover-content{ width: min-content !important; } +.popover_settings .popover-content{ width: min-content !important; } .selected-toc-item { p { font-weight: bold; - } -} \ No newline at end of file + } +} + +.mainMenuItem { + cursor: pointer; +} diff --git a/src/theme/_tei.scss b/src/theme/_tei.scss index db83d240..e7b336fd 100644 --- a/src/theme/_tei.scss +++ b/src/theme/_tei.scss @@ -14,7 +14,6 @@ padding-left: 100px; } h3.title { - font-variant:small-caps; text-align: center; font-weight: normal !important; } @@ -105,7 +104,7 @@ .bottomMargin { margin-bottom: 1em; } - p.dateline + p.dateline { margin-top: 1.0em; margin-bottom: 1.0em; @@ -196,11 +195,11 @@ margin-top: 0; margin-bottom: 0; } - p.lIndent + p.lIndent { text-indent:-9px !important; } - .tei_indent + .tei_indent { padding-left:30px !important; display: inline-block; @@ -395,7 +394,7 @@ padding-left: 30px !important; text-indent: -30px !important; } - p.noteLegend + p.noteLegend { text-indent: 0px !important; margin-bottom: 1em; @@ -417,7 +416,7 @@ content:" – "; } .noteText { - + } /* MANUSCRIPT */ @@ -808,6 +807,9 @@ .highlightForeign { background-color: #cae4eb !important; } + .highlightWork { + background-color: #cae4eb !important; + } // these will be svg img.tooltiptrigger { @@ -842,6 +844,9 @@ .tei.show_placeInfo span.placeName {background-color: color($colors, location);} .tei.show_placeInfo span.placeName[id] {cursor:pointer;} +.tei.show_workInfo span.title {background-color: color($colors, work);} +.tei.show_workInfo span.title[id] {cursor:pointer;} + .tei span.corr_hide {display: none;} .tei.show_choiceStyle span.corr_hide {display: inherit;} .tei.show_choiceStyle span.corr {background-color: color($colors, light); cursor: default;} @@ -889,3 +894,6 @@ .tei.smallFont p { font-size: 1rem; line-height: 1.5rem;} .tei.largeFont p { font-size: 1.6rem; line-height: 2rem;} .tei.mediumFont p { font-size: 1.2rem; line-height: 1.8rem;} + +@media only screen and (max-width: 600px) { div.tei{ max-width: 300px; } } +@media only screen and (min-width: 800px) { div.tei{ max-width: 670px; } } diff --git a/src/theme/variables.scss b/src/theme/variables.scss index de885ea1..24633be5 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -39,7 +39,8 @@ $colors: ( ms: #b0e162, inl: #e162ac, tit: #62c3e1, - var: #b262e1 + var: #b262e1, + work: #e6572c ); diff --git a/tsconfig.json b/tsconfig.json index d71e3ea2..cf0daf86 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,9 @@ "noUnusedLocals": false, "lib": [ "dom", - "es2015" + "es2015", + "es2017.object", + "es2016.array.include" ], "module": "es2015", "moduleResolution": "node", @@ -30,4 +32,4 @@ "atom": { "rewriteTsconfig": false } -} \ No newline at end of file +} diff --git a/tslint.json b/tslint.json index bf73b2a1..c997f8f4 100644 --- a/tslint.json +++ b/tslint.json @@ -1,6 +1,6 @@ { "rules": { - "callable-types": true, + "callable-types": true, "class-name": true, "comment-format": [ true, @@ -65,7 +65,7 @@ true, "single" ], - "radix": true, + "radix": false, "semicolon": [ "always" ], @@ -98,4 +98,4 @@ "rulesDirectory": [ "node_modules/tslint-eslint-rules/dist/rules" ] -} \ No newline at end of file +}