From 0dd28abbe5c3d0f16de360f4fea4eb785eddbf68 Mon Sep 17 00:00:00 2001 From: Yves Rijckaert Date: Tue, 28 Feb 2023 18:20:26 +0100 Subject: [PATCH 1/3] feat: add tooltip btn --- package.json | 1 + src/constants.ts | 10 + src/features/tagging/getLivePreviewProps.ts | 29 -- src/features/tagging/index.tsx | 2 - src/field-tagging.ts | 127 +++++++ src/index.ts | 34 ++ src/index.tsx | 1 - src/styles.css | 45 +++ .../getProps.spec.ts} | 11 +- src/tests/init.spec.ts | 25 ++ src/tests/resolveIncomingMessage.spec.ts | 36 ++ src/types.ts | 11 + vite.config.ts | 2 +- yarn.lock | 328 +++++++++++++++++- 14 files changed, 608 insertions(+), 54 deletions(-) create mode 100644 src/constants.ts delete mode 100644 src/features/tagging/getLivePreviewProps.ts delete mode 100644 src/features/tagging/index.tsx create mode 100644 src/field-tagging.ts create mode 100644 src/index.ts delete mode 100644 src/index.tsx create mode 100644 src/styles.css rename src/{features/tagging/getLivePreviewProps.spec.ts => tests/getProps.spec.ts} (64%) create mode 100644 src/tests/init.spec.ts create mode 100644 src/tests/resolveIncomingMessage.spec.ts create mode 100644 src/types.ts diff --git a/package.json b/package.json index c44194ad..ffdc3f58 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-import-helpers": "^1.2.1", "husky": "^8.0.3", + "jsdom": "^21.1.0", "lint-staged": "^13.1.2", "prettier": "^2.8.4", "react": "^18.2.0", diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..31b006e3 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,10 @@ +export const DATA_FIELD_ID = 'data-contentful-field-id'; +export const DATA_ENTRY_ID = 'data-contentful-entry-id'; +export const DATA_LOCALE = 'data-contentful-locale'; +export const DATA_CURR_FIELD_ID = 'current-data-contentful-field-id'; +export const DATA_CURR_ENTRY_ID = 'current-data-contentful-entry-id'; +export const DATA_CURR_LOCALE = 'current-data-contentful-locale'; +export const TOOLTIP_CLASS = 'contentful-tooltip'; + +export const TOOLTIP_HEIGHT = 32; +export const TOOLTIP_PADDING_LEFT = 5; diff --git a/src/features/tagging/getLivePreviewProps.ts b/src/features/tagging/getLivePreviewProps.ts deleted file mode 100644 index 79b7ce12..00000000 --- a/src/features/tagging/getLivePreviewProps.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type LivePreviewProps = { - fieldId: string | null | undefined; - entryId: string | null | undefined; - locale: string | null | undefined; -}; - -enum TagAttributes { - FIELD_ID = 'data-contentful-field-id', - ENTRY_ID = 'data-contentful-entry-id', - LOCALE = 'data-contentful-locale', -} - -export const getLivePreviewProps = ({ fieldId, entryId, locale }: LivePreviewProps) => { - return { - [TagAttributes.FIELD_ID]: fieldId, - [TagAttributes.ENTRY_ID]: entryId, - [TagAttributes.LOCALE]: locale, - onClick: () => - window.top?.postMessage( - { - fieldId, - entryId, - locale, - }, - //todo: check if there is any security risk with this - '*' - ), - }; -}; diff --git a/src/features/tagging/index.tsx b/src/features/tagging/index.tsx deleted file mode 100644 index 3b70b419..00000000 --- a/src/features/tagging/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export type { LivePreviewProps } from './getLivePreviewProps'; -export { getLivePreviewProps } from './getLivePreviewProps'; diff --git a/src/field-tagging.ts b/src/field-tagging.ts new file mode 100644 index 00000000..0ab5b221 --- /dev/null +++ b/src/field-tagging.ts @@ -0,0 +1,127 @@ +import { + DATA_CURR_ENTRY_ID, + DATA_CURR_FIELD_ID, + DATA_CURR_LOCALE, + DATA_ENTRY_ID, + DATA_FIELD_ID, + DATA_LOCALE, + TOOLTIP_CLASS, + TOOLTIP_HEIGHT, + TOOLTIP_PADDING_LEFT, +} from './constants'; + +export default class FieldTagging { + private tooltip: HTMLButtonElement | null = null; // this tooltip scrolls to the correct field in the entry editor + private currentElementBesideTooltip: HTMLElement | null = null; // this element helps to position the tooltip + + constructor() { + this.tooltip = null; + this.currentElementBesideTooltip = null; + + this.resolveIncomingMessage = this.resolveIncomingMessage.bind(this); + this.updateTooltipPosition = this.updateTooltipPosition.bind(this); + this.addStyleOnHover = this.addStyleOnHover.bind(this); + this.createTooltip = this.createTooltip.bind(this); + this.clickHandler = this.clickHandler.bind(this); + + this.createTooltip(); + window.addEventListener('message', this.resolveIncomingMessage); + window.addEventListener('scroll', this.updateTooltipPosition); + window.addEventListener('mouseover', this.addStyleOnHover); + } + + // Handles incoming messages from Contentful + private resolveIncomingMessage(e: MessageEvent) { + if (typeof e.data !== 'object') return; + if (e.data.from !== 'live-preview') return; + // Toggle the contentful-inspector--active class on the body element based on the isInspectorActive boolean + document.body.classList.toggle('contentful-inspector--active', e.data.isInspectorActive); + } + + // Updates the position of the tooltip + private updateTooltipPosition() { + if (!this.currentElementBesideTooltip || !this.tooltip) return false; + + const currentRectOfElement = this.currentElementBesideTooltip.getBoundingClientRect(); + const currentRectOfParentOfElement = this.tooltip.parentElement?.getBoundingClientRect(); + + if (currentRectOfElement && currentRectOfParentOfElement) { + let upperBoundOfTooltip = currentRectOfElement.top - TOOLTIP_HEIGHT; + const left = currentRectOfElement.left - TOOLTIP_PADDING_LEFT; + + if (upperBoundOfTooltip < 0) { + if (currentRectOfElement.top < 0) upperBoundOfTooltip = currentRectOfElement.top; + else upperBoundOfTooltip = 0; + } + + this.tooltip.style.top = upperBoundOfTooltip + 'px'; + this.tooltip.style.left = left + 'px'; + + return true; + } + + return false; + } + + private addStyleOnHover(e: MouseEvent) { + let trigger = true; + const eventTargets = e.composedPath(); + + for (const eventTarget of eventTargets) { + const element = eventTarget as HTMLElement; + if (element.nodeName === 'BODY') break; + if (typeof element?.getAttribute !== 'function') continue; + + const currFieldId = element.getAttribute(DATA_FIELD_ID); + const currEntryId = element.getAttribute(DATA_ENTRY_ID); + const currLocale = element.getAttribute(DATA_LOCALE); + + if (trigger && currFieldId && currEntryId && currLocale) { + this.currentElementBesideTooltip = element; + + if (this.updateTooltipPosition()) { + this.tooltip?.setAttribute(DATA_CURR_FIELD_ID, currFieldId); + this.tooltip?.setAttribute(DATA_CURR_ENTRY_ID, currEntryId); + this.tooltip?.setAttribute(DATA_CURR_LOCALE, currLocale); + } + + trigger = false; + } + } + } + + private createTooltip() { + if (!document.querySelector(`.${TOOLTIP_CLASS}`)) { + const tooltip = document.createElement('button'); + tooltip.classList.add(TOOLTIP_CLASS); + tooltip.innerHTML = ` + + Edit`; + window.document.body.insertAdjacentElement('beforeend', tooltip); + tooltip.addEventListener('click', this.clickHandler); + this.tooltip = tooltip; + } + this.updateTooltipPosition(); + } + + // responsible for handling the event when the user clicks on the edit button in the tooltip + private clickHandler() { + if (!this.tooltip) { + return; + } + const fieldId = this.tooltip.getAttribute(DATA_CURR_FIELD_ID); + const entryId = this.tooltip.getAttribute(DATA_CURR_ENTRY_ID); + const locale = this.tooltip.getAttribute(DATA_CURR_LOCALE); + + window.top?.postMessage( + { + from: 'live-preview', + fieldId, + entryId, + locale, + }, + //todo: check if there is any security risk with this + '*' + ); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..0ec55ff2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,34 @@ +import './styles.css'; +import FieldTagging from './field-tagging'; +import { LivePreviewProps, TagAttributes } from './types'; + +export class ContentfulLivePreview { + static fieldTagging: FieldTagging | null = null; + + // Static method to initialize the LivePreview SDK + static init(): Promise | undefined { + // Check if running in a browser environment + if (typeof window !== 'undefined') { + if (ContentfulLivePreview.fieldTagging) { + console.log('You have already initialized the Live Preview SDK.'); + return Promise.resolve(ContentfulLivePreview.fieldTagging); + } else { + ContentfulLivePreview.fieldTagging = new FieldTagging(); + return Promise.resolve(ContentfulLivePreview.fieldTagging); + } + } + } + + // Static method to render live preview data-attributes to HTML element output + static getProps({ + fieldId, + entryId, + locale, + }: LivePreviewProps): Record { + return { + [TagAttributes.FIELD_ID]: fieldId, + [TagAttributes.ENTRY_ID]: entryId, + [TagAttributes.LOCALE]: locale, + }; + } +} diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 6f860db8..00000000 --- a/src/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './features/tagging'; diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 00000000..bf43ff55 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,45 @@ +[data-contentful-field-id] { + outline: 1px dashed rgba(64, 160, 255, 0) !important; + transition: outline-color 0.3s ease-in-out; +} + +.contentful-inspector--active [data-contentful-field-id] { + outline: 1px dashed rgba(64, 160, 255, 1) !important; +} + +button.contentful-tooltip { + padding: 0; + display: none; + outline: none; + border: none; + z-index: 999999 !important; + position: fixed; + margin: 0; + height: 32px; + width: 72px; + background: rgb(3, 111, 227); + font-weight: 500 !important; + color: #ffffff !important; + transition: background 0.2s; + text-align: center !important; + border-radius: 6px !important; + font-size: 14px !important; + justify-content: center; + align-items: center; + box-shadow: 0px 1px 0px rgba(17, 27, 43, 0.05); + box-sizing: border-box; + cursor: pointer; + gap: 6px; +} + +button.contentful-tooltip:hover { + background: rgb(0, 89, 200); +} + +button.contentful-tooltip:active:hover { + background: rgb(0, 65, 171); +} + +.contentful-inspector--active button.contentful-tooltip { + display: flex; +} diff --git a/src/features/tagging/getLivePreviewProps.spec.ts b/src/tests/getProps.spec.ts similarity index 64% rename from src/features/tagging/getLivePreviewProps.spec.ts rename to src/tests/getProps.spec.ts index bd00d2a1..3f342c2e 100644 --- a/src/features/tagging/getLivePreviewProps.spec.ts +++ b/src/tests/getProps.spec.ts @@ -1,23 +1,22 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; +import { ContentfulLivePreview } from '../index'; -import { getLivePreviewProps } from './getLivePreviewProps'; - -describe('getLivePreviewProps', () => { +describe('getProps', () => { it('returns the expected props with a given entryId, fieldId and locale', () => { const entryId = 'test-entry-id'; const fieldId = 'test-field-id'; const locale = 'test-locale'; - const result = getLivePreviewProps({ + const result = ContentfulLivePreview.getProps({ entryId, fieldId, locale, }); + expect(result).toStrictEqual({ 'data-contentful-field-id': fieldId, 'data-contentful-entry-id': entryId, 'data-contentful-locale': locale, - onClick: vi.spyOn(result, 'onClick'), }); }); }); diff --git a/src/tests/init.spec.ts b/src/tests/init.spec.ts new file mode 100644 index 00000000..2e4fe32d --- /dev/null +++ b/src/tests/init.spec.ts @@ -0,0 +1,25 @@ +// @vitest-environment jsdom +import { describe, it, expect } from 'vitest'; +import { ContentfulLivePreview } from '../index'; +import FieldTagging from '../field-tagging'; + +describe('init', () => { + it('returns a Promise that resolves to a LivePreview instance when running in a browser environment', async () => { + const livePreviewInstance = await ContentfulLivePreview.init(); + expect(livePreviewInstance).toBeInstanceOf(FieldTagging); + }); + + it('returns undefined when not running in a browser environment', () => { + const windowBackup = global.window; + (global as any).window = undefined; + const result = ContentfulLivePreview.init(); + expect(result).toBeUndefined(); + global.window = windowBackup; + }); + + it('returns a Promise that resolves to the same LivePreview instance when called multiple times', async () => { + const livePreviewInstance1 = await ContentfulLivePreview.init(); + const livePreviewInstance2 = await ContentfulLivePreview.init(); + expect(livePreviewInstance1).toBe(livePreviewInstance2); + }); +}); diff --git a/src/tests/resolveIncomingMessage.spec.ts b/src/tests/resolveIncomingMessage.spec.ts new file mode 100644 index 00000000..ff94dae3 --- /dev/null +++ b/src/tests/resolveIncomingMessage.spec.ts @@ -0,0 +1,36 @@ +// @vitest-environment jsdom +import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; +import FieldTagging from '../field-tagging'; + +describe('resolveIncomingMessage', () => { + let fieldTagging: FieldTagging; + + beforeEach(() => { + fieldTagging = new FieldTagging(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + test('should return if incoming data is not an object', () => { + const spy = vi.spyOn(document.body.classList, 'toggle'); + fieldTagging['resolveIncomingMessage']('not an object' as unknown as MessageEvent); + expect(spy).not.toHaveBeenCalled(); + }); + + test('should return if incoming message is not from live preview', () => { + const spy = vi.spyOn(document.body.classList, 'toggle'); + fieldTagging['resolveIncomingMessage']({ + data: { from: 'not-live-preview' }, + } as unknown as MessageEvent); + expect(spy).not.toHaveBeenCalled(); + }); + + test('should toggle "contentful-inspector--active" class on document.body based on value of isInspectorActive', () => { + const spy = vi.spyOn(document.body.classList, 'toggle'); + fieldTagging['resolveIncomingMessage']({ + data: { from: 'live-preview', isInspectorActive: true }, + } as unknown as MessageEvent); + expect(spy).toHaveBeenCalledWith('contentful-inspector--active', true); + }); +}); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..32d0b49b --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +export type LivePreviewProps = { + fieldId: string | null | undefined; + entryId: string | null | undefined; + locale: string | null | undefined; +}; + +export enum TagAttributes { + FIELD_ID = 'data-contentful-field-id', + ENTRY_ID = 'data-contentful-entry-id', + LOCALE = 'data-contentful-locale', +} diff --git a/vite.config.ts b/vite.config.ts index c5f0c5bc..a36badf8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,7 @@ import dts from 'vite-plugin-dts'; export default defineConfig({ build: { lib: { - entry: resolve(__dirname, 'src/index.tsx'), + entry: resolve(__dirname, 'src/index.ts'), name: 'LivePreview', fileName: 'live-preview', formats: ['cjs', 'es'], diff --git a/yarn.lock b/yarn.lock index 2c626806..6f035220 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1282,17 +1282,30 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + abbrev@^1.0.0, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1, acorn-walk@^8.2.0: +acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -1302,7 +1315,7 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.1.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -1533,6 +1546,11 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -1918,6 +1936,13 @@ columnify@^1.6.0: strip-ansi "^6.0.1" wcwidth "^1.0.0" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^9.4.1: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" @@ -2086,6 +2111,23 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" @@ -2115,6 +2157,15 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -2152,6 +2203,11 @@ decamelize@^1.1.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js@^10.4.2: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + dedent@0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -2192,7 +2248,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -2226,6 +2282,11 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -2290,6 +2351,13 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2333,6 +2401,11 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" +entities@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" + integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + env-ci@^5.0.0: version "5.5.0" resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.5.0.tgz#43364e3554d261a586dec707bc32be81112b545f" @@ -2481,6 +2554,18 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-prettier@^6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9" @@ -2691,7 +2776,7 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -esprima@^4.0.0, esprima@~4.0.0: +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -2797,7 +2882,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -2915,6 +3000,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -3280,6 +3374,13 @@ hosted-git-info@^5.0.0, hosted-git-info@^5.2.1: dependencies: lru-cache "^7.5.1" +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -3294,7 +3395,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -3324,6 +3425,13 @@ husky@^8.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +iconv-lite@0.6.3, iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3331,13 +3439,6 @@ iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -3619,6 +3720,11 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -3772,6 +3878,38 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^21.1.0: + version "21.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.1.0.tgz#d56ba4a84ed478260d83bd53dc181775f2d8e6ef" + integrity sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -3898,6 +4036,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + libnpmaccess@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" @@ -4341,6 +4487,18 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" @@ -4804,6 +4962,11 @@ npmlog@^6.0.0, npmlog@^6.0.2: gauge "^4.0.3" set-blocking "^2.0.0" +nwsapi@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4918,6 +5081,18 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -5117,6 +5292,13 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== +parse5@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" @@ -5226,6 +5408,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + prettier@^2.8.4: version "2.8.4" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" @@ -5299,7 +5486,12 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -punycode@^2.1.0: +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== @@ -5314,6 +5506,11 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5500,6 +5697,11 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -5636,6 +5838,13 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" @@ -6065,6 +6274,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + table@^6.0.9: version "6.8.1" resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" @@ -6173,6 +6387,23 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -6254,6 +6485,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -6364,6 +6602,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -6381,6 +6624,14 @@ url-join@^4.0.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6488,6 +6739,13 @@ vitest@^0.28.5: vite-node "0.28.5" why-is-node-running "^2.2.2" +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" + walk-up-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" @@ -6505,6 +6763,31 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -6575,7 +6858,7 @@ wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -word-wrap@^1.0.3, word-wrap@^1.2.3: +word-wrap@^1.0.3, word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -6616,6 +6899,21 @@ write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^8.11.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" + integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From 87ab7992f7780a720827060526cf2c60554f7414 Mon Sep 17 00:00:00 2001 From: Yves Rijckaert Date: Thu, 2 Mar 2023 12:54:02 +0100 Subject: [PATCH 2/3] refactor: rename and introduce break --- src/field-tagging.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/field-tagging.ts b/src/field-tagging.ts index 0ab5b221..dd61d972 100644 --- a/src/field-tagging.ts +++ b/src/field-tagging.ts @@ -20,14 +20,14 @@ export default class FieldTagging { this.resolveIncomingMessage = this.resolveIncomingMessage.bind(this); this.updateTooltipPosition = this.updateTooltipPosition.bind(this); - this.addStyleOnHover = this.addStyleOnHover.bind(this); + this.addTooltipOnHover = this.addTooltipOnHover.bind(this); this.createTooltip = this.createTooltip.bind(this); this.clickHandler = this.clickHandler.bind(this); this.createTooltip(); window.addEventListener('message', this.resolveIncomingMessage); window.addEventListener('scroll', this.updateTooltipPosition); - window.addEventListener('mouseover', this.addStyleOnHover); + window.addEventListener('mouseover', this.addTooltipOnHover); } // Handles incoming messages from Contentful @@ -63,8 +63,7 @@ export default class FieldTagging { return false; } - private addStyleOnHover(e: MouseEvent) { - let trigger = true; + private addTooltipOnHover(e: MouseEvent) { const eventTargets = e.composedPath(); for (const eventTarget of eventTargets) { @@ -76,7 +75,7 @@ export default class FieldTagging { const currEntryId = element.getAttribute(DATA_ENTRY_ID); const currLocale = element.getAttribute(DATA_LOCALE); - if (trigger && currFieldId && currEntryId && currLocale) { + if (currFieldId && currEntryId && currLocale) { this.currentElementBesideTooltip = element; if (this.updateTooltipPosition()) { @@ -85,7 +84,7 @@ export default class FieldTagging { this.tooltip?.setAttribute(DATA_CURR_LOCALE, currLocale); } - trigger = false; + break; } } } From c686bff5c81d684bbd83ac1db6cb47bab6f0bacd Mon Sep 17 00:00:00 2001 From: Yves Rijckaert Date: Thu, 2 Mar 2023 13:13:17 +0100 Subject: [PATCH 3/3] refactor: update readme and code changes --- README.md | 20 ++++++++++++++++---- src/constants.ts | 3 --- src/field-tagging.ts | 10 ++++------ src/tests/getProps.spec.ts | 7 ++++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ed91e311..4923f6b1 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,30 @@ npm install @contentful/live-preview ## Documentation +### Initializing the SDK + +To establish a communication between your preview frontend and Contentful, you simply need to initialize the live preview SDK. This can be done by executing the following command: + +``` +import { ContentfulLivePreview } from '@contentful/live-preview'; + +... + +ContentfulLivePreview.init(); +``` + ### Field Tagging To tag fields you need to add the live preview data-attributes to the rendered HTML element output. You can do this in React via our helper function. +The necessary styles for the live edit tags can be found in the '@contentful/live-preview/dist/style.css' file. ``` -import { getLivePreviewProps } from '@contentful/live-preview'; - +import { ContentfulLivePreview } from '@contentful/live-preview'; +import '@contentful/live-preview/dist/style.css'; ... -

