mirror of
https://github.com/PeWu/topola-viewer.git
synced 2025-12-23 18:50:04 +00:00
Display sources and links to non-image files in details panel (#210)
This commit is contained in:
parent
91a8bd0115
commit
3438c29ab8
29
src/details/additional-files.tsx
Normal file
29
src/details/additional-files.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import {List} from 'semantic-ui-react';
|
||||
|
||||
export interface FileEntry {
|
||||
url: string;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
files?: FileEntry[];
|
||||
}
|
||||
|
||||
export function AdditionalFiles({files}: Props) {
|
||||
if (!files?.length) return null;
|
||||
|
||||
return (
|
||||
<List>
|
||||
{files.map((file, index) => (
|
||||
<List.Item key={index}>
|
||||
<List.Icon verticalAlign="middle" name="circle" size="tiny" />
|
||||
<List.Content>
|
||||
<a target="_blank" href={file.url} rel="noopener noreferrer">
|
||||
{file.filename || file.url.split('/').pop() || file.url}
|
||||
</a>
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
@ -8,9 +8,13 @@ import {
|
||||
getData,
|
||||
getFileName,
|
||||
getImageFileEntry,
|
||||
getNonImageFileEntry,
|
||||
mapToSource,
|
||||
} from '../util/gedcom_util';
|
||||
import {AdditionalFiles} from './additional-files';
|
||||
import {Events} from './events';
|
||||
import {MultilineText} from './multiline-text';
|
||||
import {Sources} from './sources';
|
||||
import {TranslatedTag} from './translated-tag';
|
||||
import {WrappedImage} from './wrapped-image';
|
||||
|
||||
@ -55,23 +59,95 @@ function dataDetails(entry: GedcomEntry) {
|
||||
);
|
||||
}
|
||||
|
||||
function fileDetails(objectEntry: GedcomEntry) {
|
||||
const imageFileEntry = getImageFileEntry(objectEntry);
|
||||
function imageDetails(objectEntryReference: GedcomEntry, gedcom: GedcomData) {
|
||||
const imageEntry = dereference(
|
||||
objectEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
|
||||
return imageFileEntry ? (
|
||||
const imageFileEntry = getImageFileEntry(imageEntry);
|
||||
|
||||
if (!imageFileEntry || !hasData(imageEntry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="person-image">
|
||||
<WrappedImage
|
||||
url={imageFileEntry.data}
|
||||
filename={getFileName(imageFileEntry) || ''}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
);
|
||||
}
|
||||
|
||||
function noteDetails(entry: GedcomEntry) {
|
||||
function sourceDetails(
|
||||
sourceReferenceEntries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
) {
|
||||
const sources = sourceReferenceEntries.map((sourceEntryReference) =>
|
||||
mapToSource(sourceEntryReference, gedcom),
|
||||
);
|
||||
|
||||
if (!sources.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="item-header">
|
||||
<Header as="span" size="small">
|
||||
<TranslatedTag tag="SOUR" />
|
||||
</Header>
|
||||
</div>
|
||||
<Sources sources={sources} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function fileDetails(objectEntries: GedcomEntry[], gedcom: GedcomData) {
|
||||
const files = objectEntries
|
||||
.map((objectEntry) =>
|
||||
dereference(objectEntry, gedcom, (gedcom) => gedcom.other),
|
||||
)
|
||||
.map((objectEntry) => getNonImageFileEntry(objectEntry))
|
||||
.filter((objectEntry): objectEntry is GedcomEntry => !!objectEntry)
|
||||
.map((fileEntry) => ({
|
||||
url: fileEntry.data,
|
||||
filename: getFileName(fileEntry),
|
||||
}));
|
||||
|
||||
if (!files.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="item-header">
|
||||
<Header as="span" size="small">
|
||||
<TranslatedTag tag="OBJE" />
|
||||
</Header>
|
||||
</div>
|
||||
<AdditionalFiles files={files} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function noteDetails(noteEntryReference: GedcomEntry, gedcom: GedcomData) {
|
||||
const noteEntry = dereference(
|
||||
noteEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
|
||||
if (!noteEntry || !hasData(noteEntry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultilineText
|
||||
lines={getData(entry).map((line, index) => (
|
||||
lines={getData(noteEntry).map((line, index) => (
|
||||
<i key={index}>{line}</i>
|
||||
))}
|
||||
/>
|
||||
@ -103,15 +179,19 @@ function nameDetails(entry: GedcomEntry) {
|
||||
);
|
||||
}
|
||||
|
||||
function getDetails(
|
||||
function getSectionForEachMatchingEntry(
|
||||
entries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
tags: string[],
|
||||
detailsFunction: (entry: GedcomEntry) => React.ReactNode | null,
|
||||
detailsFunction: (
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
) => React.ReactNode | null,
|
||||
): React.ReactNode[] {
|
||||
return flatMap(tags, (tag) =>
|
||||
entries
|
||||
.filter((entry) => entry.tag === tag)
|
||||
.map((entry) => detailsFunction(entry)),
|
||||
.map((entry) => detailsFunction(entry, gedcom)),
|
||||
)
|
||||
.filter((element) => element !== null)
|
||||
.map((element, index) => (
|
||||
@ -121,6 +201,34 @@ function getDetails(
|
||||
));
|
||||
}
|
||||
|
||||
function combineAllMatchingEntriesIntoSingleSection(
|
||||
entries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
tags: string[],
|
||||
detailsFunction: (
|
||||
entries: GedcomEntry[],
|
||||
gedcom: GedcomData,
|
||||
) => React.ReactNode | null,
|
||||
): React.ReactNode {
|
||||
const entriesWithMatchingTag = flatMap(tags, (tag) =>
|
||||
entries.filter((entry) => entry.tag === tag),
|
||||
).filter((element) => element !== null);
|
||||
|
||||
const sectionWithDetails = entriesWithMatchingTag.length
|
||||
? detailsFunction(entriesWithMatchingTag, gedcom)
|
||||
: null;
|
||||
|
||||
if (!sectionWithDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Item>
|
||||
<Item.Content>{sectionWithDetails}</Item.Content>
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is displayable information in this entry.
|
||||
* Returns false if there is no data in this entry or this is only a reference
|
||||
@ -130,9 +238,10 @@ function hasData(entry: GedcomEntry) {
|
||||
return entry.tree.length > 0 || (entry.data && !entry.data.startsWith('@'));
|
||||
}
|
||||
|
||||
function getOtherDetails(entries: GedcomEntry[]) {
|
||||
function getOtherSections(entries: GedcomEntry[], gedcom: GedcomData) {
|
||||
return entries
|
||||
.filter((entry) => !EXCLUDED_TAGS.includes(entry.tag))
|
||||
.map((entry) => dereference(entry, gedcom, (gedcom) => gedcom.other))
|
||||
.filter(hasData)
|
||||
.map((entry) => dataDetails(entry))
|
||||
.filter((element) => element !== null)
|
||||
@ -150,18 +259,42 @@ interface Props {
|
||||
|
||||
export function Details(props: Props) {
|
||||
const entries = props.gedcom.indis[props.indi].tree;
|
||||
const entriesWithData = entries
|
||||
.map((entry) => dereference(entry, props.gedcom, (gedcom) => gedcom.other))
|
||||
.filter(hasData);
|
||||
|
||||
return (
|
||||
<div className="details">
|
||||
<Item.Group divided>
|
||||
{getDetails(entries, ['NAME'], nameDetails)}
|
||||
{getDetails(entriesWithData, ['OBJE'], fileDetails)}
|
||||
{getSectionForEachMatchingEntry(
|
||||
entries,
|
||||
props.gedcom,
|
||||
['NAME'],
|
||||
nameDetails,
|
||||
)}
|
||||
{getSectionForEachMatchingEntry(
|
||||
entries,
|
||||
props.gedcom,
|
||||
['OBJE'],
|
||||
imageDetails,
|
||||
)}
|
||||
<Events gedcom={props.gedcom} entries={entries} indi={props.indi} />
|
||||
{getOtherDetails(entriesWithData)}
|
||||
{getDetails(entriesWithData, ['NOTE'], noteDetails)}
|
||||
{getOtherSections(entries, props.gedcom)}
|
||||
{getSectionForEachMatchingEntry(
|
||||
entries,
|
||||
props.gedcom,
|
||||
['NOTE'],
|
||||
noteDetails,
|
||||
)}
|
||||
{combineAllMatchingEntriesIntoSingleSection(
|
||||
entries,
|
||||
props.gedcom,
|
||||
['OBJE'],
|
||||
fileDetails,
|
||||
)}
|
||||
{combineAllMatchingEntriesIntoSingleSection(
|
||||
entries,
|
||||
props.gedcom,
|
||||
['SOUR'],
|
||||
sourceDetails,
|
||||
)}
|
||||
</Item.Group>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
import {FormattedMessage, IntlShape, useIntl} from 'react-intl';
|
||||
import Linkify from 'react-linkify';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {
|
||||
Icon,
|
||||
Item,
|
||||
@ -11,9 +9,10 @@ import {
|
||||
Popup,
|
||||
Tab,
|
||||
} from 'semantic-ui-react';
|
||||
import {DateOrRange} from 'topola';
|
||||
import {formatDateOrRange} from '../util/date_util';
|
||||
import {Source} from '../util/gedcom_util';
|
||||
import {AdditionalFiles, FileEntry} from './additional-files';
|
||||
import {MultilineText} from './multiline-text';
|
||||
import {Sources} from './sources';
|
||||
import {WrappedImage} from './wrapped-image';
|
||||
|
||||
export interface Image {
|
||||
@ -22,19 +21,12 @@ export interface Image {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface Source {
|
||||
title?: string;
|
||||
author?: string;
|
||||
page?: string;
|
||||
date?: DateOrRange;
|
||||
publicationInfo?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
images?: Image[];
|
||||
notes?: string[][];
|
||||
sources?: Source[];
|
||||
indi: string;
|
||||
files?: FileEntry[];
|
||||
}
|
||||
|
||||
function eventImages(images: Image[] | undefined) {
|
||||
@ -69,37 +61,7 @@ function eventNotes(notes: string[][] | undefined) {
|
||||
);
|
||||
}
|
||||
|
||||
function eventSources(sources: Source[] | undefined, intl: IntlShape) {
|
||||
return (
|
||||
!!sources?.length && (
|
||||
<List>
|
||||
{sources.map((source, index) => (
|
||||
<List.Item key={index}>
|
||||
<List.Icon verticalAlign="middle" name="circle" size="tiny" />
|
||||
<List.Content>
|
||||
<List.Header>
|
||||
<Linkify properties={{target: '_blank'}}>
|
||||
{[source.author, source.title, source.publicationInfo]
|
||||
.filter((sourceElement) => sourceElement)
|
||||
.join(', ')}
|
||||
</Linkify>
|
||||
</List.Header>
|
||||
<List.Description>
|
||||
<Linkify properties={{target: '_blank'}}>{source.page}</Linkify>
|
||||
{source.date
|
||||
? ' [' + formatDateOrRange(source.date, intl) + ']'
|
||||
: null}
|
||||
</List.Description>
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function EventExtras(props: Props) {
|
||||
const intl = useIntl();
|
||||
const [activeIndex, setActiveIndex] = useState(-1);
|
||||
const [indi, setIndi] = useState('');
|
||||
|
||||
@ -109,7 +71,7 @@ export function EventExtras(props: Props) {
|
||||
}
|
||||
|
||||
function handleTabOnClick(
|
||||
event: React.MouseEvent<HTMLAnchorElement>,
|
||||
_event: React.MouseEvent<HTMLAnchorElement>,
|
||||
menuItemProps: MenuItemProps,
|
||||
) {
|
||||
menuItemProps.index !== undefined && activeIndex !== menuItemProps.index
|
||||
@ -162,10 +124,37 @@ export function EventExtras(props: Props) {
|
||||
/>
|
||||
</Menu.Item>
|
||||
),
|
||||
render: () => <Tab.Pane>{eventSources(props.sources, intl)}</Tab.Pane>,
|
||||
render: () => (
|
||||
<Tab.Pane>
|
||||
<Sources sources={props.sources} />
|
||||
</Tab.Pane>
|
||||
),
|
||||
};
|
||||
|
||||
const panes = [imageTab, noteTab, sourceTab].flatMap((tab) =>
|
||||
const filesTab = props.files?.length && {
|
||||
menuItem: (
|
||||
<Menu.Item fitted key="files" onClick={handleTabOnClick}>
|
||||
<Popup
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="extras.files"
|
||||
defaultMessage="Additonal files"
|
||||
/>
|
||||
}
|
||||
size="mini"
|
||||
position="bottom center"
|
||||
trigger={<Icon circular name="file alternate outline" />}
|
||||
/>
|
||||
</Menu.Item>
|
||||
),
|
||||
render: () => (
|
||||
<Tab.Pane>
|
||||
<AdditionalFiles files={props.files} />
|
||||
</Tab.Pane>
|
||||
),
|
||||
};
|
||||
|
||||
const panes = [imageTab, noteTab, sourceTab, filesTab].flatMap((tab) =>
|
||||
tab ? [tab] : [],
|
||||
);
|
||||
|
||||
|
||||
@ -14,9 +14,14 @@ import {
|
||||
getFileName,
|
||||
getImageFileEntry,
|
||||
getName,
|
||||
getNonImageFileEntry,
|
||||
mapToSource,
|
||||
pointerToId,
|
||||
resolveDate,
|
||||
Source,
|
||||
} from '../util/gedcom_util';
|
||||
import {EventExtras, Image, Source} from './event-extras';
|
||||
import {FileEntry} from './additional-files';
|
||||
import {EventExtras, Image} from './event-extras';
|
||||
import {TranslatedTag} from './translated-tag';
|
||||
|
||||
function PersonLink(props: {person: GedcomEntry}) {
|
||||
@ -53,6 +58,7 @@ interface EventData {
|
||||
personLink?: GedcomEntry;
|
||||
place?: string[];
|
||||
images?: Image[];
|
||||
files?: FileEntry[];
|
||||
notes?: string[][];
|
||||
sources?: Source[];
|
||||
indi: string;
|
||||
@ -74,7 +80,7 @@ const FAMILY_EVENT_TAGS = ['MARR', 'DIV'];
|
||||
function EventHeader(props: {event: EventData}) {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div className="event-header">
|
||||
<div className="item-header">
|
||||
<Header as="span" size="small">
|
||||
<TranslatedTag tag={props.event.type} />
|
||||
</Header>
|
||||
@ -144,48 +150,29 @@ function eventImages(entry: GedcomEntry, gedcom: GedcomData): Image[] {
|
||||
);
|
||||
}
|
||||
|
||||
function eventFiles(entry: GedcomEntry, gedcom: GedcomData): Image[] {
|
||||
return entry.tree
|
||||
.filter((subEntry) => 'OBJE' === subEntry.tag)
|
||||
.map((objectEntry) =>
|
||||
dereference(objectEntry, gedcom, (gedcom) => gedcom.other),
|
||||
)
|
||||
.map((objectEntry) => getNonImageFileEntry(objectEntry))
|
||||
.flatMap((fileEntry) =>
|
||||
fileEntry
|
||||
? [
|
||||
{
|
||||
url: fileEntry?.data || '',
|
||||
filename: getFileName(fileEntry) || '',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
function eventSources(entry: GedcomEntry, gedcom: GedcomData): Source[] {
|
||||
return entry.tree
|
||||
.filter((subEntry) => 'SOUR' === subEntry.tag)
|
||||
.map((sourceEntryReference) => {
|
||||
const sourceEntry = dereference(
|
||||
sourceEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
|
||||
const title = sourceEntry.tree.find(
|
||||
(subEntry) => 'TITL' === subEntry.tag,
|
||||
);
|
||||
|
||||
const abbr = sourceEntry.tree.find((subEntry) => 'ABBR' === subEntry.tag);
|
||||
|
||||
const author = sourceEntry.tree.find(
|
||||
(subEntry) => 'AUTH' === subEntry.tag,
|
||||
);
|
||||
|
||||
const publicationInfo = sourceEntry.tree.find(
|
||||
(subEntry) => 'PUBL' === subEntry.tag,
|
||||
);
|
||||
|
||||
const page = sourceEntryReference.tree.find(
|
||||
(subEntry) => 'PAGE' === subEntry.tag,
|
||||
);
|
||||
|
||||
const sourceData = sourceEntryReference.tree.find(
|
||||
(subEntry) => 'DATA' === subEntry.tag,
|
||||
);
|
||||
|
||||
const date = sourceData ? resolveDate(sourceData) : undefined;
|
||||
|
||||
return {
|
||||
title: title?.data || abbr?.data,
|
||||
author: author?.data,
|
||||
page: page?.data,
|
||||
date: date ? getDate(date.data) : undefined,
|
||||
publicationInfo: publicationInfo?.data,
|
||||
};
|
||||
});
|
||||
.map((sourceEntryReference) => mapToSource(sourceEntryReference, gedcom));
|
||||
}
|
||||
|
||||
function eventNotes(entry: GedcomEntry, gedcom: GedcomData): string[][] {
|
||||
@ -221,6 +208,7 @@ function toIndiEvent(
|
||||
age: getAge(entry, indi, gedcom, intl),
|
||||
place: eventPlace(entry),
|
||||
images: eventImages(entry, gedcom),
|
||||
files: eventFiles(entry, gedcom),
|
||||
notes: eventNotes(entry, gedcom),
|
||||
sources: eventSources(entry, gedcom),
|
||||
indi: indi,
|
||||
@ -228,10 +216,6 @@ function toIndiEvent(
|
||||
];
|
||||
}
|
||||
|
||||
function resolveDate(entry: GedcomEntry) {
|
||||
return entry.tree.find((subEntry) => subEntry.tag === 'DATE');
|
||||
}
|
||||
|
||||
function toFamilyEvents(
|
||||
entry: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
@ -248,6 +232,7 @@ function toFamilyEvents(
|
||||
personLink: getSpouse(indi, family, gedcom),
|
||||
place: eventPlace(familyMarriageEvent),
|
||||
images: eventImages(familyMarriageEvent, gedcom),
|
||||
files: eventFiles(familyMarriageEvent, gedcom),
|
||||
notes: eventNotes(familyMarriageEvent, gedcom),
|
||||
sources: eventSources(familyMarriageEvent, gedcom),
|
||||
indi: indi,
|
||||
@ -272,6 +257,7 @@ function Event(props: {event: EventData}) {
|
||||
notes={props.event.notes}
|
||||
sources={props.event.sources}
|
||||
indi={props.event.indi}
|
||||
files={props.event.files}
|
||||
/>
|
||||
</Item.Content>
|
||||
</Item>
|
||||
|
||||
38
src/details/sources.tsx
Normal file
38
src/details/sources.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import {useIntl} from 'react-intl';
|
||||
import Linkify from 'react-linkify';
|
||||
import {List} from 'semantic-ui-react';
|
||||
import {formatDateOrRange} from '../util/date_util';
|
||||
import {Source} from '../util/gedcom_util';
|
||||
|
||||
interface Props {
|
||||
sources?: Source[];
|
||||
}
|
||||
|
||||
export function Sources({sources}: Props) {
|
||||
const intl = useIntl();
|
||||
|
||||
if (!sources?.length) return null;
|
||||
|
||||
return (
|
||||
<List>
|
||||
{sources.map((source, index) => (
|
||||
<List.Item key={index}>
|
||||
<List.Icon verticalAlign="middle" name="circle" size="tiny" />
|
||||
<List.Content>
|
||||
<List.Header>
|
||||
<Linkify properties={{target: '_blank'}}>
|
||||
{[source.author, source.title, source.publicationInfo]
|
||||
.filter((sourceElement) => !!sourceElement)
|
||||
.join(', ')}
|
||||
</Linkify>
|
||||
</List.Header>
|
||||
<List.Description>
|
||||
<Linkify properties={{target: '_blank'}}>{source.page}</Linkify>
|
||||
{source.date && ` [${formatDateOrRange(source.date, intl)}]`}
|
||||
</List.Description>
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
@ -22,6 +22,8 @@ const TAG_DESCRIPTIONS = new Map([
|
||||
['OCCU', 'Occupation'],
|
||||
['TITL', 'Title'],
|
||||
['WWW', 'WWW'],
|
||||
['OBJE', 'Additional files'],
|
||||
['SOUR', 'Sources'],
|
||||
['birth', 'Birth name'],
|
||||
['married', 'Married name'],
|
||||
['maiden', 'Maiden name'],
|
||||
|
||||
@ -153,13 +153,13 @@ div.zoom {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.details .event-header {
|
||||
.details .item-header {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.details .event-header .header {
|
||||
.details .item-header .header {
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
min-width: 40%;
|
||||
|
||||
@ -62,6 +62,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Обръщение",
|
||||
"gedcom.WWW": "Линк",
|
||||
"gedcom.OBJE": "Допълнителни файлове",
|
||||
"gedcom.SOUR": "Източници",
|
||||
"gedcom._UPD": "Последно обновление",
|
||||
"gedcom.birth": "Рождено име",
|
||||
"gedcom.married": "Име след брак",
|
||||
@ -106,5 +108,6 @@
|
||||
"name.unknown_name": "Неизвестно име",
|
||||
"extras.images": "Изображение",
|
||||
"extras.notes": "Бележки",
|
||||
"extras.sources": "Източници"
|
||||
"extras.sources": "Източници",
|
||||
"extras.files": "Допълнителни файлове"
|
||||
}
|
||||
|
||||
@ -63,6 +63,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Titul",
|
||||
"gedcom.WWW": "Stránka WWW",
|
||||
"gedcom.OBJE": "Další soubory",
|
||||
"gedcom.SOUR": "Zdroje",
|
||||
"gedcom.RELI": "Vyznání",
|
||||
"gedcom._UPD": "Poslední aktualizace",
|
||||
"gedcom.birth": "Rodné jméno",
|
||||
@ -107,5 +109,6 @@
|
||||
"name.unknown_name": "N.N.",
|
||||
"extras.images": "Obrázky",
|
||||
"extras.notes": "Poznámky",
|
||||
"extras.sources": "Zdroje"
|
||||
"extras.sources": "Zdroje",
|
||||
"extras.files": "Další soubory"
|
||||
}
|
||||
|
||||
@ -50,6 +50,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Titel",
|
||||
"gedcom.WWW": "Website",
|
||||
"gedcom.OBJE": "Zusätzliche Dateien",
|
||||
"gedcom.SOUR": "Quellen",
|
||||
"gedcom._UPD": "Zuletzt aktualisiert",
|
||||
"gedcom.birth": "Geburtsname",
|
||||
"gedcom.married": "Ehenamen",
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Titre",
|
||||
"gedcom.WWW": "Site Web",
|
||||
"gedcom.OBJE": "Fichiers supplémentaires",
|
||||
"gedcom.SOUR": "Sources",
|
||||
"gedcom._UPD": "Dernière mise à jour",
|
||||
"gedcom.MARR": "Mariage",
|
||||
"gedcom.DIV": "Divorce",
|
||||
|
||||
@ -52,6 +52,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Titolo",
|
||||
"gedcom.WWW": "Sito web",
|
||||
"gedcom.OBJE": "File aggiuntivi",
|
||||
"gedcom.SOUR": "Fonti",
|
||||
"gedcom._UPD": "Ultimo aggiornamento",
|
||||
"gedcom.birth": "Nome alla nascita",
|
||||
"gedcom.married": "Nome da coniugato/a",
|
||||
|
||||
@ -55,6 +55,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Tytuł",
|
||||
"gedcom.WWW": "Strona WWW",
|
||||
"gedcom.OBJE": "Dodatkowe pliki",
|
||||
"gedcom.SOUR": "Źródła",
|
||||
"gedcom._UPD": "Ostatnia aktualizacja",
|
||||
"gedcom.MARR": "Małżeństwo",
|
||||
"gedcom.DIV": "Rozwód",
|
||||
@ -94,5 +96,6 @@
|
||||
"name.unknown_name": "N.N.",
|
||||
"extras.images": "Zdjęcia",
|
||||
"extras.notes": "Notatki",
|
||||
"extras.sources": "Źródła"
|
||||
"extras.sources": "Źródła",
|
||||
"extras.files": "Dodatkowe pliki"
|
||||
}
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
"gedcom.RIN": "ID",
|
||||
"gedcom.TITL": "Титул",
|
||||
"gedcom.WWW": "Веб-сайт WWW",
|
||||
"gedcom.OBJE": "Дополнительные файлы",
|
||||
"gedcom.SOUR": "Источники",
|
||||
"gedcom._UPD": "Последнее обновление",
|
||||
"gedcom.birth": "Имя при рождении",
|
||||
"gedcom.married": "Имя в браке",
|
||||
@ -97,5 +99,6 @@
|
||||
"name.unknown_name": "Н.И.",
|
||||
"extras.images": "Картинки",
|
||||
"extras.notes": "Примечание",
|
||||
"extras.sources": "Источники"
|
||||
"extras.sources": "Источники",
|
||||
"extras.files": "Дополнительные файлы"
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import {GedcomEntry, parse as parseGedcom} from 'parse-gedcom';
|
||||
import {
|
||||
DateOrRange,
|
||||
gedcomEntriesToJson,
|
||||
getDate,
|
||||
JsonFam,
|
||||
JsonGedcomData,
|
||||
JsonImage,
|
||||
@ -25,6 +27,14 @@ export interface TopolaData {
|
||||
gedcom: GedcomData;
|
||||
}
|
||||
|
||||
export interface Source {
|
||||
title?: string;
|
||||
author?: string;
|
||||
page?: string;
|
||||
date?: DateOrRange;
|
||||
publicationInfo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier extracted from a pointer string.
|
||||
* E.g. '@I123@' -> 'I123'
|
||||
@ -296,13 +306,67 @@ export function getFileName(fileEntry: GedcomEntry): string | undefined {
|
||||
return fileTitle && fileExtension && fileTitle + '.' + fileExtension;
|
||||
}
|
||||
|
||||
export function getImageFileEntry(
|
||||
function findFileEntry(
|
||||
objectEntry: GedcomEntry,
|
||||
predicate: (entry: GedcomEntry) => boolean,
|
||||
): GedcomEntry | undefined {
|
||||
return objectEntry.tree.find(
|
||||
(entry) =>
|
||||
entry.tag === 'FILE' &&
|
||||
entry.data.startsWith('http') &&
|
||||
isImageFile(entry.data),
|
||||
entry.tag === 'FILE' && entry.data.startsWith('http') && predicate(entry),
|
||||
);
|
||||
}
|
||||
|
||||
export function getNonImageFileEntry(
|
||||
objectEntry: GedcomEntry,
|
||||
): GedcomEntry | undefined {
|
||||
return findFileEntry(objectEntry, (entry) => !isImageFile(entry.data));
|
||||
}
|
||||
|
||||
export function getImageFileEntry(
|
||||
objectEntry: GedcomEntry,
|
||||
): GedcomEntry | undefined {
|
||||
return findFileEntry(objectEntry, (entry) => isImageFile(entry.data));
|
||||
}
|
||||
|
||||
export function resolveDate(entry: GedcomEntry) {
|
||||
return entry.tree.find((subEntry) => subEntry.tag === 'DATE');
|
||||
}
|
||||
|
||||
export function mapToSource(
|
||||
sourceEntryReference: GedcomEntry,
|
||||
gedcom: GedcomData,
|
||||
) {
|
||||
const sourceEntry = dereference(
|
||||
sourceEntryReference,
|
||||
gedcom,
|
||||
(gedcom) => gedcom.other,
|
||||
);
|
||||
|
||||
const title = sourceEntry.tree.find((subEntry) => 'TITL' === subEntry.tag);
|
||||
|
||||
const abbr = sourceEntry.tree.find((subEntry) => 'ABBR' === subEntry.tag);
|
||||
|
||||
const author = sourceEntry.tree.find((subEntry) => 'AUTH' === subEntry.tag);
|
||||
|
||||
const publicationInfo = sourceEntry.tree.find(
|
||||
(subEntry) => 'PUBL' === subEntry.tag,
|
||||
);
|
||||
|
||||
const page = sourceEntryReference.tree.find(
|
||||
(subEntry) => 'PAGE' === subEntry.tag,
|
||||
);
|
||||
|
||||
const sourceData = sourceEntryReference.tree.find(
|
||||
(subEntry) => 'DATA' === subEntry.tag,
|
||||
);
|
||||
|
||||
const date = sourceData ? resolveDate(sourceData) : undefined;
|
||||
|
||||
return {
|
||||
title: title?.data || abbr?.data,
|
||||
author: author?.data,
|
||||
page: page?.data,
|
||||
date: date ? getDate(date.data) : undefined,
|
||||
publicationInfo: publicationInfo?.data,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user