import { h, Fragment, Component } from 'preact'; import type { AnnotationSourceDescriptor } from '../storage/AnnotationSource'; import { RpcClient } from 'webextension-rpc'; import type { backgroundRpcServer } from '../background'; import { MarginalAnnotations } from './MarginalAnnotations'; import { ToolbarButtons } from './ToolbarButtons'; import { discoverAnnotationsEmbeddedAsJSONLD, discoverAnnotationSources, } from './discovery'; import { targetsUrl } from 'web-annotation-utils'; import { unique } from '../util/unique'; import type { IAnnotationWithSource } from '../storage/Annotation'; import { AnnotationCreationHelper } from './AnnotationCreationHelper'; const backgroundRpc = new RpcClient(); const getAnnotationsForTargetUrls = backgroundRpc.func( 'getAnnotationsForTargetUrls', ); const isSourceSubscribed = backgroundRpc.func('isSourceSubscribed'); const updateAnnotation = backgroundRpc.func('updateAnnotation'); const deleteAnnotation = backgroundRpc.func('deleteAnnotation'); interface AppProps { appContainer: Node; } interface AppState { storedAnnotations: IAnnotationWithSource[]; embeddedAnnotations: IAnnotationWithSource[]; annotationInFocus?: IAnnotationWithSource; discoveredLinkedSources: (AnnotationSourceDescriptor & { subscribed: boolean; })[]; discoveredEmbeddedSources: (AnnotationSourceDescriptor & { subscribed: boolean; })[]; } export class App extends Component { state: AppState = { storedAnnotations: [], embeddedAnnotations: [], discoveredLinkedSources: [], discoveredEmbeddedSources: [], }; async componentDidMount() { try { await Promise.all([ this.loadStoredAnnotations(), this.discoverEmbeddedAnnotations(), this.discoverLinkedAnnotationSources(), ]); } catch (error) { console.log(error); } } async loadStoredAnnotations() { // Find annotations in our storage that target the current page. const urls = [document.URL]; const canonicalLink = document.querySelector( 'link[rel~="canonical"]', ) as HTMLLinkElement | null; if (canonicalLink) urls.push(canonicalLink.href); const storedAnnotations = await getAnnotationsForTargetUrls(urls); console.log( `We got these annotations for <${document.URL}>:`, storedAnnotations, ); this.setState({ storedAnnotations, }); } async discoverEmbeddedAnnotations() { // Find annotations embedded inside the page. const embeddedAnnotations = discoverAnnotationsEmbeddedAsJSONLD(); const embeddedAnnotationsTargetingThisPage = embeddedAnnotations.filter( (annotation) => targetsUrl(annotation.target, document.URL), ); console.log( `Found ${embeddedAnnotations.length} embedded annotations in the page, of which ${embeddedAnnotationsTargetingThisPage.length} target this page itself.`, ); this.setState({ embeddedAnnotations: embeddedAnnotationsTargetingThisPage.map( (annotation) => ({ _id: 0, source: { _id: -1, active: false, type: 'embeddedJsonld', url: document.URL, }, annotation, }), ), }); // A page with embedded annotations targeting *other* pages is considered an annotation source. if ( embeddedAnnotations.length > embeddedAnnotationsTargetingThisPage.length ) { const pageAsAnnotationSource: AnnotationSourceDescriptor = { title: document.title, url: document.URL.split('#')[0], type: 'embeddedJsonld', }; this.setState({ discoveredEmbeddedSources: await this.checkDiscoveredAnnotationSources([ pageAsAnnotationSource, ]), }); } } async discoverLinkedAnnotationSources() { // Find annotations sources advertised by the current page. const discoveredSources = discoverAnnotationSources(); this.setState({ discoveredLinkedSources: await this.checkDiscoveredAnnotationSources( discoveredSources, ), }); } async checkDiscoveredAnnotationSources( discoveredSources: AnnotationSourceDescriptor[], ) { // For each discovered source, note if we already have it in our database. return await Promise.all( discoveredSources.map(async (source) => ({ ...source, subscribed: await isSourceSubscribed(source), })), ); } async onAnnotationCreated(annotation: IAnnotationWithSource) { await this.loadStoredAnnotations(); this.setState({ annotationInFocus: annotation }); } onSubscriptionChange() { this.discoverLinkedAnnotationSources(); this.discoverEmbeddedAnnotations(); } render( { appContainer }: AppProps, { storedAnnotations, embeddedAnnotations, annotationInFocus, discoveredLinkedSources, discoveredEmbeddedSources, }: AppState, ) { const annotationsToShow = unique( [...storedAnnotations, ...embeddedAnnotations], (obj) => obj.annotation.canonical || obj.annotation.id, ); const discoveredSources = [ ...discoveredLinkedSources, ...discoveredEmbeddedSources, ]; const toolbarButtons = discoveredSources.length > 0 ? ( this.onSubscriptionChange(), discoveredSources, }} /> ) : undefined; return ( <> { await updateAnnotation(...args); // messes up text while editing. // await this.loadStoredAnnotations(); }, onDeleteAnnotation: async (...args) => { await deleteAnnotation(...args); await this.loadStoredAnnotations(); }, }} /> this.onAnnotationCreated(annotation) } /> ); } }