From 7b5b73a28950fd7fabe5fd20a5499b1588eff982 Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Sun, 7 Jun 2020 01:09:04 +0200 Subject: [PATCH] show local and remote History (#128) * added history toolbar functionality for local and remote history --- public/api/v2/history | 37 +++++ public/locales/en.json | 14 ++ .../pages/history/common/close-button.scss | 8 +- .../pages/history/common/close-button.tsx | 12 +- .../pages/history/common/pin-button.scss | 11 +- .../pages/history/common/pin-button.tsx | 12 +- .../pages/history/common/sync-status.scss | 10 ++ .../pages/history/common/sync-status.tsx | 21 +++ .../history-card/history-card-list.tsx | 11 +- .../history/history-card/history-card.scss | 7 + .../history/history-card/history-card.tsx | 49 +++--- .../history-content/history-content.tsx | 32 ++-- .../history-table/history-table-row.tsx | 21 ++- .../history/history-table/history-table.tsx | 5 +- .../history-toolbar/history-toolbar.tsx | 14 +- .../landing/pages/history/history.tsx | 147 +++++++++++++----- src/components/pagination/pager.tsx | 10 +- src/style/index.scss | 7 + src/utils/historyUtils.ts | 38 ++++- 19 files changed, 336 insertions(+), 130 deletions(-) create mode 100644 public/api/v2/history create mode 100644 src/components/landing/pages/history/common/sync-status.scss create mode 100644 src/components/landing/pages/history/common/sync-status.tsx create mode 100644 src/components/landing/pages/history/history-card/history-card.scss diff --git a/public/api/v2/history b/public/api/v2/history new file mode 100644 index 000000000..b1414bcb6 --- /dev/null +++ b/public/api/v2/history @@ -0,0 +1,37 @@ +[ + { + "id": "29QLD0AmT-adevdOPECtqg", + "title": "CodiMD community call 2020-04-26", + "lastVisited": "2020-05-16T22:26:56.547Z", + "tags": [ + "CodiMD", + "Community Call" + ] + }, + { + "id": "features", + "title": "Features", + "lastVisited": "2020-05-31T15:20:36.088Z", + "tags": [ + "features", + "cool", + "updated" + ] + }, + { + "id": "ODakLc2MQkyyFc_Xmb53sg", + "title": "CodiMD V2 API", + "lastVisited": "2020-05-25T19:48:14.025Z", + "tags": [] + }, + { + "id": "l8JuWxApTR6Fqa0LCrpnLg", + "title": "Community call - Let’s meet! (2020-06-06 18:00 UTC / 20:00 CEST)", + "lastVisited": "2020-05-24T16:04:36.433Z", + "tags": [ + "agenda", + "CodiMD community", + "community call" + ] + } +] diff --git a/public/locales/en.json b/public/locales/en.json index cf002c259..b95c85fb7 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -14,6 +14,20 @@ "screenShotAltText": "CodiMD Screenshot" }, "history": { + "error": { + "getHistory": { + "title": "Load History Error", + "text": "While trying to load the history form the server an error occurred" + }, + "deleteHistory": { + "title": "Delete History Error", + "text": "While trying to delete the history on the server an error occurred" + }, + "setHistory": { + "title": "Upload History Error", + "text": "While trying to upload the history to the server an error occurred" + } + }, "noHistory": "No history", "localHistory": "Below is history from this browser", "toolbar": { diff --git a/src/components/landing/pages/history/common/close-button.scss b/src/components/landing/pages/history/common/close-button.scss index 70ba9e95b..ff4aea08b 100644 --- a/src/components/landing/pages/history/common/close-button.scss +++ b/src/components/landing/pages/history/common/close-button.scss @@ -1,7 +1,9 @@ .history-close { - opacity: 0.5; + .fa { + opacity: 0.5; + } - &:hover { + &:hover .fa { opacity: 1; } -} \ No newline at end of file +} diff --git a/src/components/landing/pages/history/common/close-button.tsx b/src/components/landing/pages/history/common/close-button.tsx index ecc9739cf..a429abbd5 100644 --- a/src/components/landing/pages/history/common/close-button.tsx +++ b/src/components/landing/pages/history/common/close-button.tsx @@ -1,19 +1,17 @@ import React from 'react' import { Button } from 'react-bootstrap' -import './close-button.scss' import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon' +import './close-button.scss' export interface CloseButtonProps { isDark: boolean; + className?: string } -const CloseButton: React.FC = ({ isDark }) => { +const CloseButton: React.FC = ({ isDark, className }) => { return ( - ) } diff --git a/src/components/landing/pages/history/common/pin-button.scss b/src/components/landing/pages/history/common/pin-button.scss index e9a3e00b5..17c93de9f 100644 --- a/src/components/landing/pages/history/common/pin-button.scss +++ b/src/components/landing/pages/history/common/pin-button.scss @@ -1,12 +1,15 @@ .history-pin { - opacity: 0.2; - transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out; - &:hover { + .fa { + opacity: 0.2; + transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out; + } + + &:hover .fa { opacity: 1; } - &.active { + &.pinned .fa { color: #d43f3a; opacity: 1; } diff --git a/src/components/landing/pages/history/common/pin-button.tsx b/src/components/landing/pages/history/common/pin-button.tsx index f19921ba1..7582f46ec 100644 --- a/src/components/landing/pages/history/common/pin-button.tsx +++ b/src/components/landing/pages/history/common/pin-button.tsx @@ -1,22 +1,20 @@ import React from 'react' -import './pin-button.scss' import { Button } from 'react-bootstrap' import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon' +import './pin-button.scss' export interface PinButtonProps { isPinned: boolean; onPinClick: () => void; isDark: boolean; + className?: string } -export const PinButton: React.FC = ({ isPinned, onPinClick, isDark }) => { +export const PinButton: React.FC = ({ isPinned, onPinClick, isDark, className }) => { return ( ) } diff --git a/src/components/landing/pages/history/common/sync-status.scss b/src/components/landing/pages/history/common/sync-status.scss new file mode 100644 index 000000000..8f3617c0b --- /dev/null +++ b/src/components/landing/pages/history/common/sync-status.scss @@ -0,0 +1,10 @@ +.sync-icon { + .fa { + opacity: 0.2; + transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out; + } + + &:hover .fa { + opacity: 1; + } +} diff --git a/src/components/landing/pages/history/common/sync-status.tsx b/src/components/landing/pages/history/common/sync-status.tsx new file mode 100644 index 000000000..8357415b0 --- /dev/null +++ b/src/components/landing/pages/history/common/sync-status.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Button } from 'react-bootstrap' +import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon' +import { Location } from '../history' +import './sync-status.scss' + +export interface SyncStatusProps { + isDark: boolean + location: Location + onSync: () => void + className?: string +} + +export const SyncStatus: React.FC = ({ isDark, location, onSync, className }) => { + const icon = location === Location.REMOTE ? 'cloud' : 'laptop' + return ( + + ) +} diff --git a/src/components/landing/pages/history/history-card/history-card-list.tsx b/src/components/landing/pages/history/history-card/history-card-list.tsx index a29a69838..b8ba17b42 100644 --- a/src/components/landing/pages/history/history-card/history-card-list.tsx +++ b/src/components/landing/pages/history/history-card/history-card-list.tsx @@ -1,19 +1,20 @@ import React from 'react' +import { Row } from 'react-bootstrap' +import { Pager } from '../../../../pagination/pager' import { HistoryEntriesProps } from '../history-content/history-content' import { HistoryCard } from './history-card' -import { Pager } from '../../../../pagination/pager' -import { Row } from 'react-bootstrap' -export const HistoryCardList: React.FC = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => { +export const HistoryCardList: React.FC = ({ entries, onPinClick, onSyncClick, pageIndex, onLastPageIndexChange }) => { return ( - - + + { entries.map((entry) => ( )) } diff --git a/src/components/landing/pages/history/history-card/history-card.scss b/src/components/landing/pages/history/history-card/history-card.scss new file mode 100644 index 000000000..39163f7f3 --- /dev/null +++ b/src/components/landing/pages/history/history-card/history-card.scss @@ -0,0 +1,7 @@ +.card-min-height { + min-height: 160px; +} + +.card-footer-min-height { + min-height: 27px; +} diff --git a/src/components/landing/pages/history/history-card/history-card.tsx b/src/components/landing/pages/history/history-card/history-card.tsx index c431d5550..05e009ba9 100644 --- a/src/components/landing/pages/history/history-card/history-card.tsx +++ b/src/components/landing/pages/history/history-card/history-card.tsx @@ -1,34 +1,41 @@ +import moment from 'moment' import React from 'react' import { Badge, Card } from 'react-bootstrap' import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon' -import { PinButton } from '../common/pin-button' -import { CloseButton } from '../common/close-button' -import moment from 'moment' -import { HistoryEntryProps } from '../history-content/history-content' import { formatHistoryDate } from '../../../../../utils/historyUtils' +import { CloseButton } from '../common/close-button' +import { PinButton } from '../common/pin-button' +import { SyncStatus } from '../common/sync-status' +import { HistoryEntryProps } from '../history-content/history-content' +import './history-card.scss' -export const HistoryCard: React.FC = ({ entry, onPinClick }) => { +export const HistoryCard: React.FC = ({ entry, onPinClick, onSyncClick }) => { return (
- -
- { - onPinClick(entry.id) - }}/> - {entry.title} - -
- -
- {moment(entry.lastVisited).fromNow()}
- {formatHistoryDate(entry.lastVisited)} + + +
+ onPinClick(entry.id)}/> + onSyncClick(entry.id)} + className={'mt-1'}/> +
+
+ {entry.title}
- { - entry.tags.map((tag) => {tag}) - } +
+ {moment(entry.lastVisited).fromNow()}
+ {formatHistoryDate(entry.lastVisited)} +
+
+ { + entry.tags.map((tag) => {tag}) + } +
+
+ +
diff --git a/src/components/landing/pages/history/history-content/history-content.tsx b/src/components/landing/pages/history/history-content/history-content.tsx index d6e54dd65..91b3d371b 100644 --- a/src/components/landing/pages/history/history-content/history-content.tsx +++ b/src/components/landing/pages/history/history-content/history-content.tsx @@ -1,31 +1,34 @@ import React, { Fragment, useState } from 'react' -import { HistoryEntry, pinClick } from '../history' -import { HistoryTable } from '../history-table/history-table' import { Alert, Row } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { HistoryCardList } from '../history-card/history-card-list' -import { ViewStateEnum } from '../history-toolbar/history-toolbar' import { PagerPagination } from '../../../../pagination/pager-pagination' +import { LocatedHistoryEntry } from '../history' +import { HistoryCardList } from '../history-card/history-card-list' +import { HistoryTable } from '../history-table/history-table' +import { ViewStateEnum } from '../history-toolbar/history-toolbar' export interface HistoryContentProps { viewState: ViewStateEnum - entries: HistoryEntry[] - onPinClick: pinClick + entries: LocatedHistoryEntry[] + onPinClick: (entryId: string) => void + onSyncClick: (entryId: string) => void } export interface HistoryEntryProps { - entry: HistoryEntry, - onPinClick: pinClick + entry: LocatedHistoryEntry, + onPinClick: (entryId: string) => void + onSyncClick: (entryId: string) => void } export interface HistoryEntriesProps { - entries: HistoryEntry[] - onPinClick: pinClick + entries: LocatedHistoryEntry[] + onPinClick: (entryId: string) => void + onSyncClick: (entryId: string) => void pageIndex: number onLastPageIndexChange: (lastPageIndex: number) => void } -export const HistoryContent: React.FC = ({ viewState, entries, onPinClick }) => { +export const HistoryContent: React.FC = ({ viewState, entries, onPinClick, onSyncClick }) => { useTranslation() const [pageIndex, setPageIndex] = useState(0) const [lastPageIndex, setLastPageIndex] = useState(0) @@ -44,10 +47,13 @@ export const HistoryContent: React.FC = ({ viewState, entri switch (viewState) { default: case ViewStateEnum.CARD: - return case ViewStateEnum.TABLE: - return } } diff --git a/src/components/landing/pages/history/history-table/history-table-row.tsx b/src/components/landing/pages/history/history-table/history-table-row.tsx index 72870e798..d031c96f6 100644 --- a/src/components/landing/pages/history/history-table/history-table-row.tsx +++ b/src/components/landing/pages/history/history-table/history-table-row.tsx @@ -1,27 +1,26 @@ import React from 'react' -import { PinButton } from '../common/pin-button' -import { CloseButton } from '../common/close-button' -import { HistoryEntryProps } from '../history-content/history-content' -import { formatHistoryDate } from '../../../../../utils/historyUtils' import { Badge } from 'react-bootstrap' +import { formatHistoryDate } from '../../../../../utils/historyUtils' +import { CloseButton } from '../common/close-button' +import { PinButton } from '../common/pin-button' +import { SyncStatus } from '../common/sync-status' +import { HistoryEntryProps } from '../history-content/history-content' -export const HistoryTableRow: React.FC = ({ entry, onPinClick }) => { +export const HistoryTableRow: React.FC = ({ entry, onPinClick, onSyncClick }) => { return ( {entry.title} {formatHistoryDate(entry.lastVisited)} { - entry.tags.map((tag) => {tag}) } - { - onPinClick(entry.id) - }}/> -   - + onSyncClick(entry.id)} className={'mb-1 mr-1'}/> + onPinClick(entry.id)} className={'mb-1 mr-1'}/> + ) diff --git a/src/components/landing/pages/history/history-table/history-table.tsx b/src/components/landing/pages/history/history-table/history-table.tsx index 5abc49f90..a62eb2fb3 100644 --- a/src/components/landing/pages/history/history-table/history-table.tsx +++ b/src/components/landing/pages/history/history-table/history-table.tsx @@ -6,7 +6,7 @@ import { HistoryEntriesProps } from '../history-content/history-content' import { HistoryTableRow } from './history-table-row' import './history-table.scss' -export const HistoryTable: React.FC = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => { +export const HistoryTable: React.FC = ({ entries, onPinClick, onSyncClick, pageIndex, onLastPageIndexChange }) => { useTranslation() return ( @@ -19,13 +19,14 @@ export const HistoryTable: React.FC = ({ entries, onPinClic - + { entries.map((entry) => ) } diff --git a/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx b/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx index f097b988d..b302a3184 100644 --- a/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx +++ b/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx @@ -37,7 +37,7 @@ export interface HistoryToolbarProps { export const initState: HistoryToolbarState = { viewState: ViewStateEnum.CARD, titleSortDirection: SortModeEnum.no, - lastVisitedSortDirection: SortModeEnum.no, + lastVisitedSortDirection: SortModeEnum.down, keywordSearch: '', selectedTags: [] } @@ -114,14 +114,16 @@ export const HistoryToolbar: React.FC = ({ onSettingsChange - { toggleViewChanged(newViewState) }}> - - + + + + + + diff --git a/src/components/landing/pages/history/history.tsx b/src/components/landing/pages/history/history.tsx index 2fac26bb1..23ed5371d 100644 --- a/src/components/landing/pages/history/history.tsx +++ b/src/components/landing/pages/history/history.tsx @@ -1,12 +1,18 @@ -import React, { Fragment, useEffect, useState } from 'react' +import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react' import { Row } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { deleteHistory, getHistory, setHistory } from '../../../../api/history' +import { ApplicationState } from '../../../../redux' import { + collectEntries, downloadHistory, loadHistoryFromLocalStore, + mergeEntryArrays, setHistoryToLocalStore, sortAndFilterEntries } from '../../../../utils/historyUtils' +import { ErrorModal } from '../../../error-modal/error-modal' import { HistoryContent } from './history-content/history-content' import { HistoryToolbar, HistoryToolbarState, initState as toolbarInitState } from './history-toolbar/history-toolbar' @@ -23,49 +29,87 @@ export interface HistoryJson { entries: HistoryEntry[] } -export type pinClick = (entryId: string) => void; +export type LocatedHistoryEntry = HistoryEntry & HistoryEntryLocation + +export interface HistoryEntryLocation { + location: Location +} + +export enum Location { + LOCAL = 'local', + REMOTE = 'remote' +} export const History: React.FC = () => { useTranslation() - const [historyEntries, setHistoryEntries] = useState([]) - const [viewState, setViewState] = useState(toolbarInitState) + const [localHistoryEntries, setLocalHistoryEntries] = useState(loadHistoryFromLocalStore) + const [remoteHistoryEntries, setRemoteHistoryEntries] = useState([]) + const [toolbarState, setToolbarState] = useState(toolbarInitState) + const user = useSelector((state: ApplicationState) => state.user) + const [error, setError] = useState('') - useEffect(() => { - refreshHistory() + const historyWrite = useCallback((entries: HistoryEntry[]) => { + if (!entries) { + return + } + setHistoryToLocalStore(entries) }, []) useEffect(() => { - if (!historyEntries || historyEntries === []) { - return - } - setHistoryToLocalStore(historyEntries) - }, [historyEntries]) + historyWrite(localHistoryEntries) + }, [historyWrite, localHistoryEntries]) - const exportHistory = () => { + const importHistory = useCallback((entries: HistoryEntry[]): void => { + if (user) { + setHistory(entries) + .then(() => setRemoteHistoryEntries(entries)) + .catch(() => setError('setHistory')) + } else { + historyWrite(entries) + setLocalHistoryEntries(entries) + } + }, [historyWrite, user]) + + const refreshHistory = useCallback(() => { + const localHistory = loadHistoryFromLocalStore() + setLocalHistoryEntries(localHistory) + if (user) { + getHistory() + .then((remoteHistory) => setRemoteHistoryEntries(remoteHistory)) + .catch(() => setError('getHistory')) + } + }, [user]) + + useEffect(() => { + refreshHistory() + }, [refreshHistory]) + + const exportHistory = useCallback(() => { const dataObject: HistoryJson = { version: 2, - entries: historyEntries + entries: mergeEntryArrays(localHistoryEntries, remoteHistoryEntries) } downloadHistory(dataObject) - } + }, [localHistoryEntries, remoteHistoryEntries]) - const importHistory = (entries: HistoryEntry[]): void => { - setHistoryToLocalStore(entries) - setHistoryEntries(entries) - } + const clearHistory = useCallback(() => { + setLocalHistoryEntries([]) + if (user) { + deleteHistory() + .then(() => setRemoteHistoryEntries([])) + .catch(() => setError('deleteHistory')) + } + historyWrite([]) + }, [historyWrite, user]) - const refreshHistory = () => { - const history = loadHistoryFromLocalStore() - setHistoryEntries(history) - } + const syncClick = useCallback((entryId: string): void => { + console.log(entryId) + // ToDo: add syncClick + }, []) - const clearHistory = () => { - setHistoryToLocalStore([]) - setHistoryEntries([]) - } - - const pinClick: pinClick = (entryId: string) => { - setHistoryEntries((entries) => { + const pinClick = useCallback((entryId: string): void => { + // ToDo: determine if entry is local or remote + setLocalHistoryEntries((entries) => { return entries.map((entry) => { if (entry.id === entryId) { entry.pinned = !entry.pinned @@ -73,24 +117,43 @@ export const History: React.FC = () => { return entry }) }) + }, []) + + const resetError = () => { + setError('') } - const tags = historyEntries.map(entry => entry.tags) - .reduce((a, b) => ([...a, ...b]), []) - .filter((value, index, array) => { - if (index === 0) { - return true - } - return (value !== array[index - 1]) - }) - const entriesToShow = sortAndFilterEntries(historyEntries, viewState) + const allEntries = useMemo(() => { + return collectEntries(localHistoryEntries, remoteHistoryEntries) + }, [localHistoryEntries, remoteHistoryEntries]) + + const tags = useMemo(() => { + return allEntries.map(entry => entry.tags) + .reduce((a, b) => ([...a, ...b]), []) + .filter((value, index, array) => { + if (index === 0) { + return true + } + return (value !== array[index - 1]) + }) + }, [allEntries]) + + const entriesToShow = useMemo(() => + sortAndFilterEntries(allEntries, toolbarState), + [allEntries, toolbarState]) return ( + +
+ +
+

{ onImportHistory={importHistory} /> - + onPinClick={pinClick} + onSyncClick={syncClick} + />
) } diff --git a/src/components/pagination/pager.tsx b/src/components/pagination/pager.tsx index 6cca9d58a..d753b72b3 100644 --- a/src/components/pagination/pager.tsx +++ b/src/components/pagination/pager.tsx @@ -7,16 +7,18 @@ export interface PagerPageProps { } export const Pager: React.FC = ({ children, numberOfElementsPerPage, pageIndex, onLastPageIndexChange }) => { + const maxPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1 + const correctedPageIndex = Math.min(maxPageIndex, Math.max(0, pageIndex)) + useEffect(() => { - const lastPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1 - onLastPageIndexChange(lastPageIndex) - }, [children, numberOfElementsPerPage, onLastPageIndexChange]) + onLastPageIndexChange(maxPageIndex) + }, [children, maxPageIndex, numberOfElementsPerPage, onLastPageIndexChange]) return { React.Children.toArray(children).filter((value, index) => { const pageOfElement = Math.floor((index) / numberOfElementsPerPage) - return (pageOfElement === pageIndex) + return (pageOfElement === correctedPageIndex) }) } diff --git a/src/style/index.scss b/src/style/index.scss index 6b984788d..8ad595f4e 100644 --- a/src/style/index.scss +++ b/src/style/index.scss @@ -16,3 +16,10 @@ body { outline: 0 !important; } +.mt-1dot5 { + margin-top: 0.375rem !important; +} + +.fa.fa-fix-line-height { + line-height: inherit; +} diff --git a/src/utils/historyUtils.ts b/src/utils/historyUtils.ts index 1b0d8cce4..f04300179 100644 --- a/src/utils/historyUtils.ts +++ b/src/utils/historyUtils.ts @@ -1,13 +1,39 @@ import moment from 'moment' -import { HistoryEntry, HistoryJson } from '../components/landing/pages/history/history' +import { HistoryEntry, HistoryJson, LocatedHistoryEntry, Location } from '../components/landing/pages/history/history' import { HistoryToolbarState } from '../components/landing/pages/history/history-toolbar/history-toolbar' import { SortModeEnum } from '../components/sort-button/sort-button' -export function sortAndFilterEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { - return sortEntries(filterByKeywordSearch(filterBySelectedTags(entries, viewState.selectedTags), viewState.keywordSearch), viewState) +export function collectEntries (localEntries: HistoryEntry[], remoteEntries: HistoryEntry[]): LocatedHistoryEntry[] { + const locatedLocalEntries = locateEntries(localEntries, Location.LOCAL) + const locatedRemoteEntries = locateEntries(remoteEntries, Location.REMOTE) + return mergeEntryArrays(locatedLocalEntries, locatedRemoteEntries) } -function filterBySelectedTags (entries: HistoryEntry[], selectedTags: string[]): HistoryEntry[] { +export function sortAndFilterEntries (entries: LocatedHistoryEntry[], toolbarState: HistoryToolbarState): LocatedHistoryEntry[] { + const filteredBySelectedTagsEntries = filterBySelectedTags(entries, toolbarState.selectedTags) + const filteredByKeywordSearchEntries = filterByKeywordSearch(filteredBySelectedTagsEntries, toolbarState.keywordSearch) + return sortEntries(filteredByKeywordSearchEntries, toolbarState) +} + +function locateEntries (entries: HistoryEntry[], location: Location): LocatedHistoryEntry[] { + return entries.map(entry => { + return { + ...entry, + location: location + } + }) +} + +export function mergeEntryArrays (locatedLocalEntries: T[], locatedRemoteEntries: T[]): T[] { + const filteredLocalEntries = locatedLocalEntries.filter(localEntry => { + const entry = locatedRemoteEntries.find(remoteEntry => remoteEntry.id === localEntry.id) + return !entry + }) + + return filteredLocalEntries.concat(locatedRemoteEntries) +} + +function filterBySelectedTags (entries: LocatedHistoryEntry[], selectedTags: string[]): LocatedHistoryEntry[] { return entries.filter(entry => { return (selectedTags.length === 0 || arrayCommonCheck(entry.tags, selectedTags)) } @@ -23,12 +49,12 @@ function arrayCommonCheck (array1: T[], array2: T[]): boolean { return !!foundElement } -function filterByKeywordSearch (entries: HistoryEntry[], keywords: string): HistoryEntry[] { +function filterByKeywordSearch (entries: LocatedHistoryEntry[], keywords: string): LocatedHistoryEntry[] { const searchTerm = keywords.toLowerCase() return entries.filter(entry => entry.title.toLowerCase().indexOf(searchTerm) !== -1) } -function sortEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { +function sortEntries (entries: LocatedHistoryEntry[], viewState: HistoryToolbarState): LocatedHistoryEntry[] { return entries.sort((firstEntry, secondEntry) => { if (firstEntry.pinned && !secondEntry.pinned) { return -1