diff --git a/src/common/redux/actions/annotation/exportW3CAnnotationSet.ts b/src/common/redux/actions/annotation/exportW3CAnnotationSet.ts new file mode 100644 index 0000000000..7c63bd7c06 --- /dev/null +++ b/src/common/redux/actions/annotation/exportW3CAnnotationSet.ts @@ -0,0 +1,29 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import { Action } from "readium-desktop/common/models/redux"; + +export const ID = "ANNOTATION_EXPORT_W3CANNOTATIONSET"; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Payload { + publicationIdentifier: string; + filePath: string, +} + +export function build(publicationIdentifier: string, filePath: string): Action { + + return { + type: ID, + payload: { + publicationIdentifier, + filePath, + }, + }; +} +build.toString = () => ID; // Redux StringableActionCreator +export type TAction = ReturnType; diff --git a/src/common/redux/actions/annotation/index.ts b/src/common/redux/actions/annotation/index.ts new file mode 100644 index 0000000000..cd168a42ff --- /dev/null +++ b/src/common/redux/actions/annotation/index.ts @@ -0,0 +1,12 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import * as exportW3CAnnotationSetFromAnnotations from "./exportW3CAnnotationSet"; + +export { + exportW3CAnnotationSetFromAnnotations, +}; diff --git a/src/common/redux/actions/index.ts b/src/common/redux/actions/index.ts index c60d9a2d5b..6a7b02c981 100644 --- a/src/common/redux/actions/index.ts +++ b/src/common/redux/actions/index.ts @@ -18,6 +18,7 @@ import * as loadActions from "./load"; import * as netActions from "./net/"; import * as readerActions from "./reader/"; import * as toastActions from "./toast/"; +import * as annotationActions from "./annotation"; export { historyActions, @@ -33,4 +34,5 @@ export { downloadActions, keyboardActions, loadActions, + annotationActions, }; diff --git a/src/main/redux/sagas/annotation.ts b/src/main/redux/sagas/annotation.ts new file mode 100644 index 0000000000..701fc187d8 --- /dev/null +++ b/src/main/redux/sagas/annotation.ts @@ -0,0 +1,57 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import * as debug_ from "debug"; +// import { LocaleConfigIdentifier, LocaleConfigValueType } from "readium-desktop/common/config"; +import { annotationActions } from "readium-desktop/common/redux/actions"; +import { takeSpawnLeading } from "readium-desktop/common/redux/sagas/takeSpawnLeading"; +import { error } from "readium-desktop/main/tools/error"; +// eslint-disable-next-line local-rules/typed-redux-saga-use-typed-effects +import { call as callTyped, select as selectTyped } from "typed-redux-saga/macro"; +import { getPublication } from "./api/publication/getPublication"; +import { convertAnnotationListToW3CAnnotationModelSet, convertPublicationToAnnotationStateAbout } from "readium-desktop/main/w3c/annotation/converter"; +import { RootState } from "../states"; +import { IAnnotationState } from "readium-desktop/common/redux/states/annotation"; +import { saveW3CAnnotationModelSetFromFilePath } from "readium-desktop/main/w3c/annotation/fs"; + +// Logger +const filename_ = "readium-desktop:main:saga:annotation"; +const debug = debug_(filename_); +debug("_"); + +function* exportW3CAnnotation(action: annotationActions.exportW3CAnnotationSetFromAnnotations.TAction) { + + const { publicationIdentifier, filePath } = action.payload; + + const pub = yield* callTyped(getPublication, publicationIdentifier); + + let annotations: IAnnotationState[] = []; + + const sessionReader = yield* selectTyped((state: RootState) => state.win.session.reader); + if (Object.keys(sessionReader).find((v) => v === publicationIdentifier)) { + annotations = (sessionReader[publicationIdentifier]?.reduxState?.annotation || []).map(([,v]) => v); + } else { + const sessionRegistry = yield* selectTyped((state: RootState) => state.win.registry.reader); + if (Object.keys(sessionRegistry).find((v) => v === publicationIdentifier)) { + annotations = (sessionRegistry[publicationIdentifier]?.reduxState?.annotation || []).map(([, v]) => v); + } + } + + const publicationMetadata = yield* callTyped(convertPublicationToAnnotationStateAbout, pub, publicationIdentifier); + const W3CAnnotationSet = yield* callTyped(convertAnnotationListToW3CAnnotationModelSet, annotations, publicationMetadata); + + yield* callTyped(saveW3CAnnotationModelSetFromFilePath, filePath, W3CAnnotationSet); +} + +export function saga() { + + return takeSpawnLeading( + annotationActions.exportW3CAnnotationSetFromAnnotations.ID, + exportW3CAnnotation, + (e) => error(filename_, e), + ); +} diff --git a/src/main/w3c/annotation/annotationModel.type.ts b/src/main/w3c/annotation/annotationModel.type.ts index f96a0a6dd2..f6122420c6 100644 --- a/src/main/w3c/annotation/annotationModel.type.ts +++ b/src/main/w3c/annotation/annotationModel.type.ts @@ -65,12 +65,22 @@ interface Generator { homepage: string; } +export interface IW3CAnnotationSetAboutView { + identiferArrayString: string[]; + mimeType: string; + title: string; + publisher: string[]; + creator: string[]; + publishedAt: string; + source: string; +} + interface About { "dc:identifier": string[]; "dc:format": string; "dc:title": string; - "dc:publisher": string; - "dc:creator": string; + "dc:publisher": string[]; + "dc:creator": string[]; "dc:date": string; "dc:source"?: string; } diff --git a/src/main/w3c/annotation/converter.ts b/src/main/w3c/annotation/converter.ts index 72d52ab006..1805e03785 100644 --- a/src/main/w3c/annotation/converter.ts +++ b/src/main/w3c/annotation/converter.ts @@ -6,9 +6,10 @@ // ==LICENSE-END== import { IAnnotationState } from "readium-desktop/common/redux/states/annotation"; -import { IW3CAnnotationModel, IW3CAnnotationModelSet } from "./annotationModel.type"; +import { IW3CAnnotationModel, IW3CAnnotationModelSet, IW3CAnnotationSetAboutView } from "./annotationModel.type"; import { v4 as uuidv4 } from "uuid"; import { _APP_VERSION } from "readium-desktop/preprocessor-directives"; +import { PublicationView } from "readium-desktop/common/views/publication"; function rgbToHex(color: { red: number; green: number; blue: number }): string { const { red, green, blue } = color; @@ -78,11 +79,11 @@ export function convertAnnotationToW3CAnnotationModel(annotation: IAnnotationSta }; } -export function convertAnnotationListToW3CAnnotationModelSel(annotationArray: IAnnotationState[], - publicationMetadata: { identiferArrayString: string[], mimeType: string, title: string, publisher: string, creator: string, dateYear: string, sourceIsbn: string }, +export function convertAnnotationListToW3CAnnotationModelSet(annotationArray: IAnnotationState[], + publicationMetadata: IW3CAnnotationSetAboutView, ): IW3CAnnotationModelSet { - const { identiferArrayString, mimeType, title, publisher, creator, dateYear, sourceIsbn } = publicationMetadata; + const { identiferArrayString, mimeType, title, publisher, creator, publishedAt, source } = publicationMetadata; const currentDate = new Date(); const dateString: string = currentDate.toISOString(); @@ -102,12 +103,25 @@ export function convertAnnotationListToW3CAnnotationModelSel(annotationArray: IA "dc:identifier": identiferArrayString || [], "dc:format": mimeType || "", "dc:title": title || "", - "dc:publisher": publisher || "", - "dc:creator": creator || "", - "dc:date": dateYear || "", - "dc:source": sourceIsbn || "", + "dc:publisher": publisher || [], + "dc:creator": creator || [], + "dc:date": publishedAt || "", + "dc:source": source || "", }, total: annotationArray.length, items: (annotationArray || []).map((v) => convertAnnotationToW3CAnnotationModel(v)), }; } + +export function convertPublicationToAnnotationStateAbout(publicationView: PublicationView, publicationIdentifier: string): IW3CAnnotationSetAboutView { + + return { + identiferArrayString: ["urn:isbn:" + publicationView.workIdentifier || ""], + mimeType: "application/epub+zip", + title: publicationView.documentTitle || "", + publisher: publicationView.publishers || [], + creator: publicationView.authors || [], + publishedAt: publicationView.publishedAt || "", + source: "urn:uuid:" + publicationIdentifier, + }; +} diff --git a/src/main/w3c/annotation/fs.ts b/src/main/w3c/annotation/fs.ts new file mode 100644 index 0000000000..ddbcf04b3f --- /dev/null +++ b/src/main/w3c/annotation/fs.ts @@ -0,0 +1,20 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import { writeFileSync } from "fs"; +import { IW3CAnnotationModelSet } from "./annotationModel.type"; + +export function saveW3CAnnotationModelSetFromFilePath(filePath: string, data: IW3CAnnotationModelSet) { + try { + const jsonData = JSON.stringify(data, null, 2); + writeFileSync(filePath, jsonData, "utf-8"); + + console.log(`Data successfully written to ${filePath}`); + } catch (error) { + console.error(`Error writing data to ${filePath}: ${error}`); + } +} diff --git a/src/renderer/library/redux/middleware/sync.ts b/src/renderer/library/redux/middleware/sync.ts index 9578b45e4d..9286a28ab3 100644 --- a/src/renderer/library/redux/middleware/sync.ts +++ b/src/renderer/library/redux/middleware/sync.ts @@ -6,6 +6,7 @@ // ==LICENSE-END== import { + annotationActions, apiActions, authActions, downloadActions, i18nActions, keyboardActions, readerActions, } from "readium-desktop/common/redux/actions"; import { syncFactory } from "readium-desktop/renderer/common/redux/middleware/syncFactory"; @@ -31,6 +32,8 @@ const SYNCHRONIZABLE_ACTIONS: string[] = [ keyboardActions.reloadShortcuts.ID, downloadActions.abort.ID, + + annotationActions.exportW3CAnnotationSetFromAnnotations.ID, ]; export const reduxSyncMiddleware = syncFactory(SYNCHRONIZABLE_ACTIONS);