diff --git a/client/common-client-plugin.ts b/client/common-client-plugin.ts index afa67391..88956777 100644 --- a/client/common-client-plugin.ts +++ b/client/common-client-plugin.ts @@ -6,13 +6,13 @@ import type { RegisterClientOptions } from '@peertube/peertube-types/client' import type { RegisterClientFormFieldOptions } from '@peertube/peertube-types' import { registerConfiguration } from './common/configuration/register' import { registerRoom } from './common/room/register' -import { registerClientOptionsSubject$ } from './common/lib/contexts/peertube' +import { initPtContext } from './common/lib/contexts/peertube' import './common/lib/elements' // Import shared elements. async function register (clientOptions: RegisterClientOptions): Promise { const { peertubeHelpers, registerHook, registerVideoField } = clientOptions - registerClientOptionsSubject$.next(clientOptions) + initPtContext(clientOptions) registerHook({ target: 'action:router.navigation-end', diff --git a/client/common/configuration/elements/channel-configuration.ts b/client/common/configuration/elements/channel-configuration.ts index 23d23612..20d864e7 100644 --- a/client/common/configuration/elements/channel-configuration.ts +++ b/client/common/configuration/elements/channel-configuration.ts @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import type { RegisterClientOptions } from '@peertube/peertube-types/client' import type { ChannelConfiguration } from 'shared/lib/types' import { TemplateResult, html, nothing } from 'lit' import { customElement, property, state } from 'lit/decorators.js' @@ -12,17 +11,12 @@ import { Task } from '@lit/task' import { ChannelDetailsService } from '../services/channel-details' import { provide } from '@lit/context' import { channelConfigurationContext, channelDetailsServiceContext } from '../contexts/channel' -import { registerClientOptionsContext } from '../../lib/contexts/peertube' import { LivechatElement } from '../../lib/elements/livechat' import { ValidationError, ValidationErrorType } from '../../lib/models/validation' import { classMap } from 'lit/directives/class-map.js' @customElement('livechat-channel-configuration') export class ChannelConfigurationElement extends LivechatElement { - @provide({ context: registerClientOptionsContext }) - @property({ attribute: false }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public channelId?: number @@ -48,14 +42,12 @@ export class ChannelConfigurationElement extends LivechatElement { protected _initTask (): Task { return new Task(this, { - task: async ([registerClientOptions]) => { - if (registerClientOptions) { - this._channelDetailsService = new ChannelDetailsService(registerClientOptions) - this._channelConfiguration = await this._channelDetailsService.fetchConfiguration(this.channelId ?? 0) - this._actionDisabled = false // in case of reset - } + task: async () => { + this._channelDetailsService = new ChannelDetailsService(this.ptOptions) + this._channelConfiguration = await this._channelDetailsService.fetchConfiguration(this.channelId ?? 0) + this._actionDisabled = false // in case of reset }, - args: () => [this.registerClientOptions] + args: () => [] }) } @@ -74,10 +66,9 @@ export class ChannelConfigurationElement extends LivechatElement { this._channelConfiguration.configuration) .then(() => { this._validationError = undefined - this.registerClientOptions?.peertubeHelpers.translate(LOC_SUCCESSFULLY_SAVED).then((msg) => { - this.registerClientOptions - ?.peertubeHelpers.notifier.info(msg) - }) + this.ptTranslate(LOC_SUCCESSFULLY_SAVED).then((msg) => { + this.ptNotifier.info(msg) + }, () => {}) this.requestUpdate('_validationError') }) .catch(async (error: Error) => { @@ -86,10 +77,10 @@ export class ChannelConfigurationElement extends LivechatElement { this._validationError = error } console.warn(`A validation error occurred in saving configuration. ${error.name}: ${error.message}`) - this.registerClientOptions?.peertubeHelpers.notifier.error( + this.ptNotifier.error( error.message ? error.message - : await this.registerClientOptions.peertubeHelpers.translate('error') + : await this.ptTranslate(LOC_ERROR) ) this.requestUpdate('_validationError') }) diff --git a/client/common/configuration/elements/channel-emojis.ts b/client/common/configuration/elements/channel-emojis.ts index cb4ac3ac..25e656d7 100644 --- a/client/common/configuration/elements/channel-emojis.ts +++ b/client/common/configuration/elements/channel-emojis.ts @@ -2,11 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import type { RegisterClientOptions } from '@peertube/peertube-types/client' import type { ChannelEmojisConfiguration } from 'shared/lib/types' import type { DynamicFormHeader, DynamicFormSchema } from '../../lib/elements/dynamic-table-form' import { LivechatElement } from '../../lib/elements/livechat' -import { registerClientOptionsContext } from '../../lib/contexts/peertube' import { ChannelDetailsService } from '../services/channel-details' import { channelDetailsServiceContext } from '../contexts/channel' import { maxEmojisPerChannel } from 'shared/lib/emojis' @@ -22,10 +20,6 @@ import { html } from 'lit' */ @customElement('livechat-channel-emojis') export class ChannelEmojisElement extends LivechatElement { - @provide({ context: registerClientOptionsContext }) - @property({ attribute: false }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public channelId?: number @@ -159,13 +153,10 @@ export class ChannelEmojisElement extends LivechatElement { protected _initTask (): Task { return new Task(this, { task: async () => { - if (!this.registerClientOptions) { - throw new Error('Missing client options') - } if (!this.channelId) { throw new Error('Missing channelId') } - this._channelDetailsService = new ChannelDetailsService(this.registerClientOptions) + this._channelDetailsService = new ChannelDetailsService(this.ptOptions) this._channelEmojisConfiguration = await this._channelDetailsService.fetchEmojisConfiguration(this.channelId) this._actionDisabled = false // in case of reset }, @@ -182,11 +173,9 @@ export class ChannelEmojisElement extends LivechatElement { private async _saveEmojis (ev?: Event): Promise { ev?.preventDefault() - const peertubeHelpers = this.registerClientOptions?.peertubeHelpers - if (!peertubeHelpers) { return } // Should not happen if (!this._channelDetailsService || !this._channelEmojisConfiguration || !this.channelId) { - peertubeHelpers.notifier.error(await peertubeHelpers.translate(LOC_ERROR)) + this.ptNotifier.error(await this.ptTranslate(LOC_ERROR)) return } @@ -194,7 +183,7 @@ export class ChannelEmojisElement extends LivechatElement { this._actionDisabled = true await this._channelDetailsService.saveEmojisConfiguration(this.channelId, this._channelEmojisConfiguration.emojis) this._validationError = undefined - peertubeHelpers.notifier.info(await peertubeHelpers.translate(LOC_SUCCESSFULLY_SAVED)) + this.ptNotifier.info(await this.ptTranslate(LOC_SUCCESSFULLY_SAVED)) this.requestUpdate('_validationError') } catch (error) { this._validationError = undefined @@ -205,8 +194,8 @@ export class ChannelEmojisElement extends LivechatElement { msg = error.message } } - msg ??= await peertubeHelpers.translate(LOC_ERROR) - peertubeHelpers.notifier.error(msg) + msg ??= await this.ptTranslate(LOC_ERROR) + this.ptNotifier.error(msg) this.requestUpdate('_validationError') } finally { this._actionDisabled = false @@ -282,11 +271,11 @@ export class ChannelEmojisElement extends LivechatElement { this.requestUpdate('_channelEmojisConfiguration') - this.registerClientOptions?.peertubeHelpers.notifier.info( - await this.registerClientOptions?.peertubeHelpers.translate(LOC_ACTION_IMPORT_EMOJIS_INFO) + this.ptNotifier.info( + await this.ptTranslate(LOC_ACTION_IMPORT_EMOJIS_INFO) ) } catch (err: any) { - this.registerClientOptions?.peertubeHelpers.notifier.error(err.toString()) + this.ptNotifier.error(err.toString()) } finally { this._actionDisabled = false } @@ -322,7 +311,7 @@ export class ChannelEmojisElement extends LivechatElement { a.remove() } catch (err: any) { console.error(err) - this.registerClientOptions?.peertubeHelpers.notifier.error(err.toString()) + this.ptNotifier.error(err.toString()) } finally { this._actionDisabled = false } diff --git a/client/common/configuration/elements/channel-home.ts b/client/common/configuration/elements/channel-home.ts index 99459e79..f912adbe 100644 --- a/client/common/configuration/elements/channel-home.ts +++ b/client/common/configuration/elements/channel-home.ts @@ -2,24 +2,18 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import type { RegisterClientOptions } from '@peertube/peertube-types/client' import { html } from 'lit' -import { customElement, property, state } from 'lit/decorators.js' +import { customElement, state } from 'lit/decorators.js' import { ptTr } from '../../lib/directives/translation' import { Task } from '@lit/task' import type { ChannelLiveChatInfos } from 'shared/lib/types' import { ChannelDetailsService } from '../services/channel-details' import { provide } from '@lit/context' import { channelDetailsServiceContext } from '../contexts/channel' -import { registerClientOptionsContext } from '../../lib/contexts/peertube' import { LivechatElement } from '../../lib/elements/livechat' @customElement('livechat-channel-home') export class ChannelHomeElement extends LivechatElement { - @provide({ context: registerClientOptionsContext }) - @property({ attribute: false }) - public registerClientOptions?: RegisterClientOptions - @state() public _channels?: ChannelLiveChatInfos[] @@ -30,19 +24,17 @@ export class ChannelHomeElement extends LivechatElement { public _formStatus: boolean | any = undefined private readonly _asyncTaskRender = new Task(this, { - task: async ([registerClientOptions]) => { + task: async () => { // Getting the current username in localStorage. Don't know any cleaner way to do. const username = window.localStorage.getItem('username') if (!username) { throw new Error('Can\'t get the current username.') } - if (registerClientOptions) { - this._channelDetailsService = new ChannelDetailsService(registerClientOptions) - this._channels = await this._channelDetailsService.fetchUserChannels(username) - } + this._channelDetailsService = new ChannelDetailsService(this.ptOptions) + this._channels = await this._channelDetailsService.fetchUserChannels(username) }, - args: () => [this.registerClientOptions] + args: () => [] }) protected override render = (): unknown => { diff --git a/client/common/configuration/elements/channel-tabs.ts b/client/common/configuration/elements/channel-tabs.ts index ca9e6c7c..43199067 100644 --- a/client/common/configuration/elements/channel-tabs.ts +++ b/client/common/configuration/elements/channel-tabs.ts @@ -2,19 +2,13 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import type { RegisterClientOptions } from '@peertube/peertube-types/client' -import { registerClientOptionsContext } from '../../lib/contexts/peertube' import { LivechatElement } from '../../lib/elements/livechat' import { ptTr } from '../../lib/directives/translation' import { html, TemplateResult } from 'lit' import { customElement, property } from 'lit/decorators.js' -import { consume } from '@lit/context' @customElement('livechat-channel-tabs') export class ChannelHomeElement extends LivechatElement { - @consume({ context: registerClientOptionsContext, subscribe: true }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public channelId?: number diff --git a/client/common/lib/contexts/peertube.ts b/client/common/lib/contexts/peertube.ts index 4f614111..b7b6bb59 100644 --- a/client/common/lib/contexts/peertube.ts +++ b/client/common/lib/contexts/peertube.ts @@ -3,11 +3,22 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { RegisterClientOptions } from '@peertube/peertube-types/client/types' -import { createContext } from '@lit/context' -import { BehaviorSubject } from 'rxjs' -export const registerClientOptionsContext = - createContext(Symbol('register-client-options')) +export interface PtContext { + ptOptions: RegisterClientOptions +} -export const registerClientOptionsSubject$ = - new BehaviorSubject(undefined) +let context: PtContext + +export function getPtContext (): PtContext { + if (!context) { + throw new Error('Peertube context not set yet, getPtContext was called too soon.') + } + return context +} + +export function initPtContext (ptOptions: RegisterClientOptions): void { + context = { + ptOptions + } +} diff --git a/client/common/lib/directives/translation.ts b/client/common/lib/directives/translation.ts index 83ea8623..acfa96f4 100644 --- a/client/common/lib/directives/translation.ts +++ b/client/common/lib/directives/translation.ts @@ -8,38 +8,21 @@ import { AsyncDirective } from 'lit/async-directive.js' import { RegisterClientHelpers } from '@peertube/peertube-types/client' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { html } from 'lit' -import { registerClientOptionsSubject$ } from '../contexts/peertube' -import { Subscription, filter, map } from 'rxjs' +import { getPtContext } from '../contexts/peertube' export class TranslationDirective extends AsyncDirective { - private _peertubeHelpers?: RegisterClientHelpers + private readonly _peertubeHelpers: RegisterClientHelpers private _translatedValue: string = '' private _localizationId: string = '' private _allowUnsafeHTML = false - private _subscriptionHandle: Subscription = new Subscription() - constructor (partInfo: PartInfo) { super(partInfo) - this.reconnected() - } - - protected override disconnected = (): void => { - this._subscriptionHandle.unsubscribe() - } - - protected override reconnected = (): void => { - this._subscriptionHandle.unsubscribe() - this._subscriptionHandle = registerClientOptionsSubject$ - .pipe(filter(Boolean)) - .pipe(map(registerClientOptions => registerClientOptions.peertubeHelpers)) - .subscribe((registerClientHelpers: RegisterClientHelpers) => { - this._peertubeHelpers = registerClientHelpers - this._asyncUpdateTranslation().then(() => {}, () => {}) - }) + this._peertubeHelpers = getPtContext().ptOptions.peertubeHelpers + this._asyncUpdateTranslation().then(() => {}, () => {}) } public override render = (locId: string, allowHTML: boolean = false): TemplateResult | string => { @@ -61,10 +44,7 @@ export class TranslationDirective extends AsyncDirective { } private readonly _asyncUpdateTranslation = async (): Promise => { - if (!this._peertubeHelpers) { - console.error('Translation directive: missing peertubeHelpers') - } - const newValue = await this._peertubeHelpers?.translate(this._localizationId) ?? '' + const newValue = await this._peertubeHelpers.translate(this._localizationId) ?? '' if (newValue !== '' && newValue !== this._translatedValue) { this._translatedValue = newValue diff --git a/client/common/lib/elements/dynamic-table-form.ts b/client/common/lib/elements/dynamic-table-form.ts index d6beba99..9f1975dc 100644 --- a/client/common/lib/elements/dynamic-table-form.ts +++ b/client/common/lib/elements/dynamic-table-form.ts @@ -5,8 +5,6 @@ import type { TagsInputElement } from './tags-input' import type { DirectiveResult } from 'lit/directive' -import type { RegisterClientOptions } from '@peertube/peertube-types/client' -import { registerClientOptionsContext } from '../../lib/contexts/peertube' import { ValidationErrorType } from '../models/validation' import { maxSize, inputFileAccept } from 'shared/lib/emojis' import { html, nothing, TemplateResult } from 'lit' @@ -17,7 +15,6 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { classMap } from 'lit/directives/class-map.js' import { LivechatElement } from './livechat' import { ptTr } from '../directives/translation' -import { consume } from '@lit/context' // This content comes from the file assets/images/plus-square.svg, from the Feather icons set https://feathericons.com/ const AddSVG: string = @@ -95,9 +92,6 @@ export interface DynamicFormSchema { [key: string]: CellDataSchema } @customElement('livechat-dynamic-table-form') export class DynamicTableFormElement extends LivechatElement { - @consume({ context: registerClientOptionsContext, subscribe: true }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public header: DynamicFormHeader = {} @@ -162,14 +156,9 @@ export class DynamicTableFormElement extends LivechatElement { } private async _removeRow (rowId: number): Promise { - if (!this.registerClientOptions) { - console.error('Missing registreClientOptions.') - return - } - const peertubeHelpers = this.registerClientOptions.peertubeHelpers - const confirmMsg = await peertubeHelpers.translate(LOC_ACTION_REMOVE_ENTRY_CONFIRM) + const confirmMsg = await this.ptTranslate(LOC_ACTION_REMOVE_ENTRY_CONFIRM) await new Promise((resolve, reject) => { - peertubeHelpers.showModal({ + this.ptOptions.peertubeHelpers.showModal({ title: confirmMsg, content: '', close: true, diff --git a/client/common/lib/elements/help-button.ts b/client/common/lib/elements/help-button.ts index ebac17ed..2177ad7d 100644 --- a/client/common/lib/elements/help-button.ts +++ b/client/common/lib/elements/help-button.ts @@ -6,9 +6,6 @@ import { html } from 'lit' import { customElement, property, state } from 'lit/decorators.js' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { helpButtonSVG } from '../../../videowatch/buttons' -import { consume } from '@lit/context' -import { registerClientOptionsContext } from '../contexts/peertube' -import type { RegisterClientOptions } from '@peertube/peertube-types/client' import { Task } from '@lit/task' import { localizedHelpUrl } from '../../../utils/help' import { ptTr } from '../directives/translation' @@ -17,9 +14,6 @@ import { LivechatElement } from './livechat' @customElement('livechat-help-button') export class HelpButtonElement extends LivechatElement { - @consume({ context: registerClientOptionsContext, subscribe: true }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public buttonTitle: string | DirectiveResult = ptTr(LOC_ONLINE_HELP) @@ -30,12 +24,12 @@ export class HelpButtonElement extends LivechatElement { public url: URL = new URL('https://lmddgtfy.net/') private readonly _asyncTaskRender = new Task(this, { - task: async ([registerClientOptions]) => { - this.url = new URL(registerClientOptions - ? await localizedHelpUrl(registerClientOptions, { page: this.page }) - : '') + task: async () => { + this.url = new URL( + await localizedHelpUrl(this.ptOptions, { page: this.page }) + ) }, - args: () => [this.registerClientOptions] + args: () => [] }) protected override render = (): unknown => { diff --git a/client/common/lib/elements/image-file-input.ts b/client/common/lib/elements/image-file-input.ts index c08d3625..1d5e2581 100644 --- a/client/common/lib/elements/image-file-input.ts +++ b/client/common/lib/elements/image-file-input.ts @@ -2,12 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import type { RegisterClientOptions } from '@peertube/peertube-types/client' import { LivechatElement } from './livechat' -import { registerClientOptionsContext } from '../contexts/peertube' import { html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import { consume } from '@lit/context' /** * Special element to upload image files. @@ -23,9 +20,6 @@ import { consume } from '@lit/context' */ @customElement('livechat-image-file-input') export class ImageFileInputElement extends LivechatElement { - @consume({ context: registerClientOptionsContext, subscribe: true }) - public registerClientOptions?: RegisterClientOptions - @property({ attribute: false }) public name?: string @@ -68,11 +62,11 @@ export class ImageFileInputElement extends LivechatElement { } if (this.maxSize && file.size > this.maxSize) { - let msg = await this.registerClientOptions?.peertubeHelpers.translate(LOC_INVALID_VALUE_FILE_TOO_BIG) + let msg = await this.ptTranslate(LOC_INVALID_VALUE_FILE_TOO_BIG) if (msg) { // FIXME: better unit handling (here we force kb) msg = msg.replace('%s', Math.round(this.maxSize / 1024).toString() + 'k') - this.registerClientOptions?.peertubeHelpers.notifier.error(msg) + this.ptNotifier.error(msg) } return } diff --git a/client/common/lib/elements/livechat.ts b/client/common/lib/elements/livechat.ts index b3445f40..08e6f6d1 100644 --- a/client/common/lib/elements/livechat.ts +++ b/client/common/lib/elements/livechat.ts @@ -1,13 +1,29 @@ // SPDX-FileCopyrightText: 2024 Mehdi Benadel +// SPDX-FileCopyrightText: 2024 John Livingston // // SPDX-License-Identifier: AGPL-3.0-only +import type { RegisterClientOptions } from '@peertube/peertube-types/client' +import { getPtContext, PtContext } from '../contexts/peertube' import { LitElement } from 'lit' /** * Base class for all Custom Elements. */ export class LivechatElement extends LitElement { + public readonly ptContext: PtContext + public readonly ptOptions: RegisterClientOptions + public readonly ptTranslate: RegisterClientOptions['peertubeHelpers']['translate'] + public readonly ptNotifier: RegisterClientOptions['peertubeHelpers']['notifier'] + + constructor () { + super() + this.ptContext = getPtContext() + this.ptOptions = this.ptContext.ptOptions + this.ptNotifier = this.ptOptions.peertubeHelpers.notifier + this.ptTranslate = this.ptOptions.peertubeHelpers.translate + } + protected override createRenderRoot = (): Element | ShadowRoot => { return this } diff --git a/package-lock.json b/package-lock.json index 46cdb61e..a3f5b653 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "eslint-plugin-standard": "^5.0.0", "lit": "^2.4.0", "npm-run-all": "^4.1.5", - "rxjs": "^7.8.1", "sass": "^1.43.4", "sharp": "^0.33.2", "stylelint": "^14.0.1", @@ -10627,21 +10626,6 @@ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", "dev": true }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -20523,23 +20507,6 @@ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", "dev": true }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - } - } - }, "safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", diff --git a/package.json b/package.json index 06524358..57c53f0a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "eslint-plugin-standard": "^5.0.0", "lit": "^2.4.0", "npm-run-all": "^4.1.5", - "rxjs": "^7.8.1", "sass": "^1.43.4", "sharp": "^0.33.2", "stylelint": "^14.0.1",