diff --git a/packages/annotation/package.json b/packages/annotation/package.json new file mode 100644 index 0000000..a663402 --- /dev/null +++ b/packages/annotation/package.json @@ -0,0 +1,25 @@ +{ + "name": "@apache-annotator/annotation", + "version": "0.3.0", + "description": "Web Annotation types and utilities.", + "homepage": "https://annotator.apache.org", + "repository": { + "type": "git", + "url": "https://github.com/apache/incubator-annotator.git", + "directory": "packages/annotation" + }, + "license": "Apache-2.0", + "author": "Apache Software Foundation", + "type": "module", + "exports": "./lib/index.js", + "main": "./lib/index.js", + "dependencies": { + "@babel/runtime-corejs3": "^7.13.10" + }, + "engines": { + "node": "^14.15 || ^15.4 || >=16" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/annotation/src/index.ts b/packages/annotation/src/index.ts new file mode 100644 index 0000000..1e5f467 --- /dev/null +++ b/packages/annotation/src/index.ts @@ -0,0 +1,25 @@ +/** + * @license + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-FileCopyrightText: The Apache Software Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './web-annotation.js'; +export * from './multiplicity.js'; diff --git a/packages/annotation/src/multiplicity.ts b/packages/annotation/src/multiplicity.ts new file mode 100644 index 0000000..93a74d4 --- /dev/null +++ b/packages/annotation/src/multiplicity.ts @@ -0,0 +1,48 @@ +/** + * @license + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-FileCopyrightText: The Apache Software Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +export type OneOrMore = T | T[]; +export type ZeroOrMore = undefined | null | T | T[]; + +export type OneOrMoreIncluding = + | RequiredValue + | [RequiredValue, ...Other[]] + | [...Other[], RequiredValue]; +// | [Other, ...OneOrMoreIncluding]; // FIXME TypeScript complains about the circular reference.. + +/** + * OnlyOne extracts the T from a One/ZeroOrMore type + */ +export type OnlyOne = T extends (infer X)[] ? X : T; + +export function asArray(value: ZeroOrMore): T[] { + if (Array.isArray(value)) return value; + if (value === undefined || value === null) return []; + return [value]; +} + +export function asSingleValue(value: ZeroOrMore): T | undefined { + if (value instanceof Array) return value[0]; + if (value === undefined || value === null) return undefined; + return value; +} diff --git a/packages/annotation/src/web-annotation.ts b/packages/annotation/src/web-annotation.ts new file mode 100644 index 0000000..013abef --- /dev/null +++ b/packages/annotation/src/web-annotation.ts @@ -0,0 +1,227 @@ +/** + * @license + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-FileCopyrightText: The Apache Software Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + OneOrMore, + OneOrMoreIncluding, + ZeroOrMore, +} from './multiplicity.js'; + +/** + * A Web Annotation object. + * + * This is an interpretation of the Web Annotation Data Model: + * + * + * TODO Deal more systemically with ‘relations’, i.e. values that could be + * either a nested object or a URI referring to such an object. + */ +export interface WebAnnotation { + '@context': OneOrMoreIncluding; + type: OneOrMoreIncluding; + id: string; + target: OneOrMore; + creator?: ZeroOrMore; + created?: UtcDateTime; + generator?: ZeroOrMore; + generated?: UtcDateTime; + modified?: UtcDateTime; + motivation?: ZeroOrMore; + audience?: ZeroOrMore; + rights?: ZeroOrMore; + canonical?: string; + via?: ZeroOrMore; + body?: BodyChoice | OneOrMore; + bodyValue?: string; +} + +/** + * A slightly stricter type for WebAnnotation, not allowing both a body and bodyValue. + */ +export type WebAnnotationStrict = WebAnnotation & (WithBody | WithBodyValue | WithoutBody); + +interface WithBody { + body: BodyChoice | OneOrMore; + bodyValue?: undefined; +} + +interface WithBodyValue { + body?: undefined; + bodyValue: string; +} + +interface WithoutBody { + body?: undefined; + bodyValue?: undefined; +} + +export type Body = string | BodyObject; + +export type BodyObject = { + creator?: ZeroOrMore; + created?: UtcDateTime; + modified?: UtcDateTime; + purpose?: ZeroOrMore; +} & (TextualBody | SpecificResource | ExternalResource); + +export type Target = string | SpecificResource | ExternalResource; + +export type Agent = + | string + | { + id?: string; + type?: ZeroOrMore<'Person' | 'Organization' | 'Software'>; + name?: ZeroOrMore; + nickname?: ZeroOrMore; + email?: ZeroOrMore; + email_sha1?: ZeroOrMore; + homepage?: ZeroOrMore; + }; + +export type Audience = + | string + | { + id?: string; + type?: string; + }; + +export interface BodyChoice { + type: 'Choice'; + items: Body[]; +} + +export interface TextualBody extends Omit { + id?: string; + type: 'TextualBody'; + value: string; +} + +export interface SpecificResource { + id?: string; + type?: 'SpecificResource'; + source: string; + selector?: string | OneOrMore; + accessibility?: AccessibilityFeatures; + rights?: ZeroOrMore; + canonical?: string; + via?: ZeroOrMore; +} + +/** + * A {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors + * | Selector} object of the Web Annotation Data Model. + * + * Corresponds to RDF class {@link http://www.w3.org/ns/oa#Selector} + * + * @public + */ +export interface Selector { + type?: string; + + /** + * A Selector can be refined by another Selector. + * + * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection + * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model. + * + * Corresponds to RDF property {@link http://www.w3.org/ns/oa#refinedBy} + */ + refinedBy?: Selector; +} + +export interface ExternalResource { + id: string; + // XXX type’s value SHOULD be one of these, “but MAY come from other vocabularies”. + type?: ZeroOrMore<'Dataset' | 'Image' | 'Video' | 'Sound' | 'Text'>; + format?: ZeroOrMore; + language?: ZeroOrMore; + processingLanguage?: string; + textDirection?: 'ltr' | 'rtl' | 'auto'; + accessibility?: AccessibilityFeatures; + rights?: ZeroOrMore; + canonical?: string; + via?: ZeroOrMore; +} + +export type Motivation = + | 'assessing' + | 'bookmarking' + | 'classifying' + | 'commenting' + | 'describing' + | 'editing' + | 'highlighting' + | 'identifying' + | 'linking' + | 'moderating' + | 'questioning' + | 'replying' + | 'tagging'; + +// “The datetime MUST be a xsd:dateTime with the UTC timezone expressed as "Z".” +type UtcDateTime = `${string}Z`; + +// To help usage, narrow the type of Date.toISOString(); it is guaranteed to end with a 'Z'. +declare global { + interface Date { + toISOString(): UtcDateTime; + } +} + +// From +export type AccessibilityFeatures = + | ZeroOrMore + | 'none' + | ['none']; +export type AccessibilityFeature = + | 'annotations' + | 'ARIA' + | 'bookmarks' + | 'index' + | 'printPageNumbers' + | 'readingOrder' + | 'structuralNavigation' + | 'tableOfContents' + | 'taggedPDF' + | 'alternativeText' + | 'audioDescription' + | 'captions' + | 'describedMath' + | 'longDescription' + | 'rubyAnnotations' + | 'signLanguage' + | 'transcript' + | 'displayTransformability' + | 'synchronizedAudioText' + | 'timingControl' + | 'unlocked' + | 'ChemML' + | 'latex' + | 'MathML' + | 'ttsMarkup' + | 'highContrastAudio' + | 'highContrastDisplay' + | 'largePrint' + | 'braille' + | 'tactileGraphic' + | 'tactileObject'; diff --git a/packages/annotation/test/model/multiplicity.test.ts b/packages/annotation/test/model/multiplicity.test.ts new file mode 100644 index 0000000..6e18e10 --- /dev/null +++ b/packages/annotation/test/model/multiplicity.test.ts @@ -0,0 +1,39 @@ +/** + * @license + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-FileCopyrightText: The Apache Software Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { strict as assert } from 'assert'; +import { asArray, asSingleValue } from '../../src/multiplicity'; +import type { OneOrMore, OnlyOne, ZeroOrMore } from '../../src/multiplicity'; + +describe('asArray', () => { + it('wraps a single value', () => { + const input: OneOrMore = 'blub'; + const output = asArray(input); + assert.strictEqual(output, ['blub']); + }); + it('leaves an array untouched', () => { + const input: OneOrMore = ['blub']; + const output = asArray(input); + assert.strictEqual(output, input); + }); +}); diff --git a/packages/annotation/tsconfig.json b/packages/annotation/tsconfig.json new file mode 100644 index 0000000..653b0a5 --- /dev/null +++ b/packages/annotation/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + } +} diff --git a/packages/apache-annotator/package.json b/packages/apache-annotator/package.json index ebec725..597dd7e 100644 --- a/packages/apache-annotator/package.json +++ b/packages/apache-annotator/package.json @@ -15,6 +15,7 @@ "./*": "./lib/*.js" }, "dependencies": { + "@apache-annotator/annotation": "^0.3.0", "@apache-annotator/dom": "^0.3.0", "@apache-annotator/selector": "^0.3.0", "@babel/runtime-corejs3": "^7.13.10" diff --git a/packages/apache-annotator/src/annotation.ts b/packages/apache-annotator/src/annotation.ts new file mode 100644 index 0000000..98bc670 --- /dev/null +++ b/packages/apache-annotator/src/annotation.ts @@ -0,0 +1,37 @@ +/** + * @license + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-FileCopyrightText: The Apache Software Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This module provides types and utilities for the {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/ + * | Web Annotation Data Model}. + * + * Besides a type definition, it provides convenience functions for dealing with + * Web Annotations, such as getting the URL(s) of pages an annotation targets, + * or the plain text content the annotation body. It aims to provide some basic + * tools to get started writing interoperable annotation tools without having to + * deal with the intricacies of the data model. + * + * @module + */ + +export * from '@apache-annotator/annotation'; diff --git a/packages/apache-annotator/tsconfig.json b/packages/apache-annotator/tsconfig.json index 0ac1cf1..cabb91b 100644 --- a/packages/apache-annotator/tsconfig.json +++ b/packages/apache-annotator/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "src" }, "references": [ + { "path": "../annotation" }, { "path": "../dom" }, { "path": "../selector" } ] diff --git a/packages/selector/src/types.ts b/packages/selector/src/types.ts index d3c227b..afbf3b4 100644 --- a/packages/selector/src/types.ts +++ b/packages/selector/src/types.ts @@ -21,25 +21,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * A {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors - * | Selector} object of the Web Annotation Data Model. - * - * Corresponds to RDF class {@link http://www.w3.org/ns/oa#Selector} - * - * @public - */ -export interface Selector { - /** - * A Selector can be refined by another Selector. - * - * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection - * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model. - * - * Corresponds to RDF property {@link http://www.w3.org/ns/oa#refinedBy} - */ - refinedBy?: Selector; -} +import type { Selector } from '@apache-annotator/annotation'; +export type { Selector }; /** * The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#css-selector diff --git a/packages/selector/tsconfig.json b/packages/selector/tsconfig.json index 653b0a5..2281984 100644 --- a/packages/selector/tsconfig.json +++ b/packages/selector/tsconfig.json @@ -4,5 +4,8 @@ "compilerOptions": { "outDir": "lib", "rootDir": "src" - } + }, + "references": [ + { "path": "../annotation" } + ] } diff --git a/tsconfig.json b/tsconfig.json index b9a2c0d..126dcba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "files": [], "references": [ { "path": "packages/apache-annotator" }, + { "path": "packages/annotation" }, { "path": "packages/dom" }, { "path": "packages/selector" }, { "path": "tsconfig.test.json"} diff --git a/tsconfig.test.json b/tsconfig.test.json index f439fba..0c2f128 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "include": ["test", "packages/*/test"], "references": [ + { "path": "packages/annotation" }, { "path": "packages/dom" }, { "path": "packages/selector" } ]