mirror of
https://github.com/gramps-project/gramps-web.git
synced 2026-10-16 21:01:38 +08:00
Blog, fixes, improvements
This commit is contained in:
+2
-2
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="description" content="Gramps.js Genealogical Research Web App">
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@100;200;300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;200;300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
|
||||
<link rel="icon" href="images/favicon.ico">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
+1
-1
@@ -532,7 +532,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
|
||||
+20
-2
@@ -37,7 +37,9 @@ import './views/GrampsjsViewFamily.js'
|
||||
import './views/GrampsjsViewPlace.js'
|
||||
import './views/GrampsjsViewEvent.js'
|
||||
import './views/GrampsjsViewSource.js'
|
||||
import './views/GrampsjsViewBlog.js'
|
||||
import './views/GrampsjsViewCitation.js'
|
||||
import './views/GrampsjsViewDashboard.js'
|
||||
import './views/GrampsjsViewRepository.js'
|
||||
import './views/GrampsjsViewNote.js'
|
||||
import './views/GrampsjsViewMedia.js'
|
||||
@@ -236,7 +238,8 @@ export class GrampsJs extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
${this.renderContent()}
|
||||
<mwc-snackbar id="error-snackbar"></mwc-snackbar>
|
||||
<mwc-snackbar id="error-snackbar" leading></mwc-snackbar>
|
||||
<mwc-snackbar id="notification-snackbar" leading></mwc-snackbar>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -356,7 +359,7 @@ export class GrampsJs extends LitElement {
|
||||
<span>${this._('Home Page')}</span>
|
||||
<mwc-icon slot="graphic">home</mwc-icon>
|
||||
</grampsjs-list-item>
|
||||
<grampsjs-list-item href="/" graphic="icon">
|
||||
<grampsjs-list-item href="/blog" graphic="icon">
|
||||
<span>${this._('Blog')}</span>
|
||||
<mwc-icon slot="graphic">rss_feed</mwc-icon>
|
||||
</grampsjs-list-item>
|
||||
@@ -394,6 +397,9 @@ export class GrampsJs extends LitElement {
|
||||
|
||||
${this._tabHtml(tabs)}
|
||||
|
||||
<grampsjs-view-dashboard class="page" ?active=${this._page === 'home'} .strings="${this._strings}"></grampsjs-view-dashboard>
|
||||
<grampsjs-view-blog class="page" ?active=${this._page === 'blog'} .strings="${this._strings}"></grampsjs-view-blog>
|
||||
|
||||
<grampsjs-view-people class="page" ?active=${this._page === 'people'} .strings="${this._strings}"></grampsjs-view-people>
|
||||
<grampsjs-view-families class="page" ?active=${this._page === 'families'} .strings="${this._strings}"></grampsjs-view-families>
|
||||
<grampsjs-view-events class="page" ?active=${this._page === 'events'} .strings="${this._strings}"></grampsjs-view-events>
|
||||
@@ -453,6 +459,7 @@ export class GrampsJs extends LitElement {
|
||||
installMediaQueryWatcher('(min-width: 768px)', (matches) => {this.wide = matches})
|
||||
this.addEventListener('nav', this._handleNav.bind(this))
|
||||
this.addEventListener('grampsjs:error', this._handleError.bind(this))
|
||||
this.addEventListener('grampsjs:notification', this._handleNotification.bind(this))
|
||||
this.addEventListener('progress:on', this._progressOn.bind(this))
|
||||
this.addEventListener('progress:off', this._progressOff.bind(this))
|
||||
window.addEventListener('user:loggedout', this._handleLogout.bind(this))
|
||||
@@ -523,6 +530,11 @@ export class GrampsJs extends LitElement {
|
||||
this._showError(message)
|
||||
}
|
||||
|
||||
_handleNotification(e) {
|
||||
const {message} = e.detail
|
||||
this._showToast(message)
|
||||
}
|
||||
|
||||
_handleLoginKey(event) {
|
||||
if(event.code === 'Enter') {
|
||||
this._submitLogin()
|
||||
@@ -597,6 +609,12 @@ export class GrampsJs extends LitElement {
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
_showToast(msg) {
|
||||
const snackbar = this.shadowRoot.getElementById('notification-snackbar')
|
||||
snackbar.labelText = msg
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
_openUserMenu() {
|
||||
const userMenu = this.shadowRoot.getElementById('user-menu')
|
||||
userMenu.open = true
|
||||
|
||||
+56
-50
@@ -97,7 +97,11 @@ export async function apiGetTokens(username, password) {
|
||||
|
||||
|
||||
|
||||
export async function apiRefreshAuthToken(refreshToken) {
|
||||
export async function apiRefreshAuthToken() {
|
||||
const refreshToken = localStorage.getItem('refresh_token')
|
||||
if (refreshToken === null) {
|
||||
return {error: 'No refresh token found!'}
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`${__APIHOST__}/api/token/refresh/`, {
|
||||
method: 'POST',
|
||||
@@ -116,7 +120,7 @@ export async function apiRefreshAuthToken(refreshToken) {
|
||||
throw(new Error('Access token missing in response'))
|
||||
}
|
||||
const expires = Date.now() + 15 * 60 * 1000
|
||||
storeAuthToken(resp.access_token, expires)
|
||||
storeAuthToken(data.access_token, expires)
|
||||
return {}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -162,6 +166,56 @@ export async function apiGet(endpoint) {
|
||||
}
|
||||
};
|
||||
|
||||
async function apiPutPost(method, endpoint, payload) {
|
||||
const accessToken = localStorage.getItem('access_token')
|
||||
let headers = {}
|
||||
if (accessToken !== null) {
|
||||
headers = {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`${__APIHOST__}${endpoint}`, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
if (resp.status === 401) {
|
||||
const refreshToken = localStorage.getItem('refresh_token')
|
||||
if (refreshToken === null) {
|
||||
throw(new Error('Missing refresh token'))
|
||||
}
|
||||
const refreshResp = await apiRefreshAuthToken()
|
||||
if ('error' in refreshResp) {
|
||||
throw(new Error(refreshResp.error))
|
||||
}
|
||||
}
|
||||
if (resp.status === 400 || resp.status === 401 || resp.status === 403) {
|
||||
throw(new Error('Authorization error'))
|
||||
}
|
||||
if (resp.status !== 201) {
|
||||
throw(new Error(`Error ${resp.status}`))
|
||||
}
|
||||
return {}
|
||||
}
|
||||
catch (error) {
|
||||
return {'error': error.message}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export async function apiPut(endpoint, payload) {
|
||||
return apiPutPost('PUT', endpoint, payload)
|
||||
}
|
||||
|
||||
|
||||
export async function apiPost(endpoint, payload) {
|
||||
return apiPutPost('POST', endpoint, payload)
|
||||
}
|
||||
|
||||
|
||||
export function getMediaUrl(handle) {
|
||||
const jwt = localStorage.getItem('access_token')
|
||||
if (jwt === null) { return '' }
|
||||
@@ -189,51 +243,3 @@ export function getThumbnailUrlCropped(handle, rect, size, square=false) {
|
||||
const [x1, y1, x2, y2] = rect
|
||||
return `${__APIHOST__}/api/media/${handle}/cropped/${x1}/${y1}/${x2}/${y2}/thumbnail/${size}?jwt=${jwt}&square=${square}`
|
||||
}
|
||||
|
||||
|
||||
export async function changeOwnPassword(oldPw, newPw) {
|
||||
try {
|
||||
const resp = await fetch(`${__APIHOST__}/api/users/-/password/change/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({'old_password': oldPw, 'new_password': newPw})
|
||||
})
|
||||
if (resp.status === 400 || resp.status === 401 || resp.status === 403) {
|
||||
throw(new Error('Wrong password'))
|
||||
}
|
||||
if (resp.status !== 201) {
|
||||
throw(new Error(`Error ${resp.status}`))
|
||||
}
|
||||
return {}
|
||||
}
|
||||
catch (error) {
|
||||
return {'error': error.message}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function changeOwnDetails(payload) {
|
||||
try {
|
||||
const resp = await fetch(`${__APIHOST__}/api/users/-/`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
if (resp.status === 400 || resp.status === 401 || resp.status === 403) {
|
||||
throw(new Error('Not allowed'))
|
||||
}
|
||||
if (resp.status !== 201) {
|
||||
throw(new Error(`Error ${resp.status}`))
|
||||
}
|
||||
return {}
|
||||
}
|
||||
catch (error) {
|
||||
return {'error': error.message}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ class GrampsjsImg extends LitElement {
|
||||
sharedStyles,
|
||||
css`
|
||||
img {
|
||||
max-height:100vh;
|
||||
/* max-height:100vh; */
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.round {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import {html, css, LitElement} from 'lit-element'
|
||||
import {sharedStyles} from '../SharedStyles.js'
|
||||
import '@material/mwc-button'
|
||||
|
||||
import './GrampsJsImage.js'
|
||||
import {prettyTimeDiffTimestamp} from '../util.js'
|
||||
|
||||
|
||||
export class GrampsjsBlogPost extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
h2 {
|
||||
color: #222222;
|
||||
font-weight: 300;
|
||||
font-size: 40px;
|
||||
padding-bottom: 0.3em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #222222;
|
||||
}
|
||||
|
||||
h3.author {
|
||||
font-family: Roboto;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.15em;
|
||||
margin-bottom: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#img-container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#image {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
#note {
|
||||
margin: 4em 0em;
|
||||
}
|
||||
|
||||
#note-wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
grampsjs-note-content {
|
||||
--grampsjs-note-line-height: 1.7em;
|
||||
--grampsjs-note-font-size: 18px;
|
||||
--grampsjs-note-font-family: Roboto Slab;
|
||||
--grampsjs-note-color: #333;
|
||||
}
|
||||
|
||||
#btn-details {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
h2 {
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
grampsjs-note-content {
|
||||
--grampsjs-note-font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
source: {type: Object},
|
||||
note: {type: Object},
|
||||
strings: {type: Object},
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.source = {}
|
||||
this.note = {}
|
||||
this.strings = {}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (Object.keys(this.source).length === 0) {
|
||||
return html``
|
||||
}
|
||||
return html`
|
||||
<div class="blog-preview">
|
||||
<h2>${this.source.title}</h2>
|
||||
<h3 class="author">${this.source.author} ~ ${this.strings.__lang__ ? prettyTimeDiffTimestamp(this.source.change, this.strings.__lang__) : ''}</h3>
|
||||
<div id="image">
|
||||
${this.source?.media_list?.length ? this._renderImage() : ''}
|
||||
</div>
|
||||
<div id="note">
|
||||
<div id="note-wrapper">
|
||||
<grampsjs-note-content
|
||||
?framed="${1 === 2}"
|
||||
grampsId="${this.note.grampsId}"
|
||||
content="${this.note?.formatted?.html || this.note?.text?.string || 'Error loading note'}"
|
||||
>
|
||||
</grampsjs-note-content>
|
||||
|
||||
<mwc-button id="btn-details" @click="${() => this._clickDetails(this.source.gramps_id)}">Details</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
_clickDetails(grampsId) {
|
||||
this.dispatchEvent(new CustomEvent('nav', {bubbles: true, composed: true, detail: {
|
||||
path: `source/${grampsId}`}}))
|
||||
}
|
||||
|
||||
_renderImage() {
|
||||
const ref = this.source.media_list[0]
|
||||
const obj = this.source.extended.media[0]
|
||||
return html`
|
||||
<div id="img-container">
|
||||
<grampsjs-img
|
||||
handle="${obj.handle}"
|
||||
size="1000"
|
||||
.rect="${ref.rect || []}"
|
||||
mime="${obj.mime}"
|
||||
></grampsjs-img>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-blog-post', GrampsjsBlogPost)
|
||||
@@ -0,0 +1,68 @@
|
||||
import {html, css, LitElement} from 'lit-element'
|
||||
import {sharedStyles} from '../SharedStyles.js'
|
||||
|
||||
import './GrampsJsImage.js'
|
||||
|
||||
|
||||
export class GrampsjsBlogPreview extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
data: {type: Object},
|
||||
strings: {type: Object},
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.data = {}
|
||||
this.strings = {}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (Object.keys(this.data).length === 0) {
|
||||
return html``
|
||||
}
|
||||
return html`
|
||||
<div class="blog-preview">
|
||||
<h2>${this.data.title}</h2>
|
||||
<h3>${this.data.author}</h3>
|
||||
${this.data?.extended?.notes?.length ? this._renderNoteSnippet() : ''}
|
||||
${this.data?.media_list?.length ? this._renderImage() : ''}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
_renderNoteSnippet() {
|
||||
const note = this.data.extended.notes[0]
|
||||
return html`
|
||||
<p>${note?.text?.string || ''}</p>
|
||||
`
|
||||
}
|
||||
|
||||
_renderImage() {
|
||||
const ref = this.data.media_list[0]
|
||||
const obj = this.data.extended.media[0]
|
||||
return html`
|
||||
<grampsjs-img
|
||||
handle="${obj.handle}"
|
||||
size="200"
|
||||
displayHeight="200"
|
||||
.rect="${ref.rect || []}"
|
||||
square
|
||||
mime="${obj.mime}"
|
||||
></grampsjs-img>
|
||||
`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-blog-preview', GrampsjsBlogPreview)
|
||||
@@ -21,6 +21,7 @@ export class GrampsjsNote extends GrampsjsObject {
|
||||
<h2><mwc-icon class="person">sticky_note_2</mwc-icon> ${this._(this.data?.type || 'Note')}</h2>
|
||||
|
||||
<grampsjs-note-content
|
||||
framed
|
||||
grampsId="${this.data.gramps_id}"
|
||||
content="${this.data?.formatted?.html || this.data?.text?.string || 'Error loading note'}"
|
||||
></grampsjs-note-content>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { html, css, LitElement } from 'lit-element';
|
||||
import { sharedStyles } from '../SharedStyles.js'
|
||||
import {html, css, LitElement} from 'lit-element'
|
||||
import {sharedStyles} from '../SharedStyles.js'
|
||||
|
||||
|
||||
export class GrampsjsNoteContent extends LitElement {
|
||||
@@ -8,49 +8,59 @@ export class GrampsjsNoteContent extends LitElement {
|
||||
sharedStyles,
|
||||
css`
|
||||
:host {
|
||||
font-family: var(--grampsjs-note-font-family, Roboto Slab);
|
||||
font-size: var(--grampsjs-note-font-size, 20px);
|
||||
line-height: var(--grampsjs-note-line-height, 1.5em);
|
||||
color: var(--grampsjs-note-color, #000000);
|
||||
}
|
||||
|
||||
.note {
|
||||
font-family: Roboto Slab;
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.note.frame {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 8px;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.note p {
|
||||
.note.frame p {
|
||||
margin: 2em 0em;
|
||||
}
|
||||
|
||||
.note p:first-child {
|
||||
.note.frame p:first-child {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
.note p:last-child {
|
||||
.note.frame p:last-child {
|
||||
margin-bottom:0;
|
||||
}
|
||||
`
|
||||
];
|
||||
]
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
grampsId: { type: String },
|
||||
content: { type: String },
|
||||
};
|
||||
grampsId: {type: String},
|
||||
content: {type: String},
|
||||
framed: {type: Boolean}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.framed = false
|
||||
}
|
||||
|
||||
render() {
|
||||
// <p>${this.grampsId}</p>
|
||||
return html`
|
||||
<div class="note" id="note-content"></div>
|
||||
<div class="note ${this.framed ? 'frame' : ''}" id="note-content"></div>
|
||||
`
|
||||
}
|
||||
|
||||
updated() {
|
||||
const noteContent = this.shadowRoot.getElementById("note-content")
|
||||
const noteContent = this.shadowRoot.getElementById('note-content')
|
||||
noteContent.innerHTML = this.content
|
||||
}
|
||||
|
||||
@@ -58,4 +68,4 @@ export class GrampsjsNoteContent extends LitElement {
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-note-content', GrampsjsNoteContent);
|
||||
window.customElements.define('grampsjs-note-content', GrampsjsNoteContent)
|
||||
|
||||
@@ -195,11 +195,11 @@ export class GrampsjsObject extends LitElement {
|
||||
latitude="${this.data.profile.lat}"
|
||||
longitude="${this.data.profile.long}"
|
||||
mapid="place-map"
|
||||
id="map"
|
||||
>
|
||||
<grampsjs-map-marker>
|
||||
<grampsjs-map-marker
|
||||
latitude="${this.data.profile.lat}"
|
||||
longitude="${this.data.profile.long}"
|
||||
popup="Hallo"
|
||||
>
|
||||
</grampsjs-map-marker>
|
||||
</grampsjs-map>`
|
||||
@@ -224,11 +224,11 @@ export class GrampsjsObject extends LitElement {
|
||||
case('participants'):
|
||||
return html`<grampsjs-participants .strings=${this.strings} .data=${[this.data?.profile?.participants]}></grampsjs-participants>`
|
||||
case('references'):
|
||||
return html`<grampsjs-references .strings=${this.strings} .data=${[this.data?.extended?.backlinks]}></grampsjs-references>`
|
||||
return html`<grampsjs-references .strings=${this.strings} .data=${[this.data?.extended?.backlinks]} .profile=${this.data?.profile?.references || {}}></grampsjs-references>`
|
||||
default:
|
||||
break
|
||||
}
|
||||
return html`Missing: ${this._currentTab}`
|
||||
return html``
|
||||
}
|
||||
|
||||
_getTabs() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {html} from 'lit-element'
|
||||
|
||||
import {GrampsjsTableBase} from './GrampsjsTableBase.js'
|
||||
import {getName} from '../util.js'
|
||||
import {getName, getNameFromProfile} from '../util.js'
|
||||
|
||||
|
||||
function capitalize(string) {
|
||||
@@ -9,6 +9,18 @@ function capitalize(string) {
|
||||
}
|
||||
|
||||
export class GrampsjsReferences extends GrampsjsTableBase {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
profile: {type: Object}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.profile = {}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.data.length === 0 || Object.keys(this.data[0]).length === 0) {
|
||||
return html``
|
||||
@@ -19,10 +31,10 @@ export class GrampsjsReferences extends GrampsjsTableBase {
|
||||
<th>${this._('Type')}</th>
|
||||
<th>${this._('Description')}</th>
|
||||
</tr>
|
||||
${Object.keys(this.data[0]).map((type) => this.data[0][type].map((obj) => html`
|
||||
${Object.keys(this.data[0]).map((type) => this.data[0][type].map((obj, index) => html`
|
||||
<tr @click=${() => this._handleClick(type, obj.gramps_id)}>
|
||||
<td>${this._(capitalize(type))}</td>
|
||||
<td>${getName(obj, type) || ''}</td>
|
||||
<td>${type in this.profile ? getNameFromProfile(this.profile[type][index] || {}, type, this.strings) || '' : getName(obj, type)}</td>
|
||||
</tr>
|
||||
`, this),
|
||||
this)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement, css, html } from 'lit-element';
|
||||
import {LitElement, css, html} from 'lit-element'
|
||||
|
||||
import { hex12ToCss } from '../color.js';
|
||||
import { sharedStyles } from '../SharedStyles.js';
|
||||
import {hex12ToCss} from '../color.js'
|
||||
import {sharedStyles} from '../SharedStyles.js'
|
||||
|
||||
|
||||
export class GrampsjsTags extends LitElement {
|
||||
@@ -22,21 +22,22 @@ export class GrampsjsTags extends LitElement {
|
||||
}
|
||||
|
||||
.tags {
|
||||
clear: left;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
`
|
||||
];
|
||||
]
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
data: { type: Array },
|
||||
};
|
||||
data: {type: Array},
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.data = [];
|
||||
super()
|
||||
this.data = []
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +60,6 @@ export class GrampsjsTags extends LitElement {
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-tags', GrampsjsTags);
|
||||
window.customElements.define('grampsjs-tags', GrampsjsTags)
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export const grampsStrings = [
|
||||
'Addresses',
|
||||
'Age at death',
|
||||
'and',
|
||||
'Archive',
|
||||
'Association Note',
|
||||
'Associations',
|
||||
'Attribute Note',
|
||||
@@ -71,6 +72,7 @@ export const grampsStrings = [
|
||||
'Farm',
|
||||
'Father',
|
||||
'Gallery',
|
||||
'General',
|
||||
'Given name',
|
||||
'Gramps ID',
|
||||
'Hamlet',
|
||||
|
||||
+26
-2
@@ -44,7 +44,7 @@ export function eventTitleFromProfile(eventProfile, strings) {
|
||||
const families = eventProfile?.participants?.families.filter((obj) => obj.role === family) || []
|
||||
const primaryPeople = `${people.map((obj) => personTitleFromProfile(obj.person)).join(', ')}
|
||||
${families.map((obj) => familyTitleFromProfile(obj.family)).join(', ')}`
|
||||
return html`${eventProfile.type}: ${primaryPeople || '?'}`
|
||||
return html`${eventProfile.type}${primaryPeople.trim() ? `: ${primaryPeople}` : ''}${eventProfile.date ? ` (${eventProfile.date})` : ''}`
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ export function showObject(type, obj, strings) {
|
||||
return html`
|
||||
<mwc-icon class="inline">place</mwc-icon>
|
||||
<a href="/${type}/${obj.gramps_id}"
|
||||
>${getName(obj, type) || type}
|
||||
>${obj?.profile?.name || obj.title || type}
|
||||
</a>
|
||||
`
|
||||
case 'source':
|
||||
@@ -178,3 +178,27 @@ export function debounce (func, wait) {
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getNameFromProfile(obj, type, strings) {
|
||||
switch(type) {
|
||||
case 'person':
|
||||
return personTitleFromProfile(obj)
|
||||
case 'event':
|
||||
return eventTitleFromProfile(obj, strings)
|
||||
case 'family':
|
||||
return familyTitleFromProfile(obj)
|
||||
case 'place':
|
||||
return obj.title
|
||||
case 'source':
|
||||
return obj.title
|
||||
case 'repository':
|
||||
return obj.name
|
||||
case 'citation':
|
||||
return citationTitleFromProfile(obj)
|
||||
case 'media':
|
||||
return obj.desc
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ export class GrampsjsView extends LitElement {
|
||||
render() {
|
||||
if (this.error) {
|
||||
this.dispatchEvent(new CustomEvent('grampsjs:error', {bubbles: true, composed: true, detail: {message: this._errorMessage}}))
|
||||
return html``
|
||||
}
|
||||
return this.renderContent()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import {css, html} from 'lit-element'
|
||||
|
||||
import {GrampsjsView} from './GrampsjsView.js'
|
||||
import '../components/GrampsjsBlogPost.js'
|
||||
import {apiGet} from '../api.js'
|
||||
|
||||
|
||||
export class GrampsjsViewBlog extends GrampsjsView {
|
||||
static get styles() {
|
||||
return [
|
||||
super.styles,
|
||||
css`
|
||||
.muted {
|
||||
opacity: 0.4;
|
||||
}
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_dataSources: {type: Array},
|
||||
_dataNotes: {type: Array},
|
||||
_totalCount: {type: Number},
|
||||
_page: {type: Number},
|
||||
_pages: {type: Number}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._dataSources = []
|
||||
this._dataNotes = []
|
||||
this._page = 1
|
||||
this._pageSize = 1
|
||||
this._firstLoaded = false
|
||||
this._totalCount = -1
|
||||
this._pages = -1
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return html`
|
||||
${this.renderPosts()}
|
||||
${this._totalCount > 0 ? this.renderPagination() : ''}
|
||||
`
|
||||
}
|
||||
|
||||
renderPosts() {
|
||||
if (this._firstLoaded && this._dataSources.length === 0) {
|
||||
return html`
|
||||
<h2>${this._('Blog')}</h2>
|
||||
<p class="muted">To start using the blog, add a source with tag 'Blog'.</p>
|
||||
`
|
||||
}
|
||||
if (this.loading) {
|
||||
return html``
|
||||
}
|
||||
return html`
|
||||
${this._dataSources.map((source, i) => this.renderPost(source, this._dataNotes[i]), this)}
|
||||
`
|
||||
}
|
||||
|
||||
renderPagination() {
|
||||
return html`
|
||||
<grampsjs-pagination
|
||||
page="${this._page}"
|
||||
pages="${this._pages}"
|
||||
@page:changed="${this._handlePageChanged}"
|
||||
></grampsjs-pagination>
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
_handlePageChanged(event) {
|
||||
this._page = event.detail.page
|
||||
this._fetchData()
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
renderPost(source, note) {
|
||||
return html`
|
||||
<grampsjs-blog-post
|
||||
.source="${source}"
|
||||
.note="${note}"
|
||||
.strings="${this.strings}"
|
||||
></grampsjs-blog-post>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this._fetchData()
|
||||
}
|
||||
|
||||
_getNotesUrl() {
|
||||
const grampsIds = this._dataSources.map(obj => obj?.extended?.notes[0]?.gramps_id).filter(obj => obj)
|
||||
const rules = {
|
||||
function: 'or',
|
||||
rules: grampsIds.map(grampsId =>({
|
||||
name: 'HasIdOf',
|
||||
values: [grampsId]
|
||||
})
|
||||
)
|
||||
}
|
||||
const options = {
|
||||
link_format: '/{obj_class}/{gramps_id}'
|
||||
}
|
||||
return `/api/notes/?profile=all&extend=all&formats=html&rules=${encodeURIComponent(JSON.stringify(rules))}&format_options=${encodeURIComponent(JSON.stringify(options))}`
|
||||
}
|
||||
|
||||
|
||||
async _fetchData() {
|
||||
this.loading = true
|
||||
const rules = {
|
||||
rules: [
|
||||
{
|
||||
name: 'HasTag',
|
||||
values: ['Blog']
|
||||
}
|
||||
]
|
||||
}
|
||||
const uri = `/api/sources/?rules=${encodeURIComponent(JSON.stringify(rules))}&page=${this._page}&pagesize=${this._pageSize}&sort=-change&profile=all&extend=all`
|
||||
await apiGet(uri).then(data => {
|
||||
if ('data' in data) {
|
||||
this._dataSources = data.data
|
||||
this._totalCount = data.total_count
|
||||
this._pages = Math.ceil(this._totalCount / this._pageSize)
|
||||
} else if ('error' in data) {
|
||||
this.error = true
|
||||
this._errorMessage = data.error
|
||||
}
|
||||
})
|
||||
const uriNotes = this._getNotesUrl()
|
||||
await apiGet(uriNotes).then(data => {
|
||||
if ('data' in data) {
|
||||
this._dataNotes = this._dataSources.map(obj => {
|
||||
const noteGrampsId = obj?.extended?.notes[0]?.gramps_id
|
||||
if (!noteGrampsId) {
|
||||
return {}
|
||||
}
|
||||
return data.data.find(note => note.gramps_id === noteGrampsId) || {}
|
||||
})
|
||||
} else if ('error' in data) {
|
||||
this.error = true
|
||||
this._errorMessage = data.error
|
||||
}
|
||||
})
|
||||
this.loading = false
|
||||
this._firstLoaded = true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-view-blog', GrampsjsViewBlog)
|
||||
@@ -0,0 +1,22 @@
|
||||
import {html} from 'lit-element'
|
||||
|
||||
import {GrampsjsView} from './GrampsjsView.js'
|
||||
|
||||
|
||||
export class GrampsjsViewDashboard extends GrampsjsView {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
renderContent() {
|
||||
return html`
|
||||
Dashboard
|
||||
`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.customElements.define('grampsjs-view-dashboard', GrampsjsViewDashboard)
|
||||
@@ -6,7 +6,7 @@ import '../components/GrampsjsLightbox.js'
|
||||
import '../components/GrampsjsRectContainer.js'
|
||||
import '../components/GrampsjsRect.js'
|
||||
import {apiGet} from '../api.js'
|
||||
|
||||
import {getNameFromProfile} from '../util.js'
|
||||
|
||||
export class GrampsjsViewMediaLightbox extends GrampsjsView {
|
||||
static get styles() {
|
||||
@@ -17,6 +17,10 @@ export class GrampsjsViewMediaLightbox extends GrampsjsView {
|
||||
:host {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
grampsjs-img {
|
||||
max-height:100vh;
|
||||
}
|
||||
`
|
||||
]
|
||||
}
|
||||
@@ -42,7 +46,7 @@ export class GrampsjsViewMediaLightbox extends GrampsjsView {
|
||||
${this._getRectangles().map(obj => html`
|
||||
<grampsjs-rect
|
||||
.rect="${obj.rect}"
|
||||
label="..."
|
||||
label="${obj.label}"
|
||||
target="${obj.type}/${obj.grampsId}"
|
||||
>
|
||||
</grampsjs-rect>
|
||||
@@ -109,16 +113,19 @@ export class GrampsjsViewMediaLightbox extends GrampsjsView {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_getRectangles() {
|
||||
const backlinks = this._data?.extended?.backlinks || {}
|
||||
const references = this._data?.profile?.references || {}
|
||||
if (Object.keys(backlinks).length === 0) {
|
||||
return []
|
||||
}
|
||||
return Object.keys(backlinks).map(key => backlinks[key].map(obj => {
|
||||
return Object.keys(backlinks).map(key => backlinks[key].map((obj, index) => {
|
||||
const refs = key in references ? references[key] : []
|
||||
const label = refs.length >= index ? getNameFromProfile(refs[index] || {}, key, this.strings) : '...'
|
||||
return {
|
||||
rect: obj.media_list.find(mobj => mobj.ref === this._data.handle)?.rect,
|
||||
type: key,
|
||||
label,
|
||||
grampsId: obj.gramps_id
|
||||
}
|
||||
})
|
||||
|
||||
@@ -47,6 +47,7 @@ export class GrampsjsViewObjectNotes extends GrampsjsViewObjectsDetail {
|
||||
renderNote(obj) {
|
||||
return html`
|
||||
<grampsjs-note-content
|
||||
framed
|
||||
grampsId="${obj.gramps_id}"
|
||||
content="${obj?.formatted?.html || obj?.text?.string}"
|
||||
></grampsjs-note-content>
|
||||
|
||||
@@ -78,7 +78,7 @@ export class GrampsjsViewRecentObject extends GrampsjsView {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const query = this._data.map(obj => obj.grampsId).filter(grampsId => grampsId !== undefined).join(' OR ')
|
||||
const query = this._data.map(obj => obj.grampsId).filter(grampsId => grampsId && grampsId.trim()).join(' OR ')
|
||||
const data = await apiGet(`/api/search/?query=${query}&profile=all&page=1&pagesize=100`)
|
||||
this.loading = false
|
||||
if ('data' in data) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {html} from 'lit-element'
|
||||
|
||||
import {GrampsjsViewSettingsOnboarding} from './GrampsjsViewSettingsOnboarding.js'
|
||||
import {doLogout, changeOwnDetails, changeOwnPassword} from '../api.js'
|
||||
import {doLogout, apiPost, apiPut} from '../api.js'
|
||||
import '@material/mwc-textfield'
|
||||
import '@material/mwc-button'
|
||||
import '@material/mwc-select'
|
||||
@@ -91,11 +91,15 @@ export class GrampsjsViewSettings extends GrampsjsViewSettingsOnboarding {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
changeOwnDetails({email: form.value}).then(data => {
|
||||
const payload = {email: form.value}
|
||||
apiPut('/api/users/-/', payload).then(data => {
|
||||
this.loading = false
|
||||
if ('error' in data) {
|
||||
this.error = true
|
||||
this._errorMessage = data.error
|
||||
} else{
|
||||
this.dispatchEvent(new CustomEvent('grampsjs:notification', {bubbles: true, composed: true, detail: {message: 'E-mail successfully updated'}}))
|
||||
form.value = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -107,11 +111,16 @@ export class GrampsjsViewSettings extends GrampsjsViewSettingsOnboarding {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
changeOwnPassword(formOldPw.value, formNewPw.value).then(data => {
|
||||
const payload = {old_password: formOldPw.value, new_password: formNewPw.value}
|
||||
apiPost('/api/users/-/password/change', payload).then(data => {
|
||||
this.loading = false
|
||||
if ('error' in data) {
|
||||
this.error = true
|
||||
this._errorMessage = data.error
|
||||
} else {
|
||||
this.dispatchEvent(new CustomEvent('grampsjs:notification', {bubbles: true, composed: true, detail: {message: 'Password successfully updated'}}))
|
||||
formOldPw.value = ''
|
||||
formNewPw.value = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ export class GrampsjsViewTree extends GrampsjsView {
|
||||
--mdc-ripple-focus-opacity: 0;
|
||||
--mdc-theme-primary: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
#outer-container {
|
||||
clear: left;
|
||||
padding-top: 30px;
|
||||
}
|
||||
`]
|
||||
}
|
||||
|
||||
@@ -68,7 +73,7 @@ export class GrampsjsViewTree extends GrampsjsView {
|
||||
super()
|
||||
this.grampsId = ''
|
||||
this._data = []
|
||||
this._depth = 4
|
||||
this._depth = 3
|
||||
this._zoom = 1
|
||||
this._history = []
|
||||
}
|
||||
@@ -86,36 +91,40 @@ export class GrampsjsViewTree extends GrampsjsView {
|
||||
return html`
|
||||
<section id="pedigree-section">
|
||||
|
||||
<div id="slider-block">
|
||||
<span class="label">${this._('Number of generations:')}</span>
|
||||
<span class="slider-container"><mwc-slider
|
||||
value="${this._depth}"
|
||||
min="2"
|
||||
max="6"
|
||||
step="1"
|
||||
@change="${this._updateDepth}"
|
||||
id="slider"
|
||||
pin
|
||||
></mwc-slider></span>
|
||||
<div id="controls">
|
||||
<div id="slider-block">
|
||||
<span class="label">${this._('Number of generations:')}</span>
|
||||
<span class="slider-container"><mwc-slider
|
||||
value="${this._depth}"
|
||||
min="2"
|
||||
max="6"
|
||||
step="1"
|
||||
@change="${this._updateDepth}"
|
||||
id="slider"
|
||||
pin
|
||||
></mwc-slider></span>
|
||||
</div>
|
||||
<div id="button-block">
|
||||
<mwc-button outlined
|
||||
id="btn-back"
|
||||
@click="${this._prevPerson}"
|
||||
?disabled="${this._history.length < 2}"
|
||||
icon="undo"></mwc-button>
|
||||
<mwc-button outlined id="btn-home"
|
||||
@click="${this._backToHomePerson}"
|
||||
?disabled="${this.grampsId === this.settings.homePerson}"
|
||||
>${this._('Home Person')}</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="button-block">
|
||||
<mwc-button outlined
|
||||
id="btn-back"
|
||||
@click="${this._prevPerson}"
|
||||
?disabled="${this._history.length < 2}"
|
||||
icon="undo"></mwc-button>
|
||||
<mwc-button outlined id="btn-home"
|
||||
@click="${this._backToHomePerson}"
|
||||
?disabled="${this.grampsId === this.settings.homePerson}"
|
||||
>${this._('Home Person')}</mwc-button>
|
||||
</div>
|
||||
<div style="transform: scale(${this._zoom}); transform-origin: top left;" id="pedigree-container">
|
||||
<grampsjs-pedigree
|
||||
.people="${this._data}"
|
||||
grampsId="${this.grampsId}"
|
||||
depth="${this._depth}"
|
||||
id="pedigree"
|
||||
></grampsjs-pedigree>
|
||||
<div id="outer-container">
|
||||
<div style="transform: scale(${this._zoom}); transform-origin: top left;" id="pedigree-container">
|
||||
<grampsjs-pedigree
|
||||
.people="${this._data}"
|
||||
grampsId="${this.grampsId}"
|
||||
depth="${this._depth}"
|
||||
id="pedigree"
|
||||
></grampsjs-pedigree>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user