├── examples ├── tsconfig.json ├── package.json ├── javascript │ ├── resolve-documents-links.js │ ├── resolve-dates.js │ └── resolve-links.js ├── typescript │ ├── resolve-dates.ts │ ├── resolve-documents-links.ts │ └── resolve-links.ts ├── typescript-graphql │ ├── resolve-dates.ts │ └── resolve-links.ts ├── javascript-graphql │ ├── resolve-dates.js │ └── resolve-links.js └── document.mock.json ├── .size-limit.cjs ├── test ├── __testutils__ │ ├── linkResolver.ts │ ├── htmlMapSerializer.ts │ └── htmlFunctionSerializer.ts ├── __fixtures__ │ ├── document.ts │ ├── richText.ts │ ├── emptyDocument.json │ ├── xssRichText.json │ └── enRichText.json ├── __setup__.ts ├── index.test.ts ├── asText.test.ts ├── asDate.test.ts ├── asImageSrc.test.ts ├── graphql-asLink.test.ts ├── asImagePixelDensitySrcSet.test.ts ├── asHTML.test.ts ├── documentToLinkField.test.ts ├── asLink.test.ts ├── asImageWidthSrcSet.test.ts └── isFilled.test.ts ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── issues.yml │ ├── issues--feature_request.md │ ├── issues--bug_report.md │ └── ci.yml ├── PULL_REQUEST_TEMPLATE.md └── prismic-oss-ecosystem.svg ├── .gitattributes ├── .editorconfig ├── src ├── graphql │ ├── index.ts │ ├── types.ts │ └── asLink.ts ├── index.ts ├── asText.ts ├── lib │ ├── escapeHTML.ts │ └── serializerHelpers.ts ├── asImageSrc.ts ├── documentToLinkField.ts ├── asDate.ts ├── asImagePixelDensitySrcSet.ts ├── asLink.ts ├── types.ts ├── asImageWidthSrcSet.ts ├── asHTML.ts └── isFilled.ts ├── .versionrc ├── vite.config.ts ├── tsconfig.json ├── .gitignore ├── .prettierrc ├── .eslintignore ├── .prettierignore ├── .eslintrc.cjs ├── package.json ├── README.md ├── LICENSE ├── CHANGELOG.md └── CONTRIBUTING.md /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.size-limit.cjs: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | 3 | module.exports = [pkg.module, pkg.main] 4 | .filter(Boolean) 5 | .map((path) => ({ path })); 6 | -------------------------------------------------------------------------------- /test/__testutils__/linkResolver.ts: -------------------------------------------------------------------------------- 1 | import { LinkResolverFunction } from "../../src"; 2 | 3 | export const linkResolver: LinkResolverFunction = (doc) => `/${doc.uid}`; 4 | -------------------------------------------------------------------------------- /test/__fixtures__/document.ts: -------------------------------------------------------------------------------- 1 | import { PrismicDocument } from "@prismicio/types"; 2 | 3 | import emptyDocumentJSON from "./emptyDocument.json"; 4 | 5 | export const documentFixture = { 6 | empty: emptyDocumentJSON as PrismicDocument, 7 | }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 👪 Prismic Community Forum 4 | url: https://community.prismic.io 5 | about: Ask a question about the package or raise an issue directly related to Prismic. You will usually get support there more quickly! 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # asserts everything is text 2 | * text eol=lf 3 | 4 | # treats lock files as binaries to prevent merge headache 5 | package-lock.json -diff 6 | yarn.lock -diff 7 | 8 | # treats assets as binaries 9 | *.png binary 10 | *.jpg binary 11 | *.jpeg binary 12 | *.gif binary 13 | *.ico binary 14 | -------------------------------------------------------------------------------- /test/__testutils__/htmlMapSerializer.ts: -------------------------------------------------------------------------------- 1 | import { HTMLMapSerializer } from "../../src"; 2 | 3 | export const htmlMapSerializer: HTMLMapSerializer = { 4 | heading1: ({ children }) => `

${children}

`, 5 | // `undefined` serializers should be treated the same as not including it. 6 | heading2: undefined, 7 | }; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export { asDate } from "../asDate"; 2 | export { asLink } from "./asLink"; 3 | export { asText } from "@prismicio/richtext"; 4 | export { asHTML } from "../asHTML"; 5 | 6 | export type { LinkResolverFunction } from "./types"; 7 | 8 | export type { HTMLFunctionSerializer, HTMLMapSerializer } from "../types"; 9 | -------------------------------------------------------------------------------- /test/__fixtures__/richText.ts: -------------------------------------------------------------------------------- 1 | import { RichTextField } from "@prismicio/types"; 2 | 3 | import enRichTextJSON from "./enRichText.json"; 4 | import xssRichTextJSON from "./xssRichText.json"; 5 | 6 | export const richTextFixture = { 7 | en: enRichTextJSON as RichTextField, 8 | xss: xssRichTextJSON as RichTextField, 9 | }; 10 | -------------------------------------------------------------------------------- /test/__setup__.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach } from "vitest"; 2 | import { createMockFactory, MockFactory } from "@prismicio/mock"; 3 | 4 | declare module "vitest" { 5 | export interface TestContext { 6 | mock: MockFactory; 7 | } 8 | } 9 | 10 | beforeEach((ctx) => { 11 | ctx.mock = createMockFactory({ seed: ctx.meta.name }); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prismicio/helpers.examples", 3 | "version": "1.0.0", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "ts": "node -r ts-eager/register" 7 | }, 8 | "dependencies": { 9 | "@prismicio/helpers": "alpha" 10 | }, 11 | "devDependencies": { 12 | "ts-eager": "^2.0.2", 13 | "typescript": "^4.3.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/javascript/resolve-documents-links.js: -------------------------------------------------------------------------------- 1 | const { asLink, documentToLinkField } = require("@prismicio/helpers"); 2 | 3 | // An hypothetic document from Prismic... 4 | const { rest: doc } = require("../document.mock.json"); 5 | 6 | const linkResolver = (doc) => `/${doc.uid}`; 7 | 8 | const docLink = asLink(documentToLinkField(doc), linkResolver); 9 | console.info({ docLink }); 10 | -------------------------------------------------------------------------------- /examples/typescript/resolve-dates.ts: -------------------------------------------------------------------------------- 1 | import { asDate } from "@prismicio/helpers"; 2 | 3 | // An hypothetic document from Prismic... 4 | import { rest as doc } from "../document.mock.json"; 5 | 6 | const date = asDate(doc.data.date); 7 | console.info({ date: date.toUTCString() }); 8 | 9 | const timestamp = asDate(doc.data.timestamp); 10 | console.info({ timestamp: timestamp.toUTCString() }); 11 | -------------------------------------------------------------------------------- /examples/javascript/resolve-dates.js: -------------------------------------------------------------------------------- 1 | const { asDate } = require("@prismicio/helpers"); 2 | 3 | // An hypothetic document from Prismic... 4 | const { rest: doc } = require("../document.mock.json"); 5 | 6 | const date = asDate(doc.data.date); 7 | console.info({ date: date.toUTCString() }); 8 | 9 | const timestamp = asDate(doc.data.timestamp); 10 | console.info({ timestamp: timestamp.toUTCString() }); 11 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "Features" 6 | }, 7 | { 8 | "type": "fix", 9 | "section": "Bug Fixes" 10 | }, 11 | { 12 | "type": "refactor", 13 | "section": "Refactor" 14 | }, 15 | { 16 | "type": "docs", 17 | "section": "Documentation" 18 | }, 19 | { 20 | "type": "chore", 21 | "section": "Chore" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/typescript-graphql/resolve-dates.ts: -------------------------------------------------------------------------------- 1 | import { asDate } from "@prismicio/helpers/dist/graphql"; 2 | 3 | // An hypothetic document from Prismic... 4 | import { graphql as doc } from "../document.mock.json"; 5 | 6 | const date = asDate(doc.date); 7 | console.info({ date: date.toUTCString() }); 8 | 9 | const timestamp = asDate(doc.timestamp); 10 | console.info({ timestamp: timestamp.toUTCString() }); 11 | -------------------------------------------------------------------------------- /examples/javascript-graphql/resolve-dates.js: -------------------------------------------------------------------------------- 1 | const { asDate } = require("@prismicio/helpers/dist/graphql"); 2 | 3 | // An hypothetic document from Prismic... 4 | const { graphql: doc } = require("../document.mock.json"); 5 | 6 | const date = asDate(doc.date); 7 | console.info({ date: date.toUTCString() }); 8 | 9 | const timestamp = asDate(doc.timestamp); 10 | console.info({ timestamp: timestamp.toUTCString() }); 11 | -------------------------------------------------------------------------------- /test/__testutils__/htmlFunctionSerializer.ts: -------------------------------------------------------------------------------- 1 | import { Element } from "@prismicio/richtext"; 2 | import { HTMLFunctionSerializer } from "../../src"; 3 | 4 | export const htmlFunctionSerializer: HTMLFunctionSerializer = ( 5 | _type, 6 | node, 7 | _text, 8 | children, 9 | ) => { 10 | switch (node.type) { 11 | case Element.heading1: { 12 | return `

${children}

`; 13 | } 14 | } 15 | 16 | return null; 17 | }; 18 | -------------------------------------------------------------------------------- /examples/typescript/resolve-documents-links.ts: -------------------------------------------------------------------------------- 1 | import { 2 | asLink, 3 | documentToLinkField, 4 | LinkResolverFunction, 5 | } from "@prismicio/helpers"; 6 | 7 | // An hypothetic document from Prismic... 8 | import { rest as doc } from "../document.mock.json"; 9 | 10 | const linkResolver: LinkResolverFunction = (doc) => `/${doc.uid}`; 11 | 12 | const docLink = asLink(documentToLinkField(doc), linkResolver); 13 | console.info({ docLink }); 14 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | import * as prismicR from "@prismicio/richtext"; 3 | 4 | import * as prismicH from "../src"; 5 | 6 | it("Element is an alias for @prismicio/richtext's Element", () => { 7 | expect(prismicH.Element).toStrictEqual(prismicR.Element); 8 | }); 9 | 10 | // TODO: Remove in v3. 11 | it("Elements is a temporary alias for Element", () => { 12 | expect(prismicH.Elements).toStrictEqual(prismicH.Element); 13 | }); 14 | -------------------------------------------------------------------------------- /test/__fixtures__/emptyDocument.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "XvoFFREAAM0WGBng", 3 | "uid": "test", 4 | "url": "/test", 5 | "type": "page", 6 | "href": "https://example.cdn.prismic.io/api/v2/documents/search", 7 | "tags": [], 8 | "first_publication_date": "2020-06-29T15:13:27+0000", 9 | "last_publication_date": "2021-05-18T15:44:01+0000", 10 | "slugs": ["slug"], 11 | "linked_documents": [], 12 | "lang": "en-us", 13 | "alternate_languages": [], 14 | "data": {} 15 | } 16 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import sdk from "vite-plugin-sdk"; 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: { 8 | index: "./src/index.ts", 9 | graphql: "./src/graphql/index.ts", 10 | }, 11 | }, 12 | }, 13 | plugins: [sdk()], 14 | test: { 15 | deps: { 16 | interopDefault: true, 17 | }, 18 | coverage: { 19 | reporter: ["lcovonly", "text"], 20 | }, 21 | setupFiles: ["./test/__setup__"], 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /examples/javascript/resolve-links.js: -------------------------------------------------------------------------------- 1 | const { asLink } = require("@prismicio/helpers"); 2 | 3 | // An hypothetic document from Prismic... 4 | const { rest: doc } = require("../document.mock.json"); 5 | 6 | const linkResolver = (doc) => `/${doc.uid}`; 7 | 8 | const relation = asLink(doc.data.relation, linkResolver); 9 | console.info({ relation }); 10 | 11 | const link = asLink(doc.data.link, linkResolver); 12 | console.info({ link }); 13 | 14 | const media = asLink(doc.data.media, linkResolver); 15 | console.info({ media }); 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "skipLibCheck": true, 5 | 6 | "target": "esnext", 7 | "module": "esnext", 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true, 13 | 14 | "forceConsistentCasingInFileNames": true, 15 | "jsx": "preserve", 16 | "lib": ["esnext", "dom"], 17 | "types": ["node"] 18 | }, 19 | "exclude": ["node_modules", "dist", "examples"] 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # custom 2 | dist 3 | examples/**/package-lock.json 4 | 5 | # os 6 | .DS_Store 7 | ._* 8 | 9 | # node 10 | logs 11 | *.log 12 | node_modules 13 | 14 | # yarn 15 | yarn-debug.log* 16 | yarn-error.log* 17 | lerna-debug.log* 18 | .yarn-integrity 19 | yarn.lock 20 | 21 | # npm 22 | npm-debug.log* 23 | 24 | # tests 25 | coverage 26 | .eslintcache 27 | .nyc_output 28 | 29 | # .env 30 | .env 31 | .env.test 32 | .env*.local 33 | 34 | # vscode 35 | .vscode/* 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | *.code-workspace 39 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-jsdoc"], 3 | "jsdocSeparateReturnsFromParam": true, 4 | "jsdocSingleLineComment": false, 5 | "tsdoc": true, 6 | "printWidth": 80, 7 | "useTabs": true, 8 | "semi": true, 9 | "singleQuote": false, 10 | "quoteProps": "as-needed", 11 | "jsxSingleQuote": false, 12 | "trailingComma": "all", 13 | "bracketSpacing": true, 14 | "bracketSameLine": false, 15 | "arrowParens": "always", 16 | "requirePragma": false, 17 | "insertPragma": false, 18 | "htmlWhitespaceSensitivity": "css", 19 | "endOfLine": "lf" 20 | } 21 | -------------------------------------------------------------------------------- /examples/typescript/resolve-links.ts: -------------------------------------------------------------------------------- 1 | import { asLink, LinkResolverFunction } from "@prismicio/helpers"; 2 | 3 | // An hypothetic document from Prismic... 4 | import { rest as doc } from "../document.mock.json"; 5 | 6 | const linkResolver: LinkResolverFunction = (doc) => `/${doc.uid}`; 7 | 8 | const relation = asLink(doc.data.relation, linkResolver); 9 | console.info({ relation }); 10 | 11 | const link = asLink(doc.data.link, linkResolver); 12 | console.info({ link }); 13 | 14 | const media = asLink(doc.data.media, linkResolver); 15 | console.info({ media }); 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # .gitignore copy 2 | 3 | # custom 4 | dist 5 | examples/**/package-lock.json 6 | 7 | # os 8 | .DS_Store 9 | ._* 10 | 11 | # node 12 | logs 13 | *.log 14 | node_modules 15 | 16 | # yarn 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | .yarn-integrity 21 | yarn.lock 22 | 23 | # npm 24 | npm-debug.log* 25 | 26 | # tests 27 | coverage 28 | .eslintcache 29 | .nyc_output 30 | 31 | # .env 32 | .env 33 | .env.test 34 | .env*.local 35 | 36 | # vscode 37 | .vscode/* 38 | !.vscode/tasks.json 39 | !.vscode/launch.json 40 | *.code-workspace 41 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # .gitignore copy 2 | 3 | # custom 4 | dist 5 | examples/**/package-lock.json 6 | 7 | # os 8 | .DS_Store 9 | ._* 10 | 11 | # node 12 | logs 13 | *.log 14 | node_modules 15 | 16 | # yarn 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | .yarn-integrity 21 | yarn.lock 22 | 23 | # npm 24 | npm-debug.log* 25 | 26 | # tests 27 | coverage 28 | .eslintcache 29 | .nyc_output 30 | 31 | # .env 32 | .env 33 | .env.test 34 | .env*.local 35 | 36 | # vscode 37 | .vscode/* 38 | !.vscode/tasks.json 39 | !.vscode/launch.json 40 | *.code-workspace 41 | -------------------------------------------------------------------------------- /test/asText.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | import * as prismicR from "@prismicio/richtext"; 3 | 4 | import { richTextFixture } from "./__fixtures__/richText"; 5 | 6 | import * as prismicH from "../src"; 7 | 8 | it("is an alias for @prismicio/richtext's `asText` function for non-nullish inputs", () => { 9 | expect(prismicH.asText(richTextFixture.en)).toBe( 10 | prismicR.asText(richTextFixture.en), 11 | ); 12 | }); 13 | 14 | it("returns null for nullish inputs", () => { 15 | expect(prismicH.asText(null)).toBeNull(); 16 | expect(prismicH.asText(undefined)).toBeNull(); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/javascript-graphql/resolve-links.js: -------------------------------------------------------------------------------- 1 | const { asLink } = require("@prismicio/helpers/dist/graphql"); 2 | 3 | // An hypothetic document from Prismic... 4 | const { graphql: doc } = require("../document.mock.json"); 5 | 6 | const linkResolver = (doc) => `/${doc._meta.uid}`; 7 | 8 | const relation = asLink(doc.relation, linkResolver); 9 | console.info({ relation }); 10 | 11 | const link = asLink(doc.link, linkResolver); 12 | console.info({ link }); 13 | 14 | const image = asLink(doc.image, linkResolver); 15 | console.info({ image }); 16 | 17 | const file = asLink(doc.file, linkResolver); 18 | console.info({ file }); 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { asDate } from "./asDate"; 2 | export { asLink } from "./asLink"; 3 | export { asText } from "./asText"; 4 | export { asHTML } from "./asHTML"; 5 | export { asImageSrc } from "./asImageSrc"; 6 | export { asImageWidthSrcSet } from "./asImageWidthSrcSet"; 7 | export { asImagePixelDensitySrcSet } from "./asImagePixelDensitySrcSet"; 8 | export * as isFilled from "./isFilled"; 9 | 10 | export { documentToLinkField } from "./documentToLinkField"; 11 | 12 | import { Element } from "@prismicio/richtext"; 13 | export { Element }; 14 | /** 15 | * @deprecated Renamed to `Element` (without an "s"). 16 | */ 17 | // TODO: Remove in v3. 18 | export const Elements = Element; 19 | 20 | export type { 21 | LinkResolverFunction, 22 | HTMLFunctionSerializer, 23 | HTMLMapSerializer, 24 | } from "./types"; 25 | -------------------------------------------------------------------------------- /test/asDate.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | 3 | import { asDate } from "../src"; 4 | 5 | it("returns null for nullish inputs", () => { 6 | expect(asDate(null)).toBeNull(); 7 | expect(asDate(undefined)).toBeNull(); 8 | }); 9 | 10 | it("returns null when date field is empty", () => { 11 | const field = null; 12 | 13 | expect(asDate(field)).toBeNull(); 14 | }); 15 | 16 | it("returns a date object from a date field", () => { 17 | const field = "2021-05-12"; 18 | 19 | expect(asDate(field)).toBeInstanceOf(Date); 20 | }); 21 | 22 | it("returns null when timestamp field is empty", () => { 23 | const field = null; 24 | 25 | expect(asDate(field)).toBeNull(); 26 | }); 27 | 28 | it("returns a date object from a timestamp field", () => { 29 | const field = "2021-05-11T22:00:00+0000"; 30 | 31 | expect(asDate(field)).toBeInstanceOf(Date); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/typescript-graphql/resolve-links.ts: -------------------------------------------------------------------------------- 1 | import { asLink, LinkResolverFunction } from "@prismicio/helpers/dist/graphql"; 2 | import { FilledMinimalLinkToDocumentField } from "@prismicio/types/dist/graphql"; 3 | 4 | // An hypothetic document from Prismic... 5 | import { graphql as doc } from "../document.mock.json"; 6 | 7 | interface MyLinkToDocumentField extends FilledMinimalLinkToDocumentField { 8 | _meta: { 9 | uid: string | null; 10 | }; 11 | } 12 | 13 | const linkResolver: LinkResolverFunction = (doc) => 14 | `/${doc._meta.uid}`; 15 | 16 | const relation = asLink(doc.relation, linkResolver); 17 | console.info({ relation }); 18 | 19 | const link = asLink(doc.link, linkResolver); 20 | console.info({ link }); 21 | 22 | const image = asLink(doc.image, linkResolver); 23 | console.info({ image }); 24 | 25 | const file = asLink(doc.file, linkResolver); 26 | console.info({ file }); 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🙋‍♀️ Feature request 3 | about: Suggest an idea or enhancement for the package. 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | 10 | 11 | ### Is your feature request related to a problem? Please describe. 12 | 13 | 14 | 15 | ### Describe the solution you'd like 16 | 17 | 18 | 19 | ### Describe alternatives you've considered 20 | 21 | 22 | 23 | ### Additional context 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/graphql/types.ts: -------------------------------------------------------------------------------- 1 | // TODO: Migrate once fixed https://github.com/microsoft/TypeScript/issues/33079 2 | import type { FilledMinimalLinkToDocumentField } from "@prismicio/types/dist/graphql"; 3 | 4 | /** 5 | * Resolves a link to a Prismic document to a URL 6 | * 7 | * @typeParam LinkToDocumentField - An extended version of the minimal link to 8 | * document field 9 | * @typeParam ReturnType - Return type of your link resolver function, useful if 10 | * you prefer to return a complex object 11 | * @param linkToDocumentField - A document link to resolve 12 | * 13 | * @returns Resolved URL 14 | * @experimental 15 | * @see Prismic link resolver documentation: {@link https://prismic.io/docs/technologies/link-resolver-javascript} 16 | */ 17 | export type LinkResolverFunction< 18 | LinkToDocumentField extends FilledMinimalLinkToDocumentField = FilledMinimalLinkToDocumentField, 19 | ReturnType = string, 20 | > = (linkToDocumentField: LinkToDocumentField) => ReturnType; 21 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: issues 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | issues: 10 | if: github.event.issue.author_association == 'NONE' 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@master 16 | 17 | - name: Reply bug report 18 | if: contains(github.event.issue.labels.*.name, 'bug') 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | ISSUE_URL: ${{ github.event.issue.html_url }} 22 | run: gh issue comment $ISSUE_URL --body-file ./.github/workflows/issues--bug_report.md 23 | 24 | - name: Reply feature request 25 | if: contains(github.event.issue.labels.*.name, 'enhancement') 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | ISSUE_URL: ${{ github.event.issue.html_url }} 29 | run: gh issue comment $ISSUE_URL --body-file ./.github/workflows/issues--feature_request.md 30 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | }, 7 | parserOptions: { 8 | parser: "@typescript-eslint/parser", 9 | ecmaVersion: 2020, 10 | }, 11 | extends: [ 12 | "plugin:@typescript-eslint/eslint-recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | "plugin:prettier/recommended", 15 | ], 16 | plugins: ["eslint-plugin-tsdoc"], 17 | rules: { 18 | "no-console": ["warn", { allow: ["info", "warn", "error"] }], 19 | "no-debugger": "warn", 20 | "no-undef": "off", 21 | curly: "error", 22 | "prefer-const": "error", 23 | "padding-line-between-statements": [ 24 | "error", 25 | { blankLine: "always", prev: "*", next: "return" }, 26 | ], 27 | "@typescript-eslint/no-unused-vars": [ 28 | "error", 29 | { 30 | argsIgnorePattern: "^_", 31 | varsIgnorePattern: "^_", 32 | }, 33 | ], 34 | "@typescript-eslint/no-var-requires": "off", 35 | "@typescript-eslint/explicit-module-boundary-types": "error", 36 | "tsdoc/syntax": "warn", 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚨 Bug report 3 | about: Report a bug report to help improve the package. 4 | title: "" 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | 16 | 17 | ### Versions 18 | 19 | - @prismicio/helpers: 20 | - node: 21 | 22 | ### Reproduction 23 | 24 | 25 | 26 |
27 | Additional Details 28 |
29 | 30 |
31 | 32 | ### Steps to reproduce 33 | 34 | ### What is expected? 35 | 36 | ### What is actually happening? 37 | -------------------------------------------------------------------------------- /src/asText.ts: -------------------------------------------------------------------------------- 1 | import { asText as baseAsText } from "@prismicio/richtext"; 2 | import { RichTextField } from "@prismicio/types"; 3 | 4 | /** 5 | * The return type of `asText()`. 6 | */ 7 | type AsTextReturnType = 8 | Field extends RichTextField ? string : null; 9 | 10 | /** 11 | * Serializes a rich text or title field to a plain text string 12 | * 13 | * @param richTextField - A rich text or title field from Prismic 14 | * @param separator - Separator used to join each element, defaults to a space 15 | * 16 | * @returns Plain text equivalent of the provided rich text or title field 17 | * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript} 18 | */ 19 | export const asText = ( 20 | richTextField: Field, 21 | separator?: string, 22 | ): AsTextReturnType => { 23 | if (richTextField) { 24 | return baseAsText(richTextField, separator) as AsTextReturnType; 25 | } else { 26 | return null as AsTextReturnType; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/lib/escapeHTML.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ** Core logic from https://github.com/component/escape-html 3 | ** Many thanks to @component 4 | */ 5 | 6 | const matchHtmlRegExp = /["'&<>]/; 7 | 8 | export const escapeHTML = (string?: string | null): string => { 9 | const str = "" + string; 10 | const match = matchHtmlRegExp.exec(str); 11 | 12 | if (!match) { 13 | return str; 14 | } 15 | 16 | let escape; 17 | let html = ""; 18 | let index = 0; 19 | let lastIndex = 0; 20 | 21 | for (index = match.index; index < str.length; index++) { 22 | switch (str.charCodeAt(index)) { 23 | case 34: // " 24 | escape = """; 25 | break; 26 | case 38: // & 27 | escape = "&"; 28 | break; 29 | case 39: // ' 30 | escape = "'"; 31 | break; 32 | case 60: // < 33 | escape = "<"; 34 | break; 35 | case 62: // > 36 | escape = ">"; 37 | break; 38 | default: 39 | continue; 40 | } 41 | 42 | if (lastIndex !== index) { 43 | html += str.substring(lastIndex, index); 44 | } 45 | 46 | lastIndex = index + 1; 47 | html += escape; 48 | } 49 | 50 | return lastIndex !== index ? html + str.substring(lastIndex, index) : html; 51 | }; 52 | -------------------------------------------------------------------------------- /.github/workflows/issues--feature_request.md: -------------------------------------------------------------------------------- 1 | > This issue has been labeled as a **feature request** since it was created using the [🙋‍♀️ **Feature Request Template**](./new?assignees=&labels=enhancement&template=feature_request.md&title=). 2 | 3 | Hi there, thank you so much for your request! 4 | 5 | Following our [Maintenance Process](../blob/HEAD/CONTRIBUTING.md#maintaining), we will review your request and get back to you soon. If we decide to implement it, will proceed to implement the feature during the _last week of the month_. In the meantime, feel free to provide any details to help us better understand your request, such as: 6 | 7 | - The context that made you think of this feature request 8 | - As many details about the solution you'd like to see implemented, how it should behave, etc. 9 | - Any alternative solution you have considered 10 | 11 | If you think you can implement the proposed change yourself, you're more than welcome to [open a pull request](../pulls) implementing the new feature. Check out our [quick start guide](../blob/HEAD/CONTRIBUTING.md#quick-start) for a simple contribution process. Please note that submitting a pull request does not guarantee the feature will be merged. 12 | 13 | _- The Prismic Open-Source Team_ 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Types of changes 4 | 5 | 6 | 7 | - [ ] Chore (a non-breaking change which is related to package maintenance) 8 | - [ ] Bug fix (a non-breaking change which fixes an issue) 9 | - [ ] New feature (a non-breaking change which adds functionality) 10 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 11 | 12 | ## Description 13 | 14 | 15 | 16 | 17 | 18 | ## Checklist: 19 | 20 | 21 | 22 | 23 | - [ ] My change requires an update to the official documentation. 24 | - [ ] All [TSDoc](https://tsdoc.org) comments are up-to-date and new ones have been added where necessary. 25 | - [ ] All new and existing tests are passing. 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/asImageSrc.test.ts: -------------------------------------------------------------------------------- 1 | import { ImageField } from "@prismicio/types"; 2 | import { it, expect } from "vitest"; 3 | 4 | import { asImageSrc } from "../src"; 5 | 6 | it("returns null for nullish inputs", () => { 7 | expect(asImageSrc(null)).toBeNull(); 8 | expect(asImageSrc(undefined)).toBeNull(); 9 | }); 10 | 11 | it("returns an image field URL", () => { 12 | const field: ImageField = { 13 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 14 | alt: null, 15 | copyright: null, 16 | dimensions: { width: 400, height: 300 }, 17 | }; 18 | 19 | expect(asImageSrc(field)).toBe(field.url); 20 | }); 21 | 22 | it("applies given Imgix URL parameters", () => { 23 | const field: ImageField = { 24 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 25 | alt: null, 26 | copyright: null, 27 | dimensions: { width: 400, height: 300 }, 28 | }; 29 | 30 | expect(asImageSrc(field, { sat: 100 })).toBe(`${field.url}&sat=100`); 31 | expect(asImageSrc(field, { w: 100 })).toBe(`${field.url}&w=100`); 32 | expect(asImageSrc(field, { width: 100 })).toBe(`${field.url}&width=100`); 33 | }); 34 | 35 | it("returns null when image field is empty", () => { 36 | const field: ImageField = {}; 37 | 38 | expect(asImageSrc(field)).toBeNull(); 39 | }); 40 | -------------------------------------------------------------------------------- /.github/workflows/issues--bug_report.md: -------------------------------------------------------------------------------- 1 | > This issue has been labeled as a **bug** since it was created using the [🚨 **Bug Report Template**](./new?assignees=&labels=bug&template=bug_report.md&title=). 2 | 3 | Hi there, thank you so much for the report! 4 | 5 | Following our [Maintenance Process](../blob/HEAD/CONTRIBUTING.md#maintaining), we will review your bug report and get back to you _next Wednesday_. To ensure a smooth review of your issue and avoid unnecessary delays, please make sure your issue includes the following: 6 | 7 | - Information about your environment and packages you use (Node.js version, package names and their versions, etc.) 8 | _Feel free to attach a copy of your `package.json` file._ 9 | - Any troubleshooting steps you already went through 10 | - A minimal reproduction of the issue, and/or instructions on how to reproduce it 11 | 12 | If you have identified the cause of the bug described in your report and know how to fix it, you're more than welcome to [open a pull request](../pulls) addressing it. Check out our [quick start guide](../blob/HEAD/CONTRIBUTING.md#quick-start) for a simple contribution process. 13 | 14 | If you think your issue is a _question_ (not a bug) and would like quicker support, please _close this issue_ and forward it to an appropriate section on our community forum: https://community.prismic.io 15 | 16 | _- The Prismic Open-Source Team_ 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | node: [14] 19 | 20 | steps: 21 | - name: Set up Node 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node }} 25 | 26 | - name: Checkout 27 | uses: actions/checkout@master 28 | 29 | - name: Cache node_modules 30 | uses: actions/cache@v2 31 | with: 32 | path: node_modules 33 | key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/package-lock.json')) }} 34 | 35 | - name: Install dependencies 36 | if: steps.cache.outputs.cache-hit != 'true' 37 | run: npm ci 38 | 39 | - name: Lint 40 | run: npm run lint 41 | 42 | - name: Unit 43 | run: npm run unit 44 | 45 | - name: Build 46 | run: npm run build 47 | 48 | - name: Coverage 49 | if: matrix.os == 'ubuntu-latest' && matrix.node == 14 50 | uses: codecov/codecov-action@v1 51 | 52 | - name: Size 53 | if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == 14 54 | uses: andresz1/size-limit-action@v1 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | -------------------------------------------------------------------------------- /src/asImageSrc.ts: -------------------------------------------------------------------------------- 1 | import { ImageFieldImage } from "@prismicio/types"; 2 | import { buildURL, ImgixURLParams } from "imgix-url-builder"; 3 | 4 | import { imageThumbnail as isImageThumbnailFilled } from "./isFilled"; 5 | 6 | /** 7 | * The return type of `asImageSrc()`. 8 | */ 9 | type AsImageSrcReturnType = 10 | Field extends ImageFieldImage<"filled"> ? string : null; 11 | 12 | /** 13 | * Returns the URL of an Image field with optional image transformations (via 14 | * Imgix URL parameters). 15 | * 16 | * @example 17 | * 18 | * ```ts 19 | * const src = asImageSrc(document.data.imageField, { sat: -100 }); 20 | * // => https://images.prismic.io/repo/image.png?sat=-100 21 | * ``` 22 | * 23 | * @param field - Image field (or one of its responsive views) from which to get 24 | * an image URL. 25 | * @param params - An object of Imgix URL API parameters to transform the image. 26 | * 27 | * @returns The Image field's image URL with transformations applied (if given). 28 | * If the Image field is empty, `null` is returned. 29 | * @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering 30 | */ 31 | export const asImageSrc = ( 32 | field: Field, 33 | params: ImgixURLParams = {}, 34 | ): AsImageSrcReturnType => { 35 | if (field && isImageThumbnailFilled(field)) { 36 | return buildURL(field.url, params) as AsImageSrcReturnType; 37 | } else { 38 | return null as AsImageSrcReturnType; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /test/__fixtures__/xssRichText.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "paragraph", 4 | "text": "This is a link with XSS (\")", 5 | "spans": [ 6 | { 7 | "start": 10, 8 | "end": 14, 9 | "type": "hyperlink", 10 | "data": { 11 | "link_type": "Web", 12 | "url": "https://example.org\" onmouseover=\"alert(document.cookie);" 13 | } 14 | } 15 | ] 16 | }, 17 | { 18 | "type": "paragraph", 19 | "text": "This is a link with XSS (')", 20 | "spans": [ 21 | { 22 | "start": 10, 23 | "end": 14, 24 | "type": "hyperlink", 25 | "data": { 26 | "link_type": "Web", 27 | "url": "https://example.org' onmouseover='alert(document.cookie);" 28 | } 29 | } 30 | ] 31 | }, 32 | { 33 | "type": "paragraph", 34 | "text": "This is a link with XSS (&, <, >)", 35 | "spans": [ 36 | { 37 | "start": 10, 38 | "end": 14, 39 | "type": "hyperlink", 40 | "data": { 41 | "link_type": "Web", 42 | "url": "https://example.org&<>" 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | "type": "paragraph", 49 | "text": "This is a normal link.", 50 | "spans": [ 51 | { 52 | "start": 17, 53 | "end": 21, 54 | "type": "hyperlink", 55 | "data": { 56 | "link_type": "Web", 57 | "url": "https://prismic.io" 58 | } 59 | } 60 | ] 61 | }, 62 | { 63 | "type": "image", 64 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 65 | "alt": "An \"Atlantic\" Puffin", 66 | "copyright": "\"unsplash\"", 67 | "dimensions": { 68 | "width": 2400, 69 | "height": 1602 70 | } 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /src/documentToLinkField.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FilledLinkToDocumentField, 3 | LinkType, 4 | PrismicDocument, 5 | } from "@prismicio/types"; 6 | 7 | type SetOptional = Omit & 8 | Partial>; 9 | 10 | /** 11 | * Converts a document into a link field, this is useful when crawling the API 12 | * for document links 13 | * 14 | * @typeParam TDocument - Specific interface of the provided document 15 | * @param prismicDocument - A document coming from Prismic 16 | * 17 | * @returns The equivalent link field to use with `asLink()` 18 | * @internal 19 | */ 20 | export const documentToLinkField = < 21 | TDocument extends SetOptional, 22 | >( 23 | prismicDocument: TDocument, 24 | ): FilledLinkToDocumentField< 25 | TDocument["type"], 26 | TDocument["lang"], 27 | TDocument["data"] 28 | > => { 29 | return { 30 | link_type: LinkType.Document, 31 | id: prismicDocument.id, 32 | uid: prismicDocument.uid ?? undefined, 33 | type: prismicDocument.type, 34 | tags: prismicDocument.tags, 35 | lang: prismicDocument.lang, 36 | url: prismicDocument.url ?? undefined, 37 | slug: prismicDocument.slugs?.[0], // Slug field is not available with GraphQl 38 | // The REST API does not include a `data` property if the data 39 | // object is empty. 40 | // 41 | // A presence check for `prismicDocument.data` is done to 42 | // support partial documents. While `documentToLinkField` is 43 | // not typed to accept partial documents, passing a partial 44 | // document can happen in untyped projects. 45 | ...(prismicDocument.data && Object.keys(prismicDocument.data).length > 0 46 | ? { data: prismicDocument.data } 47 | : {}), 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /examples/document.mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "rest": { 3 | "id": "XvoFFREAAM0WGBng", 4 | "uid": "test", 5 | "type": "page", 6 | "url": null, 7 | "href": "https://example.cdn.prismic.io/api/v2/documents/search", 8 | "tags": [], 9 | "first_publication_date": "2020-06-29T15:13:27+0000", 10 | "last_publication_date": "2021-05-18T15:44:01+0000", 11 | "slugs": ["slug"], 12 | "linked_documents": [], 13 | "lang": "en-us", 14 | "alternate_languages": [], 15 | "data": { 16 | "date": "2021-05-12", 17 | "timestamp": "2021-05-11T22:00:00+0000", 18 | "relation": { 19 | "id": "XvoFFREAAM0WGBng", 20 | "type": "page", 21 | "tags": [], 22 | "slug": "slug", 23 | "lang": "en-us", 24 | "uid": "test", 25 | "link_type": "Document", 26 | "isBroken": false 27 | }, 28 | "link": { 29 | "link_type": "Web", 30 | "url": "https://prismic.io" 31 | }, 32 | "media": { 33 | "link_type": "Media", 34 | "name": "test.jpg", 35 | "kind": "image", 36 | "url": "https://prismic.io", 37 | "size": "420", 38 | "height": "42", 39 | "width": "42" 40 | } 41 | } 42 | }, 43 | "graphql": { 44 | "_meta": { 45 | "id": "XvoFFREAAM0WGBng", 46 | "uid": "test", 47 | "type": "page", 48 | "tags": [], 49 | "lang": "en-us" 50 | }, 51 | "date": "2021-05-12", 52 | "timestamp": "2021-05-11T22:00:00+0000", 53 | "relation": { 54 | "_linkType": "Link.document", 55 | "_meta": { 56 | "uid": "test" 57 | } 58 | }, 59 | "link": { 60 | "_linkType": "Link.web", 61 | "url": "https://prismic.io" 62 | }, 63 | "image": { 64 | "_linkType": "Link.image", 65 | "url": "https://prismic.io" 66 | }, 67 | "file": { 68 | "_linkType": "Link.file", 69 | "url": "https://prismic.io" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/graphql/asLink.ts: -------------------------------------------------------------------------------- 1 | // TODO: Migrate once fixed https://github.com/microsoft/TypeScript/issues/33079 2 | import { 3 | FilledMinimalLinkToDocumentField, 4 | LinkField, 5 | LinkType, 6 | } from "@prismicio/types/dist/graphql"; 7 | import { LinkResolverFunction } from "./types"; 8 | 9 | /** 10 | * Resolves any type of link field to a URL 11 | * 12 | * @typeParam LinkResolverLinkToDocumentField - Link resolver link to document 13 | * field type 14 | * @typeParam LinkResolverFunctionReturnType - Link resolver function return 15 | * type 16 | * @param linkField - Any kind of link field to resolve 17 | * @param linkResolver - A link resolver function, without it you're expected to 18 | * use the `routes` from the API 19 | * 20 | * @returns Resolved URL, null if provided link is empty 21 | * @experimental 22 | * @see Prismic link resolver documentation: {@link https://prismic.io/docs/technologies/link-resolver-javascript} 23 | * @see Prismic API `routes` options documentation: {@link https://prismic.io/docs/technologies/route-resolver-nuxtjs} 24 | */ 25 | export const asLink = < 26 | LinkResolverLinkToDocumentField extends FilledMinimalLinkToDocumentField = FilledMinimalLinkToDocumentField, 27 | LinkResolverFunctionReturnType = string, 28 | >( 29 | linkField: LinkField, 30 | linkResolver?: LinkResolverFunction< 31 | LinkResolverLinkToDocumentField, 32 | LinkResolverFunctionReturnType 33 | > | null, 34 | ): 35 | | ReturnType< 36 | LinkResolverFunction< 37 | LinkResolverLinkToDocumentField, 38 | LinkResolverFunctionReturnType 39 | > 40 | > 41 | | string 42 | | null => { 43 | if (!linkField) { 44 | return null; 45 | } 46 | 47 | if ("url" in linkField) { 48 | return linkField.url; 49 | } else if (linkField._linkType === LinkType.Document) { 50 | return linkResolver ? linkResolver(linkField) : null; 51 | } else { 52 | return null; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/asDate.ts: -------------------------------------------------------------------------------- 1 | import type { DateField, TimestampField } from "@prismicio/types"; 2 | 3 | /** 4 | * The return type of `asDate()`. 5 | */ 6 | type AsDateReturnType< 7 | Field extends DateField | TimestampField | null | undefined, 8 | > = Field extends DateField<"filled"> | TimestampField<"filled"> ? Date : null; 9 | 10 | /** 11 | * Transforms a date or timestamp field into a JavaScript Date object 12 | * 13 | * @param dateOrTimestampField - A date or timestamp field from Prismic 14 | * 15 | * @returns A Date object, null if provided date is falsy 16 | * @see Templating date field from Prismic {@link https://prismic.io/docs/technologies/templating-date-field-javascript} 17 | */ 18 | export const asDate = < 19 | Field extends DateField | TimestampField | null | undefined, 20 | >( 21 | dateOrTimestampField: Field, 22 | ): AsDateReturnType => { 23 | if (!dateOrTimestampField) { 24 | return null as AsDateReturnType; 25 | } 26 | 27 | // If field is a timestamp field... 28 | if (dateOrTimestampField.length === 24) { 29 | /** 30 | * Converts basic ISO 8601 to ECMAScript simplified ISO 8601 format for 31 | * browser compatibility issues 32 | * 33 | * From: YYYY-MM-DDTHH:mm:ssZ To: YYYY-MM-DDTHH:mm:ss.sssZ 34 | * 35 | * @see MDN documentation: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#timestamp_string} 36 | * @see ECMAScript 2020 language specification: {@link https://262.ecma-international.org/11.0/#sec-date-time-string-format} 37 | * @see Related forum issue: {@link https://community.prismic.io/t/prismics-date-api/2520} 38 | * @see Regex101 expression: {@link https://regex101.com/r/jxyETT/1} 39 | */ 40 | return new Date( 41 | dateOrTimestampField.replace(/(\+|-)(\d{2})(\d{2})$/, ".000$1$2:$3"), 42 | ) as AsDateReturnType; 43 | } else { 44 | // ...else field is a date field 45 | return new Date(dateOrTimestampField) as AsDateReturnType; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /test/graphql-asLink.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | 3 | import type { FilledMinimalLinkToDocumentField } from "@prismicio/types/dist/graphql"; 4 | 5 | import { asLink, LinkResolverFunction } from "../src/graphql"; 6 | 7 | interface MyLinkToDocumentField extends FilledMinimalLinkToDocumentField { 8 | _meta: { 9 | uid: string | null; 10 | }; 11 | } 12 | 13 | const linkResolver: LinkResolverFunction = (doc) => 14 | `/${doc._meta.uid}`; 15 | 16 | it("returns null when link field is falsy", () => { 17 | const field = undefined; 18 | 19 | // @ts-expect-error testing JavaScript failsafe on purpose 20 | expect(asLink(field, linkResolver)).toBeNull(); 21 | }); 22 | 23 | it("returns null when link field is empty", () => { 24 | const field = { 25 | _linkType: "Link.any", 26 | } as const; 27 | 28 | // @ts-expect-error testing JavaScript failsafe on purpose 29 | expect(asLink(field, linkResolver)).toBeNull(); 30 | }); 31 | 32 | it("resolves a link to file field", () => { 33 | const field = { 34 | _linkType: "Link.file", 35 | url: "https://prismic.io", 36 | } as const; 37 | 38 | expect(asLink(field, linkResolver)).toBe("https://prismic.io"); 39 | }); 40 | 41 | it("resolves a link to image field", () => { 42 | const field = { 43 | _linkType: "Link.image", 44 | url: "https://prismic.io", 45 | } as const; 46 | 47 | expect(asLink(field, linkResolver)).toBe("https://prismic.io"); 48 | }); 49 | 50 | it("resolves a link to web field", () => { 51 | const field = { 52 | _linkType: "Link.web", 53 | url: "https://prismic.io", 54 | } as const; 55 | 56 | expect(asLink(field, linkResolver)).toBe("https://prismic.io"); 57 | }); 58 | 59 | it("resolves a link to document field", () => { 60 | const field = { 61 | _linkType: "Link.document", 62 | _meta: { 63 | uid: "test", 64 | }, 65 | } as const; 66 | 67 | expect(asLink(field, linkResolver), "/test"); 68 | }); 69 | 70 | it("returns null when given a document field and linkResolver is not provided ", () => { 71 | const field = { 72 | _linkType: "Link.document", 73 | _meta: { 74 | uid: "test", 75 | }, 76 | } as const; 77 | 78 | expect(asLink(field)).toBeNull(); 79 | }); 80 | -------------------------------------------------------------------------------- /test/asImagePixelDensitySrcSet.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | 3 | import { ImageField } from "@prismicio/types"; 4 | 5 | import { asImagePixelDensitySrcSet } from "../src"; 6 | 7 | it("returns null for nullish inputs", () => { 8 | expect(asImagePixelDensitySrcSet(null)).toBeNull(); 9 | expect(asImagePixelDensitySrcSet(undefined)).toBeNull(); 10 | }); 11 | 12 | it("returns an image field pixel-density-based srcset with [1, 2, 3] pxiel densities by default", () => { 13 | const field: ImageField = { 14 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 15 | alt: null, 16 | copyright: null, 17 | dimensions: { width: 400, height: 300 }, 18 | }; 19 | 20 | expect(asImagePixelDensitySrcSet(field)).toStrictEqual({ 21 | src: field.url, 22 | srcset: 23 | `${field.url}&dpr=1 1x, ` + 24 | `${field.url}&dpr=2 2x, ` + 25 | `${field.url}&dpr=3 3x`, 26 | }); 27 | }); 28 | 29 | it("supports custom pixel densities", () => { 30 | const field: ImageField = { 31 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 32 | alt: null, 33 | copyright: null, 34 | dimensions: { width: 400, height: 300 }, 35 | }; 36 | 37 | expect( 38 | asImagePixelDensitySrcSet(field, { 39 | pixelDensities: [2, 4, 6], 40 | }), 41 | ).toStrictEqual({ 42 | src: field.url, 43 | srcset: 44 | `${field.url}&dpr=2 2x, ` + 45 | `${field.url}&dpr=4 4x, ` + 46 | `${field.url}&dpr=6 6x`, 47 | }); 48 | }); 49 | 50 | it("applies given Imgix URL parameters", () => { 51 | const field: ImageField = { 52 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 53 | alt: null, 54 | copyright: null, 55 | dimensions: { width: 400, height: 300 }, 56 | }; 57 | 58 | expect( 59 | asImagePixelDensitySrcSet(field, { 60 | sat: 100, 61 | }), 62 | ).toStrictEqual({ 63 | src: `${field.url}&sat=100`, 64 | srcset: 65 | `${field.url}&sat=100&dpr=1 1x, ` + 66 | `${field.url}&sat=100&dpr=2 2x, ` + 67 | `${field.url}&sat=100&dpr=3 3x`, 68 | }); 69 | }); 70 | 71 | it("returns null when image field is empty", () => { 72 | const field: ImageField = {}; 73 | 74 | expect(asImagePixelDensitySrcSet(field)).toBeNull(); 75 | }); 76 | -------------------------------------------------------------------------------- /test/asHTML.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | import { RichTextField } from "@prismicio/types"; 3 | 4 | import { richTextFixture } from "./__fixtures__/richText"; 5 | import { linkResolver } from "./__testutils__/linkResolver"; 6 | import { htmlFunctionSerializer } from "./__testutils__/htmlFunctionSerializer"; 7 | import { htmlMapSerializer } from "./__testutils__/htmlMapSerializer"; 8 | 9 | import { asHTML } from "../src"; 10 | 11 | it("serializes with default serializer", () => { 12 | expect(asHTML(richTextFixture.en, linkResolver)).toMatchSnapshot(); 13 | }); 14 | 15 | it("serializes with a custom function serializer", () => { 16 | expect( 17 | asHTML(richTextFixture.en, linkResolver, htmlFunctionSerializer), 18 | ).toMatchSnapshot(); 19 | }); 20 | 21 | it("serializes with a custom map serializer", () => { 22 | expect( 23 | asHTML(richTextFixture.en, linkResolver, htmlMapSerializer), 24 | ).toMatchSnapshot(); 25 | }); 26 | 27 | it("escapes external links to prevent XSS", () => { 28 | expect(asHTML(richTextFixture.xss, linkResolver)).toMatchSnapshot(); 29 | }); 30 | 31 | it("omits target attribute on links without a target value", () => { 32 | const field: RichTextField = [ 33 | { 34 | type: "paragraph", 35 | text: "link", 36 | spans: [ 37 | { 38 | type: "hyperlink", 39 | start: 0, 40 | end: 4, 41 | data: { 42 | link_type: "Web", 43 | url: "https://example.org", 44 | }, 45 | }, 46 | ], 47 | }, 48 | ]; 49 | 50 | expect(asHTML(field, linkResolver)).toMatchInlineSnapshot( 51 | '"

link

"', 52 | ); 53 | }); 54 | 55 | it("includes target attribute on links with a target value", () => { 56 | const field: RichTextField = [ 57 | { 58 | type: "paragraph", 59 | text: "link", 60 | spans: [ 61 | { 62 | type: "hyperlink", 63 | start: 0, 64 | end: 4, 65 | data: { 66 | link_type: "Web", 67 | url: "https://example.org", 68 | target: "_blank", 69 | }, 70 | }, 71 | ], 72 | }, 73 | ]; 74 | 75 | expect(asHTML(field, linkResolver)).toMatchInlineSnapshot( 76 | '"

link

"', 77 | ); 78 | }); 79 | 80 | it("returns null for nullish inputs", () => { 81 | expect(asHTML(null)).toBeNull(); 82 | expect(asHTML(undefined)).toBeNull(); 83 | }); 84 | -------------------------------------------------------------------------------- /test/documentToLinkField.test.ts: -------------------------------------------------------------------------------- 1 | import { LinkType } from "@prismicio/types"; 2 | import { it, expect } from "vitest"; 3 | 4 | import { documentFixture } from "./__fixtures__/document"; 5 | 6 | import { documentToLinkField } from "../src"; 7 | 8 | it("returns equivalent link field from given document", () => { 9 | const document = { ...documentFixture.empty, url: null }; 10 | 11 | expect(documentToLinkField(document)).toStrictEqual({ 12 | link_type: LinkType.Document, 13 | id: "XvoFFREAAM0WGBng", 14 | uid: "test", 15 | type: "page", 16 | tags: [], 17 | lang: "en-us", 18 | url: undefined, 19 | slug: "slug", 20 | }); 21 | }); 22 | 23 | it("returns equivalent link field from given document with `apiOptions.routes`", () => { 24 | const document = { ...documentFixture.empty }; 25 | 26 | expect(documentToLinkField(document)).toStrictEqual({ 27 | link_type: LinkType.Document, 28 | id: "XvoFFREAAM0WGBng", 29 | uid: "test", 30 | type: "page", 31 | tags: [], 32 | lang: "en-us", 33 | url: "/test", 34 | slug: "slug", 35 | }); 36 | }); 37 | 38 | it("returns equivalent link field from given document without uid", () => { 39 | const document = { ...documentFixture.empty, uid: null }; 40 | 41 | expect(documentToLinkField(document)).toStrictEqual({ 42 | link_type: LinkType.Document, 43 | id: "XvoFFREAAM0WGBng", 44 | uid: undefined, 45 | type: "page", 46 | tags: [], 47 | lang: "en-us", 48 | url: "/test", 49 | slug: "slug", 50 | }); 51 | }); 52 | 53 | it("returns equivalent link field from given document with non-empty data", () => { 54 | const document = { ...documentFixture.empty, data: { foo: "bar" } }; 55 | 56 | expect(documentToLinkField(document)).toStrictEqual({ 57 | link_type: LinkType.Document, 58 | id: "XvoFFREAAM0WGBng", 59 | uid: "test", 60 | type: "page", 61 | tags: [], 62 | lang: "en-us", 63 | url: "/test", 64 | slug: "slug", 65 | data: { foo: "bar" }, 66 | }); 67 | }); 68 | 69 | // This test checks support for Gatsby users. The `slugs` field is not 70 | // queriable in Gatsby since it is deprecated. 71 | // Deprecation info: https://community.prismic.io/t/what-are-slugs/6493 72 | it("supports documents without slugs field", () => { 73 | const document = { 74 | ...documentFixture.empty, 75 | url: null, 76 | slugs: undefined, 77 | }; 78 | 79 | expect(documentToLinkField(document)).toStrictEqual({ 80 | link_type: LinkType.Document, 81 | id: "XvoFFREAAM0WGBng", 82 | uid: "test", 83 | type: "page", 84 | tags: [], 85 | lang: "en-us", 86 | url: undefined, 87 | slug: undefined, 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prismicio/helpers", 3 | "version": "2.3.9", 4 | "description": "Set of helpers to manage Prismic data", 5 | "keywords": [ 6 | "typescript", 7 | "helpers", 8 | "utils", 9 | "toolbox", 10 | "prismic" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "ssh://git@github.com/prismicio/prismic-helpers.git" 15 | }, 16 | "license": "Apache-2.0", 17 | "author": "Prismic (https://prismic.io)", 18 | "sideEffects": false, 19 | "type": "module", 20 | "exports": { 21 | ".": { 22 | "require": "./dist/index.cjs", 23 | "import": "./dist/index.js" 24 | }, 25 | "./graphql": { 26 | "require": "./dist/graphql.cjs", 27 | "import": "./dist/graphql.js" 28 | }, 29 | "./dist/graphql": { 30 | "require": "./dist/graphql.cjs", 31 | "import": "./dist/graphql.js" 32 | }, 33 | "./package.json": "./package.json" 34 | }, 35 | "main": "dist/index.cjs", 36 | "module": "dist/index.js", 37 | "react-native": "dist/index.js", 38 | "types": "dist/index.d.ts", 39 | "files": [ 40 | "dist", 41 | "src" 42 | ], 43 | "scripts": { 44 | "build": "vite build", 45 | "dev": "vite build --watch", 46 | "format": "prettier --write .", 47 | "lint": "eslint --ext .js,.ts .", 48 | "prepare": "npm run build", 49 | "release": "npm run test && standard-version && git push --follow-tags && npm run build && npm publish", 50 | "release:beta": "npm run test && standard-version --release-as major --prerelease beta && git push --follow-tags && npm run build && npm publish --tag beta", 51 | "release:beta:dry": "standard-version --release-as major --prerelease beta --dry-run", 52 | "release:dry": "standard-version --dry-run", 53 | "size": "size-limit", 54 | "test": "npm run lint && npm run unit && npm run build && npm run size", 55 | "unit": "vitest run --coverage", 56 | "unit:watch": "vitest watch" 57 | }, 58 | "dependencies": { 59 | "@prismicio/richtext": "^2.1.4", 60 | "@prismicio/types": "^0.2.7", 61 | "imgix-url-builder": "^0.0.3" 62 | }, 63 | "devDependencies": { 64 | "@prismicio/mock": "^0.1.1", 65 | "@size-limit/preset-small-lib": "^8.1.2", 66 | "@typescript-eslint/eslint-plugin": "^5.49.0", 67 | "@typescript-eslint/parser": "^5.49.0", 68 | "@vitest/coverage-c8": "^0.28.3", 69 | "eslint": "^8.32.0", 70 | "eslint-config-prettier": "^8.6.0", 71 | "eslint-plugin-prettier": "^4.2.1", 72 | "eslint-plugin-tsdoc": "^0.2.17", 73 | "prettier": "^2.8.3", 74 | "prettier-plugin-jsdoc": "^0.4.2", 75 | "size-limit": "^8.1.2", 76 | "standard-version": "^9.5.0", 77 | "typescript": "^4.9.4", 78 | "vite": "^4.0.4", 79 | "vite-plugin-sdk": "^0.1.0", 80 | "vitest": "0.28.3" 81 | }, 82 | "engines": { 83 | "node": ">=12.7.0" 84 | }, 85 | "publishConfig": { 86 | "access": "public" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib/serializerHelpers.ts: -------------------------------------------------------------------------------- 1 | import { escapeHTML } from "./escapeHTML"; 2 | import { 3 | RTBlockNode, 4 | RTInlineNode, 5 | RTPreformattedNode, 6 | RTImageNode, 7 | RTEmbedNode, 8 | RTLinkNode, 9 | LinkType, 10 | RichTextNodeType, 11 | } from "@prismicio/types"; 12 | 13 | import { asLink } from "../asLink"; 14 | import { LinkResolverFunction } from "../types"; 15 | 16 | export const getLabel = (node: RTBlockNode | RTInlineNode): string => { 17 | return "data" in node && "label" in node.data 18 | ? ` class="${node.data.label}"` 19 | : ""; 20 | }; 21 | 22 | export const serializeStandardTag = ( 23 | tag: string, 24 | node: RTBlockNode | RTInlineNode, 25 | children: string[], 26 | ): string => { 27 | return `<${tag}${getLabel(node)}>${children.join("")}`; 28 | }; 29 | 30 | export const serializePreFormatted = (node: RTPreformattedNode): string => { 31 | return `${escapeHTML(node.text)}`; 32 | }; 33 | 34 | export const serializeImage = ( 35 | linkResolver: LinkResolverFunction | undefined | null, 36 | node: RTImageNode, 37 | ): string => { 38 | let imageTag = `${escapeHTML(node.alt)}`; 41 | 42 | // If the image has a link, we wrap it with an anchor tag 43 | if (node.linkTo) { 44 | imageTag = serializeHyperlink( 45 | linkResolver, 46 | { 47 | type: RichTextNodeType.hyperlink, 48 | data: node.linkTo, 49 | start: 0, 50 | end: 0, 51 | }, 52 | [imageTag], 53 | ); 54 | } 55 | 56 | return `

${imageTag}

`; 57 | }; 58 | 59 | export const serializeEmbed = (node: RTEmbedNode): string => { 60 | return `
${ 63 | node.oembed.html 64 | }
`; 65 | }; 66 | 67 | export const serializeHyperlink = ( 68 | linkResolver: LinkResolverFunction | undefined | null, 69 | node: RTLinkNode, 70 | children: string[], 71 | ): string => { 72 | switch (node.data.link_type) { 73 | case LinkType.Web: { 74 | return `${children.join("")}`; 77 | } 78 | 79 | case LinkType.Document: { 80 | return `${children.join("")}`; 83 | } 84 | 85 | case LinkType.Media: { 86 | return `${children.join( 87 | "", 88 | )}`; 89 | } 90 | } 91 | }; 92 | 93 | export const serializeSpan = (content?: string): string => { 94 | return content ? escapeHTML(content).replace(/\n/g, "
") : ""; 95 | }; 96 | -------------------------------------------------------------------------------- /src/asImagePixelDensitySrcSet.ts: -------------------------------------------------------------------------------- 1 | import { ImageFieldImage } from "@prismicio/types"; 2 | import { 3 | buildPixelDensitySrcSet, 4 | BuildPixelDensitySrcSetParams, 5 | buildURL, 6 | } from "imgix-url-builder"; 7 | 8 | import { imageThumbnail as isImageThumbnailFilled } from "./isFilled"; 9 | 10 | /** 11 | * The default pixel densities used to generate a `srcset` value. 12 | */ 13 | const DEFAULT_PIXEL_DENSITIES = [1, 2, 3]; 14 | 15 | /** 16 | * Configuration for `asImagePixelDensitySrcSet()`. 17 | */ 18 | type AsImagePixelDensitySrcSetConfig = Omit< 19 | BuildPixelDensitySrcSetParams, 20 | "pixelDensities" 21 | > & 22 | Partial>; 23 | 24 | /** 25 | * The return type of `asImagePixelDensitySrcSet()`. 26 | */ 27 | type AsImagePixelDensitySrcSetReturnType< 28 | Field extends ImageFieldImage | null | undefined, 29 | > = Field extends ImageFieldImage<"filled"> 30 | ? { 31 | /** 32 | * The Image field's image URL with Imgix URL parameters (if given). 33 | */ 34 | src: string; 35 | 36 | /** 37 | * A pixel-densitye-based `srcset` attribute value for the Image field's 38 | * image with Imgix URL parameters (if given). 39 | */ 40 | srcset: string; 41 | } 42 | : null; 43 | 44 | /** 45 | * Creates a pixel-density-based `srcset` from an Image field with optional 46 | * image transformations (via Imgix URL parameters). 47 | * 48 | * If a `pixelDensities` parameter is not given, the following pixel densities 49 | * will be used by default: 1, 2, 3. 50 | * 51 | * @example 52 | * 53 | * ```ts 54 | * const srcset = asImagePixelDensitySrcSet(document.data.imageField, { 55 | * pixelDensities: [1, 2], 56 | * sat: -100, 57 | * }); 58 | * // => { 59 | * // src: 'https://images.prismic.io/repo/image.png?sat=-100', 60 | * // srcset: 'https://images.prismic.io/repo/image.png?sat=-100&dpr=1 1x, ' + 61 | * // 'https://images.prismic.io/repo/image.png?sat=-100&dpr=2 2x' 62 | * // } 63 | * ``` 64 | * 65 | * @param field - Image field (or one of its responsive views) from which to get 66 | * an image URL. 67 | * @param params - An object of Imgix URL API parameters. The `pixelDensities` 68 | * parameter defines the resulting `srcset` widths. 69 | * 70 | * @returns A `srcset` attribute value for the Image field with Imgix URL 71 | * parameters (if given). If the Image field is empty, `null` is returned. 72 | * @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering 73 | */ 74 | export const asImagePixelDensitySrcSet = < 75 | Field extends ImageFieldImage | null | undefined, 76 | >( 77 | field: Field, 78 | params: AsImagePixelDensitySrcSetConfig = {}, 79 | ): AsImagePixelDensitySrcSetReturnType => { 80 | if (field && isImageThumbnailFilled(field)) { 81 | // We are using destructuring to omit `pixelDensities` from the 82 | // object we will pass to `buildURL()`. 83 | const { pixelDensities = DEFAULT_PIXEL_DENSITIES, ...imgixParams } = params; 84 | 85 | return { 86 | src: buildURL(field.url, imgixParams), 87 | srcset: buildPixelDensitySrcSet(field.url, { 88 | ...imgixParams, 89 | pixelDensities, 90 | }), 91 | } as AsImagePixelDensitySrcSetReturnType; 92 | } else { 93 | return null as AsImagePixelDensitySrcSetReturnType; 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/asLink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FilledLinkToDocumentField, 3 | FilledLinkToMediaField, 4 | FilledLinkToWebField, 5 | LinkField, 6 | LinkType, 7 | PrismicDocument, 8 | } from "@prismicio/types"; 9 | 10 | import { documentToLinkField } from "./documentToLinkField"; 11 | import { LinkResolverFunction } from "./types"; 12 | 13 | /** 14 | * The return type of `asLink()`. 15 | */ 16 | type AsLinkReturnType< 17 | LinkResolverFunctionReturnType = string, 18 | Field extends LinkField | PrismicDocument | null | undefined = 19 | | LinkField 20 | | PrismicDocument 21 | | null 22 | | undefined, 23 | > = Field extends 24 | | FilledLinkToWebField 25 | | FilledLinkToMediaField 26 | | FilledLinkToDocumentField 27 | | PrismicDocument 28 | ? LinkResolverFunctionReturnType | string | null 29 | : null; 30 | 31 | /** 32 | * Resolves any type of link field or document to a URL 33 | * 34 | * @typeParam LinkResolverFunctionReturnType - Link resolver function return 35 | * type 36 | * @param linkFieldOrDocument - Any kind of link field or a document to resolve 37 | * @param linkResolver - An optional link resolver function, without it you're 38 | * expected to use the `routes` options from the API 39 | * 40 | * @returns Resolved URL, null if provided link is empty 41 | * @see Prismic link resolver documentation: {@link https://prismic.io/docs/technologies/link-resolver-javascript} 42 | * @see Prismic API `routes` options documentation: {@link https://prismic.io/docs/technologies/route-resolver-nuxtjs} 43 | */ 44 | export const asLink = < 45 | LinkResolverFunctionReturnType = string, 46 | Field extends LinkField | PrismicDocument | null | undefined = 47 | | LinkField 48 | | PrismicDocument 49 | | null 50 | | undefined, 51 | >( 52 | linkFieldOrDocument: Field, 53 | linkResolver?: LinkResolverFunction | null, 54 | ): AsLinkReturnType => { 55 | if (!linkFieldOrDocument) { 56 | return null as AsLinkReturnType; 57 | } 58 | 59 | // Converts document to link field if needed 60 | const linkField = 61 | // prettier-ignore 62 | ( 63 | // @ts-expect-error - Bug in TypeScript 4.9: https://github.com/microsoft/TypeScript/issues/51501 64 | // TODO: Remove the `prettier-ignore` comment when this bug is fixed. 65 | "link_type" in linkFieldOrDocument 66 | ? linkFieldOrDocument 67 | : documentToLinkField(linkFieldOrDocument) 68 | ) as LinkField; 69 | 70 | switch (linkField.link_type) { 71 | case LinkType.Media: 72 | case LinkType.Web: 73 | return ("url" in linkField ? linkField.url : null) as AsLinkReturnType< 74 | LinkResolverFunctionReturnType, 75 | Field 76 | >; 77 | 78 | case LinkType.Document: { 79 | if ("id" in linkField && linkResolver) { 80 | // When using Link Resolver... 81 | const resolvedURL = linkResolver(linkField); 82 | 83 | if (resolvedURL != null) { 84 | return resolvedURL as AsLinkReturnType< 85 | LinkResolverFunctionReturnType, 86 | Field 87 | >; 88 | } 89 | } 90 | 91 | if ("url" in linkField && linkField.url) { 92 | // When using Route Resolver... 93 | return linkField.url as AsLinkReturnType< 94 | LinkResolverFunctionReturnType, 95 | Field 96 | >; 97 | } 98 | 99 | // When empty or Link Resolver and Route Resolver are not used... 100 | return null as AsLinkReturnType; 101 | } 102 | 103 | case LinkType.Any: 104 | default: 105 | return null as AsLinkReturnType; 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /test/asLink.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | 3 | import { documentFixture } from "./__fixtures__/document"; 4 | import { linkResolver } from "./__testutils__/linkResolver"; 5 | 6 | import { asLink } from "../src"; 7 | import { LinkType } from "@prismicio/types"; 8 | 9 | it("returns null for nullish inputs", () => { 10 | expect(asLink(null, linkResolver)).toBeNull(); 11 | expect(asLink(undefined, linkResolver)).toBeNull(); 12 | }); 13 | 14 | it("returns null when link to document field is empty", () => { 15 | const field = { 16 | link_type: LinkType.Document, 17 | }; 18 | 19 | expect(asLink(field, linkResolver)).toBeNull(); 20 | }); 21 | 22 | it("returns null when link to media field is empty", () => { 23 | const field = { 24 | link_type: LinkType.Media, 25 | }; 26 | 27 | expect(asLink(field, linkResolver)).toBeNull(); 28 | }); 29 | 30 | it("returns null when link field is empty", () => { 31 | const field = { 32 | link_type: LinkType.Any, 33 | }; 34 | 35 | expect(asLink(field, linkResolver)).toBeNull(); 36 | }); 37 | 38 | it("resolves a link to document field without Route Resolver", () => { 39 | const field = { 40 | id: "XvoFFREAAM0WGBng", 41 | type: "page", 42 | tags: [], 43 | slug: "slug", 44 | lang: "en-us", 45 | uid: "test", 46 | link_type: LinkType.Document, 47 | isBroken: false, 48 | }; 49 | 50 | expect( 51 | asLink(field), 52 | "returns null if both Link Resolver and Route Resolver are not used", 53 | ).toBeNull(); 54 | expect( 55 | asLink(field, linkResolver), 56 | "uses Link Resolver URL if Link Resolver returns a non-nullish value", 57 | ).toBe("/test"); 58 | expect( 59 | asLink(field, () => undefined), 60 | "returns null if Link Resolver returns undefined", 61 | ).toBeNull(); 62 | expect( 63 | asLink(field, () => null), 64 | "returns null if Link Resolver returns null", 65 | ).toBeNull(); 66 | }); 67 | 68 | it("resolves a link to document field with Route Resolver", () => { 69 | const field = { 70 | id: "XvoFFREAAM0WGBng", 71 | type: "page", 72 | tags: [], 73 | slug: "slug", 74 | lang: "en-us", 75 | uid: "uid", 76 | url: "url", 77 | link_type: LinkType.Document, 78 | isBroken: false, 79 | }; 80 | 81 | expect( 82 | asLink(field), 83 | "uses Route Resolver URL if Link Resolver is not given", 84 | ).toBe(field.url); 85 | expect( 86 | asLink(field, () => "link-resolver-value"), 87 | "uses Link Resolver URL if Link Resolver returns a non-nullish value", 88 | ).toBe("link-resolver-value"); 89 | expect( 90 | asLink(field, () => undefined), 91 | "uses Route Resolver URL if Link Resolver returns undefined", 92 | ).toBe(field.url); 93 | expect( 94 | asLink(field, () => null), 95 | "uses Route Resolver URL if Link Resolver returns null", 96 | ).toBe(field.url); 97 | }); 98 | 99 | it("returns null when given a document field and linkResolver is not provided ", () => { 100 | const field = { 101 | id: "XvoFFREAAM0WGBng", 102 | link_type: LinkType.Document, 103 | }; 104 | 105 | expect(asLink(field)).toBeNull(); 106 | }); 107 | 108 | it("resolves a link to web field", () => { 109 | const field = { 110 | link_type: LinkType.Web, 111 | url: "https://prismic.io", 112 | }; 113 | 114 | expect(asLink(field, linkResolver), "https://prismic.io"); 115 | }); 116 | 117 | it("resolves a link to media field", () => { 118 | const field = { 119 | link_type: LinkType.Media, 120 | name: "test.jpg", 121 | kind: "image", 122 | url: "https://prismic.io", 123 | size: "420", 124 | height: "42", 125 | width: "42", 126 | }; 127 | 128 | expect(asLink(field, linkResolver), "https://prismic.io"); 129 | }); 130 | 131 | it("resolves a document", () => { 132 | const document = { ...documentFixture.empty }; 133 | 134 | expect(asLink(document), "/test"); 135 | }); 136 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { FilledLinkToDocumentField } from "@prismicio/types"; 2 | import { 3 | RichTextFunctionSerializer, 4 | RichTextMapSerializer, 5 | RichTextMapSerializerFunction, 6 | } from "@prismicio/richtext"; 7 | 8 | /** 9 | * Resolves a link to a Prismic document to a URL 10 | * 11 | * @typeParam ReturnType - Return type of your link resolver function, useful if 12 | * you prefer to return a complex object 13 | * @param linkToDocumentField - A document link field to resolve 14 | * 15 | * @returns Resolved URL 16 | * @see Prismic link resolver documentation: {@link https://prismic.io/docs/technologies/link-resolver-javascript} 17 | */ 18 | export type LinkResolverFunction = ( 19 | linkToDocumentField: FilledLinkToDocumentField, 20 | ) => ReturnType; 21 | 22 | /** 23 | * Serializes a node from a rich text or title field with a function to HTML 24 | * 25 | * Unlike a typical `@prismicio/richtext` function serializer, this serializer 26 | * converts the `children` argument to a single string rather than an array of 27 | * strings. 28 | * 29 | * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript} 30 | */ 31 | export type HTMLFunctionSerializer = ( 32 | type: Parameters>[0], 33 | node: Parameters>[1], 34 | text: Parameters>[2], 35 | children: Parameters>[3][number], 36 | key: Parameters>[4], 37 | ) => string | null | undefined; 38 | 39 | /** 40 | * Serializes a node from a rich text or title field with a map to HTML 41 | * 42 | * Unlike a typical `@prismicio/richtext` map serializer, this serializer 43 | * converts the `children` property to a single string rather than an array of 44 | * strings. 45 | * 46 | * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript} 47 | */ 48 | export type HTMLMapSerializer = { 49 | [P in keyof RichTextMapSerializer]: (payload: { 50 | type: Parameters>[0]["type"]; 51 | node: Parameters>[0]["node"]; 52 | text: Parameters>[0]["text"]; 53 | children: Parameters>[0]["children"][number]; 54 | key: Parameters>[0]["key"]; 55 | }) => string | null | undefined; 56 | }; 57 | 58 | /** 59 | * A {@link RichTextMapSerializerFunction} type specifically for 60 | * {@link HTMLMapSerializer}. 61 | * 62 | * @typeParam BlockName - The serializer's Rich Text block type. 63 | */ 64 | type HTMLMapSerializerFunction< 65 | BlockType extends keyof RichTextMapSerializer, 66 | > = RichTextMapSerializerFunction< 67 | string, 68 | ExtractNodeGeneric[BlockType]>, 69 | ExtractTextTypeGeneric[BlockType]> 70 | >; 71 | 72 | /** 73 | * Returns the `Node` generic from {@link RichTextMapSerializerFunction}. 74 | * 75 | * @typeParam T - The `RichTextMapSerializerFunction` containing the needed 76 | * `Node` generic. 77 | */ 78 | type ExtractNodeGeneric = T extends RichTextMapSerializerFunction< 79 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 | any, 81 | infer U, 82 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 83 | any 84 | > 85 | ? U 86 | : never; 87 | 88 | /** 89 | * Returns the `TextType` generic from {@link RichTextMapSerializerFunction}. 90 | * 91 | * @typeParam T - The `RichTextMapSerializerFunction` containing the needed 92 | * `TextType` generic. 93 | */ 94 | type ExtractTextTypeGeneric = T extends RichTextMapSerializerFunction< 95 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 96 | any, 97 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 98 | any, 99 | infer U 100 | > 101 | ? U 102 | : never; 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Note**: **This package has been merged into [`@prismicio/client`](https://github.com/prismicio/prismic-client) ≥ v7.** 2 | > 3 | > This package and repository will no longer be updated, except in special circumstances. 4 | 5 | # @prismicio/helpers 6 | 7 | [![npm version][npm-version-src]][npm-version-href] 8 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 9 | [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] 10 | [![Codecov][codecov-src]][codecov-href] 11 | [![Conventional Commits][conventional-commits-src]][conventional-commits-href] 12 | [![License][license-src]][license-href] 13 | 14 | Set of helpers to manage [Prismic][prismic] data. 15 | 16 | - 📅  Transform Date and Timestamp fields into Date objects; 17 | - 🗺  Resolve any kind of Link fields; 18 | - 🌐  Dedicated GraphQL API support. 19 | 20 | ## Install 21 | 22 | ```bash 23 | npm install @prismicio/helpers 24 | ``` 25 | 26 | ## Documentation 27 | 28 | To discover what's new on this package check out [the changelog][changelog]. For full documentation, visit the [official Prismic documentation][prismic-docs]. 29 | 30 | ## Contributing 31 | 32 | Whether you're helping us fix bugs, improve the docs, or spread the word, we'd love to have you as part of the Prismic developer community! 33 | 34 | **Asking a question**: [Open a new topic][forum-question] on our community forum explaining what you want to achieve / your question. Our support team will get back to you shortly. 35 | 36 | **Reporting a bug**: [Open an issue][repo-bug-report] explaining your application's setup and the bug you're encountering. 37 | 38 | **Suggesting an improvement**: [Open an issue][repo-feature-request] explaining your improvement or feature so we can discuss and learn more. 39 | 40 | **Submitting code changes**: For small fixes, feel free to [open a pull request][repo-pull-requests] with a description of your changes. For large changes, please first [open an issue][repo-feature-request] so we can discuss if and how the changes should be implemented. 41 | 42 | For more clarity on this project and its structure you can also check out the detailed [CONTRIBUTING.md][contributing] document. 43 | 44 | ## License 45 | 46 | ``` 47 | Copyright 2013-2022 Prismic (https://prismic.io) 48 | 49 | Licensed under the Apache License, Version 2.0 (the "License"); 50 | you may not use this file except in compliance with the License. 51 | You may obtain a copy of the License at 52 | 53 | http://www.apache.org/licenses/LICENSE-2.0 54 | 55 | Unless required by applicable law or agreed to in writing, software 56 | distributed under the License is distributed on an "AS IS" BASIS, 57 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 58 | See the License for the specific language governing permissions and 59 | limitations under the License. 60 | ``` 61 | 62 | 63 | 64 | [prismic]: https://prismic.io 65 | [prismic-docs]: https://prismic.io/docs/technical-reference/prismicio-helpers 66 | [changelog]: ./CHANGELOG.md 67 | [contributing]: ./CONTRIBUTING.md 68 | [forum-question]: https://community.prismic.io/c/kits-and-dev-languages/javascript/14 69 | [repo-bug-report]: https://github.com/prismicio/prismic-helpers/issues/new?assignees=&labels=bug&template=bug_report.md&title= 70 | [repo-feature-request]: https://github.com/prismicio/prismic-helpers/issues/new?assignees=&labels=enhancement&template=feature_request.md&title= 71 | [repo-pull-requests]: https://github.com/prismicio/prismic-helpers/pulls 72 | 73 | 74 | 75 | [npm-version-src]: https://img.shields.io/npm/v/@prismicio/helpers/latest.svg 76 | [npm-version-href]: https://npmjs.com/package/@prismicio/helpers 77 | [npm-downloads-src]: https://img.shields.io/npm/dm/@prismicio/helpers.svg 78 | [npm-downloads-href]: https://npmjs.com/package/@prismicio/helpers 79 | [github-actions-ci-src]: https://github.com/prismicio/prismic-helpers/workflows/ci/badge.svg 80 | [github-actions-ci-href]: https://github.com/prismicio/prismic-helpers/actions?query=workflow%3Aci 81 | [codecov-src]: https://img.shields.io/codecov/c/github/prismicio/prismic-helpers.svg 82 | [codecov-href]: https://codecov.io/gh/prismicio/prismic-helpers 83 | [conventional-commits-src]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg 84 | [conventional-commits-href]: https://conventionalcommits.org 85 | [license-src]: https://img.shields.io/npm/l/@prismicio/helpers.svg 86 | [license-href]: https://npmjs.com/package/@prismicio/helpers 87 | -------------------------------------------------------------------------------- /src/asImageWidthSrcSet.ts: -------------------------------------------------------------------------------- 1 | import { ImageFieldImage } from "@prismicio/types"; 2 | import { 3 | buildURL, 4 | buildWidthSrcSet, 5 | BuildWidthSrcSetParams, 6 | } from "imgix-url-builder"; 7 | 8 | import { imageThumbnail as isImageThumbnailFilled } from "./isFilled"; 9 | 10 | /** 11 | * The default widths used to generate a `srcset` value. 12 | */ 13 | const DEFAULT_WIDTHS = [640, 828, 1200, 2048, 3840]; 14 | 15 | /** 16 | * The return type of `asImageWidthSrcSet()`. 17 | */ 18 | type AsImageWidthSrcSetReturnType< 19 | Field extends ImageFieldImage | null | undefined, 20 | > = Field extends ImageFieldImage<"filled"> 21 | ? { 22 | /** 23 | * The Image field's image URL with Imgix URL parameters (if given). 24 | */ 25 | src: string; 26 | 27 | /** 28 | * A width-based `srcset` attribute value for the Image field's image with 29 | * Imgix URL parameters (if given). 30 | */ 31 | srcset: string; 32 | } 33 | : null; 34 | 35 | /** 36 | * Configuration for `asImageWidthSrcSet()`. 37 | */ 38 | type AsImageWidthSrcSetConfig = Omit & { 39 | widths?: "thumbnails" | BuildWidthSrcSetParams["widths"]; 40 | }; 41 | 42 | /** 43 | * Creates a width-based `srcset` from an Image field with optional image 44 | * transformations (via Imgix URL parameters). 45 | * 46 | * If a `widths` parameter is not given, the following widths will be used by 47 | * default: 640, 750, 828, 1080, 1200, 1920, 2048, 3840. 48 | * 49 | * If the Image field contains responsive views, each responsive view can be 50 | * used as a width in the resulting `srcset` by passing `"thumbnails"` as the 51 | * `widths` parameter. 52 | * 53 | * @example 54 | * 55 | * ```ts 56 | * const srcset = asImageWidthSrcSet(document.data.imageField, { 57 | * widths: [400, 800, 1600], 58 | * sat: -100, 59 | * }); 60 | * // => { 61 | * // src: 'https://images.prismic.io/repo/image.png?sat=-100', 62 | * // srcset: 'https://images.prismic.io/repo/image.png?sat=-100&width=400 400w, ' + 63 | * // 'https://images.prismic.io/repo/image.png?sat=-100&width=800 800w,' + 64 | * // 'https://images.prismic.io/repo/image.png?sat=-100&width=1600 1600w' 65 | * // } 66 | * ``` 67 | * 68 | * @param field - Image field (or one of its responsive views) from which to get 69 | * an image URL. 70 | * @param params - An object of Imgix URL API parameters. The `widths` parameter 71 | * defines the resulting `srcset` widths. Pass `"thumbnails"` to automatically 72 | * use the field's responsive views. 73 | * 74 | * @returns A `srcset` attribute value for the Image field with Imgix URL 75 | * parameters (if given). If the Image field is empty, `null` is returned. 76 | * @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering 77 | */ 78 | export const asImageWidthSrcSet = < 79 | Field extends ImageFieldImage | null | undefined, 80 | >( 81 | field: Field, 82 | params: AsImageWidthSrcSetConfig = {}, 83 | ): AsImageWidthSrcSetReturnType => { 84 | if (field && isImageThumbnailFilled(field)) { 85 | // We are using destructuring to omit `widths` from the object 86 | // we will pass to `buildURL()`. 87 | let { 88 | widths = DEFAULT_WIDTHS, 89 | // eslint-disable-next-line prefer-const 90 | ...imgixParams 91 | } = params; 92 | const { 93 | url, 94 | dimensions, 95 | alt: _alt, 96 | copyright: _copyright, 97 | ...responsiveViews 98 | } = field; 99 | 100 | // The Prismic Rest API will always return thumbnail values if 101 | // the base size is filled. 102 | const responsiveViewObjects: ImageFieldImage<"filled">[] = 103 | Object.values(responsiveViews); 104 | 105 | // If this `asImageWidthSrcSet()` call is configured to use 106 | // thumbnail widths, but the field does not have thumbnails, we 107 | // fall back to the default set of widths. 108 | if (widths === "thumbnails" && responsiveViewObjects.length < 1) { 109 | widths = DEFAULT_WIDTHS; 110 | } 111 | 112 | return { 113 | src: buildURL(url, imgixParams), 114 | srcset: 115 | // By this point, we know `widths` can only be 116 | // `"thubmanils"` if the field has thumbnails. 117 | widths === "thumbnails" 118 | ? [ 119 | buildWidthSrcSet(url, { 120 | ...imgixParams, 121 | widths: [dimensions.width], 122 | }), 123 | ...responsiveViewObjects.map((thumbnail) => { 124 | return buildWidthSrcSet(thumbnail.url, { 125 | ...imgixParams, 126 | widths: [thumbnail.dimensions.width], 127 | }); 128 | }), 129 | ].join(", ") 130 | : buildWidthSrcSet(field.url, { 131 | ...imgixParams, 132 | widths, 133 | }), 134 | } as AsImageWidthSrcSetReturnType; 135 | } else { 136 | return null as AsImageWidthSrcSetReturnType; 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /test/asImageWidthSrcSet.test.ts: -------------------------------------------------------------------------------- 1 | import { ImageField } from "@prismicio/types"; 2 | import { it, expect } from "vitest"; 3 | 4 | import { asImageWidthSrcSet } from "../src"; 5 | 6 | it("returns null for nullish inputs", () => { 7 | expect(asImageWidthSrcSet(null)).toBeNull(); 8 | expect(asImageWidthSrcSet(undefined)).toBeNull(); 9 | }); 10 | 11 | it("returns an image field src and width-based srcset with [640, 750, 828, 1080, 1200, 1920, 2048, 3840] widths by default", () => { 12 | const field: ImageField = { 13 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 14 | alt: null, 15 | copyright: null, 16 | dimensions: { width: 400, height: 300 }, 17 | }; 18 | 19 | expect(asImageWidthSrcSet(field)).toStrictEqual({ 20 | src: field.url, 21 | srcset: 22 | `${field.url}&width=640 640w, ` + 23 | `${field.url}&width=828 828w, ` + 24 | `${field.url}&width=1200 1200w, ` + 25 | `${field.url}&width=2048 2048w, ` + 26 | `${field.url}&width=3840 3840w`, 27 | }); 28 | }); 29 | 30 | it("supports custom widths", () => { 31 | const field: ImageField = { 32 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 33 | alt: null, 34 | copyright: null, 35 | dimensions: { width: 400, height: 300 }, 36 | }; 37 | 38 | expect( 39 | asImageWidthSrcSet(field, { 40 | widths: [400, 800, 1600], 41 | }), 42 | ).toStrictEqual({ 43 | src: field.url, 44 | srcset: 45 | `${field.url}&width=400 400w, ` + 46 | `${field.url}&width=800 800w, ` + 47 | `${field.url}&width=1600 1600w`, 48 | }); 49 | }); 50 | 51 | it("applies given Imgix URL parameters", () => { 52 | const field: ImageField = { 53 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 54 | alt: null, 55 | copyright: null, 56 | dimensions: { width: 400, height: 300 }, 57 | }; 58 | 59 | expect( 60 | asImageWidthSrcSet(field, { 61 | sat: 100, 62 | }), 63 | ).toStrictEqual({ 64 | src: `${field.url}&sat=100`, 65 | srcset: 66 | `${field.url}&sat=100&width=640 640w, ` + 67 | `${field.url}&sat=100&width=828 828w, ` + 68 | `${field.url}&sat=100&width=1200 1200w, ` + 69 | `${field.url}&sat=100&width=2048 2048w, ` + 70 | `${field.url}&sat=100&width=3840 3840w`, 71 | }); 72 | }); 73 | 74 | it('if widths is "auto", returns a srcset of responsive views if the field contains responsive views', () => { 75 | const field = { 76 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 77 | alt: null, 78 | copyright: null, 79 | dimensions: { width: 1000, height: 800 }, 80 | foo: { 81 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 82 | alt: null, 83 | copyright: null, 84 | dimensions: { width: 500, height: 400 }, 85 | }, 86 | bar: { 87 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 88 | alt: null, 89 | copyright: null, 90 | dimensions: { width: 250, height: 200 }, 91 | }, 92 | }; 93 | 94 | expect(asImageWidthSrcSet(field, { widths: "thumbnails" })).toStrictEqual({ 95 | src: field.url, 96 | srcset: 97 | `${field.url}&width=1000 1000w, ` + 98 | `${field.foo.url}&width=500 500w, ` + 99 | `${field.bar.url}&width=250 250w`, 100 | }); 101 | }); 102 | 103 | it('if widths is "auto", but the field does not contain responsive views, uses the default widths', () => { 104 | const field = { 105 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 106 | alt: null, 107 | copyright: null, 108 | dimensions: { width: 1000, height: 800 }, 109 | }; 110 | 111 | expect(asImageWidthSrcSet(field, { widths: "thumbnails" })).toStrictEqual({ 112 | src: field.url, 113 | srcset: 114 | `${field.url}&width=640 640w, ` + 115 | `${field.url}&width=828 828w, ` + 116 | `${field.url}&width=1200 1200w, ` + 117 | `${field.url}&width=2048 2048w, ` + 118 | `${field.url}&width=3840 3840w`, 119 | }); 120 | }); 121 | 122 | it('if widths is not "auto", but the field contains responsive views, uses the default widths and ignores thumbnails', () => { 123 | const field = { 124 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 125 | alt: null, 126 | copyright: null, 127 | dimensions: { width: 1000, height: 800 }, 128 | foo: { 129 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 130 | alt: null, 131 | copyright: null, 132 | dimensions: { width: 500, height: 400 }, 133 | }, 134 | bar: { 135 | url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat", 136 | alt: null, 137 | copyright: null, 138 | dimensions: { width: 250, height: 200 }, 139 | }, 140 | }; 141 | 142 | expect(asImageWidthSrcSet(field)).toStrictEqual({ 143 | src: field.url, 144 | srcset: 145 | `${field.url}&width=640 640w, ` + 146 | `${field.url}&width=828 828w, ` + 147 | `${field.url}&width=1200 1200w, ` + 148 | `${field.url}&width=2048 2048w, ` + 149 | `${field.url}&width=3840 3840w`, 150 | }); 151 | }); 152 | 153 | it("returns null when image field is empty", () => { 154 | const field: ImageField = {}; 155 | 156 | expect(asImageWidthSrcSet(field)).toBeNull(); 157 | }); 158 | -------------------------------------------------------------------------------- /src/asHTML.ts: -------------------------------------------------------------------------------- 1 | import { 2 | serialize, 3 | Element, 4 | composeSerializers, 5 | RichTextFunctionSerializer, 6 | RichTextMapSerializer, 7 | wrapMapSerializer, 8 | } from "@prismicio/richtext"; 9 | import { RichTextField } from "@prismicio/types"; 10 | 11 | import { 12 | serializeStandardTag, 13 | serializePreFormatted, 14 | serializeImage, 15 | serializeEmbed, 16 | serializeHyperlink, 17 | serializeSpan, 18 | } from "./lib/serializerHelpers"; 19 | import { 20 | HTMLFunctionSerializer, 21 | HTMLMapSerializer, 22 | LinkResolverFunction, 23 | } from "./types"; 24 | 25 | /** 26 | * Creates a default HTML serializer with a given Link Resolver providing 27 | * sensible and safe defaults for every node type 28 | * 29 | * @internal 30 | */ 31 | const createDefaultHTMLSerializer = ( 32 | linkResolver: LinkResolverFunction | undefined | null, 33 | ): RichTextFunctionSerializer => { 34 | return (_type, node, text, children, _key) => { 35 | switch (node.type) { 36 | case Element.heading1: 37 | return serializeStandardTag("h1", node, children); 38 | case Element.heading2: 39 | return serializeStandardTag("h2", node, children); 40 | case Element.heading3: 41 | return serializeStandardTag("h3", node, children); 42 | case Element.heading4: 43 | return serializeStandardTag("h4", node, children); 44 | case Element.heading5: 45 | return serializeStandardTag("h5", node, children); 46 | case Element.heading6: 47 | return serializeStandardTag("h6", node, children); 48 | case Element.paragraph: 49 | return serializeStandardTag("p", node, children); 50 | case Element.preformatted: 51 | return serializePreFormatted(node); 52 | case Element.strong: 53 | return serializeStandardTag("strong", node, children); 54 | case Element.em: 55 | return serializeStandardTag("em", node, children); 56 | case Element.listItem: 57 | return serializeStandardTag("li", node, children); 58 | case Element.oListItem: 59 | return serializeStandardTag("li", node, children); 60 | case Element.list: 61 | return serializeStandardTag("ul", node, children); 62 | case Element.oList: 63 | return serializeStandardTag("ol", node, children); 64 | case Element.image: 65 | return serializeImage(linkResolver, node); 66 | case Element.embed: 67 | return serializeEmbed(node); 68 | case Element.hyperlink: 69 | return serializeHyperlink(linkResolver, node, children); 70 | case Element.label: 71 | return serializeStandardTag("span", node, children); 72 | case Element.span: 73 | default: 74 | return serializeSpan(text); 75 | } 76 | }; 77 | }; 78 | 79 | /** 80 | * Wraps a map serializer into a regular function serializer. The given map 81 | * serializer should accept children as a string, not as an array of strings 82 | * like `@prismicio/richtext`'s `wrapMapSerializer`. 83 | * 84 | * @param mapSerializer - Map serializer to wrap 85 | * 86 | * @returns A regular function serializer 87 | */ 88 | const wrapMapSerializerWithStringChildren = ( 89 | mapSerializer: HTMLMapSerializer, 90 | ): RichTextFunctionSerializer => { 91 | const modifiedMapSerializer = {} as RichTextMapSerializer; 92 | 93 | for (const tag in mapSerializer) { 94 | const tagSerializer = mapSerializer[tag as keyof typeof mapSerializer]; 95 | 96 | if (tagSerializer) { 97 | modifiedMapSerializer[tag as keyof typeof mapSerializer] = (payload) => { 98 | return tagSerializer({ 99 | ...payload, 100 | // @ts-expect-error - merging blockSerializer types causes TS to bail to a never type 101 | children: payload.children.join(""), 102 | }); 103 | }; 104 | } 105 | } 106 | 107 | return wrapMapSerializer(modifiedMapSerializer); 108 | }; 109 | 110 | /** 111 | * The return type of `asHTML()`. 112 | */ 113 | type AsHTMLReturnType = 114 | Field extends RichTextField ? string : null; 115 | 116 | /** 117 | * Serializes a rich text or title field to an HTML string 118 | * 119 | * @param richTextField - A rich text or title field from Prismic 120 | * @param linkResolver - An optional link resolver function to resolve links, 121 | * without it you're expected to use the `routes` options from the API 122 | * @param htmlSerializer - An optional serializer, unhandled cases will fallback 123 | * to the default serializer 124 | * 125 | * @returns HTML equivalent of the provided rich text or title field 126 | * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript} 127 | */ 128 | export const asHTML = ( 129 | richTextField: Field, 130 | linkResolver?: LinkResolverFunction | null, 131 | htmlSerializer?: HTMLFunctionSerializer | HTMLMapSerializer | null, 132 | ): AsHTMLReturnType => { 133 | if (richTextField) { 134 | let serializer: RichTextFunctionSerializer; 135 | if (htmlSerializer) { 136 | serializer = composeSerializers( 137 | typeof htmlSerializer === "object" 138 | ? wrapMapSerializerWithStringChildren(htmlSerializer) 139 | : (type, node, text, children, key) => 140 | htmlSerializer(type, node, text, children.join(""), key), 141 | createDefaultHTMLSerializer(linkResolver), 142 | ); 143 | } else { 144 | serializer = createDefaultHTMLSerializer(linkResolver); 145 | } 146 | 147 | return serialize(richTextField, serializer).join( 148 | "", 149 | ) as AsHTMLReturnType; 150 | } else { 151 | return null as AsHTMLReturnType; 152 | } 153 | }; 154 | -------------------------------------------------------------------------------- /test/isFilled.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from "vitest"; 2 | 3 | import * as prismicT from "@prismicio/types"; 4 | 5 | import * as prismicH from "../src"; 6 | 7 | it("color", (ctx) => { 8 | expect(prismicH.isFilled.color(null)).toBe(false); 9 | expect(prismicH.isFilled.color(undefined)).toBe(false); 10 | expect(prismicH.isFilled.color(ctx.mock.value.color())).toBe(true); 11 | }); 12 | 13 | it("content relationship", (ctx) => { 14 | expect(prismicH.isFilled.contentRelationship(null)).toBe(false); 15 | expect(prismicH.isFilled.contentRelationship(undefined)).toBe(false); 16 | expect( 17 | prismicH.isFilled.contentRelationship( 18 | ctx.mock.value.contentRelationship({ 19 | state: "empty", 20 | }), 21 | ), 22 | ).toBe(false); 23 | expect( 24 | prismicH.isFilled.contentRelationship( 25 | ctx.mock.value.contentRelationship({ 26 | state: "filled", 27 | }), 28 | ), 29 | ).toBe(true); 30 | }); 31 | 32 | it("date", (ctx) => { 33 | expect(prismicH.isFilled.date(null)).toBe(false); 34 | expect(prismicH.isFilled.date(undefined)).toBe(false); 35 | expect(prismicH.isFilled.date(ctx.mock.value.date())).toBe(true); 36 | }); 37 | 38 | it("embed", (ctx) => { 39 | expect(prismicH.isFilled.embed(null)).toBe(false); 40 | expect(prismicH.isFilled.embed(undefined)).toBe(false); 41 | expect(prismicH.isFilled.embed({})).toBe(false); 42 | expect(prismicH.isFilled.embed(ctx.mock.value.embed())).toBe(true); 43 | }); 44 | 45 | it("geopoint", (ctx) => { 46 | expect(prismicH.isFilled.geoPoint(null)).toBe(false); 47 | expect(prismicH.isFilled.geoPoint(undefined)).toBe(false); 48 | expect(prismicH.isFilled.geoPoint({})).toBe(false); 49 | expect(prismicH.isFilled.geoPoint(ctx.mock.value.geoPoint())).toBe(true); 50 | }); 51 | 52 | it("group", (ctx) => { 53 | expect(prismicH.isFilled.group(null)).toBe(false); 54 | expect(prismicH.isFilled.group(undefined)).toBe(false); 55 | expect(prismicH.isFilled.group([])).toBe(false); 56 | expect( 57 | prismicH.isFilled.group(ctx.mock.value.group() as prismicT.GroupField), 58 | ).toBe(true); 59 | }); 60 | 61 | it("image", (ctx) => { 62 | expect(prismicH.isFilled.image(null)).toBe(false); 63 | expect(prismicH.isFilled.image(undefined)).toBe(false); 64 | expect(prismicH.isFilled.image({})).toBe(false); 65 | expect(prismicH.isFilled.image(ctx.mock.value.image())).toBe(true); 66 | }); 67 | 68 | it("image thumbnail", () => { 69 | expect(prismicH.isFilled.imageThumbnail(null)).toBe(false); 70 | expect(prismicH.isFilled.imageThumbnail(undefined)).toBe(false); 71 | expect(prismicH.isFilled.imageThumbnail({})).toBe(false); 72 | expect( 73 | prismicH.isFilled.imageThumbnail({ 74 | url: "url", 75 | alt: null, 76 | copyright: null, 77 | dimensions: { width: 1, height: 1 }, 78 | }), 79 | ).toBe(true); 80 | }); 81 | 82 | it("integration fields", (ctx) => { 83 | expect(prismicH.isFilled.integrationFields(null)).toBe(false); 84 | expect(prismicH.isFilled.integrationFields(undefined)).toBe(false); 85 | expect( 86 | prismicH.isFilled.integrationFields(ctx.mock.value.integrationFields()), 87 | ).toBe(true); 88 | }); 89 | 90 | it("key text", (ctx) => { 91 | expect(prismicH.isFilled.keyText(null)).toBe(false); 92 | expect(prismicH.isFilled.keyText(undefined)).toBe(false); 93 | expect(prismicH.isFilled.keyText("")).toBe(false); 94 | expect(prismicH.isFilled.keyText(ctx.mock.value.keyText())).toBe(true); 95 | }); 96 | 97 | it("link", (ctx) => { 98 | expect(prismicH.isFilled.link(null)).toBe(false); 99 | expect(prismicH.isFilled.link(undefined)).toBe(false); 100 | expect(prismicH.isFilled.link(ctx.mock.value.link({ state: "empty" }))).toBe( 101 | false, 102 | ); 103 | expect(prismicH.isFilled.link(ctx.mock.value.link({ state: "filled" }))).toBe( 104 | true, 105 | ); 106 | }); 107 | 108 | it("link to media", (ctx) => { 109 | expect(prismicH.isFilled.linkToMedia(null)).toBe(false); 110 | expect(prismicH.isFilled.linkToMedia(undefined)).toBe(false); 111 | expect( 112 | prismicH.isFilled.linkToMedia( 113 | ctx.mock.value.linkToMedia({ state: "empty" }), 114 | ), 115 | ).toBe(false); 116 | expect( 117 | prismicH.isFilled.linkToMedia( 118 | ctx.mock.value.linkToMedia({ state: "filled" }), 119 | ), 120 | ).toBe(true); 121 | }); 122 | 123 | it("number", (ctx) => { 124 | expect(prismicH.isFilled.number(null)).toBe(false); 125 | expect(prismicH.isFilled.number(undefined)).toBe(false); 126 | expect(prismicH.isFilled.number(ctx.mock.value.number())).toBe(true); 127 | }); 128 | 129 | it("rich text", (ctx) => { 130 | expect(prismicH.isFilled.richText(null)).toBe(false); 131 | expect(prismicH.isFilled.richText(undefined)).toBe(false); 132 | expect(prismicH.isFilled.richText([])).toBe(false); 133 | expect( 134 | prismicH.isFilled.richText([{ type: "paragraph", text: "", spans: [] }]), 135 | ).toBe(false); 136 | expect(prismicH.isFilled.richText(ctx.mock.value.richText())).toBe(true); 137 | }); 138 | 139 | it("select", (ctx) => { 140 | expect(prismicH.isFilled.select(null)).toBe(false); 141 | expect(prismicH.isFilled.select(undefined)).toBe(false); 142 | expect( 143 | prismicH.isFilled.select( 144 | ctx.mock.value.select({ 145 | model: ctx.mock.model.select({ 146 | options: ["foo", "bar"], 147 | }), 148 | }), 149 | ), 150 | ).toBe(true); 151 | }); 152 | 153 | it("slice zone", (ctx) => { 154 | expect(prismicH.isFilled.sliceZone(null)).toBe(false); 155 | expect(prismicH.isFilled.sliceZone(undefined)).toBe(false); 156 | expect(prismicH.isFilled.sliceZone([])).toBe(false); 157 | expect( 158 | prismicH.isFilled.sliceZone( 159 | ctx.mock.value.sliceZone({ 160 | model: ctx.mock.model.sliceZone({ 161 | choices: { 162 | Foo: ctx.mock.model.slice(), 163 | Bar: ctx.mock.model.slice(), 164 | }, 165 | }), 166 | }) as prismicT.SliceZone, 167 | ), 168 | ).toBe(true); 169 | }); 170 | 171 | it("timestamp", (ctx) => { 172 | expect(prismicH.isFilled.timestamp(null)).toBe(false); 173 | expect(prismicH.isFilled.timestamp(undefined)).toBe(false); 174 | expect(prismicH.isFilled.timestamp(ctx.mock.value.timestamp())).toBe(true); 175 | }); 176 | 177 | it("title", (ctx) => { 178 | expect(prismicH.isFilled.title(null)).toBe(false); 179 | expect(prismicH.isFilled.title(undefined)).toBe(false); 180 | expect(prismicH.isFilled.title([])).toBe(false); 181 | expect( 182 | prismicH.isFilled.title([{ type: "heading1", text: "", spans: [] }]), 183 | ).toBe(false); 184 | expect(prismicH.isFilled.title(ctx.mock.value.title())).toBe(true); 185 | }); 186 | -------------------------------------------------------------------------------- /src/isFilled.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AnyOEmbed, 3 | AnyRegularField, 4 | ColorField, 5 | DateField, 6 | EmbedField, 7 | GeoPointField, 8 | GroupField, 9 | ImageField, 10 | ImageFieldImage, 11 | IntegrationFields, 12 | KeyTextField, 13 | LinkField, 14 | LinkToMediaField, 15 | NumberField, 16 | RelationField, 17 | RichTextField, 18 | SelectField, 19 | SharedSlice, 20 | Slice, 21 | SliceZone, 22 | TimestampField, 23 | TitleField, 24 | } from "@prismicio/types"; 25 | 26 | /** 27 | * Determines if a value is not nullish (i.e. not `null` or `undefined`). This 28 | * is used to check if nullable field values are filled. 29 | * 30 | * @param input - The value to check. 31 | * 32 | * @returns `true` if `input` is not nullish, `false` otherwise. 33 | */ 34 | const isNonNullish = (input: T): input is NonNullable => { 35 | return input != null; 36 | }; 37 | 38 | /** 39 | * Determines if an array is not empty. This is used to check if array-based 40 | * fields are filled. 41 | * 42 | * @param input - The array to check. 43 | * 44 | * @returns `true` if `input` has at least one element, `false` otherwise. 45 | */ 46 | const isNonEmptyArray = (input: T[]): input is [T, ...T[]] => { 47 | return !!input.length; 48 | }; 49 | 50 | /** 51 | * Determines if a Rich Text field is filled. 52 | * 53 | * @param field - Rich Text field to check. 54 | * 55 | * @returns `true` if `field` is filled, `false` otherwise. 56 | */ 57 | export const richText = ( 58 | field: RichTextField | null | undefined, 59 | ): field is RichTextField<"filled"> => { 60 | if (!isNonNullish(field)) { 61 | return false; 62 | } else if (field.length === 1 && "text" in field[0]) { 63 | return !!field[0].text; 64 | } else { 65 | return !!field.length; 66 | } 67 | }; 68 | 69 | /** 70 | * Determines if a Title field is filled. 71 | * 72 | * @param field - Title field to check. 73 | * 74 | * @returns `true` if `field` is filled, `false` otherwise. 75 | */ 76 | export const title = richText as ( 77 | field: TitleField | null | undefined, 78 | ) => field is TitleField<"filled">; 79 | 80 | /** 81 | * Determines if an Image thumbnail is filled. 82 | * 83 | * @param thumbnail - Image thumbnail to check. 84 | * 85 | * @returns `true` if `field` is filled, `false` otherwise. 86 | */ 87 | export const imageThumbnail = ( 88 | thumbnail: ImageFieldImage | null | undefined, 89 | ): thumbnail is ImageFieldImage<"filled"> => { 90 | return isNonNullish(thumbnail) && !!thumbnail.url; 91 | }; 92 | 93 | /** 94 | * Determines if an Image field is filled. 95 | * 96 | * @param field - Image field to check. 97 | * 98 | * @returns `true` if `field` is filled, `false` otherwise. 99 | */ 100 | export const image = imageThumbnail as < 101 | ThumbnailNames extends string | null = never, 102 | >( 103 | field: ImageField | null | undefined, 104 | ) => field is ImageField; 105 | 106 | /** 107 | * Determines if a Link field is filled. 108 | * 109 | * @param field - Link field to check. 110 | * 111 | * @returns `true` if `field` is filled, `false` otherwise. 112 | */ 113 | export const link = < 114 | TypeEnum = string, 115 | LangEnum = string, 116 | DataInterface extends 117 | | Record 118 | | unknown = unknown, 119 | >( 120 | field: LinkField | null | undefined, 121 | ): field is LinkField => { 122 | return isNonNullish(field) && ("id" in field || "url" in field); 123 | }; 124 | 125 | /** 126 | * Determines if a Link to Media field is filled. 127 | * 128 | * @param field - Link to Media field to check. 129 | * 130 | * @returns `true` if `field` is filled, `false` otherwise. 131 | */ 132 | export const linkToMedia = link as ( 133 | field: LinkToMediaField | null | undefined, 134 | ) => field is LinkToMediaField<"filled">; 135 | 136 | /** 137 | * Determines if a Content Relationship field is filled. 138 | * 139 | * @param field - Content Relationship field to check. 140 | * 141 | * @returns `true` if `field` is filled, `false` otherwise. 142 | */ 143 | export const contentRelationship = link as < 144 | TypeEnum = string, 145 | LangEnum = string, 146 | DataInterface extends 147 | | Record 148 | | unknown = unknown, 149 | >( 150 | field: RelationField | null | undefined, 151 | ) => field is RelationField; 152 | 153 | /** 154 | * Determines if a Date field is filled. 155 | * 156 | * @param field - Date field to check. 157 | * 158 | * @returns `true` if `field` is filled, `false` otherwise. 159 | */ 160 | export const date = isNonNullish as ( 161 | field: DateField | null | undefined, 162 | ) => field is DateField<"filled">; 163 | 164 | /** 165 | * Determines if a Timestamp field is filled. 166 | * 167 | * @param field - Timestamp field to check. 168 | * 169 | * @returns `true` if `field` is filled, `false` otherwise. 170 | */ 171 | export const timestamp = isNonNullish as ( 172 | field: TimestampField | null | undefined, 173 | ) => field is TimestampField<"filled">; 174 | 175 | /** 176 | * Determines if a Color field is filled. 177 | * 178 | * @param field - Color field to check. 179 | * 180 | * @returns `true` if `field` is filled, `false` otherwise. 181 | */ 182 | export const color = isNonNullish as ( 183 | field: ColorField | null | undefined, 184 | ) => field is ColorField<"filled">; 185 | 186 | /** 187 | * Determines if a Number field is filled. 188 | * 189 | * @param field - Number field to check. 190 | * 191 | * @returns `true` if `field` is filled, `false` otherwise. 192 | */ 193 | export const number = isNonNullish as ( 194 | field: NumberField | null | undefined, 195 | ) => field is NumberField<"filled">; 196 | 197 | /** 198 | * Determines if a Key Text field is filled. 199 | * 200 | * @param field - Key Text field to check. 201 | * 202 | * @returns `true` if `field` is filled, `false` otherwise. 203 | */ 204 | export const keyText = ( 205 | field: KeyTextField | null | undefined, 206 | ): field is KeyTextField<"filled"> => { 207 | return isNonNullish(keyText) && !!field; 208 | }; 209 | 210 | /** 211 | * Determines if a Select field is filled. 212 | * 213 | * @param field - Select field to check. 214 | * 215 | * @returns `true` if `field` is filled, `false` otherwise. 216 | */ 217 | export const select = isNonNullish as ( 218 | field: SelectField | null | undefined, 219 | ) => field is SelectField; 220 | 221 | /** 222 | * Determines if an Embed field is filled. 223 | * 224 | * @param field - Embed field to check. 225 | * 226 | * @returns `true` if `field` is filled, `false` otherwise. 227 | */ 228 | export const embed = >( 229 | field: Field | null | undefined, 230 | ): field is Extract> => { 231 | return isNonNullish(field) && !!field.embed_url; 232 | }; 233 | 234 | /** 235 | * Determines if a GeoPoint field is filled. 236 | * 237 | * @param field - GeoPoint field to check. 238 | * 239 | * @returns `true` if `field` is filled, `false` otherwise. 240 | */ 241 | export const geoPoint = ( 242 | field: GeoPointField | null | undefined, 243 | ): field is GeoPointField<"filled"> => { 244 | return isNonNullish(field) && "longitude" in field; 245 | }; 246 | 247 | /** 248 | * Determines if an Integration Fields field is filled. 249 | * 250 | * @param field - Integration Fields field to check. 251 | * 252 | * @returns `true` if `field` is filled, `false` otherwise. 253 | */ 254 | export const integrationFields = isNonNullish as < 255 | Data extends Record, 256 | >( 257 | field: IntegrationFields | null | undefined, 258 | ) => field is IntegrationFields; 259 | 260 | /** 261 | * Determines if a Group has at least one item. 262 | * 263 | * @param group - Group to check. 264 | * 265 | * @returns `true` if `group` contains at least one item, `false` otherwise. 266 | */ 267 | export const group = >( 268 | group: GroupField | null | undefined, 269 | ): group is GroupField => { 270 | return isNonNullish(group) && isNonEmptyArray(group); 271 | }; 272 | 273 | /** 274 | * Determines if a Slice Zone has at least one Slice. 275 | * 276 | * @param slices - Slice Zone to check. 277 | * 278 | * @returns `true` if `slices` contains at least one Slice, `false` otherwise. 279 | */ 280 | export const sliceZone = ( 281 | slices: SliceZone | null | undefined, 282 | ): slices is SliceZone => { 283 | return isNonNullish(slices) && isNonEmptyArray(slices); 284 | }; 285 | -------------------------------------------------------------------------------- /.github/prismic-oss-ecosystem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.3.9](https://github.com/prismicio/prismic-helpers/compare/v2.3.8...v2.3.9) (2023-01-28) 6 | 7 | 8 | ### Refactor 9 | 10 | * inline `escape-html` ([8716fe5](https://github.com/prismicio/prismic-helpers/commit/8716fe556da688a5387f02ad1201551b069a5beb)) 11 | 12 | 13 | ### Chore 14 | 15 | * **deps:** maintain dependencies ([75a77a8](https://github.com/prismicio/prismic-helpers/commit/75a77a8070ea5244fe7d4bd8aa89e8b96a99416a)) 16 | 17 | ### [2.3.8](https://github.com/prismicio/prismic-helpers/compare/v2.3.7...v2.3.8) (2022-12-19) 18 | 19 | 20 | ### Chore 21 | 22 | * **deps:** maintain dependencies ([22040aa](https://github.com/prismicio/prismic-helpers/commit/22040aa67a77498d54c4e15c58a43dcf64721f88)) 23 | 24 | ### [2.3.7](https://github.com/prismicio/prismic-helpers/compare/v2.3.6...v2.3.7) (2022-12-19) 25 | 26 | 27 | ### Chore 28 | 29 | * **deps:** maintain dependencies ([f0fe858](https://github.com/prismicio/prismic-helpers/commit/f0fe858fdc02c6dbc7170b379c06e1f1a641b556)) 30 | 31 | ### [2.3.6](https://github.com/prismicio/prismic-helpers/compare/v2.3.5...v2.3.6) (2022-11-16) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * allow Content Relationship field's `data` property overrides when using `isFilled` helpers ([#62](https://github.com/prismicio/prismic-helpers/issues/62)) ([cc75ce5](https://github.com/prismicio/prismic-helpers/commit/cc75ce5c43f4cd302beec60308d66033c544b9c4)) 37 | 38 | 39 | ### Chore 40 | 41 | * **deps:** update all dependencies ([#61](https://github.com/prismicio/prismic-helpers/issues/61)) ([471d2f5](https://github.com/prismicio/prismic-helpers/commit/471d2f5275c3193a4567b560113f8f85d28f59ae)) 42 | 43 | ### [2.3.5](https://github.com/prismicio/prismic-helpers/compare/v2.3.4...v2.3.5) (2022-09-20) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * simplify `isFilled.embed()` for better TypeScript compatibility ([#57](https://github.com/prismicio/prismic-helpers/issues/57)) ([55b093c](https://github.com/prismicio/prismic-helpers/commit/55b093c935cfd74fe5ae206004bebb1d81e4adfa)) 49 | 50 | ### [2.3.4](https://github.com/prismicio/prismic-helpers/compare/v2.3.3...v2.3.4) (2022-09-15) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * omit `target` attribute on links without a target ([#59](https://github.com/prismicio/prismic-helpers/issues/59)) ([9b2dddd](https://github.com/prismicio/prismic-helpers/commit/9b2dddd4107c4f9b9a0bc7178437ca3232cc8827)) 56 | 57 | 58 | ### Chore 59 | 60 | * **deps:** upgrade dependencies ([#60](https://github.com/prismicio/prismic-helpers/issues/60)) ([32f849c](https://github.com/prismicio/prismic-helpers/commit/32f849ccc8e87377dc9e17096899d9d1211aea36)) 61 | 62 | ### [2.3.3](https://github.com/prismicio/prismic-helpers/compare/v2.3.2...v2.3.3) (2022-08-05) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * resolve invalid compiled TypeScript types ([#56](https://github.com/prismicio/prismic-helpers/issues/56)) ([6122cb7](https://github.com/prismicio/prismic-helpers/commit/6122cb769e6f8c3f3b52d827f527257638ed55df)) 68 | 69 | ### [2.3.2](https://github.com/prismicio/prismic-helpers/compare/v2.3.1...v2.3.2) (2022-07-13) 70 | 71 | 72 | ### Chore 73 | 74 | * **deps:** upgrade dependencies ([#53](https://github.com/prismicio/prismic-helpers/issues/53)) ([ecaa893](https://github.com/prismicio/prismic-helpers/commit/ecaa89315f2dd4f231f887a4d594c7ec511d00d2)) 75 | 76 | ### [2.3.1](https://github.com/prismicio/prismic-helpers/compare/v2.3.0...v2.3.1) (2022-06-09) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * support partial document in `asLink()` and `documentToLinkField()` ([#51](https://github.com/prismicio/prismic-helpers/issues/51)) ([e3a3493](https://github.com/prismicio/prismic-helpers/commit/e3a3493adcd77356e75e09c06a445f0a295b38e8)) 82 | 83 | ## [2.3.0](https://github.com/prismicio/prismic-helpers/compare/v2.2.1...v2.3.0) (2022-04-15) 84 | 85 | 86 | ### Features 87 | 88 | * opt-in automatic responsive view support for `asImageWidthSrcSet()` ([#47](https://github.com/prismicio/prismic-helpers/issues/47)) ([2a1ad82](https://github.com/prismicio/prismic-helpers/commit/2a1ad829851bc294e3ffc5ffe868bf091a9ee99d)) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * include `url` property when using `asLink()` with a document ([#48](https://github.com/prismicio/prismic-helpers/issues/48)) ([f437962](https://github.com/prismicio/prismic-helpers/commit/f4379627c080b88bfec8b08e93924496baeac453)) 94 | 95 | 96 | ### Chore 97 | 98 | * **deps:** upgrade dependencies ([2c2360e](https://github.com/prismicio/prismic-helpers/commit/2c2360e998d278569074dcec454e1dc802326c3a)) 99 | 100 | ### [2.2.1](https://github.com/prismicio/prismic-helpers/compare/v2.2.0...v2.2.1) (2022-03-18) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * `isFilled.image()` parameter type ([#46](https://github.com/prismicio/prismic-helpers/issues/46)) ([8d99482](https://github.com/prismicio/prismic-helpers/commit/8d994825ecdddd0a412d0bb6556181617274fce8)) 106 | 107 | 108 | ### Chore 109 | 110 | * **deps:** maintain dependencies ([f3c18c5](https://github.com/prismicio/prismic-helpers/commit/f3c18c5679e897a7a1362206aaac82d0a3f4b218)) 111 | * **deps:** maintain dependencies ([1305d40](https://github.com/prismicio/prismic-helpers/commit/1305d4009bb18add6c6c88bf0dd95e81c140c51d)) 112 | * update template ([48c9b7a](https://github.com/prismicio/prismic-helpers/commit/48c9b7ab3522127998e2976bc381d866b1991983)) 113 | 114 | ## [2.2.0](https://github.com/prismicio/prismic-helpers/compare/v2.1.1...v2.2.0) (2022-02-25) 115 | 116 | 117 | ### Features 118 | 119 | * narrow return types when field is known ([#43](https://github.com/prismicio/prismic-helpers/issues/43)) ([40f41f9](https://github.com/prismicio/prismic-helpers/commit/40f41f98bcb17ce4bc3c582e52171834dcc75b2b)) 120 | * support nullish inputs for `isFilled` helpers ([#44](https://github.com/prismicio/prismic-helpers/issues/44)) ([6c5597b](https://github.com/prismicio/prismic-helpers/commit/6c5597b88db032a383fc52c6ea73304969311e42)) 121 | 122 | 123 | ### Chore 124 | 125 | * **deps:** update dependencies ([ebe24c3](https://github.com/prismicio/prismic-helpers/commit/ebe24c344adbbc71404eb6efd49d94556126bb4b)) 126 | * update license ([6026a86](https://github.com/prismicio/prismic-helpers/commit/6026a86316e1e80fd62a1b8ede9805a407c2bc22)) 127 | 128 | ### [2.1.1](https://github.com/prismicio/prismic-helpers/compare/v2.1.0...v2.1.1) (2022-02-04) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * `isFilled.image` thumbnail names should extend `string | null`, fixes [#41](https://github.com/prismicio/prismic-helpers/issues/41) ([659c212](https://github.com/prismicio/prismic-helpers/commit/659c212feb07a7abdf672da0976adec6dfaf9e8b)) 134 | 135 | 136 | ### Chore 137 | 138 | * **deps:** maintain dependencies ([ea814ca](https://github.com/prismicio/prismic-helpers/commit/ea814ca0c23917a0ab8a454c6bd3577708325f54)) 139 | 140 | ## [2.1.0](https://github.com/prismicio/prismic-helpers/compare/v2.0.1...v2.1.0) (2022-02-01) 141 | 142 | 143 | ### Features 144 | 145 | * add `asImageSrc()`, `asImageWidthSrcSet()`, `asImagePixelDensitySrcSet()` ([#38](https://github.com/prismicio/prismic-helpers/issues/38)) ([2b4984a](https://github.com/prismicio/prismic-helpers/commit/2b4984aaccdaba564cd57f875067626482c7660b)) 146 | 147 | 148 | ### Chore 149 | 150 | * revert to AVA 3 ([cccd34f](https://github.com/prismicio/prismic-helpers/commit/cccd34fced3811c77bcce7db20f4aa9a048f2148)) 151 | 152 | ### [2.0.1](https://github.com/prismicio/prismic-helpers/compare/v2.0.0...v2.0.1) (2022-01-28) 153 | 154 | 155 | ### Chore 156 | 157 | * add Size Limit repo support ([da22d5b](https://github.com/prismicio/prismic-helpers/commit/da22d5b4444fcc0cfc3d5a90777c88b6006837ad)) 158 | * **deps:** update `@prismicio/richtext` ([4c97a27](https://github.com/prismicio/prismic-helpers/commit/4c97a27b34b34ea73cd7989d526c9273d7f76370)) 159 | * **deps:** update dependencies ([ffbf66e](https://github.com/prismicio/prismic-helpers/commit/ffbf66e8711cd7d016a9982a715829b390bd152f)) 160 | * fix Size Limit integration ([660bd1e](https://github.com/prismicio/prismic-helpers/commit/660bd1ec1528146e55fd8f0bb2a19b577267a14d)) 161 | * support React Native's Metro bundler ([51151f9](https://github.com/prismicio/prismic-helpers/commit/51151f9929739b7fbfb09bf5e14704734d94a416)) 162 | 163 | ## [2.0.0](https://github.com/prismicio/prismic-helpers/compare/v1.0.3...v2.0.0) (2022-01-05) 164 | 165 | ## [2.0.0-beta.8](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2021-12-16) 166 | 167 | 168 | ### Features 169 | 170 | * add `isFilled` helpers ([#34](https://github.com/prismicio/prismic-helpers/issues/34)) ([fe086d2](https://github.com/prismicio/prismic-helpers/commit/fe086d25d82b88794b1f963974145a279623c902)) 171 | 172 | 173 | ### Documentation 174 | 175 | * update docs link [skip ci] ([662a077](https://github.com/prismicio/prismic-helpers/commit/662a0773e2f2268a8e676f496ba9ea7eacd1fad1)) 176 | 177 | ## [2.0.0-beta.7](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2021-12-06) 178 | 179 | 180 | ### Features 181 | 182 | * allow nullish field in `asHTML` and `asText` ([4c7c8bc](https://github.com/prismicio/prismic-helpers/commit/4c7c8bc95898dd6195b64f9774c6ee34f6917a41)) 183 | 184 | 185 | ### Bug Fixes 186 | 187 | * support documents without `slugs` in `documentToLinkField` ([a60f4be](https://github.com/prismicio/prismic-helpers/commit/a60f4be34acc20c9ec3a0111f02f8d9d1f874cbb)) 188 | 189 | ## [2.0.0-beta.6](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2021-12-03) 190 | 191 | 192 | ### Chore 193 | 194 | * **deps:** maintain dependencies ([dc71bc6](https://github.com/prismicio/prismic-helpers/commit/dc71bc693dfbf2fc093b07801909b1df9c7b6d27)) 195 | 196 | ## [2.0.0-beta.5](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2021-11-29) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * support nullish `slugs` property when passing a document to `asLink` ([85eedea](https://github.com/prismicio/prismic-helpers/commit/85eedead6ed988fa29c2feea39148009a4562cf6)) 202 | 203 | ## [2.0.0-beta.4](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2021-11-29) 204 | 205 | 206 | ### Features 207 | 208 | * export Elements as a deprecated alias for Element ([b6fbe0c](https://github.com/prismicio/prismic-helpers/commit/b6fbe0c9ebc245c37d6e271f72dacd39b2ca3f85)) 209 | 210 | ## [2.0.0-beta.3](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2021-11-10) 211 | 212 | 213 | ### Bug Fixes 214 | 215 | * prioritize Link Resolver over Route Resolver ([#30](https://github.com/prismicio/prismic-helpers/issues/30)) ([acc4a17](https://github.com/prismicio/prismic-helpers/commit/acc4a17fd4e45b1e188b4464548e33e5c61932d3)) 216 | 217 | ## [2.0.0-beta.2](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2021-11-05) 218 | 219 | 220 | ### Features 221 | 222 | * pass HTML serializer children as string ([c3634f9](https://github.com/prismicio/prismic-helpers/commit/c3634f9c444079d8f9892e7ec1d02e18bfe73c9d)) 223 | 224 | 225 | ### Refactor 226 | 227 | * use runtime-generated map serializer ([ff15253](https://github.com/prismicio/prismic-helpers/commit/ff15253cce791306ae03a6001f5bd03d1448a09e)) 228 | 229 | 230 | ### Documentation 231 | 232 | * update TSDoc for new HTML serializer types ([05bd482](https://github.com/prismicio/prismic-helpers/commit/05bd482865ae8ef21d6384a2fb9e40629d0e0d57)) 233 | 234 | 235 | ### Chore 236 | 237 | * **deps:** maintain dependencies ([2a8037f](https://github.com/prismicio/prismic-helpers/commit/2a8037f78aa5db74bfc9712516b2cb77f5062f32)) 238 | 239 | ## [2.0.0-beta.1](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2021-10-12) 240 | 241 | 242 | ### Bug Fixes 243 | 244 | * type issue with empty links in asLink ([542b4d9](https://github.com/prismicio/prismic-helpers/commit/542b4d9e7ad4f6654c7249598fee37a7a3392140)) 245 | 246 | 247 | ### Chore 248 | 249 | * **deps:** update dependencies ([6165b41](https://github.com/prismicio/prismic-helpers/commit/6165b41f80827212f92397e41b290a0d40343921)) 250 | * mark package as side effect free ([9636f9d](https://github.com/prismicio/prismic-helpers/commit/9636f9dab96ac32b2cb8c888c36c317bbf800de1)) 251 | 252 | 253 | ### Refactor 254 | 255 | * use currying to build HTML serializer ([b7ba08a](https://github.com/prismicio/prismic-helpers/commit/b7ba08af7de99e0c1296dfaf23d8c23f008fceb3)) 256 | 257 | ## [2.0.0-beta.0](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.9...v2.0.0-beta.0) (2021-09-29) 258 | 259 | 260 | ### Chore 261 | 262 | * **deps:** maintain dependencies ([eda08bb](https://github.com/prismicio/prismic-helpers/commit/eda08bbe0fae275bef5cef02ad2616e8aa3fe13e)) 263 | 264 | ## [2.0.0-alpha.9](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.8...v2.0.0-alpha.9) (2021-09-27) 265 | 266 | 267 | ### Features 268 | 269 | * remove documentAsLink ([5cdee16](https://github.com/prismicio/prismic-helpers/commit/5cdee1631863144c7ad67553cf171921c5569d3f)) 270 | * update asLink to handle documents ([ae14694](https://github.com/prismicio/prismic-helpers/commit/ae14694229a73a0cc46620bc98e37eac26e6f44b)) 271 | 272 | 273 | ### Chore 274 | 275 | * tag graphql functions as experimental to help with potential breaking changes ([b0432a4](https://github.com/prismicio/prismic-helpers/commit/b0432a49393e5a43d54bc3b013b7b5301cdae33d)) 276 | * update template config ([e05d381](https://github.com/prismicio/prismic-helpers/commit/e05d3819fb9efc4d50912c3fff595f80e56f6c57)) 277 | 278 | ## [2.0.0-alpha.8](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.7...v2.0.0-alpha.8) (2021-09-14) 279 | 280 | 281 | ### Chore 282 | 283 | * update dependencies ([1f7f24a](https://github.com/prismicio/prismic-helpers/commit/1f7f24a2321d12303ab2dfb795574b03ac7f249d)) 284 | 285 | ## [2.0.0-alpha.7](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) (2021-08-12) 286 | 287 | 288 | ### Features 289 | 290 | * allow explicit null for linkResolver & HTML serializer ([275bfca](https://github.com/prismicio/prismic-helpers/commit/275bfca7d527c4d8266394952d2cc0ebdfadd853)) 291 | 292 | ## [2.0.0-alpha.6](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.5...v2.0.0-alpha.6) (2021-08-11) 293 | 294 | 295 | ### Features 296 | 297 | * export serializer elements types for convenience ([715f11d](https://github.com/prismicio/prismic-helpers/commit/715f11db3776a189fcc9f59e835f08900e3059be)) 298 | 299 | 300 | ### Bug Fixes 301 | 302 | * prevent XSS on image tag serialization ([c0e6d6d](https://github.com/prismicio/prismic-helpers/commit/c0e6d6d6d8d36747de381cc6acd3c7fec6d839d2)) 303 | 304 | 305 | ### Chore 306 | 307 | * update pull request template ([8330ddd](https://github.com/prismicio/prismic-helpers/commit/8330ddd31db54760bf9d92e2c597c63a8a3e32ba)) 308 | 309 | ## [2.0.0-alpha.5](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2021-07-07) 310 | 311 | 312 | ### Features 313 | 314 | * add documentAsLink function, closes [#20](https://github.com/prismicio/prismic-helpers/issues/20) ([befcd75](https://github.com/prismicio/prismic-helpers/commit/befcd75404a743e37eb1a54ab32a3ef2c7dbc538)) 315 | * handle link on image ([1a2fb56](https://github.com/prismicio/prismic-helpers/commit/1a2fb56d48749010bcab1fd968476fd142165f22)) 316 | 317 | 318 | ### Bug Fixes 319 | 320 | * also export types for graphql export ([124f837](https://github.com/prismicio/prismic-helpers/commit/124f8376f3be3b1d85ecbf8a555ce034e133c369)) 321 | * escape external links to prevent xss ([0cb7c43](https://github.com/prismicio/prismic-helpers/commit/0cb7c43b42d1f3ab42251441f6831a0d35c62dcb)) 322 | 323 | 324 | ### Documentation 325 | 326 | * document asHTML new helper ([d40d3c6](https://github.com/prismicio/prismic-helpers/commit/d40d3c6f888091a7d1ed594417ff6e66f7073207)) 327 | 328 | 329 | ### Chore 330 | 331 | * **deps:** examples dependencies ([76e6b41](https://github.com/prismicio/prismic-helpers/commit/76e6b41e0c5e36baf7a12a4ed1a08477a3dbb103)) 332 | * ignore examples lock files ([699febc](https://github.com/prismicio/prismic-helpers/commit/699febc08fd5699e3d62d723ad517aa962e79294)) 333 | 334 | ## [2.0.0-alpha.4](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2021-07-03) 335 | 336 | 337 | ### Chore 338 | 339 | * update dependencies ([49349ac](https://github.com/prismicio/prismic-helpers/commit/49349ac583c04130d43cfc7026a2a6588f2cff0f)) 340 | 341 | ## [2.0.0-alpha.3](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2021-07-03) 342 | 343 | 344 | ### Bug Fixes 345 | 346 | * optional Link Resolver for asLink and asHTML ([#22](https://github.com/prismicio/prismic-helpers/issues/22)) ([b007ff1](https://github.com/prismicio/prismic-helpers/commit/b007ff1dbadc469aa0854794bcc58297946b8bda)), closes [#21](https://github.com/prismicio/prismic-helpers/issues/21) 347 | * retain data field in documentToLinkField ([#23](https://github.com/prismicio/prismic-helpers/issues/23)) ([81b0706](https://github.com/prismicio/prismic-helpers/commit/81b07069e3f260f03f84abd6d33aa52ff97e7b29)), closes [#19](https://github.com/prismicio/prismic-helpers/issues/19) 348 | 349 | ## [2.0.0-alpha.2](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.1...v2.0.0-alpha.2) (2021-07-01) 350 | 351 | 352 | ### Chore 353 | 354 | * update dependencies ([8ed3e88](https://github.com/prismicio/prismic-helpers/commit/8ed3e88deacc48db2cec1a7038e4de598de4e259)) 355 | 356 | ## [2.0.0-alpha.1](https://github.com/prismicio/prismic-helpers/compare/v2.0.0-alpha.0...v2.0.0-alpha.1) (2021-06-29) 357 | 358 | 359 | ### Bug Fixes 360 | 361 | * update dependencies and @prismicio/richtext types ([f30aa18](https://github.com/prismicio/prismic-helpers/commit/f30aa181635401b16b88d96e7e50c398e5fc0ba0)) 362 | 363 | 364 | ### Refactor 365 | 366 | * extract serializer helpers into a dedicated file ([5d1d2a8](https://github.com/prismicio/prismic-helpers/commit/5d1d2a849236aba7d928a1e27a45f132abca3582)) 367 | * migrate asHTML to @prismicio/richtext@2 ([a565d02](https://github.com/prismicio/prismic-helpers/commit/a565d02ef51b8609719a1f96ad408b8994b664e3)) 368 | * migrate asHtml to typescript ([f589b05](https://github.com/prismicio/prismic-helpers/commit/f589b05047e9979900899221e73b4242d1cdc445)) 369 | * provide asText from @prismicio/richtext directly ([987105f](https://github.com/prismicio/prismic-helpers/commit/987105f07f57e722f1d392fac09f149bc289ad37)) 370 | 371 | 372 | ### Chore 373 | 374 | * migrate to npm ([8a41c1a](https://github.com/prismicio/prismic-helpers/commit/8a41c1a6e1916972f9d3f9979b01cc0cac694a37)) 375 | * replace yarn scripts with npm ([9756c13](https://github.com/prismicio/prismic-helpers/commit/9756c139cb2027117da9799de6c59ff4a9283016)) 376 | * **deps:** maintain dependencies ([a3aacb3](https://github.com/prismicio/prismic-helpers/commit/a3aacb3e825f9747e1dd0e008ceb3971d184c42b)) 377 | * typo on config ([010cb0b](https://github.com/prismicio/prismic-helpers/commit/010cb0bab6fb3ac42e5fec564ddb792476605bbf)) 378 | 379 | ## [2.0.0-alpha.0](https://github.com/prismicio/prismic-helpers/compare/v1.0.3...v2.0.0-alpha.0) (2021-05-27) 380 | 381 | 382 | ### ⚠ BREAKING CHANGES 383 | 384 | * migrate existing functions to typescript 385 | 386 | ### Features 387 | 388 | * **examples:** add examples directory ([6f2e6ca](https://github.com/prismicio/prismic-helpers/commit/6f2e6ca64eba4bf97bd846b20a8a646b6f60568e)) 389 | * add graphql support ([45858a9](https://github.com/prismicio/prismic-helpers/commit/45858a94527724f70cd6024a78d69e819fa08b75)) 390 | * allow asLink to also resolve prismic documents (experimental) ([ed5ad52](https://github.com/prismicio/prismic-helpers/commit/ed5ad529beae96903eded5f6fcc4af1cb72e964c)) 391 | * allow return type to be specified for link resolver ([be75fb5](https://github.com/prismicio/prismic-helpers/commit/be75fb5d21514dc3dc1da89fe09bbc60332eafdc)) 392 | 393 | 394 | ### Bug Fixes 395 | 396 | * handle link field edge cases on asLink ([83f1a61](https://github.com/prismicio/prismic-helpers/commit/83f1a61627b8b97518997c145fbd83161057724e)) 397 | 398 | 399 | ### Refactor 400 | 401 | * **examples:** refactor example directory ([01051cb](https://github.com/prismicio/prismic-helpers/commit/01051cb1651c4bd0b42529b0df5824c5b889111b)) 402 | * **graphql:** refactor graphql asLink resolving ([b0ada1d](https://github.com/prismicio/prismic-helpers/commit/b0ada1dd7f02a90c0308fe07f377a59e0dbc682c)) 403 | * extract document cast to link field into its own function ([ef16ef5](https://github.com/prismicio/prismic-helpers/commit/ef16ef58ed730df6690c08fc37a4b65f4e5027e1)) 404 | * migrate existing functions to typescript ([a52cc56](https://github.com/prismicio/prismic-helpers/commit/a52cc564419df8ef4e2720e809d7d2368590e8b7)) 405 | * move ArgumentError to lib folder ([8726268](https://github.com/prismicio/prismic-helpers/commit/8726268d927de25e8ee63a0db824cb520a4ab428)) 406 | 407 | 408 | ### Documentation 409 | 410 | * **examples:** add graphql examples ([123eeb7](https://github.com/prismicio/prismic-helpers/commit/123eeb7840ae3c326c821e16f574e52342c98ba9)) 411 | * add todo ([b757e27](https://github.com/prismicio/prismic-helpers/commit/b757e27ca40ec960687b3eca692be3a3e104840c)) 412 | * document linkResolverFunction ([3172393](https://github.com/prismicio/prismic-helpers/commit/3172393703f5ac230b12b412571725b75bc24b39)) 413 | * update readme and issue template ([aeacfd3](https://github.com/prismicio/prismic-helpers/commit/aeacfd38cacef66419100fbf3c29f1fbc4fafe91)) 414 | 415 | 416 | ### Chore 417 | 418 | * **deps:** bump @prismicio/types ([7b5f470](https://github.com/prismicio/prismic-helpers/commit/7b5f470cfd670b6bb9c30efad3b32329463407b3)) 419 | * maintain dependencies ([12ca991](https://github.com/prismicio/prismic-helpers/commit/12ca991e3f51dace0eb8bdfa354328c1949294cd)) 420 | * revamp repo structure ([b9c05ca](https://github.com/prismicio/prismic-helpers/commit/b9c05ca8024c953bb27140c1e6fe2e170b35d4fc)) 421 | * update eslint config ([2397998](https://github.com/prismicio/prismic-helpers/commit/239799851757a6d69ade87ea0a56f65b56765e79)) 422 | * **deps:** maintain dependencies ([b76687b](https://github.com/prismicio/prismic-helpers/commit/b76687b8d28019db556699bff346f6b44486f9bb)) 423 | * update template ([3342a50](https://github.com/prismicio/prismic-helpers/commit/3342a50efa3e2bcd97aa4e4e9529fd8cf3ef56fb)) 424 | * **config:** update .eslintignore ([c5a9f3e](https://github.com/prismicio/prismic-helpers/commit/c5a9f3e4ee479ac0c54f3a26a5b4b9b4163f7734)) 425 | * **deps:** maintain dependencies ([87e7f77](https://github.com/prismicio/prismic-helpers/commit/87e7f775c3d1169f6ebe2dd61a053fbc0a493bfe)) 426 | * **deps:** upgrade @prismicio/types ([d4dea1c](https://github.com/prismicio/prismic-helpers/commit/d4dea1cd892f7f5ccd416b77d22fa3b2e5468f72)) 427 | -------------------------------------------------------------------------------- /test/__fixtures__/enRichText.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "heading1", 4 | "text": "Lorem ipsum dolor sit amet.", 5 | "spans": [] 6 | }, 7 | { 8 | "type": "heading2", 9 | "text": "Lorem ipsum dolor sit amet.", 10 | "spans": [] 11 | }, 12 | { 13 | "type": "heading3", 14 | "text": "Lorem ipsum dolor sit amet.", 15 | "spans": [] 16 | }, 17 | { 18 | "type": "heading4", 19 | "text": "Lorem ipsum dolor sit amet.", 20 | "spans": [] 21 | }, 22 | { 23 | "type": "heading5", 24 | "text": "Lorem ipsum dolor sit amet.", 25 | "spans": [] 26 | }, 27 | { 28 | "type": "heading6", 29 | "text": "Lorem ipsum dolor sit amet.", 30 | "spans": [] 31 | }, 32 | { 33 | "type": "paragraph", 34 | "text": "Lorem ipsum dolor sit amet.", 35 | "spans": [] 36 | }, 37 | { 38 | "type": "paragraph", 39 | "text": "", 40 | "spans": [] 41 | }, 42 | { 43 | "type": "heading1", 44 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 45 | "spans": [ 46 | { 47 | "start": 18, 48 | "end": 26, 49 | "type": "strong" 50 | }, 51 | { 52 | "start": 40, 53 | "end": 55, 54 | "type": "em" 55 | }, 56 | { 57 | "start": 110, 58 | "end": 122, 59 | "type": "label", 60 | "data": { 61 | "label": "label" 62 | } 63 | }, 64 | { 65 | "start": 213, 66 | "end": 230, 67 | "type": "hyperlink", 68 | "data": { 69 | "link_type": "Web", 70 | "url": "https://google.com" 71 | } 72 | }, 73 | { 74 | "start": 319, 75 | "end": 333, 76 | "type": "hyperlink", 77 | "data": { 78 | "id": "XvoFFREAAM0WGBng", 79 | "type": "page", 80 | "tags": ["test"], 81 | "slug": "have-you-heard-of-that-bird-puffin", 82 | "lang": "en-us", 83 | "uid": "home", 84 | "link_type": "Document", 85 | "isBroken": false 86 | } 87 | }, 88 | { 89 | "start": 369, 90 | "end": 381, 91 | "type": "hyperlink", 92 | "data": { 93 | "link_type": "Media", 94 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 95 | "kind": "image", 96 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 97 | "size": "252237", 98 | "height": "1602", 99 | "width": "2400" 100 | } 101 | } 102 | ] 103 | }, 104 | { 105 | "type": "paragraph", 106 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 107 | "spans": [ 108 | { 109 | "start": 18, 110 | "end": 26, 111 | "type": "strong" 112 | }, 113 | { 114 | "start": 40, 115 | "end": 55, 116 | "type": "em" 117 | }, 118 | { 119 | "start": 110, 120 | "end": 122, 121 | "type": "label", 122 | "data": { 123 | "label": "testLabel" 124 | } 125 | }, 126 | { 127 | "start": 213, 128 | "end": 230, 129 | "type": "hyperlink", 130 | "data": { 131 | "link_type": "Web", 132 | "url": "https://google.com" 133 | } 134 | }, 135 | { 136 | "start": 319, 137 | "end": 333, 138 | "type": "hyperlink", 139 | "data": { 140 | "id": "XvoFFREAAM0WGBng", 141 | "type": "page", 142 | "tags": ["test"], 143 | "slug": "have-you-heard-of-that-bird-puffin", 144 | "lang": "en-us", 145 | "uid": "home", 146 | "link_type": "Document", 147 | "isBroken": false 148 | } 149 | }, 150 | { 151 | "start": 369, 152 | "end": 381, 153 | "type": "hyperlink", 154 | "data": { 155 | "link_type": "Media", 156 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 157 | "kind": "image", 158 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 159 | "size": "252237", 160 | "height": "1602", 161 | "width": "2400" 162 | } 163 | } 164 | ] 165 | }, 166 | { 167 | "type": "o-list-item", 168 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 169 | "spans": [ 170 | { 171 | "start": 18, 172 | "end": 26, 173 | "type": "strong" 174 | }, 175 | { 176 | "start": 40, 177 | "end": 55, 178 | "type": "em" 179 | }, 180 | { 181 | "start": 110, 182 | "end": 122, 183 | "type": "label", 184 | "data": { 185 | "label": "label" 186 | } 187 | }, 188 | { 189 | "start": 213, 190 | "end": 230, 191 | "type": "hyperlink", 192 | "data": { 193 | "link_type": "Web", 194 | "url": "https://google.com" 195 | } 196 | }, 197 | { 198 | "start": 319, 199 | "end": 333, 200 | "type": "hyperlink", 201 | "data": { 202 | "id": "XvoFFREAAM0WGBng", 203 | "type": "page", 204 | "tags": ["test"], 205 | "slug": "have-you-heard-of-that-bird-puffin", 206 | "lang": "en-us", 207 | "uid": "home", 208 | "link_type": "Document", 209 | "isBroken": false 210 | } 211 | }, 212 | { 213 | "start": 369, 214 | "end": 381, 215 | "type": "hyperlink", 216 | "data": { 217 | "link_type": "Media", 218 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 219 | "kind": "image", 220 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 221 | "size": "252237", 222 | "height": "1602", 223 | "width": "2400" 224 | } 225 | } 226 | ] 227 | }, 228 | { 229 | "type": "o-list-item", 230 | "text": "Lorem ipsum dolor sit amet.", 231 | "spans": [] 232 | }, 233 | { 234 | "type": "o-list-item", 235 | "text": "Lorem ipsum dolor sit amet.", 236 | "spans": [] 237 | }, 238 | { 239 | "type": "list-item", 240 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 241 | "spans": [ 242 | { 243 | "start": 18, 244 | "end": 26, 245 | "type": "strong" 246 | }, 247 | { 248 | "start": 40, 249 | "end": 55, 250 | "type": "em" 251 | }, 252 | { 253 | "start": 110, 254 | "end": 122, 255 | "type": "label", 256 | "data": { 257 | "label": "label" 258 | } 259 | }, 260 | { 261 | "start": 213, 262 | "end": 230, 263 | "type": "hyperlink", 264 | "data": { 265 | "link_type": "Web", 266 | "url": "https://google.com" 267 | } 268 | }, 269 | { 270 | "start": 319, 271 | "end": 333, 272 | "type": "hyperlink", 273 | "data": { 274 | "id": "XvoFFREAAM0WGBng", 275 | "type": "page", 276 | "tags": ["test"], 277 | "slug": "have-you-heard-of-that-bird-puffin", 278 | "lang": "en-us", 279 | "uid": "home", 280 | "link_type": "Document", 281 | "isBroken": false 282 | } 283 | }, 284 | { 285 | "start": 369, 286 | "end": 381, 287 | "type": "hyperlink", 288 | "data": { 289 | "link_type": "Media", 290 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 291 | "kind": "image", 292 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 293 | "size": "252237", 294 | "height": "1602", 295 | "width": "2400" 296 | } 297 | } 298 | ] 299 | }, 300 | { 301 | "type": "list-item", 302 | "text": "Lorem ipsum dolor sit amet.", 303 | "spans": [] 304 | }, 305 | { 306 | "type": "list-item", 307 | "text": "Lorem ipsum dolor sit amet.", 308 | "spans": [] 309 | }, 310 | { 311 | "type": "paragraph", 312 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 313 | "spans": [ 314 | { 315 | "start": 18, 316 | "end": 26, 317 | "type": "strong" 318 | }, 319 | { 320 | "start": 40, 321 | "end": 55, 322 | "type": "em" 323 | }, 324 | { 325 | "start": 110, 326 | "end": 122, 327 | "type": "label", 328 | "data": { 329 | "label": "label" 330 | } 331 | }, 332 | { 333 | "start": 213, 334 | "end": 230, 335 | "type": "hyperlink", 336 | "data": { 337 | "link_type": "Web", 338 | "url": "https://google.com" 339 | } 340 | }, 341 | { 342 | "start": 319, 343 | "end": 333, 344 | "type": "hyperlink", 345 | "data": { 346 | "id": "XvoFFREAAM0WGBng", 347 | "type": "page", 348 | "tags": ["test"], 349 | "slug": "have-you-heard-of-that-bird-puffin", 350 | "lang": "en-us", 351 | "uid": "home", 352 | "link_type": "Document", 353 | "isBroken": false 354 | } 355 | }, 356 | { 357 | "start": 369, 358 | "end": 381, 359 | "type": "hyperlink", 360 | "data": { 361 | "link_type": "Media", 362 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 363 | "kind": "image", 364 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 365 | "size": "252237", 366 | "height": "1602", 367 | "width": "2400" 368 | } 369 | } 370 | ], 371 | "direction": "rtl" 372 | }, 373 | { 374 | "type": "paragraph", 375 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 376 | "spans": [ 377 | { 378 | "start": 18, 379 | "end": 26, 380 | "type": "strong" 381 | }, 382 | { 383 | "start": 40, 384 | "end": 55, 385 | "type": "em" 386 | }, 387 | { 388 | "start": 110, 389 | "end": 122, 390 | "type": "label", 391 | "data": { 392 | "label": "label" 393 | } 394 | }, 395 | { 396 | "start": 213, 397 | "end": 230, 398 | "type": "hyperlink", 399 | "data": { 400 | "link_type": "Web", 401 | "url": "https://google.com" 402 | } 403 | }, 404 | { 405 | "start": 319, 406 | "end": 333, 407 | "type": "hyperlink", 408 | "data": { 409 | "id": "XvoFFREAAM0WGBng", 410 | "type": "page", 411 | "tags": ["test"], 412 | "slug": "have-you-heard-of-that-bird-puffin", 413 | "lang": "en-us", 414 | "uid": "home", 415 | "link_type": "Document", 416 | "isBroken": false 417 | } 418 | }, 419 | { 420 | "start": 369, 421 | "end": 381, 422 | "type": "hyperlink", 423 | "data": { 424 | "link_type": "Media", 425 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 426 | "kind": "image", 427 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 428 | "size": "252237", 429 | "height": "1602", 430 | "width": "2400" 431 | } 432 | }, 433 | { 434 | "start": 406, 435 | "end": 445, 436 | "type": "label", 437 | "data": { 438 | "label": "testLabel" 439 | } 440 | } 441 | ] 442 | }, 443 | { 444 | "type": "image", 445 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 446 | "alt": "An Atlantic Puffin", 447 | "copyright": null, 448 | "dimensions": { 449 | "width": 2400, 450 | "height": 1602 451 | } 452 | }, 453 | { 454 | "type": "image", 455 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 456 | "alt": "An Atlantic Puffin", 457 | "copyright": "Unsplash", 458 | "dimensions": { 459 | "width": 2400, 460 | "height": 1602 461 | } 462 | }, 463 | { 464 | "type": "paragraph", 465 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 466 | "spans": [ 467 | { 468 | "start": 0, 469 | "end": 36, 470 | "type": "label", 471 | "data": { 472 | "label": "testLabel" 473 | } 474 | }, 475 | { 476 | "start": 18, 477 | "end": 26, 478 | "type": "strong" 479 | }, 480 | { 481 | "start": 40, 482 | "end": 55, 483 | "type": "em" 484 | }, 485 | { 486 | "start": 110, 487 | "end": 122, 488 | "type": "label", 489 | "data": { 490 | "label": "label" 491 | } 492 | }, 493 | { 494 | "start": 213, 495 | "end": 230, 496 | "type": "hyperlink", 497 | "data": { 498 | "link_type": "Web", 499 | "url": "https://google.com" 500 | } 501 | }, 502 | { 503 | "start": 319, 504 | "end": 333, 505 | "type": "hyperlink", 506 | "data": { 507 | "id": "XvoFFREAAM0WGBng", 508 | "type": "page", 509 | "tags": ["test"], 510 | "slug": "have-you-heard-of-that-bird-puffin", 511 | "lang": "en-us", 512 | "uid": "home", 513 | "link_type": "Document", 514 | "isBroken": false 515 | } 516 | }, 517 | { 518 | "start": 369, 519 | "end": 381, 520 | "type": "hyperlink", 521 | "data": { 522 | "link_type": "Media", 523 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 524 | "kind": "image", 525 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 526 | "size": "252237", 527 | "height": "1602", 528 | "width": "2400" 529 | } 530 | } 531 | ] 532 | }, 533 | { 534 | "type": "image", 535 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 536 | "alt": "An Atlantic Puffin", 537 | "copyright": null, 538 | "dimensions": { 539 | "width": 2400, 540 | "height": 1602 541 | } 542 | }, 543 | { 544 | "type": "paragraph", 545 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 546 | "spans": [ 547 | { 548 | "start": 0, 549 | "end": 36, 550 | "type": "label", 551 | "data": { 552 | "label": "label" 553 | } 554 | }, 555 | { 556 | "start": 18, 557 | "end": 26, 558 | "type": "strong" 559 | }, 560 | { 561 | "start": 40, 562 | "end": 55, 563 | "type": "em" 564 | }, 565 | { 566 | "start": 110, 567 | "end": 122, 568 | "type": "label", 569 | "data": { 570 | "label": "label" 571 | } 572 | }, 573 | { 574 | "start": 213, 575 | "end": 230, 576 | "type": "hyperlink", 577 | "data": { 578 | "link_type": "Web", 579 | "url": "https://google.com" 580 | } 581 | }, 582 | { 583 | "start": 319, 584 | "end": 333, 585 | "type": "hyperlink", 586 | "data": { 587 | "id": "XvoFFREAAM0WGBng", 588 | "type": "page", 589 | "tags": ["test"], 590 | "slug": "have-you-heard-of-that-bird-puffin", 591 | "lang": "en-us", 592 | "uid": "home", 593 | "link_type": "Document", 594 | "isBroken": false 595 | } 596 | }, 597 | { 598 | "start": 369, 599 | "end": 381, 600 | "type": "hyperlink", 601 | "data": { 602 | "link_type": "Media", 603 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 604 | "kind": "image", 605 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 606 | "size": "252237", 607 | "height": "1602", 608 | "width": "2400" 609 | } 610 | } 611 | ] 612 | }, 613 | { 614 | "type": "preformatted", 615 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 616 | "spans": [] 617 | }, 618 | { 619 | "type": "paragraph", 620 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 621 | "spans": [ 622 | { 623 | "start": 0, 624 | "end": 36, 625 | "type": "label", 626 | "data": { 627 | "label": "label" 628 | } 629 | }, 630 | { 631 | "start": 18, 632 | "end": 26, 633 | "type": "strong" 634 | }, 635 | { 636 | "start": 40, 637 | "end": 55, 638 | "type": "em" 639 | }, 640 | { 641 | "start": 110, 642 | "end": 122, 643 | "type": "label", 644 | "data": { 645 | "label": "label" 646 | } 647 | }, 648 | { 649 | "start": 213, 650 | "end": 230, 651 | "type": "hyperlink", 652 | "data": { 653 | "link_type": "Web", 654 | "url": "https://google.com" 655 | } 656 | }, 657 | { 658 | "start": 319, 659 | "end": 333, 660 | "type": "hyperlink", 661 | "data": { 662 | "id": "XvoFFREAAM0WGBng", 663 | "type": "page", 664 | "tags": ["test"], 665 | "slug": "have-you-heard-of-that-bird-puffin", 666 | "lang": "en-us", 667 | "uid": "home", 668 | "link_type": "Document", 669 | "isBroken": false 670 | } 671 | }, 672 | { 673 | "start": 369, 674 | "end": 381, 675 | "type": "hyperlink", 676 | "data": { 677 | "link_type": "Media", 678 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 679 | "kind": "image", 680 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 681 | "size": "252237", 682 | "height": "1602", 683 | "width": "2400" 684 | } 685 | } 686 | ] 687 | }, 688 | { 689 | "type": "embed", 690 | "oembed": { 691 | "type": "video", 692 | "embed_url": "https://www.youtube.com/watch?v=n5CwXuyNfoc", 693 | "title": "[MV] REOL - ギミアブレスタッナウ/ Give me a break Stop now", 694 | "provider_name": "YouTube", 695 | "thumbnail_url": "https://i.ytimg.com/vi/n5CwXuyNfoc/hqdefault.jpg", 696 | "provider_url": "https://www.youtube.com/", 697 | "author_name": "Reol Official", 698 | "author_url": "https://www.youtube.com/user/reolch", 699 | "height": 113, 700 | "width": 200, 701 | "version": "1.0", 702 | "thumbnail_height": 360, 703 | "thumbnail_width": 480, 704 | "html": "" 705 | } 706 | }, 707 | { 708 | "type": "paragraph", 709 | "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 710 | "spans": [ 711 | { 712 | "start": 0, 713 | "end": 36, 714 | "type": "label", 715 | "data": { 716 | "label": "label" 717 | } 718 | }, 719 | { 720 | "start": 18, 721 | "end": 26, 722 | "type": "strong" 723 | }, 724 | { 725 | "start": 40, 726 | "end": 55, 727 | "type": "em" 728 | }, 729 | { 730 | "start": 110, 731 | "end": 122, 732 | "type": "label", 733 | "data": { 734 | "label": "label" 735 | } 736 | }, 737 | { 738 | "start": 213, 739 | "end": 230, 740 | "type": "hyperlink", 741 | "data": { 742 | "link_type": "Web", 743 | "url": "https://google.com" 744 | } 745 | }, 746 | { 747 | "start": 319, 748 | "end": 333, 749 | "type": "hyperlink", 750 | "data": { 751 | "id": "XvoFFREAAM0WGBng", 752 | "type": "page", 753 | "tags": ["test"], 754 | "slug": "have-you-heard-of-that-bird-puffin", 755 | "lang": "en-us", 756 | "uid": "home", 757 | "link_type": "Document", 758 | "isBroken": false 759 | } 760 | }, 761 | { 762 | "start": 369, 763 | "end": 381, 764 | "type": "hyperlink", 765 | "data": { 766 | "link_type": "Media", 767 | "name": "guilherme-romano-KI2KaOeT670-unsplash.jpg", 768 | "kind": "image", 769 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 770 | "size": "252237", 771 | "height": "1602", 772 | "width": "2400" 773 | } 774 | } 775 | ] 776 | }, 777 | { 778 | "type": "image", 779 | "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=compress,format", 780 | "alt": "An Atlantic Puffin", 781 | "copyright": "Unsplash", 782 | "dimensions": { 783 | "width": 2400, 784 | "height": 1602 785 | }, 786 | "linkTo": { 787 | "link_type": "Web", 788 | "url": "https://google.com", 789 | "target": "_blank" 790 | } 791 | } 792 | ] 793 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Prismic 2 | 3 | This document is aimed at providing developers (mainly maintainers) documentation on this project and its structure. It is not intended to pass requirements for contributing to this project. 4 | 5 | For the latter, the [Quick Start](#quick-start) section below can help you. You are free to [open issues][repo-issue] and [submit pull requests][repo-pull-requests] toward the `master` branch directly without worrying about our standards. For pull requests, we will help you through our merging process. 6 | 7 | > For a Table of Contents, use GitHub's TOC button, top left of the document. 8 | 9 | ## Quick Start 10 | 11 | ```bash 12 | # First, fork the repository to your GitHub account if you aren't an existing maintainer 13 | 14 | # Clone your fork 15 | git clone git@github.com:/prismic-helpers.git 16 | 17 | # Create a feature branch with your initials and feature name 18 | git checkout -b / # e.g. `lh/fix-win32-paths` 19 | 20 | # Install dependencies with npm 21 | npm install 22 | 23 | # Test your changes 24 | npm run test 25 | 26 | # Commit your changes once they are ready 27 | # Conventional Commits are encouraged, but not required 28 | git add . 29 | git commit -m "short description of your changes" 30 | 31 | # Lastly, open a pull request on GitHub describing your changes 32 | ``` 33 | 34 | ## Processes 35 | 36 | Processes refer to tasks that you may need to perform while working on this project. 37 | 38 | ### Developing 39 | 40 | There is no development branch. The `master` branch refers to the latest [stable (living) version](#stable-xxx) of the project and pull requests should be made against it. 41 | 42 | If development on a [new major version](#iteration-cycle) has begun, a branch named after the major version will be created (e.g. `v2` for work on the future `v2.0.0` of the project). Pull requests targeting that new major version should be made against it. 43 | 44 | To develop locally: 45 | 46 | 1. **If you have maintainer access**:
[Clone the repository](https://help.github.com/articles/cloning-a-repository) to your local environment. 47 | 48 | **If you do no have maintainer access**:
[Fork](https://help.github.com/articles/fork-a-repo) and [clone](https://help.github.com/articles/cloning-a-repository) the repository to your local environment. 49 | 50 | 2. Create a new branch: 51 | 52 | ```bash 53 | git checkout -b / # e.g. `aa/graphql-support` 54 | ``` 55 | 56 | 3. Install dependencies with [npm][npm] (avoid using [Yarn][yarn]): 57 | 58 | ```bash 59 | npm install 60 | ``` 61 | 62 | 4. Start developing: 63 | 64 | ```bash 65 | npm run dev 66 | ``` 67 | 68 | 5. Commit your changes: 69 | 70 | If you already know the [Conventional Commits convention][conventional-commits], feel free to embrace it for your commit messages. If you don't, no worries; it can always be taken care of when the pull request is merged. 71 | 72 | ### Building 73 | 74 | Our build system is handled by [Vite][vite]. Vite takes care of: 75 | 76 | - Generating a [CommonJS (`.cjs`)](https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules) bundle and its source map; 77 | - Generating an [ECMAScript (`.mjs`)](https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules) bundle and its source map; 78 | - Generating a TypeScript declaration (`.d.ts`) file. 79 | 80 | To build the project: 81 | 82 | ```bash 83 | npm run build 84 | ``` 85 | 86 | The CI system will try to build the project on each commit targeting the `master` branch. The CI check will fail if the build does. 87 | 88 | ### Testing 89 | 90 | All projects have at least linting and unit tests. Linting is handled by [ESLint][eslint] with [Prettier][prettier] and unit tests are handled by [Vitest][vitest]. 91 | 92 | To run all tests: 93 | 94 | ```bash 95 | npm run test 96 | ``` 97 | 98 | If you'd like to run only the linter (note that it won't reformat your code; use the `format` script for that): 99 | 100 | ```bash 101 | npm run lint 102 | ``` 103 | 104 | If you'd like to run only the unit tests: 105 | 106 | ```bash 107 | npm run unit 108 | ``` 109 | 110 | If you'd like to run only the unit tests in watch mode (re-runs tests each time a file is saved): 111 | 112 | ```bash 113 | npm run unit:watch 114 | ``` 115 | 116 | When working on unit tests, you might want to update snapshots (be careful when doing so): 117 | 118 | ```bash 119 | npm run unit -- --update-snapshots 120 | ``` 121 | 122 | The CI system will run tests on each commit targeting the `master` branch. The CI check will fail if any test does. 123 | 124 | ### Publishing 125 | 126 | > ⚠  Only project maintainers with at least collaborator access to the related npm package can publish new versions. 127 | 128 | Publishing a package correctly involves multiple steps: 129 | 130 | - Writing a changelog; 131 | - [Building](#building) the project; 132 | - Publishing a new version tag to GitHub; 133 | - Publishing build artifacts to [npm][npm]. 134 | 135 | In order to make sure all these steps are consistently and correctly done, we use [Standard Version][standard-version] and build scripts. 136 | 137 | To release a new version of the project: 138 | 139 | ```bash 140 | npm run release 141 | ``` 142 | 143 | To release a new [alpha](#alpha-xxx-alphax) version of the project: 144 | 145 | ```bash 146 | npm run release:alpha 147 | ``` 148 | 149 | Those scripts will: 150 | 151 | - Run tests and try to build the project; 152 | - Figure out the new version number by looking at commit messages matching the [Conventional Commits convention][conventional-commits]; 153 | - Write the [changelog][changelog] for the new version after Conventional Commit messages; 154 | - Build the project for publishing; 155 | - Publish a new version tag to GitHub; 156 | - Publish build artifacts to [npm][npm]. 157 | 158 | Once a script has been run successfully, a new version of the package should have been published to npm. To complete the publishing process you only need to head to the repository's releases tab on GitHub to publish the new version tag that was created. 159 | 160 | If you ran any of those commands but happen not to have access to the related npm package, you can still ask a collaborator of the said package to publish it for you. 161 | 162 | Appending `:dry` (e.g. `release:dry`) to any of the above commands will dry-run the targeted release script and output the new changelog to the console. 163 | 164 | We consider maintaining project dependencies before publishing a new version a best practice. 165 | 166 | ### Maintaining 167 | 168 | Anyone can, and is welcome to, contribute to the project by opening bug reports and submitting feature requests. To remain focused and ensure we are able to respond to each contribution, we have adopted the following framework to maintain this package: 169 | 170 | **🚨  Bug reports** 171 | 172 | > **Note**: An automated reply is posted when a bug report is opened to explain our maintenance schedule. 173 | 174 | Every Wednesday is _bug squashing day_. During this day, we respond to and/or fix bug reports. 175 | 176 | At the end of each Wednesday (assuming there were issues to fix), or later during the week if reviews are required, a _patch_ version is [released](#publishing) containing any fixes that were needed. Releasing multiple patches during the same week should be avoided. 177 | 178 | Ideally, all opened bug reports are addressed each Wednesday. If a particular bug report is not able to be resolved in that timeframe, maintainers are free to continue working on the issue or to report back to it next Wednesday. Overall, while most issues should be closed within _7 days_, we consider up to _14 days_ to get back to and address an issue a reasonable delay. Beyond that threshold, an issue is considered problematic and will be given more attention. 179 | 180 | **🙋‍♀️  Feature requests** 181 | 182 | > **Note**: An automated message gets sent to people creating feature requests about this process. 183 | 184 | Every last week of a month is _feature week_. During this week, we implement new features. Discussing and coming up with implementation proposals can happen before that week, but implementations are targeted for the last week. 185 | 186 | At the end of the week (assuming there were features to implement), a _minor_ version is [released](#publishing) containing the new features. Releasing multiple minors during the same week should be avoided. 187 | 188 | Ideally, all opened feature requests are discussed each month and implemented if consensus was reached. Unlike bug reports, we do not consider delays to address feature requests as good or bad. Instead, those should essentially be driven by the community's demand on a per-request basis. 189 | 190 | **🏗  Updating the project structure** 191 | 192 | We actively maintain a [TypeScript template][template] with Prismic's latest open-source standards. Keeping every project in sync with this template is nearly impossible so we're not trying to immediately reflect changes to the template in every project. Instead we consider a best practice to manually pull changes from the template into the project whenever someone is doing project maintenance and has time for it, or wants to enjoy the latest standards from it. 193 | 194 | ## `@prismicio/helpers` in Prismic's Open-Source Ecosystem 195 | 196 | Prismic's Open-Source ecosystem is built around a 3-stage pyramid: 197 | 198 |

199 | Prismic open-source pyramid 200 |

201 | 202 | Where: 203 | 204 | - **Core**: Represents libraries providing core Prismic integration such as data fetching and content transformation, e.g. [`@prismicio/client`](https://github.com/prismicio/prismic-client); 205 | - **Integration**: Represents libraries to integration into UI libraries. They must be framework agnostic, e.g. [`@prismicio/react`](https://github.com/prismicio/prismic-react); 206 | - **Framework**: Represents libraries to integrate into frameworks, including data fetching and normalizing. They must follow frameworks' expected practices, e.g. [`@prismicio/next`](https://github.com/prismicio/prismic-next). 207 | 208 | This package is a **Core** library. 209 | 210 | 211 | 212 | 213 | ## Iteration Cycle 214 | 215 | We break iteration cycle of a project's library into 4 phases: 216 | 217 | - **Pre-alpha** 218 | - **Alpha** 219 | - **Beta** 220 | - **Stable** 221 | 222 | ### Pre-alpha 223 | 224 | > At any point we might feel the need to introduce breaking changes to a project's library (getting rid of legacy APIs, embracing latest features from a framework, etc.). When such a need has been identified for a project, it will enter the pre-alpha phase. The project might also enter the pre-alpha phase for large, non-breaking changes that need more planning and thoughts. 225 | > 226 | > The goal of the pre-alpha phase is to design and share the new project's library API through an RFC. Under certain circumstances (e.g. the API has already been clearly defined in another language and only some adaptations have to be made), the project can skip the pre-alpha phase and enter the alpha phase directly. Skipping the pre-alpha phase should be treated as an exception. 227 | 228 | During the pre-alpha phase, the following will happen: 229 | 230 | **Documenting and understanding the library's current functionality:** 231 | 232 | Doing so leads to a better understanding of the library's current functionality and limitations. Reviewing GitHub Issues will provide insight into existing bugs and user requests. We want to reduce the number of breaking changes where possible while not being afraid to do so if necessary. 233 | 234 | **Sketching the API in code, examples, and written explanations:** 235 | 236 | The library should be written in concept before its concrete implementation. This frees us from technical limitations and implementation details. It also allows us to craft the best feeling API. 237 | 238 | **Writing the public RFC:** 239 | 240 | A formal RFC should be posted publicly once the initial brainstorming session is complete that focuses on new and changed concepts. This allows everyone to read and voice their opinions should they choose to. 241 | 242 | ### Alpha (`x.x.x-alpha.x`) 243 | 244 | > As soon as the RFC has been posted, the project enters the alpha phase. 245 | > 246 | > The goal of the alpha phase is to implement the new project's library API, to test it, and to document it. 247 | 248 | During the alpha phase, the following will happen: 249 | 250 | **Writing the implementation:** 251 | 252 | The implementation must be done in TypeScript and include extensive tests and in-code documentation. Generally the process goes as follows: 253 | 254 | 1. Writing the implementation in TypeScript; 255 | 2. Testing the implementation; 256 | 3. Documenting the implementation with TSDocs (at least all publicly exported APIs). 257 | 258 | **Publishing public alpha versions:** 259 | 260 | Publishing alpha versions of the library allows for easier internal testing. With alpha versions, users can test the library as it is published publicly, however those versions are not recommended or shared extensively as breaking changes are still very likely to occur and the library is still not documented. Alpha versions can be published as often as needed. 261 | 262 | **Adjusting per internal and external feedback:** 263 | 264 | An internal code review should be performed. As users use the alpha, issues will be opened for bugs and suggestions. Appropriate adjustments to the library should be made with a focus on doing what users expect while minimizing technical debt. For that purpose, breaking changes to the new API can be introduced. 265 | 266 | **Updating documentation:** 267 | 268 | Documentation for the library should be updated on [prismic.io][prismic-docs] and is treated as the primary source of documentation. Contributors will work closely with Prismic Education team to complete this. This involves updating related documentation and code examples as well, including any starter projects on GitHub. A migration guide should be included if necessary. 269 | 270 | ### Beta (`x.x.x-beta.x`) 271 | 272 | > As soon as the implementation is completed and the updated documentation is ready to be published, the project enters the beta phase. 273 | > 274 | > The goal of the beta phase is to test the updated library's API by publicly sharing it with the community and receiving early adopters' feedback. The beta phase is the last opportunity for breaking changes to be introduced. 275 | 276 | During the beta phase, the following will happen: 277 | 278 | **Publishing public beta versions and related documentation:** 279 | 280 | A first beta should be published along the related documentation that has been worked on during the alpha phase. This release should be announced and shared to users in order to get feedback from early adopters. Subsequent beta versions can be published as often as needed, but we have to keep in mind users are now expected to be using them. 281 | 282 | **Adjusting per internal and external feedback:** 283 | 284 | As users use the beta, issues will be opened for bugs and suggestions. Appropriate adjustments to the library should be made with a focus on doing what users expect while minimizing technical debt. For that purpose, breaking changes to the new API can still be introduced and documentation should be updated accordingly at the moment of publishing a new beta. 285 | 286 | ### Stable (`x.x.x`) 287 | 288 | > Once the beta phase has arrived to maturity (users seem to be happy with it, all issues and concerns have been addressed, etc.), the project enters the stable phase. 289 | > 290 | > The goal of the stable phase is to publish the new project version and to advocate it as Prismic's latest standard. During this "living" phase of the project bug fixes and new features will be added. If the need for breaking or large changes arises, the project will enter the pre-alpha phase and begin a new cycle. 291 | 292 | During the stable phase, the following will happen: 293 | 294 | **Publishing public stable versions and related documentation:** 295 | 296 | The first stable version should be published along the related documentation. This release should be announced and shared to users extensively in order to get them up to Prismic latest standard. Subsequent stable versions can be published as often as needed but a particular attention should be paid toward testing and avoiding regressions. 297 | 298 | **Implementing new features:** 299 | 300 | New features can be implemented during the stable phase following user suggestions and new use cases discovered. To be published, new features should be extensively tested. Ideally, documentation should be updated at the time of publishing, however a delay is tolerated here as it requires coordination with the Prismic Education team. 301 | 302 | **Adding new examples:** 303 | 304 | While examples can be added at any time, it's certainly during the stable phase that most of them will be added. Examples should be added whenever it feels relevant for a use case to be pictured with a recommended recipe, allowing for later shares of said examples. 305 | 306 | ## Project Structure 307 | 308 | > Prismic open-source projects have been structured around standards maintainers brought from their different background and agreed upon on. They are meant to implement the same sensible defaults allowing for a coherent open-source ecosystem. 309 | > 310 | > Any changes to this structure are welcome but they should be first discussed on our [TypeScript template][template-issue]. Common sense still applies on a per-project basis if one requires some specific changes. 311 | 312 | Project is structured as follows (alphabetically, folders first): 313 | 314 | ### 📁  `.github` 315 | 316 | This folder is used to configure the project's GitHub repository. It contains: 317 | 318 | **Issue templates (`ISSUE_TEMPLATE/*`)** 319 | 320 | Those are used to standardize the way issues are created on the repository and to help with their triage by making sure all needed information is provided. Issue templates are also used to redirect users to our [community forum][community-forum] when relevant. 321 | 322 | **Pull request templates (`PULL_REQUEST_TEMPLATE.md`)** 323 | 324 | This one is used to standardize the way pull requests are created on the repository and to help with their triage by making sure all needed information is provided. 325 | 326 | **CI configuration (`.github/workflows/ci.yml`)** 327 | 328 | Our CI workflow is configured to run against all commits and pull requests directed toward the `master` branch. It makes sure the project builds and passes all tests configured on it (lint, unit, e2e, etc.) Coverage and bundle size are also collected by this workflow. 329 | 330 | ### 📁  `dist` 331 | 332 | This folder is not versioned. It contains the built artifacts of the project after a successful build. 333 | 334 | ### 📁  `examples` 335 | 336 | This folder contains examples of how to use the project. Examples are meant to be written over time as new use cases are discovered. 337 | 338 | ### 📁  `playground` 339 | 340 | This folder might not be available in this project. If it is, it is meant to contain a playground for developers to try the project during the development process. 341 | 342 | Scripts such as `playground:dev` or `playground:build` might be available inside the project [package.json](#-packagejson) to interact with it easily. 343 | 344 | ### 📁  `src` 345 | 346 | This folder contains the source code of the project written in TypeScript. The `index.ts` file inside it should only contain exports made available by the project. It should not contain any logic. 347 | 348 | ### 📁  `test` 349 | 350 | This folder contains tests of the project written in TypeScript. It may contain the following subdirectory: 351 | 352 | **Fixtures (`__fixtures__`)** 353 | 354 | This folder contains [fixtures](https://en.wikipedia.org/wiki/Test_fixture) used to test the project. 355 | 356 | **Test Utils (`__testutils__`)** 357 | 358 | This folder contains utility functions used to test the project. 359 | 360 | **Snapshots (`snapshots`)** 361 | 362 | This folder contains snapshots generated by the test framework when using snapshot testing strategies. It should not be altered manually. 363 | 364 | ### 📄  `.editorconfig`, `.eslintrc.cjs`, `.prettierrc`, `.size-limit.json`, `.versionrc`, `vite.config.ts`, `tsconfig.json` 365 | 366 | These files contain configuration for their eponymous tools: 367 | 368 | - [EditorConfig][editor-config]; 369 | - [ESLint][eslint]; 370 | - [Prettier][prettier]; 371 | - [Size Limit][size-limit]; 372 | - [Standard Version][standard-version]; 373 | - [Vite][vite]; 374 | - [Vitest][vitest]; 375 | - [TypeScript][typescript]. 376 | 377 | Any change to those files are welcome but they should be first discussed on our [TypeScript template][template-issue]. Common sense still applies if the project requires some specific changes. 378 | 379 | ### 📄  `.eslintignore`, `.gitignore`, `.prettierignore` 380 | 381 | These files contain ignore configuration for their eponymous tools. Ignore configuration should be based on the one available from `.gitignore` and extended from it. 382 | 383 | ### 📄  `.gitattributes` 384 | 385 | This file contains [attributes](https://git-scm.com/docs/gitattributes) used by Git to deal with the project's files. Our configuration makes sure all files use correct line endings and that lock files aren't subject to conflicts. 386 | 387 | ### 📄  `CHANGELOG.md` 388 | 389 | This file is automatically generated by [Standard Version](https://github.com/conventional-changelog/standard-version) according to commit messages following the [Conventional Commits specification][conventional-commits] whenever a release is made. 390 | 391 | ### 📄  `CONTRIBUTING.md`, `README.md` 392 | 393 | These files contain project's related information and developers (maintainers) documentation. 394 | 395 | ### 📄  `LICENSE` 396 | 397 | This file contains a copy of the project's Apache 2.0 license we use on this project. The Apache 2.0 license has a few more restrictions over the MIT one. Notably (not legal advice), any change by a third party to Apache 2.0 licensed code is required to be stated by the third party. Same applies for changes to the project name by a third party (still not legal advice). For full details refer to the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) license text itself. 398 | 399 | ### 📄  `package-lock.json` 400 | 401 | This file is the lock file generated by [npm][npm] to represent the resolved dependencies used by the project. We use npm over [Yarn][yarn] to manage dependencies and only this lock file should be versioned (`yarn.lock` is even ignored to prevent mistakes). 402 | 403 | ### 📄  `package.json` 404 | 405 | The project's package definition file. 406 | 407 | **Scripts (`scripts`)** 408 | 409 | - `build`: Builds the project; 410 | - `dev`: Builds the project with watch mode enabled; 411 | - `playground:*`: Any command related to the project [playground](#-playground) if any; 412 | - `format`: Runs Prettier on the project; 413 | - `prepare`: npm life cycle script to make sure the project is built before any npm related command (`publish`, `install`, etc.); 414 | - `release`: creates and publishes a new release of the package, version is determined after commit messages following the [Conventional Commits specification][conventional-commits]; 415 | - `release:dry`: dry-run of the `release` script; 416 | - `release:alpha`: creates and publishes a new alpha release of the package; 417 | - `release:alpha:dry`: dry-run of the `release:alpha` script; 418 | - `lint`: Runs ESLint on the project; 419 | - `unit`: Runs Vitest on the project; 420 | - `unit:watch`: Runs Vitest on the project in watch mode; 421 | - `size`: Runs Size Limit on the project; 422 | - `test`: Runs the `lint`, `unit`, `build`, and `size` scripts. 423 | 424 | **Minimum Node version supported (`engines.node`)** 425 | 426 | The minimum Node version supported by the project is stated under the `engines` object. We aim at supporting the oldest Long Term Support (LTS) version of Node that has still not reached End Of Life (EOL): [nodejs.org/en/about/releases](https://nodejs.org/en/about/releases) 427 | 428 | 429 | 430 | [prismic-docs]: https://prismic.io/docs 431 | [community-forum]: https://community.prismic.io 432 | [conventional-commits]: https://conventionalcommits.org/en/v1.0.0 433 | [npm]: https://www.npmjs.com 434 | [yarn]: https://yarnpkg.com 435 | [editor-config]: https://editorconfig.org 436 | [eslint]: https://eslint.org 437 | [prettier]: https://prettier.io 438 | [size-limit]: https://github.com/ai/size-limit 439 | [standard-version]: https://github.com/conventional-changelog/standard-version 440 | [vite]: https://vitejs.dev 441 | [vitest]: https://vitest.dev 442 | [typescript]: https://www.typescriptlang.org 443 | [template]: https://github.com/prismicio/prismic-typescript-template 444 | [template-issue]: https://github.com/prismicio/prismic-typescript-template/issues/new/choose 445 | [changelog]: ./CHANGELOG.md 446 | [forum-question]: https://community.prismic.io 447 | [repo-issue]: https://github.com/prismicio/prismic-helpers/issues/new/choose 448 | [repo-pull-requests]: https://github.com/prismicio/prismic-helpers/pulls 449 | --------------------------------------------------------------------------------