+

{title}

``` @@ -54,7 +67,6 @@ We want to provide a safe, inclusive, welcoming, and harassment-free space and e The live preview package is open source software [licensed as MIT](./LICENSE). - [contentful]: https://www.contentful.com [github-issues]: https://github.com/contentful/live-preview/issues [typescript]: https://www.typescriptlang.org/ diff --git a/src/constants.ts b/src/constants.ts index 31b006e3..6ee66907 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,3 @@ -export const DATA_FIELD_ID = 'data-contentful-field-id'; -export const DATA_ENTRY_ID = 'data-contentful-entry-id'; -export const DATA_LOCALE = 'data-contentful-locale'; export const DATA_CURR_FIELD_ID = 'current-data-contentful-field-id'; export const DATA_CURR_ENTRY_ID = 'current-data-contentful-entry-id'; export const DATA_CURR_LOCALE = 'current-data-contentful-locale'; diff --git a/src/field-tagging.ts b/src/field-tagging.ts index dd61d972..98fe0ddf 100644 --- a/src/field-tagging.ts +++ b/src/field-tagging.ts @@ -2,13 +2,11 @@ import { DATA_CURR_ENTRY_ID, DATA_CURR_FIELD_ID, DATA_CURR_LOCALE, - DATA_ENTRY_ID, - DATA_FIELD_ID, - DATA_LOCALE, TOOLTIP_CLASS, TOOLTIP_HEIGHT, TOOLTIP_PADDING_LEFT, } from './constants'; +import { TagAttributes } from './types'; export default class FieldTagging { private tooltip: HTMLButtonElement | null = null; // this tooltip scrolls to the correct field in the entry editor @@ -71,9 +69,9 @@ export default class FieldTagging { if (element.nodeName === 'BODY') break; if (typeof element?.getAttribute !== 'function') continue; - const currFieldId = element.getAttribute(DATA_FIELD_ID); - const currEntryId = element.getAttribute(DATA_ENTRY_ID); - const currLocale = element.getAttribute(DATA_LOCALE); + const currFieldId = element.getAttribute(TagAttributes.FIELD_ID); + const currEntryId = element.getAttribute(TagAttributes.ENTRY_ID); + const currLocale = element.getAttribute(TagAttributes.LOCALE); if (currFieldId && currEntryId && currLocale) { this.currentElementBesideTooltip = element; diff --git a/src/tests/getProps.spec.ts b/src/tests/getProps.spec.ts index 3f342c2e..524e9d29 100644 --- a/src/tests/getProps.spec.ts +++ b/src/tests/getProps.spec.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; import { ContentfulLivePreview } from '../index'; +import { TagAttributes } from '../types'; describe('getProps', () => { it('returns the expected props with a given entryId, fieldId and locale', () => { @@ -14,9 +15,9 @@ describe('getProps', () => { }); expect(result).toStrictEqual({ - 'data-contentful-field-id': fieldId, - 'data-contentful-entry-id': entryId, - 'data-contentful-locale': locale, + [TagAttributes.FIELD_ID]: fieldId, + [TagAttributes.ENTRY_ID]: entryId, + [TagAttributes.LOCALE]: locale, }); }); });