diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d440c23 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## v0.3.0 - December 4, 2021 + +This should bring the individual components more or less up to date, but what's lacking is a more thorough audit to be certain. + +Some components may still not be exposed at the root level except through the global namespace `Component`. + +#### Changes: +* Globally exposed component renamed `Facebook` -> `FacebookPost` to match Apple's spec +* Updated URI validation method. Now wraps around native URL class and checks against a list of valid file extensions - these may need updating. Couldn't find a canonical list of all valid file types +* Added `ColorSchema` interface. +* Removed unsupported advertisement classes: `MediumRectangleAdvertisement` and `BannerAdvertisment` +* Add missing fields to several A/V components +* Other minor housecleaning changes diff --git a/README.md b/README.md index 3ef8de7..a84019e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![CircleCI](https://circleci.com/gh/Robert-Fairley/apple-news-format.svg?style=svg&circle-token=6f3e22cb14ceb409d7241efc628ccdef34b810f8)](https://circleci.com/gh/Robert-Fairley/apple-news-format) +**Current to ANF version: `1.17.0`** + This is a collection of TypeScript types for the Apple News Format fields, metadata, and components. Included are a small selection of validation functions for certain fields that contain restrictions on the strings that are passed in. diff --git a/package-lock.json b/package-lock.json index e50986e..fc370db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "devDependencies": { "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", + "@types/node": "^16.11.11", "chai": "^4.2.0", "husky": "^1.3.1", "mocha": "^9.1.3", @@ -58,6 +59,12 @@ "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", "dev": true }, + "node_modules/@types/node": { + "version": "16.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", + "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "dev": true + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -2042,6 +2049,12 @@ "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", "dev": true }, + "@types/node": { + "version": "16.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", + "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "dev": true + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", diff --git a/package.json b/package.json index d7b3d18..ecbf11f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "devDependencies": { "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", + "@types/node": "^16.11.11", "chai": "^4.2.0", "husky": "^1.3.1", "mocha": "^9.1.3", diff --git a/src/apple-news.d.ts b/src/apple-news.d.ts index dd70d92..205740b 100644 --- a/src/apple-news.d.ts +++ b/src/apple-news.d.ts @@ -36,8 +36,7 @@ declare namespace AppleNews { export type ComponentLink = Components.ComponentLink; export type Component = Components.Component; export type Scene = Components.Scene; - export type BannerAdvertisement = Components.Advertisements.BannerAdvertisement; - export type MediumRectangleAdvertisement = Components.Advertisements.MediumRectangleAdvertisement; + export type ReplicaAdvertisement = Components.Advertisements.ReplicaAdvertisement; export type ArticleLink = Components.ArticleStructure.ArticleLink; export type Aside = Components.ArticleStructure.Aside; export type Chapter = Components.ArticleStructure.Chapter; @@ -67,9 +66,10 @@ declare namespace AppleNews { export type MapSpan = Components.Location.MapSpan; export type Map = Components.Location.Map; export type Place = Components.Location.Place; - export type Instagram = Components.SocialMedia.SocialMediaComponent; - export type Facebook = Components.SocialMedia.SocialMediaComponent; - export type Tweet = Components.SocialMedia.SocialMediaComponent; + export type Instagram = Components.SocialMedia.Instagram; + export type FacebookPost = Components.SocialMedia.FacebookPost; + export type TikTok = Components.SocialMedia.TikTok; + export type Tweet = Components.SocialMedia.Tweet; export type DataDescriptor = Components.Tables.DataDescriptor; export type DataFormat = Components.Tables.DataFormat; export type DataTableSorting = Components.Tables.DataTableSorting; diff --git a/src/article-document.ts b/src/article-document.ts index 14e4ab2..8e81d73 100644 --- a/src/article-document.ts +++ b/src/article-document.ts @@ -7,13 +7,16 @@ import { AnyComponent } from "./components/any-component"; import { DocumentStyle } from "./document-style"; import { Metadata } from "./metadata"; import { ComponentStyles } from "./styles/component-styles"; +import { ColorScheme } from "./color-scheme"; import { ComponentTextStyles, TextStyles, } from "./styles/text-styles"; +import { TextFormat } from "./primitives/text"; /** * Signature/interface for the `ArticleDocument` object + * @see https://developer.apple.com/documentation/apple_news/articledocument */ export interface ArticleDocument { components: AnyComponent[]; @@ -24,10 +27,11 @@ export interface ArticleDocument { title: string; version: string; autoplacement?: AutoPlacement; + colorScheme?: ColorScheme; componentLayouts?: ComponentLayouts; componentStyles?: ComponentStyles; documentStyle?: DocumentStyle; metadata?: Metadata; - subtitle?: string; + textFormat?: TextFormat; textStyles?: TextStyles; } diff --git a/src/article-layout/component-layout-base.ts b/src/article-layout/component-layout-base.ts index c3722d9..c064162 100644 --- a/src/article-layout/component-layout-base.ts +++ b/src/article-layout/component-layout-base.ts @@ -20,82 +20,16 @@ export type IgnoreDocumentParameters * @see https://developer.apple.com/documentation/apple_news/componentlayout */ export interface ComponentLayoutBase { - /** - * A number that indicates how many columns the component spans, based on the number of columns in the document. - * Minimum: 1 - * @type Unsigned Integer - */ columnSpan?: number; // Unisigned Integer - minimum 1 - /** - * A number that indicates which column the component‘s start position is in, - * based on the number of columns in the document or parent container. - * Minimum: 0 - * @type Unsigned Integer - */ columnStart?: number; // Unsigned Integer - /** - * A string value that sets the alignment of the content within the component. - * This property applies only when the width of the content is less than the width of the component. - * @type boolean | HorizontalAlignment - */ horizontalContentAlignment?: HorizontalAlignment; - /** - * A value that indicates whether the gutters (if any) to the left and right of the component should be ignored. - * The gutter size is defined in the Layout object at the root level of the document. - * @type boolean | IgnoreDocumentParameters - */ ignoreDocumentGutter?: boolean | IgnoreDocumentParameters; - /** - * A value that indicates whether the component should respect or ignore the document’s margins. - * Ignoring document margins positions the component based on the document's width and margin. - * @type boolean | IgnoreDocumentParameters - */ ignoreDocumentMargin?: boolean | IgnoreDocumentParameters; - /** - * A value that indicates whether the component should respect or ignore the viewport padding. - * Ignoring viewport padding positions the component at the edge of the display screen. - * This property affects the layout only if the component is in the first or last column. - * @type boolean | IgnoreDocumentParameters - */ ignoreViewportPadding?: boolean | IgnoreDocumentParameters; - /** - * A value that sets the margins for the top and bottom of the component, as a single integer - * that gets applied to the top and bottom margins, or as an object containing separate properties for top and bottom. - * @type Margin | Integer - */ margin?: Margin | number; // Integer - /** - * A value that sets the maximum width of the content within the component. - * Specify this value as a number in points or using one of the available units of measure for components. - * @type SupportedUnits | Integer - */ maximumContentWidth?: SupportedUnits | number; - /** - * A value that sets the minimum height of the component. A component is taller than its - * defined minimumHeight when the contents require the component to be taller. - * The minimum height can be defined as a number in points or using one of the - * available units of measure for components. - * @type SupportedUnits | Integer - */ minimumHeight?: SupportedUnits | number; - /** - * A value that defines the minimum width of the layout when used within a Container - * with HorizontalStackDisplay as the specified contentDisplay type. The minimum width - * can be defined as a number in points or using one of the available units of measure for components. - * @type SupportedUnits | Integer - */ minimumWidth?: SupportedUnits | number; - /** - * A value that defines the maximum width of the layout when used within a Container - * with HorizontalStackDisplay as the specified contentDisplay type. The maximum width can be - * defined as a number in points or using one of the available units of measure for components. - * @type SupportedUnits | Integer - */ maximumWidth?: SupportedUnits | number; - /** - * A value that defines the padding between the content of the component and the edges of the component. - * Padding can be defined as a number in points or using one of the available units of measure for components. - * @type SupportedUnits | Padding | Integer - */ padding?: SupportedUnits | Padding | number; } diff --git a/src/article-layout/layout.ts b/src/article-layout/layout.ts index 9cc5329..f4ee64a 100644 --- a/src/article-layout/layout.ts +++ b/src/article-layout/layout.ts @@ -5,6 +5,6 @@ export interface Layout { columns: number; // Integer - Minimum: 1 width: number; // Integer - Minimum 1 - gutter?: number; // Unsigned Integer - margin?: number; // Unsigned Integer + gutter?: number; // Integer + margin?: number; // Integer } diff --git a/src/color-scheme.ts b/src/color-scheme.ts new file mode 100644 index 0000000..c3b226b --- /dev/null +++ b/src/color-scheme.ts @@ -0,0 +1,7 @@ +/** + * Object containing information about document color scheme and dark mode. + * @see https://developer.apple.com/documentation/apple_news/articledocument/colorscheme + */ +export interface ColorScheme { + automaticDarkModeEnabled?: boolean; +} diff --git a/src/components/advertisements/banner-advertisement.ts b/src/components/advertisements/banner-advertisement.ts deleted file mode 100644 index 93dc5f1..0000000 --- a/src/components/advertisements/banner-advertisement.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component } from "../component"; - -/** - * Possible values for a bannerType field - */ -export type BannerType - = "any" - | "standard" - | "double_height" - | "large"; - - /** - * Signature/interface for a `BannerAdvertisement` object - * @see https://developer.apple.com/documentation/apple_news/banneradvertisement - * @extends {Component} - */ -export interface BannerAdvertisement extends Component { - role: "banner_advertisement"; - bannerType?: BannerType; -} diff --git a/src/components/advertisements/index.ts b/src/components/advertisements/index.ts index b4aa83d..cd34761 100644 --- a/src/components/advertisements/index.ts +++ b/src/components/advertisements/index.ts @@ -1,2 +1 @@ -export { BannerAdvertisement } from "./banner-advertisement"; -export { MediumRectangleAdvertisement } from "./medium-rectangle-advertisement"; +export { ReplicaAdvertisement } from "./replica-advertisement"; diff --git a/src/components/advertisements/medium-rectangle-advertisement.ts b/src/components/advertisements/medium-rectangle-advertisement.ts deleted file mode 100644 index 825e008..0000000 --- a/src/components/advertisements/medium-rectangle-advertisement.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from "../component"; - -/** - * Signature/interface for a `MediumRectangleAdvertisement` object - * @see https://developer.apple.com/documentation/apple_news/mediumrectangleadvertisement - * @extends {Component} - */ -export interface MediumRectangleAdvertisement extends Component { - role: "medium_rectangle_advertisement"; -} diff --git a/src/components/advertisements/replica-advertisement.ts b/src/components/advertisements/replica-advertisement.ts new file mode 100644 index 0000000..42d2c8d --- /dev/null +++ b/src/components/advertisements/replica-advertisement.ts @@ -0,0 +1,11 @@ +import { Component, ComponentLink } from ".."; +import { URI } from "../../primitives"; + +export interface ReplicaAdvertisement extends Component { + role: "replica_advertisement"; + URL: URI; + accessibilityCaption?: string; + additions?: ComponentLink[]; + caption?: string; + explicitContent?: boolean; +} diff --git a/src/components/any-component.ts b/src/components/any-component.ts index 5c80c75..0f9828c 100644 --- a/src/components/any-component.ts +++ b/src/components/any-component.ts @@ -1,7 +1,8 @@ import * as Components from "."; -export type AnyComponent = Components.Advertisements.BannerAdvertisement - | Components.Advertisements.MediumRectangleAdvertisement +export type AnyComponent + = Components.Addition + | Components.Advertisements.ReplicaAdvertisement | Components.ArticleStructure.ArticleLink | Components.ArticleStructure.Aside | Components.ArticleStructure.Chapter @@ -13,6 +14,7 @@ export type AnyComponent = Components.Advertisements.BannerAdvertisement | Components.AudioAndVideo.EmbedWebVideo | Components.AudioAndVideo.Music | Components.AudioAndVideo.Video + | Components.AudioAndVideo.Podcast | Components.AugmentedReality.ARKit | Components.GalleriesAndMosaics.Gallery | Components.GalleriesAndMosaics.Mosaic @@ -25,6 +27,10 @@ export type AnyComponent = Components.Advertisements.BannerAdvertisement | Components.Location.Map | Components.Location.Place | Components.SocialMedia.SocialMediaComponent + | Components.SocialMedia.FacebookPost + | Components.SocialMedia.Instagram + | Components.SocialMedia.TikTok + | Components.SocialMedia.Tweet | Components.Tables.DataTable | Components.Tables.HTMLTable | Components.Text.ArticleTitle diff --git a/src/components/article-structure/article-link.ts b/src/components/article-structure/article-link.ts index b8d4243..517897d 100644 --- a/src/components/article-structure/article-link.ts +++ b/src/components/article-structure/article-link.ts @@ -1,3 +1,4 @@ +import { CollectionDisplay, HorizontalStackDisplay } from "."; import { ContainerComponent } from "./container-component"; /** @@ -8,4 +9,6 @@ import { ContainerComponent } from "./container-component"; export interface ArticleLink extends ContainerComponent { role: "article_link"; articleIdentifier: string; + allowAutoplacedAds?: boolean; + contentDisplay?: CollectionDisplay | HorizontalStackDisplay; } diff --git a/src/components/article-structure/index.ts b/src/components/article-structure/index.ts index 74233c5..29115b8 100644 --- a/src/components/article-structure/index.ts +++ b/src/components/article-structure/index.ts @@ -7,4 +7,5 @@ export { Container } from "./container"; export { Divider } from "./divider"; export { Header } from "./header"; export { HorizontalStackDisplay } from "./horizontal-stack-display"; +export { LinkButton } from "./link-button"; export { Section } from "./section"; diff --git a/src/components/article-structure/link-button.ts b/src/components/article-structure/link-button.ts new file mode 100644 index 0000000..0566548 --- /dev/null +++ b/src/components/article-structure/link-button.ts @@ -0,0 +1,15 @@ +import { Component } from ".."; +import { URI } from "../../primitives"; +import { ComponentStyle } from "../../styles/component-styles"; +import { ComponentTextStyle } from "../../styles/text-styles"; + +/** + * @see https://developer.apple.com/documentation/apple_news/linkbutton + */ +export interface LinkButton extends Component { + role: "link_button"; + URL: URI; + accessibilityLabel?: string; + text?: string; + textStyle?: ComponentTextStyle | string; +} diff --git a/src/components/audio-and-video/audio.ts b/src/components/audio-and-video/audio.ts index 85ca1c5..4aa2fc2 100644 --- a/src/components/audio-and-video/audio.ts +++ b/src/components/audio-and-video/audio.ts @@ -8,5 +8,6 @@ import { AudioVideoComponent } from "./audio-video-component"; */ export interface Audio extends AudioVideoComponent { role: "audio"; + accessibilityCaption?: string; imageURL?: URI; } diff --git a/src/components/audio-and-video/embed-web-video.ts b/src/components/audio-and-video/embed-web-video.ts index 00b1008..baec3f3 100644 --- a/src/components/audio-and-video/embed-web-video.ts +++ b/src/components/audio-and-video/embed-web-video.ts @@ -9,5 +9,6 @@ import { AudioVideoComponent } from "./audio-video-component"; export interface EmbedWebVideo extends AudioVideoComponent { role: "embedwebvideo" | "embedvideo"; aspectRatio?: number; // Float + accessibilityCaption?: string; stillURL?: URI; } diff --git a/src/components/audio-and-video/index.ts b/src/components/audio-and-video/index.ts index b562218..e3e5c5b 100644 --- a/src/components/audio-and-video/index.ts +++ b/src/components/audio-and-video/index.ts @@ -1,4 +1,5 @@ export { Audio } from "./audio"; export { EmbedWebVideo } from "./embed-web-video"; export { Music } from "./music"; +export { Podcast } from "./podcast"; export { Video } from "./video"; diff --git a/src/components/audio-and-video/music.ts b/src/components/audio-and-video/music.ts index d513883..50f7fbd 100644 --- a/src/components/audio-and-video/music.ts +++ b/src/components/audio-and-video/music.ts @@ -8,5 +8,7 @@ import { AudioVideoComponent } from "./audio-video-component"; */ export interface Music extends AudioVideoComponent { role: "music"; + accessibilityCaption?: string; + caption?: string; imageURL: URI; } diff --git a/src/components/audio-and-video/podcast.ts b/src/components/audio-and-video/podcast.ts new file mode 100644 index 0000000..5804fed --- /dev/null +++ b/src/components/audio-and-video/podcast.ts @@ -0,0 +1,15 @@ +import { AudioVideoComponent } from "./audio-video-component"; + +export type PodcastOrientation + = "horizontal" + | "automatic"; + +export type PodcastTheme + = "light" + | "dark" + | "automatic"; + +export interface Podcast extends AudioVideoComponent { + orientation: PodcastOrientation; + theme: PodcastTheme; +} diff --git a/src/components/audio-and-video/video.ts b/src/components/audio-and-video/video.ts index b069245..25cd637 100644 --- a/src/components/audio-and-video/video.ts +++ b/src/components/audio-and-video/video.ts @@ -8,6 +8,8 @@ import { AudioVideoComponent } from "./audio-video-component"; */ export interface Video extends AudioVideoComponent { role: "video"; - aspectRatio?: number; // Float + accessibilityCaption?: string; + aspectRatio: number; // Float + caption?: string; stillURL?: URI; } diff --git a/src/components/social-media/facebook-post.ts b/src/components/social-media/facebook-post.ts new file mode 100644 index 0000000..c9f208d --- /dev/null +++ b/src/components/social-media/facebook-post.ts @@ -0,0 +1,8 @@ +import { SocialMediaComponent } from "./social-media-component"; + +/** + * @see https://developer.apple.com/documentation/apple_news/facebookpost + */ +export interface FacebookPost extends SocialMediaComponent { + role: "facebook_post"; +} diff --git a/src/components/social-media/index.ts b/src/components/social-media/index.ts index 365007a..72c26f9 100644 --- a/src/components/social-media/index.ts +++ b/src/components/social-media/index.ts @@ -1,21 +1,5 @@ -import { URI } from "../../primitives"; -import { Component } from "../component"; - -type SocialMediaRole - = "instagram" - | "facebook_post" - | "tiktok" - | "tweet"; - -/** - * Signature/interface for a `SocialMediaComponent` object - * @see https://developer.apple.com/documentation/apple_news/instagram - * @see https://developer.apple.com/documentation/apple_news/facebookpost - * @see https://developer.apple.com/documentation/apple_news/tiktok - * @see https://developer.apple.com/documentation/apple_news/tweet - * @extends {Component} - */ -export interface SocialMediaComponent extends Component { - role: SocialMediaRole; - URL: URI; -} +export { SocialMediaComponent } from "./social-media-component"; +export { FacebookPost } from "./facebook-post"; +export { Instagram } from "./instagram"; +export { TikTok } from "./tiktok"; +export { Tweet } from "./tweet"; diff --git a/src/components/social-media/instagram.ts b/src/components/social-media/instagram.ts new file mode 100644 index 0000000..9b377f3 --- /dev/null +++ b/src/components/social-media/instagram.ts @@ -0,0 +1,8 @@ +import { SocialMediaComponent } from "./social-media-component"; + +/** + * @see https://developer.apple.com/documentation/apple_news/instagram + */ +export interface Instagram extends SocialMediaComponent { + role: "instagram"; +} diff --git a/src/components/social-media/social-media-component.ts b/src/components/social-media/social-media-component.ts new file mode 100644 index 0000000..b361de8 --- /dev/null +++ b/src/components/social-media/social-media-component.ts @@ -0,0 +1,23 @@ +import { URI } from "../../primitives"; +import { Component } from "../component"; + + + +type SocialMediaRole + = "instagram" + | "facebook_post" + | "tiktok" + | "tweet"; + +/** + * Signature/interface for a `SocialMediaComponent` object + * @see https://developer.apple.com/documentation/apple_news/instagram + * @see https://developer.apple.com/documentation/apple_news/facebookpost + * @see https://developer.apple.com/documentation/apple_news/tiktok + * @see https://developer.apple.com/documentation/apple_news/tweet + * @extends {Component} + */ +export interface SocialMediaComponent extends Component { + role: SocialMediaRole; + URL: URI; +} diff --git a/src/components/social-media/tiktok.ts b/src/components/social-media/tiktok.ts new file mode 100644 index 0000000..3e64849 --- /dev/null +++ b/src/components/social-media/tiktok.ts @@ -0,0 +1,8 @@ +import { SocialMediaComponent } from "./social-media-component"; + +/** + * @see https://developer.apple.com/documentation/apple_news/tiktok + */ +export interface TikTok extends SocialMediaComponent { + role: "tiktok"; +} diff --git a/src/components/social-media/tweet.ts b/src/components/social-media/tweet.ts new file mode 100644 index 0000000..8e10dc1 --- /dev/null +++ b/src/components/social-media/tweet.ts @@ -0,0 +1,8 @@ +import { SocialMediaComponent } from "./social-media-component"; + +/** + * @see https://developer.apple.com/documentation/apple_news/tweet + */ +export interface Tweet extends SocialMediaComponent { + role: "tweet"; +} diff --git a/src/index.ts b/src/index.ts index c37594b..bdcd807 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,8 +36,7 @@ export namespace AppleNews { export type ComponentLink = Components.ComponentLink; export type Component = Components.Component; export type Scene = Components.Scene; - export type BannerAdvertisement = Components.Advertisements.BannerAdvertisement; - export type MediumRectangleAdvertisement = Components.Advertisements.MediumRectangleAdvertisement; + export type ReplicaAdvertisement = Components.Advertisements.ReplicaAdvertisement; export type ArticleLink = Components.ArticleStructure.ArticleLink; export type Aside = Components.ArticleStructure.Aside; export type Chapter = Components.ArticleStructure.Chapter; diff --git a/src/metadata/metadata.ts b/src/metadata/metadata.ts index 5b0a564..946af58 100644 --- a/src/metadata/metadata.ts +++ b/src/metadata/metadata.ts @@ -21,10 +21,10 @@ export interface Metadata { generatorIdentifier?: string; generatorName?: string; generatorVersion?: string; + issue?: Issue; keywords?: string[]; links?: LinkedArticle[]; thumbnailURL?: string; // ThumbnailURI transparentToolbar?: boolean; videoURL?: URI; - issue?: Issue; } diff --git a/src/primitives/text.ts b/src/primitives/text.ts index 4bd9b21..8b5a85c 100644 --- a/src/primitives/text.ts +++ b/src/primitives/text.ts @@ -117,3 +117,11 @@ export type VerticalAlignment export interface TextDecoration { color?: Color; } + +/** + * Signature/interface for a `textFormat` field + */ +export type TextFormat + = "html" + | "markdown" + | "none"; diff --git a/src/primitives/uri.ts b/src/primitives/uri.ts index 52a281a..2157921 100644 --- a/src/primitives/uri.ts +++ b/src/primitives/uri.ts @@ -3,12 +3,44 @@ * @see https://developer.apple.com/documentation/apple_news/apple_news_format/guidelines_for_using_images_videos_and_audio_files */ export type URI = string; + +export const SupportedFileExtensions = [ + "png", + "jpg", + "jpeg", + "gif", + "alac", + "heac", + "m3u8", + "mov", + "aac", + "mp3", + "mp4", + "ac3", +] as const; + +export type SupportedUrlFileExtension = typeof SupportedFileExtensions[number]; + /** * Lambda for validating string as ANF `URI` type. Returns undefined if validation fails. * @param {URI} s * @returns {URI|undefined} Validated URI string or undefined if invalid */ -export const URI = (s: string): URI | undefined => - !!(String(s).match(/^(https|http|bundle):\/\/.+\.(png|jpg|jpeg|gif|m3u8|mov|aac|mp3|mp4|ac3)$/)) - ? s - : void 0; +export const URI = (s: string): URI | undefined => { + const isInvalidFileType = (ext?: SupportedUrlFileExtension) => ext && !SupportedFileExtensions.includes(ext); + + try { + const deconstructedUrl = new URL(s); + let extension; + + if (deconstructedUrl.protocol === "bundle:") { + extension = deconstructedUrl.hostname.split(".")[1] as SupportedUrlFileExtension; + if (isInvalidFileType(extension)) { return; } + } else { + extension = deconstructedUrl.pathname.split(".")[1] as SupportedUrlFileExtension; + if (isInvalidFileType(extension)) { return; } + } + + return s; + } catch (typeError) { return; } +}; diff --git a/tests/documents/invalid.ts b/tests/documents/invalid.ts index ddfbd26..a1feea3 100644 --- a/tests/documents/invalid.ts +++ b/tests/documents/invalid.ts @@ -1,3 +1,4 @@ +import { AnyComponent } from "../../src/components/any-component"; import AppleNews from "../../src/index"; const document: AppleNews.ArticleDocument = { diff --git a/tests/utils/typecheck.js b/tests/utils/typecheck.js index d38eea4..4110287 100644 --- a/tests/utils/typecheck.js +++ b/tests/utils/typecheck.js @@ -17,11 +17,12 @@ function typecheck(filepath, options = DefaultOptions) { function formatDiagnostics(diagnostics) { return diagnostics.map((diagnostic) => { - const { messageText: info, file } = diagnostic; + const { messageText, file, code, category } = diagnostic; const result = { - message: info.messageText, - category: info.category, - code: info.code, + code, + file, + messageText, + category, }; if (file) { const { line, character } = file.getLineAndCharacterOfPosition( diff --git a/tsconfig.json b/tsconfig.json index 7f22d7c..1a3c501 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,7 +42,9 @@ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ + "types": [ + "node" + ], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */