diff --git a/src/Umbraco.Web.UI.Client/LICENSE b/src/Umbraco.Web.UI.Client/LICENSE index aaba059d33..2819c99ca7 100644 --- a/src/Umbraco.Web.UI.Client/LICENSE +++ b/src/Umbraco.Web.UI.Client/LICENSE @@ -27,10 +27,18 @@ Third-party licenses --- Lucide License -ISC License +ISC License Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +Simple Icons +CC0 1.0 Universal license + +The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. +You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. diff --git a/src/Umbraco.Web.UI.Client/apps/auth/e2e/login.spec.ts b/src/Umbraco.Web.UI.Client/apps/auth/e2e/login.spec.ts deleted file mode 100644 index d34e280a56..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/e2e/login.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('login', async ({ page }) => { - await page.goto('/'); - - // Fill input[name="email"] - await page.locator('input[name="email"]').fill('test@umbraco.com'); - - // Fill input[name="password"] - await page.locator('input[name="password"]').fill('test123456'); - - // Wait for console.log to be called - page.on('console', (message) => { - expect(message.text()).toBe('login'); - }); - - // Click [aria-label="Login"] - await page.locator('[aria-label="Login"]').click(); -}); diff --git a/src/Umbraco.Web.UI.Client/apps/auth/index.html b/src/Umbraco.Web.UI.Client/apps/auth/index.html deleted file mode 100644 index 75922ccae2..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - Umbraco Auth - - - - - - - - diff --git a/src/Umbraco.Web.UI.Client/apps/auth/package.json b/src/Umbraco.Web.UI.Client/apps/auth/package.json deleted file mode 100644 index addf6d7ebf..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@umbraco-cms/login", - "version": "0.0.0", - "license": "MIT", - "author": { - "name": "Umbraco HQ", - "email": "backoffice@umbraco.com" - }, - "type": "module", - "module": "dist/main.js", - "files": [ - "dist" - ], - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "build:watch": "vite build --watch", - "preview": "vite preview" - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/playwright.config.ts b/src/Umbraco.Web.UI.Client/apps/auth/playwright.config.ts deleted file mode 100644 index f7bf259530..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/playwright.config.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - testDir: './e2e', - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - webServer: { - command: 'npm run dev', - port: 5173, - reuseExistingServer: !process.env.CI, - }, -}; - -export default config; diff --git a/src/Umbraco.Web.UI.Client/apps/auth/public/favicon.svg b/src/Umbraco.Web.UI.Client/apps/auth/public/favicon.svg deleted file mode 100644 index 19acb4bc1e..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/public/favicon.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/src/Umbraco.Web.UI.Client/apps/auth/public/login.jpeg b/src/Umbraco.Web.UI.Client/apps/auth/public/login.jpeg deleted file mode 100644 index 412cd17865..0000000000 Binary files a/src/Umbraco.Web.UI.Client/apps/auth/public/login.jpeg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logo_white.svg deleted file mode 100644 index 3de8e82ed3..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logo_white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logomark_white.svg b/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logomark_white.svg deleted file mode 100644 index 9372e25d3e..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/public/umbraco_logomark_white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/auth-layout.element.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/auth-layout.element.ts deleted file mode 100644 index 163ece1736..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/auth-layout.element.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; -import { customElement } from 'lit/decorators.js'; - -@customElement('umb-auth-layout') -export class UmbAuthLayoutElement extends LitElement { - render() { - return html` -
- - - -
- - - -
- `; - } - - static styles: CSSResultGroup = [ - css` - #background { - position: fixed; - overflow: hidden; - background-position: 50%; - background-repeat: no-repeat; - background-size: cover; - background-image: url('login.jpeg'); - width: 100vw; - height: 100vh; - } - - #logo { - position: fixed; - top: var(--uui-size-space-5); - left: var(--uui-size-space-5); - height: 30px; - } - - #logo img { - height: 100%; - } - - #container { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - } - - #box { - width: 500px; - padding: var(--uui-size-space-6) var(--uui-size-space-5) var(--uui-size-space-5) var(--uui-size-space-5); - } - - #email, - #password { - width: 100%; - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-auth-layout': UmbAuthLayoutElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/auth-legacy.context.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/auth-legacy.context.ts deleted file mode 100644 index 42b0679aab..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/auth-legacy.context.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { LoginRequestModel, IUmbAuthContext, LoginResponse } from './types.js'; - -export class UmbAuthLegacyContext implements IUmbAuthContext { - readonly #AUTH_URL = '/umbraco/backoffice/umbracoapi/authentication/postlogin'; - readonly supportsPersistLogin = true; - - login(data: LoginRequestModel): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/auth.context.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/auth.context.ts deleted file mode 100644 index c38afcb618..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/auth.context.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { UmbAuthRepository } from './auth.repository.js'; -import { LoginRequestModel, IUmbAuthContext } from './types.js'; - -export class UmbAuthContext implements IUmbAuthContext { - readonly #AUTH_URL = '/umbraco/management/api/v1/security/back-office'; - readonly supportsPersistLogin = false; - - #authRepository = new UmbAuthRepository(this.#AUTH_URL); - - public async login(data: LoginRequestModel) { - return this.#authRepository.login(data); - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/auth.repository.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/auth.repository.ts deleted file mode 100644 index f615a915c1..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/auth.repository.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { LoginRequestModel } from './types.js'; - -export class UmbAuthRepository { - #authURL = ''; - - constructor(authUrl: string) { - this.#authURL = authUrl; - } - - public async login(data: LoginRequestModel) { - const request = new Request(this.#authURL + '/login', { - method: 'POST', - body: JSON.stringify({ - username: data.username, - password: data.password, - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - const response = await fetch(request); - - //TODO: What kind of data does the old backoffice expect? - //NOTE: this conditionally adds error and data to the response object - return { - status: response.status, - ...(!response.ok && { error: this.#getErrorText(response) }), - ...(response.ok && { data: 'WHAT DATA SHOULD BE RETURNED?' }), - }; - } - - #getErrorText(response: Response) { - switch (response.status) { - case 401: - return 'Oops! It seems like your login credentials are invalid or expired. Please double-check your username and password and try again.'; - - case 500: - return "We're sorry, but the server encountered an unexpected error. Please refresh the page or try again later.."; - - default: - return response.statusText; - } - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test.element.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test.element.ts deleted file mode 100644 index 7dda2a047e..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test.element.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { css, html, LitElement } from 'lit'; -import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui'; -import { customElement } from 'lit/decorators.js'; - -@customElement('umb-external-login-provider-test') -export class UmbExternalLoginProviderTestElement extends LitElement { - render() { - return html` - Custom External Login Provider -

This is an example of a custom external login provider using the external login provider extension point

- - `; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - padding: var(--uui-size-space-5); - border: 1px solid var(--uui-color-border); - background: var(--uui-color-surface-alt); - border-radius: var(--uui-border-radius); - } - p { - margin: 0; - } - `, - ]; -} - -export default UmbExternalLoginProviderTestElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-external-login-provider-test': UmbExternalLoginProviderTestElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test2.element.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test2.element.ts deleted file mode 100644 index 652293ad27..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/external-login-provider-test2.element.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { css, html, LitElement } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { customElement } from 'lit/decorators.js'; - -@customElement('umb-external-login-provider-test2') -export class UmbExternalLoginProviderTest2Element extends LitElement { - render() { - return html` - Another Custom External Login Provider -

This is an example of another custom external login provider

- - Email - - - - `; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - padding: var(--uui-size-space-5); - border: 1px solid var(--uui-color-border); - background: var(--uui-color-surface-alt); - border-radius: var(--uui-border-radius); - } - p { - margin: 0; - } - uui-input { - width: 100%; - } - `, - ]; -} - -export default UmbExternalLoginProviderTest2Element; - -declare global { - interface HTMLElementTagNameMap { - 'umb-external-login-provider-test2': UmbExternalLoginProviderTest2Element; - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/manifests.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/manifests.ts deleted file mode 100644 index 534b6a9cc2..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/external-login-providers/manifests.ts +++ /dev/null @@ -1,29 +0,0 @@ -// TODO: could these be renamed as login providers? -import type { ManifestExternalLoginProvider } from '@umbraco-cms/backoffice/extension-registry'; - -export const manifests: Array = [ - { - type: 'externalLoginProvider', - alias: 'Umb.ExternalLoginProvider.Test', - name: 'Test External Login Provider', - elementName: 'umb-external-login-provider-test', - element: () => import('./external-login-provider-test.element.js'), - weight: 2, - meta: { - label: 'Test External Login Provider', - pathname: 'test/test/test', - }, - }, - { - type: 'externalLoginProvider', - alias: 'Umb.ExternalLoginProvider.Test2', - name: 'Test External Login Provider 2', - elementName: 'umb-external-login-provider-test2', - element: () => import('./external-login-provider-test2.element.js'), - weight: 1, - meta: { - label: 'Test External Login Provider 2', - pathname: 'test/test/test', - }, - }, -]; diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/index.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/index.ts deleted file mode 100644 index 43ef4da338..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import 'element-internals-polyfill'; - -import '@umbraco-ui/uui-css/dist/uui-css.css'; -import '../../../src/css/umb-css.css'; -import '@umbraco-ui/uui'; - -import './login.element.js'; diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/login.element.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/login.element.ts deleted file mode 100644 index d55dd07276..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/login.element.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; - -import type { UUIButtonState } from '@umbraco-ui/uui'; -import type { IUmbAuthContext } from './types.js'; -import { UmbAuthLegacyContext } from './auth-legacy.context.js'; -import { UmbAuthContext } from './auth.context.js'; - -import './auth-layout.element.js'; - -@customElement('umb-login') -export default class UmbLoginElement extends LitElement { - #authContext: IUmbAuthContext; - - @property({ type: String, attribute: 'return-url' }) - returnUrl = ''; - - @property({ type: Boolean }) - isLegacy = false; - - @state() - private _loginState: UUIButtonState = undefined; - - @state() - private _loginError = ''; - - constructor() { - super(); - if (this.isLegacy) { - this.#authContext = new UmbAuthLegacyContext(); - } else { - this.#authContext = new UmbAuthContext(); - } - } - - #handleSubmit = async (e: SubmitEvent) => { - e.preventDefault(); - - const form = e.target as HTMLFormElement; - if (!form) return; - - if (!form.checkValidity()) return; - - const formData = new FormData(form); - - const username = formData.get('email') as string; - const password = formData.get('password') as string; - const persist = formData.has('persist'); - - this._loginState = 'waiting'; - - const response = await this.#authContext.login({ username, password, persist }); - - this._loginError = response.error || ''; - this._loginState = response.error ? 'failed' : 'success'; - - if (response.error) return; - - location.href = this.returnUrl; - }; - - get #greeting() { - return [ - 'Happy super Sunday', - 'Happy marvelous Monday', - 'Happy tubular Tuesday', - 'Happy wonderful Wednesday', - 'Happy thunderous Thursday', - 'Happy funky Friday', - 'Happy Saturday', - ][new Date().getDay()]; - } - - render() { - return html` - -
-

${this.#greeting}

- -
- - Email - - - - - Password - - - - ${this.#authContext.supportsPersistLogin - ? html` - Remember me - ` - : nothing} - - ${this.#renderErrorMessage()} - - -
-
-
-
- `; - } - - #renderErrorMessage() { - if (!this._loginError || this._loginState !== 'failed') return nothing; - - return html`

${this._loginError}

`; - } - - static styles: CSSResultGroup = [ - UUITextStyles, - css` - #email, - #password { - width: 100%; - } - .text-danger { - color: var(--uui-color-danger-standalone); - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-login': UmbLoginElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/login.test.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/login.test.ts deleted file mode 100644 index 3bdfaccdd0..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/login.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -import UmbLoginElement from './login.element.js'; - -describe('UmbLogin', () => { - let element: UmbLoginElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbLoginElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/types.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/types.ts deleted file mode 100644 index 114c515c5d..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type LoginRequestModel = { - username: string; - password: string; - persist: boolean; -}; - -export interface IUmbAuthContext { - login(data: LoginRequestModel): Promise; - supportsPersistLogin: boolean; -} - -export type LoginResponse = { - data?: string; - error?: string; - status: number; -}; diff --git a/src/Umbraco.Web.UI.Client/apps/auth/src/vite-env.d.ts b/src/Umbraco.Web.UI.Client/apps/auth/src/vite-env.d.ts deleted file mode 100644 index b0fda2b4ce..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/src/vite-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// - -/* -interface ImportMetaEnv { -} -*/ diff --git a/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.json b/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.json deleted file mode 100644 index 3f90319d83..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": false - }, - "include": ["src/**/*.ts"], - "references": [ - { - "path": "./tsconfig.node.json" - } - ] -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.node.json b/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.node.json deleted file mode 100644 index b8b8971494..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/tsconfig.node.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "esnext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/src/Umbraco.Web.UI.Client/apps/auth/vite.config.ts b/src/Umbraco.Web.UI.Client/apps/auth/vite.config.ts deleted file mode 100644 index 74cb265ca6..0000000000 --- a/src/Umbraco.Web.UI.Client/apps/auth/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from 'vite'; -import viteTSConfigPaths from 'vite-tsconfig-paths'; - -// https://vitejs.dev/config/ -export default defineConfig({ - build: { - lib: { - entry: 'src/index.ts', - formats: ['es'], - fileName: 'main', - }, - target: 'esnext', - sourcemap: true, - rollupOptions: { - external: [/^@umbraco-cms\/backoffice\//], - output: { - manualChunks: { - uui: ['@umbraco-ui/uui'], - }, - }, - }, - outDir: '../../../Umbraco.Cms.StaticAssets/wwwroot/umbraco/auth', - emptyOutDir: true, - }, - server: { - fs: { - // Allow serving files from the global node_modules folder - allow: ['.', '../../node_modules'], - }, - }, - plugins: [viteTSConfigPaths()], -}); diff --git a/src/Umbraco.Web.UI.Client/devops/icons/index.js b/src/Umbraco.Web.UI.Client/devops/icons/index.js index 29e84f3a96..909ee19094 100644 --- a/src/Umbraco.Web.UI.Client/devops/icons/index.js +++ b/src/Umbraco.Web.UI.Client/devops/icons/index.js @@ -6,12 +6,13 @@ const path = pathModule.default; const getDirName = path.dirname; const glob = globModule.default; -const moduleDirectory = 'src/shared/icon-registry'; +const moduleDirectory = 'src/packages/core/icon-registry'; const iconsOutputDirectory = `${moduleDirectory}/icons`; const umbracoSvgDirectory = `${moduleDirectory}/svgs`; const iconMapJson = `${moduleDirectory}/icon-dictionary.json`; const lucideSvgDirectory = 'node_modules/lucide-static/icons'; +const simpleIconsSvgDirectory = 'node_modules/simple-icons/icons'; const run = async () => { var icons = await collectDictionaryIcons(); @@ -30,7 +31,7 @@ const collectDictionaryIcons = async () => { // Lucide: fileJSON.lucide.forEach((iconDef) => { - if(iconDef.file && iconDef.name) { + if (iconDef.file && iconDef.name) { const path = lucideSvgDirectory + "/" + iconDef.file; try { @@ -50,15 +51,46 @@ const collectDictionaryIcons = async () => { }; icons.push(icon); - } catch(e) { - console.log(`Could not load file: '${path}'`); + } catch (e) { + console.log(`[Lucide] Could not load file: '${path}'`); + } + } + }); + + // SimpleIcons: + fileJSON.simpleIcons.forEach((iconDef) => { + if (iconDef.file && iconDef.name) { + const path = simpleIconsSvgDirectory + "/" + iconDef.file; + + try { + const rawData = readFileSync(path); + let svg = rawData.toString() + const iconFileName = iconDef.name; + + // SimpleIcons need to use fill="currentColor" + const pattern = /fill=/g; + if (!pattern.test(svg)) { + svg = svg.replace(/ { - if(iconDef.file && iconDef.name) { + if (iconDef.file && iconDef.name) { const path = umbracoSvgDirectory + "/" + iconDef.file; try { @@ -75,8 +107,8 @@ const collectDictionaryIcons = async () => { }; icons.push(icon); - } catch(e) { - console.log(`Could not load file: '${path}'`); + } catch (e) { + console.log(`[Umbraco] Could not load file: '${path}'`); } } }); @@ -104,7 +136,7 @@ const collectDiskIcons = async (icons) => { const iconName = iconFileName; // Only append not already defined icons: - if(!icons.find(x => x.name === iconName)) { + if (!icons.find(x => x.name === iconName)) { const icon = { name: iconName, diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 8c26939598..5b8e3705f4 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -9,13 +9,11 @@ "version": "14.0.0-beta003", "license": "MIT", "dependencies": { - "@openid/appauth": "^1.3.1", - "@types/diff": "^5.0.9", "@types/dompurify": "^3.0.5", "@types/uuid": "^9.0.8", "@umbraco-ui/uui": "1.7.2", "@umbraco-ui/uui-css": "1.7.2", - "diff": "^5.2.0", + "base64-js": "^1.5.1", "dompurify": "^3.0.9", "element-internals-polyfill": "^1.3.10", "lit": "^3.1.2", @@ -46,10 +44,10 @@ "@types/mocha": "^10.0.1", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^7.1.0", - "@web/dev-server-esbuild": "^1.0.1", + "@web/dev-server-esbuild": "^1.0.2", "@web/dev-server-import-maps": "^0.2.0", "@web/dev-server-rollup": "^0.6.1", - "@web/test-runner": "^0.18.0", + "@web/test-runner": "^0.18.1", "@web/test-runner-playwright": "^0.11.0", "babel-loader": "^9.1.3", "eslint": "^8.56.0", @@ -70,10 +68,11 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "remark-gfm": "^3.0.1", - "rollup": "^4.12.0", - "rollup-plugin-esbuild": "^6.1.0", - "rollup-plugin-import-css": "^3.4.0", + "rollup": "^4.14.1", + "rollup-plugin-esbuild": "^6.1.1", + "rollup-plugin-import-css": "^3.5.0", "rollup-plugin-web-worker-loader": "^1.6.1", + "simple-icons": "^11.11.0", "storybook": "^7.6.17", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.8", @@ -3344,19 +3343,6 @@ "lit-html": "^2.0.0 || ^3.0.0" } }, - "node_modules/@openid/appauth": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz", - "integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==", - "dependencies": { - "@types/base64-js": "^1.3.0", - "@types/jquery": "^3.5.5", - "base64-js": "^1.5.1", - "follow-redirects": "^1.13.3", - "form-data": "^4.0.0", - "opener": "^1.5.2" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3383,52 +3369,60 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.1.tgz", + "integrity": "sha512-QSXujx4d4ogDamQA8ckkkRieFzDgZEuZuGiey9G7CuDcbnX4iINKWxTPC5Br2AEzY9ICAvcndqgAUFMMKnS/Tw==", "dev": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "yargs": "17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@puppeteer/browsers/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" } }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -4237,9 +4231,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", + "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", "cpu": [ "arm" ], @@ -4250,9 +4244,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", + "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", "cpu": [ "arm64" ], @@ -4263,9 +4257,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", + "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", "cpu": [ "arm64" ], @@ -4276,9 +4270,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", + "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", "cpu": [ "x64" ], @@ -4289,9 +4283,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", + "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", "cpu": [ "arm" ], @@ -4302,9 +4296,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", + "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", "cpu": [ "arm64" ], @@ -4315,9 +4309,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", + "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", "cpu": [ "arm64" ], @@ -4327,10 +4321,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", + "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", + "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", "cpu": [ "riscv64" ], @@ -4340,10 +4347,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", + "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", + "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", "cpu": [ "x64" ], @@ -4354,9 +4374,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", + "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", "cpu": [ "x64" ], @@ -4367,9 +4387,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", + "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", "cpu": [ "arm64" ], @@ -4380,9 +4400,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", + "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", "cpu": [ "ia32" ], @@ -4393,9 +4413,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", + "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", "cpu": [ "x64" ], @@ -5906,11 +5926,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/base64-js": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.2.tgz", - "integrity": "sha512-Q2Xn2/vQHRGLRXhQ5+BSLwhHkR3JVflxVKywH0Q6fVoAiUE8fFYL2pE5/l2ZiOiBDfA8qUqRnSxln4G/NFz1Sg==" - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -6133,14 +6148,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/jquery": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.29.tgz", - "integrity": "sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==", - "dependencies": { - "@types/sizzle": "*" - } - }, "node_modules/@types/js-levenshtein": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz", @@ -6374,11 +6381,6 @@ "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, - "node_modules/@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -8219,15 +8221,15 @@ } }, "node_modules/@web/test-runner": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.18.0.tgz", - "integrity": "sha512-aAlQrdSqwCie1mxuSK5kM0RYDJZL4Q0Hd5LeXn1on3OtHLtgztL4dZzzNSuAWablR2/Vuve3ChwDDxmYSTqXRg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.18.1.tgz", + "integrity": "sha512-jB/9vrpGVtcLY6/7sPpKpSheQ3wWY9P5aQcz2SK2gMHTq3gNpa51NAyec0Al7EFpHvJ1wKYTGRLB2gPyEoJeDg==", "dev": true, "dependencies": { "@web/browser-logs": "^0.4.0", "@web/config-loader": "^0.3.0", "@web/dev-server": "^0.4.0", - "@web/test-runner-chrome": "^0.15.0", + "@web/test-runner-chrome": "^0.16.0", "@web/test-runner-commands": "^0.9.0", "@web/test-runner-core": "^0.13.0", "@web/test-runner-mocha": "^0.9.0", @@ -8250,16 +8252,16 @@ } }, "node_modules/@web/test-runner-chrome": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.15.0.tgz", - "integrity": "sha512-ZqkTJGQ57FDz3lWw+9CKfHuTV64S9GzBy5+0siSQulEVPfGiTzpksx9DohtA3BCLXdbEq4OHg40/XIQJomlc9w==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.16.0.tgz", + "integrity": "sha512-Edc6Y49aVB6k18S5IOj9OCX3rEf8F3jptIu0p95+imqxmcutFEh1GNmlAk2bQGnXS0U6uVY7Xbf61fiaXUQqhg==", "dev": true, "dependencies": { "@web/test-runner-core": "^0.13.0", "@web/test-runner-coverage-v8": "^0.8.0", "async-mutex": "0.4.0", "chrome-launcher": "^0.15.0", - "puppeteer-core": "^20.0.0" + "puppeteer-core": "^22.0.0" }, "engines": { "node": ">=18.0.0" @@ -8494,9 +8496,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -8922,7 +8924,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { "version": "1.0.6", @@ -8952,9 +8955,9 @@ "dev": true }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "node_modules/babel-core": { @@ -9167,6 +9170,42 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.2.3.tgz", + "integrity": "sha512-amG72llr9pstfXOBOHve1WjiuKKAMnebcmMbPWDZ7BCevAoJLpugjuAPRsDINEyjT0a6tbaVx3DctkXIRbLuJw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "streamx": "^2.13.0" + } + }, + "node_modules/bare-os": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.1.tgz", + "integrity": "sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.1.tgz", + "integrity": "sha512-OHM+iwRDRMDBsSW7kl3dO62JyHdBKO3B25FB9vNQBPcGHMo4+eA8Yj41Lfbk3pS/seDY+siNge0LdRTulAau/A==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9187,9 +9226,9 @@ ] }, "node_modules/basic-ftp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", - "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -9710,12 +9749,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.16.tgz", + "integrity": "sha512-IT5lnR44h/qZQ4GaCHvBxYIl4cQL2i9UvFyYeRyVdcpY04hx5H720HQfe/7Oz7ndxaYVLQFGpCO71J4X2Ye/Gw==", "dev": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.22.4" }, "peerDependencies": { "devtools-protocol": "*" @@ -9920,6 +9961,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -10201,15 +10243,6 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -10240,9 +10273,9 @@ "dev": true }, "node_modules/data-uri-to-buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", - "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "engines": { "node": ">= 14" @@ -10429,6 +10462,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -10518,9 +10552,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "version": "0.0.1262051", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", + "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==", "dev": true }, "node_modules/diff": { @@ -12241,25 +12275,6 @@ "node": ">=0.4.0" } }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -12301,6 +12316,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -12551,52 +12567,20 @@ } }, "node_modules/get-uri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", - "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dev": true, "dependencies": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.0", + "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "fs-extra": "^11.2.0" }, "engines": { "node": ">= 14" } }, - "node_modules/get-uri/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/get-uri/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/get-uri/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -12961,9 +12945,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -12974,9 +12958,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -13211,6 +13195,25 @@ "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -14159,6 +14162,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jscodeshift": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.2.tgz", @@ -15780,6 +15789,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -15788,6 +15798,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15869,9 +15880,9 @@ "dev": true }, "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, "node_modules/mkdirp": { @@ -16575,14 +16586,6 @@ "openapi": "bin/index.js" } }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "bin": { - "opener": "bin/opener-bin.js" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -16764,25 +16767,18 @@ } }, "node_modules/pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "dependencies": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/pac-resolver/node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true - }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -17249,19 +17245,19 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" @@ -17323,49 +17319,19 @@ } }, "node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "version": "22.6.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.6.3.tgz", + "integrity": "sha512-YrTAak5zCTWVTnVaCK1b7FD1qFCCT9bSvQhLzamnIsJ57/tfuXiT8ZvPJR2SBfahyFTYFCcaZAd/Npow3lmDGA==", "dev": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", + "@puppeteer/browsers": "2.2.1", + "chromium-bidi": "0.5.16", "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "devtools-protocol": "0.0.1262051", + "ws": "8.16.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=18" } }, "node_modules/qs": { @@ -18078,9 +18044,9 @@ } }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", + "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -18093,19 +18059,21 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.14.1", + "@rollup/rollup-android-arm64": "4.14.1", + "@rollup/rollup-darwin-arm64": "4.14.1", + "@rollup/rollup-darwin-x64": "4.14.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", + "@rollup/rollup-linux-arm64-gnu": "4.14.1", + "@rollup/rollup-linux-arm64-musl": "4.14.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", + "@rollup/rollup-linux-riscv64-gnu": "4.14.1", + "@rollup/rollup-linux-s390x-gnu": "4.14.1", + "@rollup/rollup-linux-x64-gnu": "4.14.1", + "@rollup/rollup-linux-x64-musl": "4.14.1", + "@rollup/rollup-win32-arm64-msvc": "4.14.1", + "@rollup/rollup-win32-ia32-msvc": "4.14.1", + "@rollup/rollup-win32-x64-msvc": "4.14.1", "fsevents": "~2.3.2" } }, @@ -18135,9 +18103,9 @@ "dev": true }, "node_modules/rollup-plugin-import-css": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-import-css/-/rollup-plugin-import-css-3.4.0.tgz", - "integrity": "sha512-997dJi7M7yYFn7tZer/UVt72mh4GH/hHBv48j3V4jsGSg+1DdYUXn+QB9SMMCNKF99pSv6QXmIOLTyeuijIsgg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-import-css/-/rollup-plugin-import-css-3.5.0.tgz", + "integrity": "sha512-JOVow6n00qt2C/NnsqPmIjFOfxIAudwWqC5SaC84CodMGiMFaP1gPAdgnJ8g8hcG+P85TCYp2kI98grYCEt5pg==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.4" @@ -18493,6 +18461,19 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-icons": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-11.11.0.tgz", + "integrity": "sha512-jF4FvxqJ5LGRgScMy6IWHYBuKG7SImwzsTDKHhmAB2RQVKEqvGWI8qmAHWYPKEGM8/oRA/VvKTVIbWdg7YPRsg==", + "dev": true, + "engines": { + "node": ">=0.12.18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -18551,26 +18532,26 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, @@ -18711,13 +18692,16 @@ "dev": true }, "node_modules/streamx": { - "version": "2.15.7", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.7.tgz", - "integrity": "sha512-NPEKS5+yjyo597eafGbKW5ujh7Sm6lDLHZQd/lRSz6S0VarpADBJItqfB4PnwpS+472oob1GX5cCY9vzfJpHUA==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", "dev": true, "dependencies": { "fast-fifo": "^1.1.0", "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/strict-event-emitter": { @@ -18992,14 +18976,17 @@ } }, "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", "dev": true, "dependencies": { - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { @@ -20072,6 +20059,12 @@ "punycode": "^2.1.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/use-callback-ref": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", @@ -21125,6 +21118,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index e730659f43..ad9ede3c00 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -62,6 +62,7 @@ "./property-action": "./dist-cms/packages/core/property-action/index.js", "./property-editor": "./dist-cms/packages/core/property-editor/index.js", "./property": "./dist-cms/packages/core/property/index.js", + "./recycle-bin": "./dist-cms/packages/core/recycle-bin/index.js", "./relation-type": "./dist-cms/packages/relations/relation-types/index.js", "./relations": "./dist-cms/packages/relations/relations/index.js", "./repository": "./dist-cms/packages/core/repository/index.js", @@ -89,6 +90,7 @@ "./webhook": "./dist-cms/packages/webhook/index.js", "./workspace": "./dist-cms/packages/core/workspace/index.js", "./external/backend-api": "./dist-cms/external/backend-api/index.js", + "./external/base64-js": "./dist-cms/external/base64-js/index.js", "./external/diff": "./dist-cms/external/diff/index.js", "./external/dompurify": "./dist-cms/external/dompurify/index.js", "./external/lit": "./dist-cms/external/lit/index.js", @@ -119,7 +121,6 @@ "url": "https://umbraco.com" }, "scripts": { - "auth:test:e2e": "npx playwright test --config apps/auth/", "backoffice:test:e2e": "npx playwright test", "build-storybook": "npm run wc-analyze && storybook build", "build:for:cms": "npm run build && node ./devops/build/copy-to-cms.js", @@ -148,7 +149,7 @@ "preview": "vite preview --open", "storybook:build": "npm run wc-analyze && storybook build", "storybook": "npm run wc-analyze && storybook dev -p 6006", - "test:e2e": "npm run auth:test:e2e && npm run backoffice:test:e2e", + "test:e2e": "npm run backoffice:test:e2e", "test:dev": "web-test-runner --config ./web-test-runner.dev.config.mjs", "test:dev-watch": "web-test-runner --watch --config ./web-test-runner.dev.config.mjs", "test:watch": "web-test-runner --watch", @@ -165,12 +166,12 @@ "npm": ">=10.1 < 11" }, "dependencies": { - "@openid/appauth": "^1.3.1", "@types/diff": "^5.0.9", "@types/dompurify": "^3.0.5", "@types/uuid": "^9.0.8", "@umbraco-ui/uui": "1.7.2", "@umbraco-ui/uui-css": "1.7.2", + "base64-js": "^1.5.1", "diff": "^5.2.0", "dompurify": "^3.0.9", "element-internals-polyfill": "^1.3.10", @@ -202,10 +203,10 @@ "@types/mocha": "^10.0.1", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^7.1.0", - "@web/dev-server-esbuild": "^1.0.1", + "@web/dev-server-esbuild": "^1.0.2", "@web/dev-server-import-maps": "^0.2.0", "@web/dev-server-rollup": "^0.6.1", - "@web/test-runner": "^0.18.0", + "@web/test-runner": "^0.18.1", "@web/test-runner-playwright": "^0.11.0", "babel-loader": "^9.1.3", "eslint": "^8.56.0", @@ -226,10 +227,11 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "remark-gfm": "^3.0.1", - "rollup": "^4.12.0", - "rollup-plugin-esbuild": "^6.1.0", - "rollup-plugin-import-css": "^3.4.0", + "rollup": "^4.14.1", + "rollup-plugin-esbuild": "^6.1.1", + "rollup-plugin-import-css": "^3.5.0", "rollup-plugin-web-worker-loader": "^1.6.1", + "simple-icons": "^11.11.0", "storybook": "^7.6.17", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.8", diff --git a/src/Umbraco.Web.UI.Client/public/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Client/public/umbraco_logo_white.svg deleted file mode 100644 index 3de8e82ed3..0000000000 --- a/src/Umbraco.Web.UI.Client/public/umbraco_logo_white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts new file mode 100644 index 0000000000..baaceaf0a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts @@ -0,0 +1,173 @@ +import { + UMB_AUTH_CONTEXT, + UMB_MODAL_APP_AUTH, + UMB_STORAGE_REDIRECT_URL, + type UmbUserLoginState, +} from '@umbraco-cms/backoffice/auth'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { filter, firstValueFrom, skip } from '@umbraco-cms/backoffice/external/rxjs'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { ManifestAuthProvider } from '@umbraco-cms/backoffice/extension-registry'; + +export class UmbAppAuthController extends UmbControllerBase { + #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; + #firstTimeLoggingIn = true; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_AUTH_CONTEXT, (context) => { + this.#authContext = context; + + // Observe the user's authorization state and start the authorization flow if the user is not authorized + this.observe( + context.isAuthorized.pipe( + // Skip the first since it is always false + skip(1), + // Only continue if the value is false + filter((x) => !x), + ), + () => { + this.#firstTimeLoggingIn = false; + this.makeAuthorizationRequest('timedOut'); + }, + '_authState', + ); + }); + } + + /** + * Checks if the user is authorized. + * If not, the authorization flow is started. + */ + async isAuthorized(): Promise { + if (!this.#authContext) { + throw new Error('[Fatal] Auth context is not available'); + } + + const isAuthorized = this.#authContext.getIsAuthorized(); + + if (isAuthorized) { + return true; + } + + // Make a request to the auth server to start the auth flow + return this.makeAuthorizationRequest(); + } + + /** + * Starts the authorization flow. + * It will check which providers are available and either redirect directly to the provider or show a provider selection screen. + */ + async makeAuthorizationRequest(userLoginState: UmbUserLoginState = 'loggingIn'): Promise { + if (!this.#authContext) { + throw new Error('[Fatal] Auth context is not available'); + } + + // Save location.href so we can redirect to it after login + if (location.href !== this.#authContext.getPostLogoutRedirectUrl()) { + window.sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, location.href); + } + + // Figure out which providers are available + const availableProviders = await firstValueFrom(this.#authContext.getAuthProviders()); + + // If the user is timed out, we can show the login modal directly + if (userLoginState === 'timedOut') { + const selected = await this.#showLoginModal(userLoginState, availableProviders); + + if (!selected) { + return false; + } + + return this.#updateState(); + } + + if (availableProviders.length === 0) { + throw new Error('[Fatal] No auth providers available'); + } + + if (availableProviders.length === 1) { + // One provider available (most likely the Umbraco provider), so initiate the authorization request to the default provider + this.#authContext.makeAuthorizationRequest(availableProviders[0].forProviderName); + return this.#updateState(); + } + + // Check if any provider is redirecting directly to the provider + const redirectProvider = + userLoginState === 'loggingIn' + ? availableProviders.find((provider) => provider.meta?.behavior?.autoRedirect) + : undefined; + + if (redirectProvider) { + // Redirect directly to the provider + this.#authContext.makeAuthorizationRequest(redirectProvider.forProviderName); + return this.#updateState(); + } + + // Show the provider selection screen + const selected = await this.#showLoginModal(userLoginState, availableProviders); + + if (!selected) { + return false; + } + + return this.#updateState(); + } + + async #showLoginModal( + userLoginState: UmbUserLoginState, + availableProviders: Array, + ): Promise { + if (!this.#authContext) { + throw new Error('[Fatal] Auth context is not available'); + } + + // Check if any provider denies local login + const denyLocalLogin = availableProviders.some((provider) => provider.meta?.behavior?.denyLocalLogin); + if (denyLocalLogin) { + // Unregister the Umbraco provider + umbExtensionsRegistry.unregister('Umb.AuthProviders.Umbraco'); + } + + // Show the provider selection screen + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + modalManager.remove('umbAuthModal'); + const selected = await modalManager + .open(this._host, UMB_MODAL_APP_AUTH, { + data: { + userLoginState, + }, + modal: { + key: 'umbAuthModal', + backdropBackground: this.#firstTimeLoggingIn + ? 'var(--umb-auth-backdrop, url("/umbraco/backoffice/assets/umbraco_logo_white.svg") 20px 20px / 200px no-repeat, radial-gradient(circle, rgba(2,0,36,1) 0%, rgba(40,58,151,.9) 50%, rgba(0,212,255,1) 100%))' + : 'var(--umb-auth-backdrop-timedout, rgba(0,0,0,0.75))', + }, + }) + .onSubmit() + .catch(() => undefined); + + if (!selected?.providerName) { + return false; + } + + this.#authContext.makeAuthorizationRequest(selected.providerName, selected.loginHint); + + return true; + } + + #updateState() { + if (!this.#authContext) { + throw new Error('[Fatal] Auth context is not available'); + } + + // Reinitialize the auth flow (load the state from local storage) + this.#authContext.setInitialState(); + + // The authorization flow is finished, so let the caller know if the user is authorized + return this.#authContext.getIsAuthorized(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts index d104ccab6c..de8c4ee346 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts @@ -1,8 +1,10 @@ +import { onInit } from '../../packages/core/entry-point.js'; import type { UmbAppErrorElement } from './app-error.element.js'; import { UmbAppContext } from './app.context.js'; import { UmbServerConnection } from './server-connection.js'; +import { UmbAppAuthController } from './app-auth.controller.js'; import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; -import { UMB_STORAGE_REDIRECT_URL, UmbAuthContext } from '@umbraco-cms/backoffice/auth'; +import { UmbAuthContext } from '@umbraco-cms/backoffice/auth'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui'; import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon'; @@ -11,6 +13,8 @@ import type { Guard, UmbRoute } from '@umbraco-cms/backoffice/router'; import { pathWithoutBasePath } from '@umbraco-cms/backoffice/router'; import { OpenAPI, RuntimeLevelModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbContextDebugController } from '@umbraco-cms/backoffice/debug'; +import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-app') export class UmbAppElement extends UmbLitElement { @@ -21,7 +25,12 @@ export class UmbAppElement extends UmbLitElement { * @remarks This is the base URL of the Umbraco server, not the base URL of the backoffice. */ @property({ type: String }) - serverUrl = window.location.origin; + set serverUrl(url: string) { + OpenAPI.BASE = url; + } + get serverUrl() { + return OpenAPI.BASE; + } /** * The base path of the backoffice. @@ -29,7 +38,6 @@ export class UmbAppElement extends UmbLitElement { * @attr */ @property({ type: String }) - // TODO: get from base element or maybe move to UmbAuthContext.#getRedirectUrl since it is only used there backofficePath = '/umbraco'; /** @@ -48,6 +56,13 @@ export class UmbAppElement extends UmbLitElement { component: () => import('../upgrader/upgrader.element.js'), guards: [this.#isAuthorizedGuard()], }, + { + path: 'logout', + resolve: () => { + this.#authContext?.clearTokenStorage(); + this.#authController.makeAuthorizationRequest('loggedOut'); + }, + }, { path: '**', component: () => import('../backoffice/backoffice.element.js'), @@ -56,17 +71,18 @@ export class UmbAppElement extends UmbLitElement { ]; #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; - #umbIconRegistry = new UmbIconRegistry(); - #uuiIconRegistry = new UUIIconRegistryEssential(); #serverConnection?: UmbServerConnection; + #authController = new UmbAppAuthController(this); constructor() { super(); - new UmbContextDebugController(this); + OpenAPI.BASE = window.location.origin; - this.#umbIconRegistry.attach(this); - this.#uuiIconRegistry.attach(this); + new UmbIconRegistry().attach(this); + new UUIIconRegistryEssential().attach(this); + + new UmbContextDebugController(this); } connectedCallback(): void { @@ -75,19 +91,17 @@ export class UmbAppElement extends UmbLitElement { } async #setup() { - if (this.serverUrl === undefined) throw new Error('No serverUrl provided'); - - /* All requests to the server requires the base URL to be set. - We make sure it happens before we get the server status. - TODO: find the right place to set this - */ - OpenAPI.BASE = this.serverUrl; - this.#serverConnection = await new UmbServerConnection(this.serverUrl).connect(); this.#authContext = new UmbAuthContext(this, this.serverUrl, this.backofficePath, this.bypassAuth); new UmbAppContext(this, { backofficePath: this.backofficePath, serverUrl: this.serverUrl }); + // Register Core extensions (this is specifically done here because we need these extensions to be registered before the application is initialized) + onInit(this, umbExtensionsRegistry); + + // Register public extensions + await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPublicExtensions(); + // Try to initialise the auth flow and get the runtime status try { // If the runtime level is "install" we should clear any cached tokens @@ -136,7 +150,6 @@ export class UmbAppElement extends UmbLitElement { // Instruct all requests to use the auth flow to get and use the access_token for all subsequent requests OpenAPI.TOKEN = () => this.#authContext!.getLatestToken(); OpenAPI.WITH_CREDENTIALS = true; - OpenAPI.CREDENTIALS = 'include'; } #redirect() { @@ -184,24 +197,7 @@ export class UmbAppElement extends UmbLitElement { } #isAuthorizedGuard(): Guard { - return () => { - if (!this.#authContext) { - throw new Error('[Fatal] AuthContext requested before it was initialized'); - } - - if (this.#authContext.getIsAuthorized()) { - return true; - } - - // Save location.href so we can redirect to it after login - window.sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, location.href); - - // Make a request to the auth server to start the auth flow - this.#authContext.makeAuthorizationRequest(); - - // Return false to prevent the route from being rendered - return false; - }; + return () => this.#authController.isAuthorized() ?? false; } #errorPage(errorMsg: string, error?: unknown) { diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index 4d77370af7..672117fa16 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -14,7 +14,6 @@ import './components/index.js'; const CORE_PACKAGES = [ import('../../packages/audit-log/umbraco-package.js'), import('../../packages/block/umbraco-package.js'), - import('../../packages/core/umbraco-package.js'), import('../../packages/data-type/umbraco-package.js'), import('../../packages/dictionary/umbraco-package.js'), import('../../packages/documents/umbraco-package.js'), @@ -45,7 +44,6 @@ export class UmbBackofficeElement extends UmbLitElement { /** * Backoffice extension registry. * This enables to register and unregister extensions via DevTools, or just via querying this element via the DOM. - * @type {UmbExtensionsRegistry} */ public extensionRegistry = umbExtensionsRegistry; @@ -53,9 +51,11 @@ export class UmbBackofficeElement extends UmbLitElement { super(); new UmbBackofficeContext(this); + new UmbBundleExtensionInitializer(this, umbExtensionsRegistry); new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry); - new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerAllExtensions(); + + new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions(); // So far local packages are this simple to registerer, so no need for a manager to do that: CORE_PACKAGES.forEach(async (packageImport) => { diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts index 12a8fac134..5dd782a1a0 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-header-sections.element.ts @@ -1,7 +1,7 @@ import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js'; import type { UmbBackofficeContext } from '../backoffice.context.js'; import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit'; -import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import type { ManifestSection } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbExtensionManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; @@ -62,7 +62,11 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement { + label="${ifDefined( + section.manifest?.meta.label + ? this.localize.string(section.manifest?.meta.label) + : section.manifest?.name, + )}"> `, )} diff --git a/src/Umbraco.Web.UI.Client/src/assets/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/installer.jpg deleted file mode 100644 index f0f5de8695..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/installer.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index e883fdf02f..15ae7c0767 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -1014,6 +1014,18 @@ export default { lockoutWillOccur: 'Du har været inaktiv, og du vil blive logget ud om', renewSession: 'Forny for at gemme dine ændringer', }, + login: { + greeting0: 'Velkommen', + greeting1: 'Velkommen', + greeting2: 'Velkommen', + greeting3: 'Velkommen', + greeting4: 'Velkommen', + greeting5: 'Velkommen', + greeting6: 'Velkommen', + instruction: 'Log ind på Umbraco', + signInWith: 'Log ind med {0}', + timeout: 'Du er blevet logget ud på grund af inaktivitet, vil du logge ind igen?', + }, main: { dashboard: 'Skrivebord', sections: 'Sektioner', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 134793b2f5..6cb910c6dd 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -5,61 +5,66 @@ export default { assigndomain: 'Culture and Hostnames', auditTrail: 'Audit Trail', browse: 'Browse Node', - changeDocType: 'Change Document Type', changeDataType: 'Change Data Type', - copy: 'Copy', + changeDocType: 'Change Document Type', + chooseWhereToCopy: 'Choose where to copy', + chooseWhereToImport: 'Choose where to import', + chooseWhereToMove: 'Choose where to move', + copy: 'Copy to', create: 'Create', - export: 'Export', - createPackage: 'Create Package', + createblueprint: 'Create Document Blueprint', createGroup: 'Create group', + createPackage: 'Create Package', delete: 'Delete', + deployCompare: 'Compare', + deployPartialRestore: 'Partial restore', + deployQueueForTransfer: 'Queue for transfer', + deployRestore: 'Workspace restore', + deployTransferNow: 'Transfer now', + deployTreeRestore: 'Tree restore', disable: 'Disable', editContent: 'Edit content', editSettings: 'Edit settings', emptyrecyclebin: 'Empty recycle bin', enable: 'Enable', + export: 'Export', exportDocumentType: 'Export Document Type', + folderCreate: 'Create folder', + folderDelete: 'Delete folder', + folderRename: 'Rename folder', + import: 'Import', importdocumenttype: 'Import Document Type', importPackage: 'Import Package', + infiniteEditorChooseWhereToCopy: 'Choose where to copy the selected item(s)', + infiniteEditorChooseWhereToMove: 'Choose where to move the selected item(s)', liveEdit: 'Edit in Canvas', logout: 'Exit', - move: 'Move', + move: 'Move to', notify: 'Notifications', protect: 'Restrict Public Access', publish: 'Publish', - unpublish: 'Unpublish', refreshNode: 'Reload', - republish: 'Republish entire site', remove: 'Remove', rename: 'Rename', + republish: 'Republish entire site', + resendInvite: 'Resend Invitation', restore: 'Restore', - chooseWhereToCopy: 'Choose where to copy', - chooseWhereToMove: 'Choose where to move', - chooseWhereToImport: 'Choose where to import', - toInTheTreeStructureBelow: 'to in the tree structure below', - infiniteEditorChooseWhereToCopy: 'Choose where to copy the selected item(s)', - infiniteEditorChooseWhereToMove: 'Choose where to move the selected item(s)', - wasMovedTo: 'was moved to', - wasCopiedTo: 'was copied to', - wasDeleted: 'was deleted', rights: 'Permissions', rollback: 'Rollback', sendtopublish: 'Send To Publish', sendToTranslate: 'Send To Translation', setGroup: 'Set group', - sort: 'Sort', - translate: 'Translate', - update: 'Update', setPermissions: 'Set permissions', + sort: 'Sort children of', + toInTheTreeStructureBelow: 'to in the tree structure below', + translate: 'Translate', + trash: 'Trash', unlock: 'Unlock', - createblueprint: 'Create Document Blueprint', - resendInvite: 'Resend Invitation', - deployQueueForTransfer: 'Queue for transfer', - deployRestore: 'Workspace restore', - deployTreeRestore: 'Tree restore', - deployPartialRestore: 'Partial restore', - deployTransferNow: 'Transfer now', - deployCompare: 'Compare', + unpublish: 'Unpublish', + update: 'Update', + wasCopiedTo: 'was copied to', + wasDeleted: 'was deleted', + wasMovedTo: 'was moved to', }, actionCategories: { content: 'Content', @@ -745,6 +750,7 @@ export default { deleted: 'Deleted', deleting: 'Deleting...', design: 'Design', + details: 'Details', dictionary: 'Dictionary', dimensions: 'Dimensions', discard: 'Discard', @@ -1011,6 +1017,18 @@ export default { lockoutWillOccur: "You've been idle and logout will automatically occur in", renewSession: 'Renew now to save your work', }, + login: { + greeting0: 'Welcome', + greeting1: 'Welcome', + greeting2: 'Welcome', + greeting3: 'Welcome', + greeting4: 'Welcome', + greeting5: 'Welcome', + greeting6: 'Welcome', + instruction: 'Sign in to Umbraco', + signInWith: 'Sign in with {0}', + timeout: 'Your session has timed out. Please sign in again below.', + }, main: { dashboard: 'Dashboard', sections: 'Sections', @@ -1801,6 +1819,7 @@ export default { createDate: 'User created', changePassword: 'Change your password', changePhoto: 'Change photo', + configureMfa: 'Configure MFA', emailRequired: 'Required - enter an email address for this user', newPassword: 'New password', newPasswordFormatLengthTip: 'Minimum %0% character(s) to go!', diff --git a/src/Umbraco.Web.UI.Client/src/assets/umbraco_background.jpg b/src/Umbraco.Web.UI.Client/src/assets/umbraco_background.jpg deleted file mode 100644 index 412cd17865..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/umbraco_background.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_blue.svg b/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_blue.svg new file mode 100644 index 0000000000..578bf592f6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_blue.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_white.svg index 3de8e82ed3..01f7260cd3 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_white.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/umbraco_logo_white.svg @@ -1 +1,51 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts index 17014a5ab2..42633b3570 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts @@ -152,6 +152,7 @@ export { LogLevelModel } from './models/LogLevelModel'; export type { LogMessagePropertyPresentationModel } from './models/LogMessagePropertyPresentationModel'; export type { LogMessageResponseModel } from './models/LogMessageResponseModel'; export type { LogTemplateResponseModel } from './models/LogTemplateResponseModel'; +export type { ManifestResponseModel } from './models/ManifestResponseModel'; export type { MediaCollectionResponseModel } from './models/MediaCollectionResponseModel'; export type { MediaConfigurationResponseModel } from './models/MediaConfigurationResponseModel'; export type { MediaItemResponseModel } from './models/MediaItemResponseModel'; @@ -209,7 +210,6 @@ export type { OutOfDateStatusResponseModel } from './models/OutOfDateStatusRespo export { OutOfDateTypeModel } from './models/OutOfDateTypeModel'; export type { PackageConfigurationResponseModel } from './models/PackageConfigurationResponseModel'; export type { PackageDefinitionResponseModel } from './models/PackageDefinitionResponseModel'; -export type { PackageManifestResponseModel } from './models/PackageManifestResponseModel'; export type { PackageMigrationStatusResponseModel } from './models/PackageMigrationStatusResponseModel'; export type { PagedAllowedDocumentTypeModel } from './models/PagedAllowedDocumentTypeModel'; export type { PagedAllowedMediaTypeModel } from './models/PagedAllowedMediaTypeModel'; @@ -411,6 +411,7 @@ export { IndexerResource } from './services/IndexerResource'; export { InstallResource } from './services/InstallResource'; export { LanguageResource } from './services/LanguageResource'; export { LogViewerResource } from './services/LogViewerResource'; +export { ManifestResource } from './services/ManifestResource'; export { MediaResource } from './services/MediaResource'; export { MediaTypeResource } from './services/MediaTypeResource'; export { MemberResource } from './services/MemberResource'; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PackageManifestResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ManifestResponseModel.ts similarity index 82% rename from src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PackageManifestResponseModel.ts rename to src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ManifestResponseModel.ts index c247a475af..792e0482bb 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PackageManifestResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ManifestResponseModel.ts @@ -3,7 +3,7 @@ /* tslint:disable */ /* eslint-disable */ -export type PackageManifestResponseModel = { +export type ManifestResponseModel = { name: string; version?: string | null; extensions: Array; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/ManifestResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/ManifestResource.ts new file mode 100644 index 0000000000..a206603b17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/ManifestResource.ts @@ -0,0 +1,52 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ManifestResponseModel } from '../models/ManifestResponseModel'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class ManifestResource { + + /** + * @returns any Success + * @throws ApiError + */ + public static getManifestManifest(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/manifest/manifest', + errors: { + 401: `The resource is protected and requires an authentication token`, + }, + }); + } + + /** + * @returns any Success + * @throws ApiError + */ + public static getManifestManifestPrivate(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/manifest/manifest/private', + errors: { + 401: `The resource is protected and requires an authentication token`, + }, + }); + } + + /** + * @returns any Success + * @throws ApiError + */ + public static getManifestManifestPublic(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/manifest/manifest/public', + }); + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/PackageResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/PackageResource.ts index c83524dc6c..6698b384b0 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/PackageResource.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/PackageResource.ts @@ -5,7 +5,6 @@ import type { CreatePackageRequestModel } from '../models/CreatePackageRequestModel'; import type { PackageConfigurationResponseModel } from '../models/PackageConfigurationResponseModel'; import type { PackageDefinitionResponseModel } from '../models/PackageDefinitionResponseModel'; -import type { PackageManifestResponseModel } from '../models/PackageManifestResponseModel'; import type { PagedPackageDefinitionResponseModel } from '../models/PagedPackageDefinitionResponseModel'; import type { PagedPackageMigrationStatusResponseModel } from '../models/PagedPackageMigrationStatusResponseModel'; import type { UpdatePackageRequestModel } from '../models/UpdatePackageRequestModel'; @@ -195,31 +194,6 @@ export class PackageResource { }); } - /** - * @returns any Success - * @throws ApiError - */ - public static getPackageManifest(): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/package/manifest', - errors: { - 401: `The resource is protected and requires an authentication token`, - }, - }); - } - - /** - * @returns any Success - * @throws ApiError - */ - public static getPackageManifestPublic(): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/package/manifest/public', - }); - } - /** * @returns any Success * @throws ApiError diff --git a/src/Umbraco.Web.UI.Client/src/external/base64-js/index.ts b/src/Umbraco.Web.UI.Client/src/external/base64-js/index.ts new file mode 100644 index 0000000000..d8a192ebba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/base64-js/index.ts @@ -0,0 +1 @@ +export { fromByteArray } from 'base64-js'; diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/LICENSE b/src/Umbraco.Web.UI.Client/src/external/openid/LICENSE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request.ts b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request.ts new file mode 100644 index 0000000000..668082c472 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request.ts @@ -0,0 +1,122 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DefaultCrypto } from './crypto_utils.js'; +import type { Crypto } from './crypto_utils.js'; +import { log } from './logger.js'; +import type { StringMap } from './types.js'; + +/** + * Represents an AuthorizationRequest as JSON. + */ +export interface AuthorizationRequestJson { + response_type: string; + client_id: string; + redirect_uri: string; + scope: string; + state?: string; + extras?: StringMap; + internal?: StringMap; +} + +/** + * Generates a cryptographically random new state. Useful for CSRF protection. + */ +const SIZE = 10; // 10 bytes +const newState = function (crypto: Crypto): string { + return crypto.generateRandom(SIZE); +}; + +/** + * Represents the AuthorizationRequest. + * For more information look at + * https://tools.ietf.org/html/rfc6749#section-4.1.1 + */ +export class AuthorizationRequest { + static RESPONSE_TYPE_TOKEN = 'token'; + static RESPONSE_TYPE_CODE = 'code'; + + // NOTE: + // Both redirect_uri and state are actually optional. + // However AppAuth is more opionionated, and requires you to use both. + + clientId: string; + redirectUri: string; + scope: string; + responseType: string; + state: string; + extras?: StringMap; + internal?: StringMap; + /** + * Constructs a new AuthorizationRequest. + * Use a `undefined` value for the `state` parameter, to generate a random + * state for CSRF protection. + */ + constructor( + request: AuthorizationRequestJson, + private crypto: Crypto = new DefaultCrypto(), + private usePkce: boolean = true, + ) { + this.clientId = request.client_id; + this.redirectUri = request.redirect_uri; + this.scope = request.scope; + this.responseType = request.response_type || AuthorizationRequest.RESPONSE_TYPE_CODE; + this.state = request.state || newState(crypto); + this.extras = request.extras; + // read internal properties if available + this.internal = request.internal; + } + + setupCodeVerifier(): Promise { + if (!this.usePkce) { + return Promise.resolve(); + } else { + const codeVerifier = this.crypto.generateRandom(128); + const challenge: Promise = this.crypto.deriveChallenge(codeVerifier).catch((error) => { + log('Unable to generate PKCE challenge. Not using PKCE', error); + return undefined; + }); + return challenge.then((result) => { + if (result) { + // keep track of the code used. + this.internal = this.internal || {}; + this.internal['code_verifier'] = codeVerifier; + this.extras = this.extras || {}; + this.extras['code_challenge'] = result; + // We always use S256. Plain is not good enough. + this.extras['code_challenge_method'] = 'S256'; + } + }); + } + } + + /** + * Serializes the AuthorizationRequest to a JavaScript Object. + */ + toJson(): Promise { + // Always make sure that the code verifier is setup when toJson() is called. + return this.setupCodeVerifier().then(() => { + return { + response_type: this.responseType, + client_id: this.clientId, + redirect_uri: this.redirectUri, + scope: this.scope, + state: this.state, + extras: this.extras, + internal: this.internal, + }; + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request_handler.ts b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request_handler.ts new file mode 100644 index 0000000000..90eb72b171 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_request_handler.ts @@ -0,0 +1,161 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { AuthorizationRequest } from './authorization_request.js'; +import type { AuthorizationError, AuthorizationResponse } from './authorization_response.js'; +import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js'; +import type { Crypto } from './crypto_utils.js'; +import { log } from './logger.js'; +import type { QueryStringUtils } from './query_string_utils.js'; +import type { StringMap } from './types.js'; + +/** + * This type represents a lambda that can take an AuthorizationRequest, + * and an AuthorizationResponse as arguments. + */ +export type AuthorizationListener = ( + request: AuthorizationRequest, + response: AuthorizationResponse | null, + error: AuthorizationError | null, +) => void; + +/** + * Represents a structural type holding both authorization request and response. + */ +export interface AuthorizationRequestResponse { + request: AuthorizationRequest; + response: AuthorizationResponse | null; + error: AuthorizationError | null; +} + +/** + * Authorization Service notifier. + * This manages the communication of the AuthorizationResponse to the 3p client. + */ +export class AuthorizationNotifier { + private listener: AuthorizationListener | null = null; + + setAuthorizationListener(listener: AuthorizationListener) { + this.listener = listener; + } + + /** + * The authorization complete callback. + */ + onAuthorizationComplete( + request: AuthorizationRequest, + response: AuthorizationResponse | null, + error: AuthorizationError | null, + ): void { + if (this.listener) { + // complete authorization request + this.listener(request, response, error); + } + } +} + +// TODO(rahulrav@): add more built in parameters. +/* built in parameters. */ +export const BUILT_IN_PARAMETERS = ['redirect_uri', 'client_id', 'response_type', 'state', 'scope']; + +/** + * Defines the interface which is capable of handling an authorization request + * using various methods (iframe / popup / different process etc.). + */ +export abstract class AuthorizationRequestHandler { + constructor( + public utils: QueryStringUtils, + protected crypto: Crypto, + ) {} + + // notifier send the response back to the client. + protected notifier: AuthorizationNotifier | null = null; + + /** + * A utility method to be able to build the authorization request URL. + */ + protected buildRequestUrl(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest) { + // build the query string + // coerce to any type for convenience + const requestMap: StringMap = { + redirect_uri: request.redirectUri, + client_id: request.clientId, + response_type: request.responseType, + state: request.state, + scope: request.scope, + }; + + // copy over extras + if (request.extras) { + for (const extra in request.extras) { + if (Object.prototype.hasOwnProperty.call(request.extras, extra)) { + // check before inserting to requestMap + if (BUILT_IN_PARAMETERS.indexOf(extra) < 0) { + requestMap[extra] = request.extras[extra]; + } + } + } + } + + const query = this.utils.stringify(requestMap); + const baseUrl = configuration.authorizationEndpoint; + const url = `${baseUrl}?${query}`; + return url; + } + + /** + * Completes the authorization request if necessary & when possible. + */ + completeAuthorizationRequestIfPossible(): Promise { + // call complete authorization if possible to see there might + // be a response that needs to be delivered. + log(`Checking to see if there is an authorization response to be delivered.`); + if (!this.notifier) { + log(`Notifier is not present on AuthorizationRequest handler. + No delivery of result will be possible`); + } + return this.completeAuthorizationRequest().then((result) => { + if (!result) { + log(`No result is available yet.`); + } + if (result && this.notifier) { + this.notifier.onAuthorizationComplete(result.request, result.response, result.error); + } + }); + } + + /** + * Sets the default Authorization Service notifier. + */ + setAuthorizationNotifier(notifier: AuthorizationNotifier): AuthorizationRequestHandler { + this.notifier = notifier; + return this; + } + + /** + * Makes an authorization request. + */ + abstract performAuthorizationRequest( + configuration: AuthorizationServiceConfiguration, + request: AuthorizationRequest, + ): void; + + /** + * Checks if an authorization flow can be completed, and completes it. + * The handler returns a `Promise` if ready, or a `Promise` + * if not ready. + */ + protected abstract completeAuthorizationRequest(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/authorization_response.ts b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_response.ts new file mode 100644 index 0000000000..ac074fca30 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_response.ts @@ -0,0 +1,79 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents the AuthorizationResponse as a JSON object. + */ +export interface AuthorizationResponseJson { + code: string; + state: string; +} + +/** + * Represents the AuthorizationError as a JSON object. + */ +export interface AuthorizationErrorJson { + error: string; + error_description?: string; + error_uri?: string; + state?: string; +} + +/** + * Represents the Authorization Response type. + * For more information look at + * https://tools.ietf.org/html/rfc6749#section-4.1.2 + */ +export class AuthorizationResponse { + code: string; + state: string; + + constructor(response: AuthorizationResponseJson) { + this.code = response.code; + this.state = response.state; + } + + toJson(): AuthorizationResponseJson { + return { code: this.code, state: this.state }; + } +} + +/** + * Represents the Authorization error response. + * For more information look at: + * https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ +export class AuthorizationError { + error: string; + errorDescription?: string; + errorUri?: string; + state?: string; + + constructor(error: AuthorizationErrorJson) { + this.error = error.error; + this.errorDescription = error.error_description; + this.errorUri = error.error_uri; + this.state = error.state; + } + + toJson(): AuthorizationErrorJson { + return { + error: this.error, + error_description: this.errorDescription, + error_uri: this.errorUri, + state: this.state, + }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/authorization_service_configuration.ts b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_service_configuration.ts new file mode 100644 index 0000000000..3fd30c663f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/authorization_service_configuration.ts @@ -0,0 +1,81 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Requestor } from './xhr.js'; +import { FetchRequestor } from './xhr.js'; + +/** + * Represents AuthorizationServiceConfiguration as a JSON object. + */ +export interface AuthorizationServiceConfigurationJson { + authorization_endpoint: string; + token_endpoint: string; + revocation_endpoint: string; + end_session_endpoint?: string; + userinfo_endpoint?: string; +} + +/** + * The standard base path for well-known resources on domains. + * See https://tools.ietf.org/html/rfc5785 for more information. + */ +const WELL_KNOWN_PATH = '.well-known'; + +/** + * The standard resource under the well known path at which an OpenID Connect + * discovery document can be found under an issuer's base URI. + */ +const OPENID_CONFIGURATION = 'openid-configuration'; + +/** + * Configuration details required to interact with an authorization service. + * + * More information at https://openid.net/specs/openid-connect-discovery-1_0-17.html + */ +export class AuthorizationServiceConfiguration { + authorizationEndpoint: string; + tokenEndpoint: string; + revocationEndpoint: string; + userInfoEndpoint?: string; + endSessionEndpoint?: string; + + constructor(request: AuthorizationServiceConfigurationJson) { + this.authorizationEndpoint = request.authorization_endpoint; + this.tokenEndpoint = request.token_endpoint; + this.revocationEndpoint = request.revocation_endpoint; + this.userInfoEndpoint = request.userinfo_endpoint; + this.endSessionEndpoint = request.end_session_endpoint; + } + + toJson() { + return { + authorization_endpoint: this.authorizationEndpoint, + token_endpoint: this.tokenEndpoint, + revocation_endpoint: this.revocationEndpoint, + end_session_endpoint: this.endSessionEndpoint, + userinfo_endpoint: this.userInfoEndpoint, + }; + } + + static fetchFromIssuer(openIdIssuerUrl: string, requestor?: Requestor): Promise { + const fullUrl = `${openIdIssuerUrl}/${WELL_KNOWN_PATH}/${OPENID_CONFIGURATION}`; + + const requestorToUse = requestor || new FetchRequestor(); + + return requestorToUse + .xhr({ url: fullUrl, dataType: 'json', method: 'GET' }) + .then((json) => new AuthorizationServiceConfiguration(json)); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/crypto_utils.ts b/src/Umbraco.Web.UI.Client/src/external/openid/crypto_utils.ts new file mode 100644 index 0000000000..9ee6c84ac1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/crypto_utils.ts @@ -0,0 +1,98 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as base64 from '@umbraco-cms/backoffice/external/base64-js'; + +import { AppAuthError } from './errors.js'; + +const HAS_CRYPTO = typeof window !== 'undefined' && !!(window.crypto as any); +const HAS_SUBTLE_CRYPTO = HAS_CRYPTO && !!(window.crypto.subtle as any); +const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +export function bufferToString(buffer: Uint8Array) { + const state = []; + for (let i = 0; i < buffer.byteLength; i += 1) { + const index = buffer[i] % CHARSET.length; + state.push(CHARSET[index]); + } + return state.join(''); +} + +export function urlSafe(buffer: Uint8Array): string { + const encoded = base64.fromByteArray(new Uint8Array(buffer)); + return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +} + +// adapted from source: http://stackoverflow.com/a/11058858 +// this is used in place of TextEncode as the api is not yet +// well supported: https://caniuse.com/#search=TextEncoder +export function textEncodeLite(str: string) { + const buf = new ArrayBuffer(str.length); + const bufView = new Uint8Array(buf); + + for (let i = 0; i < str.length; i++) { + bufView[i] = str.charCodeAt(i); + } + return bufView; +} + +export interface Crypto { + /** + * Generate a random string + */ + generateRandom(size: number): string; + /** + * Compute the SHA256 of a given code. + * This is useful when using PKCE. + */ + deriveChallenge(code: string): Promise; +} + +/** + * The default implementation of the `Crypto` interface. + * This uses the capabilities of the browser. + */ +export class DefaultCrypto implements Crypto { + generateRandom(size: number) { + const buffer = new Uint8Array(size); + if (HAS_CRYPTO) { + window.crypto.getRandomValues(buffer); + } else { + // fall back to Math.random() if nothing else is available + for (let i = 0; i < size; i += 1) { + buffer[i] = (Math.random() * CHARSET.length) | 0; + } + } + return bufferToString(buffer); + } + + deriveChallenge(code: string): Promise { + if (code.length < 43 || code.length > 128) { + return Promise.reject(new AppAuthError('Invalid code length.')); + } + if (!HAS_SUBTLE_CRYPTO) { + return Promise.reject(new AppAuthError('window.crypto.subtle is unavailable.')); + } + + return new Promise((resolve, reject) => { + crypto.subtle.digest('SHA-256', textEncodeLite(code)).then( + (buffer) => { + return resolve(urlSafe(new Uint8Array(buffer))); + }, + (error) => reject(error), + ); + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/errors.ts b/src/Umbraco.Web.UI.Client/src/external/openid/errors.ts new file mode 100644 index 0000000000..a37305b800 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/errors.ts @@ -0,0 +1,24 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents the AppAuthError type. + */ +export class AppAuthError { + constructor( + public message: string, + public extras?: any, + ) {} +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/flags.ts b/src/Umbraco.Web.UI.Client/src/external/openid/flags.ts new file mode 100644 index 0000000000..8a278c6c06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/flags.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Global flags that control the behavior of App Auth JS. */ + +/* Logging turned on ? */ +export const IS_LOG = true; + +/* Profiling turned on ? */ +export const IS_PROFILE = false; diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/index.ts b/src/Umbraco.Web.UI.Client/src/external/openid/index.ts index ff18b32b45..108f5e5134 100644 --- a/src/Umbraco.Web.UI.Client/src/external/openid/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/openid/index.ts @@ -1,18 +1,17 @@ -export { - BaseTokenRequestHandler, - BasicQueryStringUtils, - FetchRequestor, - LocalStorageBackend, - RedirectRequestHandler, - RevokeTokenRequest, -} from '@openid/appauth'; -export { AuthorizationRequest } from '@openid/appauth/built/authorization_request'; -export { AuthorizationNotifier } from '@openid/appauth/built/authorization_request_handler'; -export { AuthorizationServiceConfiguration } from '@openid/appauth/built/authorization_service_configuration'; -export { - GRANT_TYPE_AUTHORIZATION_CODE, - GRANT_TYPE_REFRESH_TOKEN, - TokenRequest, -} from '@openid/appauth/built/token_request'; -export { TokenResponse } from '@openid/appauth/built/token_response'; -export type { LocationLike, StringMap } from '@openid/appauth/built/types'; +export * from './authorization_request.js'; +export * from './authorization_request_handler.js'; +export * from './authorization_response.js'; +export * from './authorization_service_configuration.js'; +export * from './crypto_utils.js'; +export * from './errors.js'; +export * from './flags.js'; +export * from './logger.js'; +export * from './query_string_utils.js'; +export * from './redirect_based_handler.js'; +export * from './revoke_token_request.js'; +export * from './storage.js'; +export * from './token_request.js'; +export * from './token_request_handler.js'; +export * from './token_response.js'; +export type * from './types.js'; +export * from './xhr.js'; diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/logger.ts b/src/Umbraco.Web.UI.Client/src/external/openid/logger.ts new file mode 100644 index 0000000000..9f31c83d79 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/logger.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IS_LOG, IS_PROFILE } from './flags.js'; + +export function log(message: string, ...args: any[]) { + if (IS_LOG) { + const length = args ? args.length : 0; + if (length > 0) { + console.log(message, ...args); + } else { + console.log(message); + } + } +} + +// check to see if native support for profiling is available. +const NATIVE_PROFILE_SUPPORT = typeof window !== 'undefined' && !!window.performance && !!console.profile; + +/** + * A decorator that can profile a function. + */ +export function profile(target: any, propertyKey: string, descriptor: PropertyDescriptor) { + if (IS_PROFILE) { + return performProfile(target, propertyKey, descriptor); + } else { + // return as-is + return descriptor; + } +} + +function performProfile(target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { + const originalCallable = descriptor.value; + // name must exist + let name = originalCallable.name; + if (!name) { + name = 'anonymous function'; + } + if (NATIVE_PROFILE_SUPPORT) { + descriptor.value = function (args: any[]) { + console.profile(name); + const startTime = window.performance.now(); + const result = originalCallable.call(this || window, ...args); + const duration = window.performance.now() - startTime; + console.log(`${name} took ${duration} ms`); + console.profileEnd(); + return result; + }; + } else { + descriptor.value = function (args: any[]) { + log(`Profile start ${name}`); + const start = Date.now(); + const result = originalCallable.call(this || window, ...args); + const duration = Date.now() - start; + log(`Profile end ${name} took ${duration} ms.`); + return result; + }; + } + return descriptor; +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/query_string_utils.ts b/src/Umbraco.Web.UI.Client/src/external/openid/query_string_utils.ts new file mode 100644 index 0000000000..eb88ce5c13 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/query_string_utils.ts @@ -0,0 +1,64 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { LocationLike, StringMap } from './types.js'; + +/** + * Query String Utilities. + */ +export interface QueryStringUtils { + stringify(input: StringMap): string; + parse(query: LocationLike, useHash?: boolean): StringMap; + parseQueryString(query: string): StringMap; +} + +export class BasicQueryStringUtils implements QueryStringUtils { + parse(input: LocationLike, useHash?: boolean) { + if (useHash) { + return this.parseQueryString(input.hash); + } else { + return this.parseQueryString(input.search); + } + } + + parseQueryString(query: string): StringMap { + const result: StringMap = {}; + // if anything starts with ?, # or & remove it + query = query.trim().replace(/^(\?|#|&)/, ''); + const params = query.split('&'); + for (let i = 0; i < params.length; i += 1) { + const param = params[i]; // looks something like a=b + const parts = param.split('='); + if (parts.length >= 2) { + const key = decodeURIComponent(parts.shift()!); + const value = parts.length > 0 ? parts.join('=') : null; + if (value) { + result[key] = decodeURIComponent(value); + } + } + } + return result; + } + + stringify(input: StringMap) { + const encoded: string[] = []; + for (const key in input) { + if (Object.prototype.hasOwnProperty.call(input, key) && input[key]) { + encoded.push(`${encodeURIComponent(key)}=${encodeURIComponent(input[key])}`); + } + } + return encoded.join('&'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/redirect_based_handler.ts b/src/Umbraco.Web.UI.Client/src/external/openid/redirect_based_handler.ts new file mode 100644 index 0000000000..8344c28cf2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/redirect_based_handler.ts @@ -0,0 +1,146 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthorizationRequest } from './authorization_request.js'; +import type { AuthorizationRequestResponse } from './authorization_request_handler.js'; +import { AuthorizationRequestHandler } from './authorization_request_handler.js'; +import { AuthorizationError, AuthorizationResponse } from './authorization_response.js'; +import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js'; +import type { Crypto } from './crypto_utils.js'; +import { DefaultCrypto } from './crypto_utils.js'; +import { log } from './logger.js'; +import { BasicQueryStringUtils } from './query_string_utils.js'; +import type { StorageBackend } from './storage.js'; +import { LocalStorageBackend } from './storage.js'; +import type { LocationLike } from './types.js'; + +/** key for authorization request. */ +const authorizationRequestKey = (handle: string) => { + return `${handle}_appauth_authorization_request`; +}; + +/** key for authorization service configuration */ +const authorizationServiceConfigurationKey = (handle: string) => { + return `${handle}_appauth_authorization_service_configuration`; +}; + +/** key in local storage which represents the current authorization request. */ +const AUTHORIZATION_REQUEST_HANDLE_KEY = 'appauth_current_authorization_request'; + +/** + * Represents an AuthorizationRequestHandler which uses a standard + * redirect based code flow. + */ +export class RedirectRequestHandler extends AuthorizationRequestHandler { + constructor( + // use the provided storage backend + // or initialize local storage with the default storage backend which + // uses window.localStorage + public storageBackend: StorageBackend = new LocalStorageBackend(), + utils = new BasicQueryStringUtils(), + public locationLike: LocationLike = window.location, + crypto: Crypto = new DefaultCrypto(), + ) { + super(utils, crypto); + } + + performAuthorizationRequest(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest) { + const handle = this.crypto.generateRandom(10); + + // before you make request, persist all request related data in local storage. + const persisted = Promise.all([ + this.storageBackend.setItem(AUTHORIZATION_REQUEST_HANDLE_KEY, handle), + // Calling toJson() adds in the code & challenge when possible + request + .toJson() + .then((result) => this.storageBackend.setItem(authorizationRequestKey(handle), JSON.stringify(result))), + this.storageBackend.setItem(authorizationServiceConfigurationKey(handle), JSON.stringify(configuration.toJson())), + ]); + + persisted.then(() => { + // make the redirect request + const url = this.buildRequestUrl(configuration, request); + log('Making a request to ', request, url); + this.locationLike.assign(url); + }); + } + + /** + * Attempts to introspect the contents of storage backend and completes the + * request. + */ + protected completeAuthorizationRequest(): Promise { + // TODO(rahulrav@): handle authorization errors. + return this.storageBackend.getItem(AUTHORIZATION_REQUEST_HANDLE_KEY).then((handle) => { + if (handle) { + // we have a pending request. + // fetch authorization request, and check state + return ( + this.storageBackend + .getItem(authorizationRequestKey(handle)) + // requires a corresponding instance of result + // TODO(rahulrav@): check for inconsitent state here + .then((result) => JSON.parse(result!)) + .then((json) => new AuthorizationRequest(json)) + .then((request) => { + // check redirect_uri and state + const currentUri = `${this.locationLike.origin}${this.locationLike.pathname}`; + const queryParams = this.utils.parse(this.locationLike, true /* use hash */); + const state: string | undefined = queryParams['state']; + const code: string | undefined = queryParams['code']; + const error: string | undefined = queryParams['error']; + log('Potential authorization request ', currentUri, queryParams, state, code, error); + const shouldNotify = state === request.state; + let authorizationResponse: AuthorizationResponse | null = null; + let authorizationError: AuthorizationError | null = null; + if (shouldNotify) { + if (error) { + // get additional optional info. + const errorUri = queryParams['error_uri']; + const errorDescription = queryParams['error_description']; + authorizationError = new AuthorizationError({ + error: error, + error_description: errorDescription, + error_uri: errorUri, + state: state, + }); + } else { + authorizationResponse = new AuthorizationResponse({ code: code, state: state }); + } + // cleanup state + return Promise.all([ + this.storageBackend.removeItem(AUTHORIZATION_REQUEST_HANDLE_KEY), + this.storageBackend.removeItem(authorizationRequestKey(handle)), + this.storageBackend.removeItem(authorizationServiceConfigurationKey(handle)), + ]).then(() => { + log('Delivering authorization response'); + return { + request: request, + response: authorizationResponse, + error: authorizationError, + } as AuthorizationRequestResponse; + }); + } else { + log('Mismatched request (state and request_uri) dont match.'); + return Promise.resolve(null); + } + }) + ); + } else { + return null; + } + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/revoke_token_request.ts b/src/Umbraco.Web.UI.Client/src/external/openid/revoke_token_request.ts new file mode 100644 index 0000000000..97e21f1bec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/revoke_token_request.ts @@ -0,0 +1,73 @@ +/* eslint-disable local-rules/umb-class-prefix */ +import type { StringMap } from './types.js'; + +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Supported token types + */ +export type TokenTypeHint = 'refresh_token' | 'access_token'; + +/** + * Represents the Token Request as JSON. + */ +export interface RevokeTokenRequestJson { + token: string; + token_type_hint?: TokenTypeHint; + client_id?: string; + client_secret?: string; +} + +/** + * Represents a revoke token request. + * For more information look at: + * https://tools.ietf.org/html/rfc7009#section-2.1 + */ +export class RevokeTokenRequest { + token: string; + tokenTypeHint: TokenTypeHint | undefined; + clientId: string | undefined; + clientSecret: string | undefined; + + constructor(request: RevokeTokenRequestJson) { + this.token = request.token; + this.tokenTypeHint = request.token_type_hint; + this.clientId = request.client_id; + this.clientSecret = request.client_secret; + } + + /** + * Serializes a TokenRequest to a JavaScript object. + */ + toJson(): RevokeTokenRequestJson { + const json: RevokeTokenRequestJson = { token: this.token }; + if (this.tokenTypeHint) { + json['token_type_hint'] = this.tokenTypeHint; + } + if (this.clientId) { + json['client_id'] = this.clientId; + } + if (this.clientSecret) { + json['client_secret'] = this.clientSecret; + } + return json; + } + + toStringMap(): StringMap { + const json = this.toJson(); + // :( + return json as any; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/storage.ts b/src/Umbraco.Web.UI.Client/src/external/openid/storage.ts new file mode 100644 index 0000000000..a33c5dfcad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/storage.ts @@ -0,0 +1,101 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A subset of the `Storage` interface which we need for the backends to work. + * + * Essentially removes the indexable properties and readonly properties from + * `Storage` in lib.dom.d.ts. This is so that a custom type can extend it for + * testing. + */ +export interface UnderlyingStorage { + readonly length: number; + clear(): void; + getItem(key: string): string | null; + removeItem(key: string): void; + setItem(key: string, data: string): void; +} + +/** + * Asynchronous storage APIs. All methods return a `Promise`. + * All methods take the `DOMString` + * IDL type (as it is the lowest common denominator). + */ +export abstract class StorageBackend { + /** + * When passed a key `name`, will return that key's value. + */ + public abstract getItem(name: string): Promise; + + /** + * When passed a key `name`, will remove that key from the storage. + */ + public abstract removeItem(name: string): Promise; + + /** + * When invoked, will empty all keys out of the storage. + */ + public abstract clear(): Promise; + + /** + * The setItem() method of the `StorageBackend` interface, + * when passed a key name and value, will add that key to the storage, + * or update that key's value if it already exists. + */ + public abstract setItem(name: string, value: string): Promise; +} + +/** + * A `StorageBackend` backed by `localstorage`. + */ +export class LocalStorageBackend extends StorageBackend { + private storage: UnderlyingStorage; + constructor(storage?: UnderlyingStorage) { + super(); + this.storage = storage || window.localStorage; + } + + public getItem(name: string): Promise { + return new Promise((resolve, reject) => { + const value = this.storage.getItem(name); + if (value) { + resolve(value); + } else { + resolve(null); + } + }); + } + + public removeItem(name: string): Promise { + return new Promise((resolve, reject) => { + this.storage.removeItem(name); + resolve(); + }); + } + + public clear(): Promise { + return new Promise((resolve, reject) => { + this.storage.clear(); + resolve(); + }); + } + + public setItem(name: string, value: string): Promise { + return new Promise((resolve, reject) => { + this.storage.setItem(name, value); + resolve(); + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/token_request.ts b/src/Umbraco.Web.UI.Client/src/external/openid/token_request.ts new file mode 100644 index 0000000000..9cf5978d63 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/token_request.ts @@ -0,0 +1,99 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* eslint-disable local-rules/exported-string-constant-naming */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { StringMap } from './types.js'; + +export const GRANT_TYPE_AUTHORIZATION_CODE = 'authorization_code'; +export const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token'; + +/** + * Represents the Token Request as JSON. + */ +export interface TokenRequestJson { + grant_type: string; + code?: string; + refresh_token?: string; + redirect_uri: string; + client_id: string; + extras?: StringMap; +} + +/** + * Represents an Access Token request. + * For more information look at: + * https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +export class TokenRequest { + clientId: string; + redirectUri: string; + grantType: string; + code: string | undefined; + refreshToken: string | undefined; + extras: StringMap | undefined; + + constructor(request: TokenRequestJson) { + this.clientId = request.client_id; + this.redirectUri = request.redirect_uri; + this.grantType = request.grant_type; + this.code = request.code; + this.refreshToken = request.refresh_token; + this.extras = request.extras; + } + + /** + * Serializes a TokenRequest to a JavaScript object. + */ + toJson(): TokenRequestJson { + return { + grant_type: this.grantType, + code: this.code, + refresh_token: this.refreshToken, + redirect_uri: this.redirectUri, + client_id: this.clientId, + extras: this.extras, + }; + } + + toStringMap(): StringMap { + const map: StringMap = { + grant_type: this.grantType, + client_id: this.clientId, + redirect_uri: this.redirectUri, + }; + + if (this.code) { + map['code'] = this.code; + } + + if (this.refreshToken) { + map['refresh_token'] = this.refreshToken; + } + + // copy over extras + if (this.extras) { + for (const extra in this.extras) { + if ( + Object.prototype.hasOwnProperty.call(this.extras, extra) && + !Object.prototype.hasOwnProperty.call(map, extra) + ) { + // check before inserting to requestMap + map[extra] = this.extras[extra]; + } + } + } + return map; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/token_request_handler.ts b/src/Umbraco.Web.UI.Client/src/external/openid/token_request_handler.ts new file mode 100644 index 0000000000..b4bc6f12e4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/token_request_handler.ts @@ -0,0 +1,87 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { AuthorizationServiceConfiguration } from './authorization_service_configuration.js'; +import { AppAuthError } from './errors.js'; +import { BasicQueryStringUtils } from './query_string_utils.js'; +import type { QueryStringUtils } from './query_string_utils.js'; +import type { RevokeTokenRequest } from './revoke_token_request.js'; +import type { TokenRequest } from './token_request.js'; +import { TokenError, TokenResponse } from './token_response.js'; +import type { TokenErrorJson, TokenResponseJson } from './token_response.js'; +import { FetchRequestor, type Requestor } from './xhr.js'; + +/** + * Represents an interface which can make a token request. + */ +export interface TokenRequestHandler { + /** + * Performs the token request, given the service configuration. + */ + performTokenRequest(configuration: AuthorizationServiceConfiguration, request: TokenRequest): Promise; + + performRevokeTokenRequest( + configuration: AuthorizationServiceConfiguration, + request: RevokeTokenRequest, + ): Promise; +} + +/** + * The default token request handler. + */ +export class BaseTokenRequestHandler implements TokenRequestHandler { + constructor( + public readonly requestor: Requestor = new FetchRequestor(), + public readonly utils: QueryStringUtils = new BasicQueryStringUtils(), + ) {} + + private isTokenResponse(response: TokenResponseJson | TokenErrorJson): response is TokenResponseJson { + return (response as TokenErrorJson).error === undefined; + } + + performRevokeTokenRequest( + configuration: AuthorizationServiceConfiguration, + request: RevokeTokenRequest, + ): Promise { + const revokeTokenResponse = this.requestor.xhr({ + url: configuration.revocationEndpoint, + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: this.utils.stringify(request.toStringMap()), + }); + + return revokeTokenResponse.then((response) => { + return true; + }); + } + + performTokenRequest(configuration: AuthorizationServiceConfiguration, request: TokenRequest): Promise { + const tokenResponse = this.requestor.xhr({ + url: configuration.tokenEndpoint, + method: 'POST', + dataType: 'json', // adding implicit dataType + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: this.utils.stringify(request.toStringMap()), + }); + + return tokenResponse.then((response) => { + if (this.isTokenResponse(response)) { + return new TokenResponse(response); + } else { + return Promise.reject(new AppAuthError(response.error, new TokenError(response))); + } + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/token_response.ts b/src/Umbraco.Web.UI.Client/src/external/openid/token_response.ts new file mode 100644 index 0000000000..3afbf101d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/token_response.ts @@ -0,0 +1,137 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents the access token types. + * For more information see: + * https://tools.ietf.org/html/rfc6749#section-7.1 + */ +export type TokenType = 'bearer' | 'mac'; + +/** + * Represents the TokenResponse as a JSON Object. + */ +export interface TokenResponseJson { + access_token: string; + token_type?: TokenType /* treating token type as optional, as its going to be inferred. */; + expires_in?: string /* lifetime in seconds. */; + refresh_token?: string; + scope?: string; + id_token?: string /* https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse */; + issued_at?: number /* when was it issued ? */; +} + +/** + * Represents the possible error codes from the token endpoint. + * For more information look at: + * https://tools.ietf.org/html/rfc6749#section-5.2 + */ +export type ErrorType = + | 'invalid_request' + | 'invalid_client' + | 'invalid_grant' + | 'unauthorized_client' + | 'unsupported_grant_type' + | 'invalid_scope'; + +/** + * Represents the TokenError as a JSON Object. + */ +export interface TokenErrorJson { + error: ErrorType; + error_description?: string; + error_uri?: string; +} + +// constants +const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 mins in seconds + +/** + * Returns the instant of time in seconds. + */ +export const nowInSeconds = () => Math.round(new Date().getTime() / 1000); + +/** + * Represents the Token Response type. + * For more information look at: + * https://tools.ietf.org/html/rfc6749#section-5.1 + */ +export class TokenResponse { + accessToken: string; + tokenType: TokenType; + expiresIn: number | undefined; + refreshToken: string | undefined; + scope: string | undefined; + idToken: string | undefined; + issuedAt: number; + + constructor(response: TokenResponseJson) { + this.accessToken = response.access_token; + this.tokenType = response.token_type || 'bearer'; + if (response.expires_in) { + this.expiresIn = parseInt(response.expires_in, 10); + } + this.refreshToken = response.refresh_token; + this.scope = response.scope; + this.idToken = response.id_token; + this.issuedAt = response.issued_at || nowInSeconds(); + } + + toJson(): TokenResponseJson { + return { + access_token: this.accessToken, + id_token: this.idToken, + refresh_token: this.refreshToken, + scope: this.scope, + token_type: this.tokenType, + issued_at: this.issuedAt, + expires_in: this.expiresIn?.toString(), + }; + } + + isValid(buffer: number = AUTH_EXPIRY_BUFFER): boolean { + if (this.expiresIn) { + const now = nowInSeconds(); + return now < this.issuedAt + this.expiresIn + buffer; + } else { + return true; + } + } +} + +/** + * Represents the Token Error type. + * For more information look at: + * https://tools.ietf.org/html/rfc6749#section-5.2 + */ +export class TokenError { + error: ErrorType; + errorDescription: string | undefined; + errorUri: string | undefined; + + constructor(tokenError: TokenErrorJson) { + this.error = tokenError.error; + this.errorDescription = tokenError.error_description; + this.errorUri = tokenError.error_uri; + } + + toJson(): TokenErrorJson { + return { + error: this.error, + error_description: this.errorDescription, + error_uri: this.errorUri, + }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/types.ts b/src/Umbraco.Web.UI.Client/src/external/openid/types.ts new file mode 100644 index 0000000000..b6fb853dad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/types.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface StringMap { + [key: string]: string; +} + +/** + * Represents a window.location like object. + */ +export interface LocationLike { + hash: string; + host: string; + origin: string; + hostname: string; + pathname: string; + port: string; + protocol: string; + search: string; + assign(url: string): void; +} + +export interface XhrRequestInit extends RequestInit { + url: string; + dataType?: string; + data?: any; +} diff --git a/src/Umbraco.Web.UI.Client/src/external/openid/xhr.ts b/src/Umbraco.Web.UI.Client/src/external/openid/xhr.ts new file mode 100644 index 0000000000..fc32dbf8cb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/openid/xhr.ts @@ -0,0 +1,78 @@ +/* eslint-disable local-rules/umb-class-prefix */ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppAuthError } from './errors.js'; +import type { XhrRequestInit } from './types.js'; + +/** + * An class that abstracts away the ability to make an XMLHttpRequest. + */ +export abstract class Requestor { + abstract xhr(settings: unknown): Promise; +} + +/** + * Uses fetch API to make Ajax requests + */ +export class FetchRequestor extends Requestor { + xhr(settings: XhrRequestInit): Promise { + if (!settings.url) { + return Promise.reject(new AppAuthError('A URL must be provided.')); + } + const url: URL = new URL(settings.url); + const requestInit: RequestInit = {}; + requestInit.method = settings.method; + requestInit.mode = 'cors'; + + if (settings.data) { + if (settings.method && settings.method.toUpperCase() === 'POST') { + requestInit.body = settings.data; + } else { + const searchParams = new URLSearchParams(settings.data); + searchParams.forEach((value, key) => { + url.searchParams.append(key, value); + }); + } + } + + // Set the request headers + requestInit.headers = {}; + if (settings.headers) { + requestInit.headers = settings.headers; + } + + const isJsonDataType = settings.dataType && settings.dataType.toLowerCase() === 'json'; + + // Set 'Accept' header value for json requests (Taken from + // https://github.com/jquery/jquery/blob/e0d941156900a6bff7c098c8ea7290528e468cf8/src/ajax.js#L644 + // ) + if (isJsonDataType) { + (requestInit.headers as any).Accept = 'application/json, text/javascript, */*; q=0.01'; + } + + return fetch(url.toString(), requestInit).then((response) => { + if (response.status >= 200 && response.status < 300) { + const contentType = response.headers.get('content-type'); + if (isJsonDataType || (contentType && contentType.indexOf('application/json') !== -1)) { + return response.json(); + } else { + return response.text(); + } + } else { + return Promise.reject(new AppAuthError(response.status.toString(), response.statusText)); + } + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts index f7d17284b7..cc5b1fcd73 100644 --- a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts @@ -17,4 +17,5 @@ export { switchMap, filter, startWith, + skip, } from 'rxjs'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts index c663868854..a20359fd72 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts @@ -1,10 +1,6 @@ import type { ManifestBase } from '../types/index.js'; import { isManifestBaseType } from '../type-guards/index.js'; -import { - PackageResource, - OpenAPI, - type PackageManifestResponseModel, -} from '@umbraco-cms/backoffice/external/backend-api'; +import { OpenAPI, ManifestResource, type ManifestResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -26,7 +22,19 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase { * @remark Users must have the BACKOFFICE_ACCESS permission to access this method. */ public async registerAllExtensions() { - const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifest()); + const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifest()); + if (packages) { + await this.#loadServerPackages(packages); + } + } + + /** + * Registers all private extensions from the server. + * This is used to register all private extensions that are available to the user. + * @remark Users must have the BACKOFFICE_ACCESS permission to access this method. + */ + public async registerPrivateExtensions() { + const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifestPrivate()); if (packages) { await this.#loadServerPackages(packages); } @@ -38,13 +46,13 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase { * @remark Any user can access this method without any permissions. */ public async registerPublicExtensions() { - const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifestPublic()); + const { data: packages } = await tryExecuteAndNotify(this, ManifestResource.getManifestManifestPublic()); if (packages) { await this.#loadServerPackages(packages); } } - async #loadServerPackages(packages: PackageManifestResponseModel[]) { + async #loadServerPackages(packages: ManifestResponseModel[]) { const extensions: ManifestBase[] = []; packages.forEach((p) => { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/index.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/index.ts index cf2758ba9f..21d7768a87 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/index.ts @@ -6,4 +6,4 @@ export * from './load-manifest-api.function.js'; export * from './load-manifest-element.function.js'; export * from './load-manifest-plain-css.function.js'; export * from './load-manifest-plain-js.function.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts index c8ce6b7200..22c8863cb0 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/index.ts @@ -2,7 +2,7 @@ export * from './condition/index.js'; export * from './controller/index.js'; export * from './functions/index.js'; export * from './initializers/index.js'; -export * from './models/index.js'; +export type * from './models/index.js'; export * from './registry/extension.registry.js'; export * from './type-guards/index.js'; -export * from './types/index.js'; +export type * from './types/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts index c9b1ab775a..e85968960f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts @@ -274,6 +274,22 @@ describe('UmbLocalizeController', () => { }); }); + describe('string', () => { + it('should replace words prefixed with a # with translated value', async () => { + const str = '#close'; + const str2 = '#logout #close'; + const str3 = '#logout #missing_translation_key #close'; + expect(controller.string(str)).to.equal('Close'); + expect(controller.string(str2)).to.equal('Log out Close'); + expect(controller.string(str3)).to.equal('Log out #missing_translation_key Close'); + }); + + it('should return the word with a # if the word is not found', async () => { + const str = '#missing_translation_key'; + expect(controller.string(str)).to.equal('#missing_translation_key'); + }); + }); + describe('host element', () => { let element: UmbLocalizeControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts index 9706aa8272..1a2ebfbfdf 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts @@ -159,4 +159,21 @@ export class UmbLocalizationController { + const key = match.slice(1); + // TODO: find solution to pass dynamic string to term + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const localized = this.term(key); + // we didn't find a localized string, so we return the original string with the # + return localized === key ? match : localized; + }); + + return localizedText; + } } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts index 6a3c44ce04..a5751325f3 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts @@ -90,11 +90,11 @@ switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) { switch (import.meta.env.VITE_UMBRACO_EXTENSION_MOCKS) { case 'on': - handlers.push(manifestsHandlers.manifestDevelopmentHandler); + handlers.push(...manifestsHandlers.manifestDevelopmentHandlers); break; default: - handlers.push(manifestsHandlers.manifestEmptyHandler); + handlers.push(...manifestsHandlers.manifestEmptyHandlers); } export { handlers }; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts index 17cf239c92..30ecb97891 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/e2e-handlers.ts @@ -20,7 +20,7 @@ import { handlers as configHandlers } from './handlers/config.handlers.js'; export const handlers = [ serverHandlers.serverRunningHandler, serverHandlers.serverInformationHandler, - manifestsHandlers.manifestEmptyHandler, + ...manifestsHandlers.manifestEmptyHandlers, ...auditLogHandlers, ...installHandlers, ...upgradeHandlers, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts index 751575284d..fbfc33ed1a 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts @@ -3,107 +3,159 @@ const { rest } = window.MockServiceWorker; import type { PackageManifestResponse } from '../../packages/packages/types.js'; import { umbracoPath } from '@umbraco-cms/backoffice/utils'; -export const manifestDevelopmentHandler = rest.get(umbracoPath('/package/manifest'), (_req, res, ctx) => { - return res( - // Respond with a 200 status code - ctx.status(200), - ctx.json([ - { - name: 'My Package Name', - version: '1.0.0', - extensions: [ - { - type: 'bundle', - alias: 'My.Package.Bundle', - name: 'My Package Bundle', - js: '/App_Plugins/custom-bundle-package/index.js', - }, - ], - }, - { - name: 'Named Package', - version: '1.0.0', - extensions: [ - { - type: 'section', - alias: 'My.Section.Custom', - name: 'Custom Section', - js: '/App_Plugins/section.js', - elementName: 'my-section-custom', - weight: 1, - meta: { - label: 'Custom', - pathname: 'my-custom', +export const manifestDevelopmentHandlers = [ + rest.get(umbracoPath('/manifest/manifest/private'), (_req, res, ctx) => { + return res( + // Respond with a 200 status code + ctx.status(200), + ctx.json([ + { + name: 'My Package Name', + version: '1.0.0', + extensions: [ + { + type: 'bundle', + alias: 'My.Package.Bundle', + name: 'My Package Bundle', + js: '/App_Plugins/custom-bundle-package/index.js', }, - }, - { - type: 'propertyEditorUi', - alias: 'My.PropertyEditorUI.Custom', - name: 'My Custom Property Editor UI', - js: '/App_Plugins/property-editor.js', - elementName: 'my-property-editor-ui-custom', - meta: { - label: 'My Custom Property', - icon: 'document', - group: 'Common', - propertyEditorSchema: 'Umbraco.TextBox', + ], + }, + { + name: 'Named Package', + version: '1.0.0', + extensions: [ + { + type: 'section', + alias: 'My.Section.Custom', + name: 'Custom Section', + js: '/App_Plugins/section.js', + elementName: 'my-section-custom', + weight: 1, + meta: { + label: 'Custom', + pathname: 'my-custom', + }, }, - }, - ], - }, - { - name: 'Package with an entry point', - extensions: [ - { - type: 'entryPoint', - name: 'My Custom Entry Point', - alias: 'My.Entrypoint.Custom', - js: '/App_Plugins/custom-entrypoint.js', - }, - ], - }, - { - name: 'Package with a view', - extensions: [ - { - type: 'packageView', - alias: 'My.PackageView.Custom', - name: 'My Custom Package View', - js: '/App_Plugins/package-view.js', - meta: { - packageName: 'Package with a view', + { + type: 'propertyEditorUi', + alias: 'My.PropertyEditorUI.Custom', + name: 'My Custom Property Editor UI', + js: '/App_Plugins/property-editor.js', + elementName: 'my-property-editor-ui-custom', + meta: { + label: 'My Custom Property', + icon: 'document', + group: 'Common', + propertyEditorSchema: 'Umbraco.TextBox', + }, }, - }, - ], - }, - { - name: 'My MFA Package', - extensions: [ - { - type: 'mfaLoginProvider', - alias: 'My.MfaLoginProvider.Custom.Google', - name: 'My Custom Google MFA Provider', - forProviderName: 'Google Authenticator', - }, - { - type: 'mfaLoginProvider', - alias: 'My.MfaLoginProvider.Custom.SMS', - name: 'My Custom SMS MFA Provider', - forProviderName: 'sms', - meta: { - label: 'Setup SMS Verification', + ], + }, + { + name: 'Package with an entry point', + extensions: [ + { + type: 'entryPoint', + name: 'My Custom Entry Point', + alias: 'My.Entrypoint.Custom', + js: '/App_Plugins/custom-entrypoint.js', }, - }, - ], - }, - ]), - ); -}); + ], + }, + { + name: 'My MFA Package', + extensions: [ + { + type: 'mfaLoginProvider', + alias: 'My.MfaLoginProvider.Custom.Google', + name: 'My Custom Google MFA Provider', + forProviderName: 'Google Authenticator', + }, + { + type: 'mfaLoginProvider', + alias: 'My.MfaLoginProvider.Custom.SMS', + name: 'My Custom SMS MFA Provider', + forProviderName: 'sms', + meta: { + label: 'Setup SMS Verification', + }, + }, + ], + }, + { + name: 'Package with a view', + extensions: [ + { + type: 'packageView', + alias: 'My.PackageView.Custom', + name: 'My Custom Package View', + js: '/App_Plugins/package-view.js', + meta: { + packageName: 'Package with a view', + }, + }, + ], + }, + { + name: 'My MFA Package', + extensions: [ + { + type: 'mfaLoginProvider', + alias: 'My.MfaLoginProvider.Custom', + name: 'My Custom MFA Provider', + forProviderName: 'sms', + meta: { + label: 'Setup SMS Verification', + }, + }, + ], + }, + ]), + ); + }), + rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json([ + { + name: 'My Auth Package', + extensions: [ + { + type: 'authProvider', + alias: 'My.AuthProvider.Google', + name: 'My Custom Auth Provider', + forProviderName: 'Umbraco.Google', + meta: { + label: 'Sign in with Google', + }, + }, + { + type: 'authProvider', + alias: 'My.AuthProvider.Github', + name: 'My Github Auth Provider', + forProviderName: 'Umbraco.Github', + meta: { + label: 'GitHub', + defaultView: { + look: 'primary', + icon: 'icon-github', + color: 'success', + }, + }, + }, + ], + }, + ]), + ); + }), +]; -export const manifestEmptyHandler = rest.get(umbracoPath('/package/manifest'), (_req, res, ctx) => { - return res( - // Respond with a 200 status code - ctx.status(200), - ctx.json([]), - ); -}); +export const manifestEmptyHandlers = [ + rest.get(umbracoPath('/manifest/manifest/private'), (_req, res, ctx) => { + return res(ctx.status(200), ctx.json([])); + }), + rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => { + return res(ctx.status(200), ctx.json([])); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts index 17644bc775..f338a81368 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts @@ -21,7 +21,7 @@ export const manifests: Array = [ name: 'Save Block Grid Area Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Submit', + label: '#general_submit', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts index c92f2b2d77..0e8e9ccd27 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts @@ -9,7 +9,7 @@ export const workspaceViews: Array = [ js: () => import('./settings.element.js'), weight: 1000, meta: { - label: 'Settings', + label: '#general_settings', pathname: 'settings', icon: 'icon-settings', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts index 57f890ed14..793f06141d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts @@ -9,7 +9,7 @@ export const workspaceViews: Array = [ js: () => import('./block-grid-type-workspace-view-settings.element.js'), weight: 1000, meta: { - label: 'Settings', + label: '#general_settings', pathname: 'settings', icon: 'icon-settings', }, @@ -27,7 +27,7 @@ export const workspaceViews: Array = [ js: () => import('./block-grid-type-workspace-view-areas.element.js'), weight: 1000, meta: { - label: 'Areas', + label: '#blockEditor_areas', pathname: 'areas', icon: 'icon-grid', }, @@ -45,7 +45,7 @@ export const workspaceViews: Array = [ js: () => import('./block-grid-type-workspace-view-advanced.element.js'), weight: 1000, meta: { - label: 'Advanced', + label: '#blockEditor_tabAreas', pathname: 'advanced', icon: 'icon-wrench', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts index 03b003709b..1774211ed4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts @@ -9,7 +9,7 @@ export const workspaceViews: Array = [ js: () => import('./block-list-type-workspace-view.element.js'), weight: 1000, meta: { - label: 'Settings', + label: '#blockEditor_tabBlockSettings', pathname: 'settings', icon: 'icon-settings', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts index e574f7aa62..b94ed190cf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts @@ -9,7 +9,7 @@ export const workspaceViews: Array = [ js: () => import('./block-rte-type-workspace-view.element.js'), weight: 1000, meta: { - label: 'Settings', + label: '#general_settings', pathname: 'settings', icon: 'icon-settings', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts index 0df0592053..3bd2d3aea3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts @@ -12,7 +12,7 @@ export const manifests: Array = [ name: 'Save Block Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Submit', + label: '#general_submit', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/manifests.ts index bc76264f4d..8d31bc6c3f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/manifests.ts @@ -10,7 +10,7 @@ export const manifests: Array = [ name: 'Save Block Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Submit', + label: '#general_submit', look: 'primary', color: 'positive', }, @@ -38,7 +38,7 @@ export const manifests: Array = [ js: () => import('./views/edit/block-workspace-view-edit.element.js'), weight: 1000, meta: { - label: 'Content', + label: '#general_content', pathname: 'content', icon: 'icon-document', blockElementManagerName: 'content', @@ -63,7 +63,7 @@ export const manifests: Array = [ js: () => import('./views/edit/block-workspace-view-edit.element.js'), weight: 1000, meta: { - label: 'Settings', + label: '#general_settings', pathname: 'settings', icon: 'icon-settings', blockElementManagerName: 'settings', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts index 4ced20b66d..82455be922 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts @@ -89,6 +89,7 @@ export class UmbAuthFlow { // state readonly #configuration: AuthorizationServiceConfiguration; readonly #redirectUri: string; + readonly #postLogoutRedirectUri: string; readonly #clientId: string; readonly #scope: string; @@ -99,10 +100,12 @@ export class UmbAuthFlow { constructor( openIdConnectUrl: string, redirectUri: string, + postLogoutRedirectUri: string, clientId = 'umbraco-back-office', scope = 'offline_access', ) { this.#redirectUri = redirectUri; + this.#postLogoutRedirectUri = postLogoutRedirectUri; this.#clientId = clientId; this.#scope = scope; @@ -187,15 +190,23 @@ export class UmbAuthFlow { } /** - * This method will make an authorization request to the server. + * Make an authorization request to the server using the specified identity provider. + * This method will redirect the user to the authorization endpoint of the server. * - * @param username The username to use for the authorization request. It will be provided to the OpenID server as a hint. + * @param identityProvider The identity provider to use for the authorization request. + * @param usernameHint (Optional) The username to use for the authorization request. It will be provided to the OpenID server as a hint. */ - makeAuthorizationRequest(username?: string): void { + makeAuthorizationRequest(identityProvider: string, usernameHint?: string): void { const extras: StringMap = { prompt: 'consent', access_type: 'offline' }; - if (username) { - extras['login_hint'] = username; + // If the identity provider is not 'Umbraco', we will add it to the extras. + if (identityProvider !== 'Umbraco') { + extras['identity_provider'] = identityProvider; + } + + // If there is a username hint, we will add it to the extras. + if (usernameHint) { + extras['login_hint'] = usernameHint; } // create a request @@ -275,7 +286,7 @@ export class UmbAuthFlow { // which will redirect the user back to the client // and the client will then try and log in again (if the user is not logged in) // which will redirect the user to the login page - location.href = `${this.#configuration.endSessionEndpoint}?post_logout_redirect_uri=${this.#redirectUri}`; + location.href = `${this.#configuration.endSessionEndpoint}?post_logout_redirect_uri=${this.#postLogoutRedirectUri}`; } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts index f457655b18..be37177cc0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts @@ -1,3 +1,4 @@ +import { umbExtensionsRegistry } from '../extension-registry/index.js'; import { UmbAuthFlow } from './auth-flow.js'; import { UMB_AUTH_CONTEXT } from './auth.context.token.js'; import type { UmbOpenApiConfiguration } from './models/openApiConfiguration.js'; @@ -5,11 +6,15 @@ import { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; +import { ReplaySubject, filter, switchMap } from '@umbraco-cms/backoffice/external/rxjs'; export class UmbAuthContext extends UmbContextBase { #isAuthorized = new UmbBooleanState(false); readonly isAuthorized = this.#isAuthorized.asObservable(); + #isInitialized = new ReplaySubject(1); + readonly isInitialized = this.#isInitialized.asObservable().pipe(filter((isInitialized) => isInitialized)); + #isBypassed = false; #serverUrl; #backofficePath; @@ -21,14 +26,16 @@ export class UmbAuthContext extends UmbContextBase { this.#serverUrl = serverUrl; this.#backofficePath = backofficePath; - this.#authFlow = new UmbAuthFlow(serverUrl, this.#getRedirectUrl()); + this.#authFlow = new UmbAuthFlow(serverUrl, this.getRedirectUrl(), this.getPostLogoutRedirectUrl()); } /** * Initiates the login flow. + * @param identityProvider The provider to use for login. Default is 'Umbraco'. + * @param usernameHint The username hint to use for login. */ - makeAuthorizationRequest() { - return this.#authFlow.makeAuthorizationRequest(); + makeAuthorizationRequest(identityProvider = 'Umbraco', usernameHint?: string) { + return this.#authFlow.makeAuthorizationRequest(identityProvider, usernameHint); } /** @@ -88,6 +95,16 @@ export class UmbAuthContext extends UmbContextBase { return this.#authFlow.clearTokenStorage(); } + /** + * Handles the case where the user has timed out, i.e. the token has expired. + * This will clear the token storage and set the user as unauthorized. + * @memberof UmbAuthContext + */ + timeOut() { + this.clearTokenStorage(); + this.#isAuthorized.setValue(false); + } + /** * Signs the user out by removing any tokens from the browser. * @memberof UmbAuthContext @@ -141,7 +158,19 @@ export class UmbAuthContext extends UmbContextBase { }; } - #getRedirectUrl() { + setInitialized() { + this.#isInitialized.next(true); + } + + getAuthProviders() { + return this.isInitialized.pipe(switchMap(() => umbExtensionsRegistry.byType('authProvider'))); + } + + getRedirectUrl() { return `${window.location.origin}${this.#backofficePath}`; } + + getPostLogoutRedirectUrl() { + return `${window.location.origin}${this.#backofficePath.endsWith('/') ? this.#backofficePath : this.#backofficePath + '/'}logout`; + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/auth-provider-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/auth-provider-default.element.ts new file mode 100644 index 0000000000..c6c10745c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/auth-provider-default.element.ts @@ -0,0 +1,55 @@ +import type { ManifestAuthProvider } from '../../extension-registry/models/index.js'; +import type { UmbAuthProviderDefaultProps } from '../types.js'; +import { UmbLitElement } from '../../lit-element/lit-element.element.js'; +import { UmbTextStyles } from '../../style/index.js'; +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; + +@customElement('umb-auth-provider-default') +export class UmbAuthProviderDefaultElement extends UmbLitElement implements UmbAuthProviderDefaultProps { + @property({ attribute: false }) + manifest!: ManifestAuthProvider; + + @property({ attribute: false }) + onSubmit!: (providerName: string, loginHint?: string) => void; + + connectedCallback(): void { + super.connectedCallback(); + this.setAttribute('part', 'auth-provider-default'); + } + + render() { + return html` + this.onSubmit(this.manifest.forProviderName)} + id="auth-provider-button" + .label=${this.manifest.meta?.label ?? this.manifest.forProviderName} + .look=${this.manifest.meta?.defaultView?.look ?? 'outline'} + .color=${this.manifest.meta?.defaultView?.color ?? 'default'}> + ${this.manifest.meta?.defaultView?.icon + ? html`` + : nothing} + ${this.manifest.meta?.label ?? this.manifest.forProviderName} + + `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + } + + #auth-provider-button { + width: 100%; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-auth-provider-default': UmbAuthProviderDefaultElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/index.ts new file mode 100644 index 0000000000..e5fdd6f189 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/components/index.ts @@ -0,0 +1 @@ +export * from './auth-provider-default.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/index.ts index e6dc7d478d..2f5cf6b67d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/index.ts @@ -1,3 +1,9 @@ +import './components/index.js'; + export * from './auth.context.js'; export * from './auth.context.token.js'; +export * from './modals/index.js'; export * from './models/openApiConfiguration.js'; +export * from './components/index.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/manifests.ts new file mode 100644 index 0000000000..f0f6228203 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/manifests.ts @@ -0,0 +1,5 @@ +import type { ManifestTypes } from '../extension-registry/models/index.js'; +import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as providerManifests } from './providers/manifests.js'; + +export const manifests: Array = [...modalManifests, ...providerManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/index.ts new file mode 100644 index 0000000000..c574cd0af1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/index.ts @@ -0,0 +1 @@ +export * from './umb-app-auth-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/manifests.ts new file mode 100644 index 0000000000..315736dd1f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/manifests.ts @@ -0,0 +1,10 @@ +import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.AppAuth', + name: 'Umb App Auth Modal', + js: () => import('./umb-app-auth-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts new file mode 100644 index 0000000000..9fb5122908 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.element.ts @@ -0,0 +1,92 @@ +import { UmbModalBaseElement } from '../../modal/index.js'; +import type { UmbModalAppAuthConfig, UmbModalAppAuthValue } from './umb-app-auth-modal.token.js'; +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +@customElement('umb-app-auth-modal') +export class UmbAppAuthModalElement extends UmbModalBaseElement { + get props() { + return { + userLoginState: this.data?.userLoginState ?? 'loggingIn', + onSubmit: this.onSubmit, + }; + } + + get headline() { + return this.data?.userLoginState === 'timedOut' + ? this.localize.term('login_instruction') + : this.localize.term( + [ + 'login_greeting0', + 'login_greeting1', + 'login_greeting2', + 'login_greeting3', + 'login_greeting4', + 'login_greeting5', + 'login_greeting6', + ][new Date().getDay()], + ); + } + + render() { + return html` + +

${this.headline}

+ ${this.data?.userLoginState === 'timedOut' + ? html`

${this.localize.term('login_timeout')}

` + : ''} + +
+ `; + } + + private onSubmit = (providerName: string) => { + this.value = { providerName }; + this._submitModal(); + }; + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + + --umb-body-layout-color-background: #fff; + } + + #login-layout { + width: 380px; + max-width: 80vw; + min-height: 327px; + } + + #greeting { + width: 100%; + color: var(--umb-login-header-color, var(--uui-color-interactive)); + text-align: center; + font-weight: 400; + font-size: var(--umb-login-header-font-size, 2rem); + line-height: 1.2; + margin: 0; + } + + #providers { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-5); + } + `, + ]; +} + +export default UmbAppAuthModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-app-auth-modal': UmbAppAuthModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.token.ts new file mode 100644 index 0000000000..97dd990b68 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/modals/umb-app-auth-modal.token.ts @@ -0,0 +1,26 @@ +import { UmbModalToken } from '../../modal/token/index.js'; +import type { UmbUserLoginState } from '../types.js'; + +export type UmbModalAppAuthConfig = { + userLoginState: UmbUserLoginState; +}; + +export type UmbModalAppAuthValue = { + /** + * The name of the provider that the user has selected to authenticate with. + * @required + */ + providerName?: string; + + /** + * The login hint that the user has provided to the provider. + * @optional + */ + loginHint?: string; +}; + +export const UMB_MODAL_APP_AUTH = new UmbModalToken('Umb.Modal.AppAuth', { + modal: { + type: 'dialog', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/providers/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/providers/manifests.ts new file mode 100644 index 0000000000..5c8e7dc2de --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/providers/manifests.ts @@ -0,0 +1,18 @@ +import type { ManifestAuthProvider } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'authProvider', + alias: 'Umb.AuthProviders.Umbraco', + name: 'Umbraco login provider', + forProviderName: 'Umbraco', + weight: 1000, + meta: { + label: 'Sign in with Umbraco', + defaultView: { + icon: 'icon-umbraco', + look: 'primary', + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/types.ts new file mode 100644 index 0000000000..7074b49380 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/types.ts @@ -0,0 +1,26 @@ +import type { ManifestAuthProvider } from '../extension-registry/index.js'; + +/** + * User login state that can be used to determine the current state of the user. + * @example 'loggedIn' + */ +export type UmbUserLoginState = 'loggingIn' | 'loggedOut' | 'timedOut'; + +export interface UmbAuthProviderDefaultProps { + /** + * The manifest for the registered provider. + */ + manifest?: ManifestAuthProvider; + + /** + * The current user login state. + */ + userLoginState?: UmbUserLoginState; + + /** + * Callback that is called when the user selects a provider. + * @param providerName The name of the provider that the user selected. + * @param loginHint The login hint to use for login if available. + */ + onSubmit: (providerName: string, loginHint?: string) => void; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts index 02eed66b19..0f6024506d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts @@ -1,9 +1,9 @@ import { expect } from '@open-wc/testing'; +import type { ManifestCollectionView } from '../extension-registry/models/index.js'; +import { umbExtensionsRegistry } from '../extension-registry/index.js'; import { UmbCollectionViewManager } from './collection-view.manager.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; -import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('test-my-controller-host') diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts index 48d0068860..bd01e0acf1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts @@ -1,6 +1,6 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit'; -import { css, html, repeat, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, repeat, customElement, state, nothing, property } from '@umbraco-cms/backoffice/external/lit'; import type { UmbModalManagerContext, UmbModalContext } from '@umbraco-cms/backoffice/modal'; import { UMB_MODAL_MANAGER_CONTEXT, UmbModalElement } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -13,6 +13,9 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { @state() _modals: Array = []; + @property({ reflect: true, attribute: 'fill-background' }) + fillBackground = false; + private _modalManager?: UmbModalManagerContext; constructor() { @@ -35,6 +38,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { /** We cannot render the umb-modal element directly in the uui-modal-container because it wont get recognized by UUI. * We therefore have a helper class which creates the uui-modal element and returns it. */ #createModalElements(modals: Array) { + this.removeAttribute('fill-background'); const oldValue = this._modals; this._modals = modals; @@ -61,6 +65,15 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { modal.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modal.key)); this._modalElementMap.set(modal.key, modalElement); + + // If any of the modals are fillBackground, set the fillBackground property to true + if (modal.backdropBackground) { + this.fillBackground = true; + this.shadowRoot + ?.getElementById('container') + ?.style.setProperty('--backdrop-background', modal.backdropBackground); + } + this.requestUpdate(); }); } @@ -78,13 +91,13 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { render() { return html` - + ${this._modals.length > 0 ? repeat( this._modals, (modal) => modal.key, (modal) => this.#renderModal(modal.key), - ) + ) : ''} `; @@ -97,6 +110,10 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { position: absolute; z-index: 1000; } + + :host([fill-background]) #container::after { + background: var(--backdrop-background); + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts index 1d589df449..22be2b754a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts @@ -134,7 +134,7 @@ export class UmbBodyLayoutElement extends LitElement { css` :host { display: flex; - background-color: var(--uui-color-background); + background-color: var(--umb-body-layout-color-background, var(--uui-color-background)); width: 100%; height: 100%; flex-direction: column; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/manifest.ts index 42b52d383f..86a9442024 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/manifest.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/manifest.ts @@ -11,7 +11,7 @@ export const contentTypeDesignEditorManifest: UmbBackofficeManifestKind = { element: () => import('./content-type-design-editor.element.js'), weight: 1000, meta: { - label: 'Design', + label: '#general_design', pathname: 'design', icon: 'icon-document-dashed-line', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.kind.ts index bf2f0136f6..12fb21ad02 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/delete/delete.action.kind.ts @@ -11,11 +11,11 @@ export const manifest: UmbBackofficeManifestKind = { type: 'entityAction', kind: 'delete', api: () => import('./delete.action.js'), - weight: 900, + weight: 1100, forEntityTypes: [], meta: { icon: 'icon-trash', - label: 'Delete...', + label: '#actions_delete', itemRepositoryAlias: '', detailRepositoryAlias: '', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/duplicate/duplicate.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/duplicate/duplicate.action.kind.ts index 0174d495ef..051ebdde0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/duplicate/duplicate.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/duplicate/duplicate.action.kind.ts @@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-documents', - label: 'Duplicate to...', + label: '#actions_copy', itemRepositoryAlias: '', duplicateRepositoryAlias: '', pickerModal: '', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts index b597275cdd..005791ff7b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts @@ -2,4 +2,3 @@ export * from './duplicate/duplicate.action.js'; export * from './delete/delete.action.js'; export * from './move/move.action.js'; export * from './sort-children-of/sort-children-of.action.js'; -export * from './trash/trash.action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/move/move.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/move/move.action.kind.ts index a872359e9f..09b3fb9010 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/move/move.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/move/move.action.kind.ts @@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-enter', - label: 'Move to (TBD)...', + label: '#actions_move', itemRepositoryAlias: '', moveRepositoryAlias: '', pickerModal: '', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/sort-children-of/sort-children-of.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/sort-children-of/sort-children-of.action.kind.ts index 42dab12acb..3845a19139 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/sort-children-of/sort-children-of.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/sort-children-of/sort-children-of.action.kind.ts @@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-height', - label: 'Sort Children...', + label: '#actions_sort', itemRepositoryAlias: '', sortRepositoryAlias: '', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts deleted file mode 100644 index 3c4fc550c3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { UmbEntityActionBase } from '../../entity-action-base.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -//import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; - -export class UmbTrashEntityAction extends UmbEntityActionBase { - constructor(host: UmbControllerHost, args: any) { - super(host, args); - } - - async execute() { - console.log(`execute trash for: ${this.args.unique}`); - /* - if (!this.unique) throw new Error('Unique is not available'); - if (!this.repository) return; - - const { data } = await this.repository.requestItems([this.unique]); - - if (data) { - const item = data[0]; - - await umbConfirmModal(this._host, { - headline: `Trash ${item.name}`, - content: 'Are you sure you want to move this item to the recycle bin?', - color: 'danger', - confirmLabel: 'Trash', - }); - - this.repository?.trash(this.unique); - } - */ - } -} - -export default UmbTrashEntityAction; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/default/entity-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/default/entity-action.element.ts index e0c639c52c..de1f6cab4e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/default/entity-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/default/entity-action.element.ts @@ -53,7 +53,9 @@ export class UmbEntityActionElement< render() { return html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts index dcdfb2d4b3..bbe018524f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts @@ -2,7 +2,6 @@ import { manifests as defaultEntityActionManifests } from './default/manifests.j import { manifests as deleteEntityActionManifests } from './common/delete/manifests.js'; import { manifests as duplicateEntityActionManifests } from './common/duplicate/manifests.js'; import { manifests as moveEntityActionManifests } from './common/move/manifests.js'; -import { manifests as trashEntityActionManifests } from './common/trash/manifests.js'; import { manifests as sortChildrenOfEntityActionManifests } from './common/sort-children-of/manifests.js'; export const manifests = [ @@ -10,6 +9,5 @@ export const manifests = [ ...deleteEntityActionManifests, ...duplicateEntityActionManifests, ...moveEntityActionManifests, - ...trashEntityActionManifests, ...sortChildrenOfEntityActionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts index f591e15ea4..1f88e46d6a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts @@ -1,3 +1,4 @@ +import { UMB_AUTH_CONTEXT } from './auth/auth.context.token.js'; import { UmbBackofficeNotificationContainerElement, UmbBackofficeModalContainerElement } from './components/index.js'; import { UmbActionEventContext } from './action/action-event.context.js'; import { manifests as coreManifests } from './manifests.js'; @@ -17,12 +18,17 @@ export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { extensionRegistry.registerMany(coreManifests); const notificationContainerElement = new UmbBackofficeNotificationContainerElement(); - host.appendChild(notificationContainerElement); + host.shadowRoot?.appendChild(notificationContainerElement); const modalContainerElement = new UmbBackofficeModalContainerElement(); - host.appendChild(modalContainerElement); + host.shadowRoot?.appendChild(modalContainerElement); new UmbNotificationContext(host); new UmbModalManagerContext(host); new UmbActionEventContext(host); + + host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => { + // Initialize the auth context to let the app context know that the core module is ready + authContext.setInitialized(); + }); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts index 9d63aeebbb..7442cd6ae9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts @@ -1,6 +1,6 @@ export * from './conditions/index.js'; -export * from './interfaces/index.js'; -export * from './models/index.js'; +export type * from './interfaces/index.js'; +export type * from './models/index.js'; export * from './registry.js'; export * from './utils/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/auth-provider.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/auth-provider.model.ts new file mode 100644 index 0000000000..116be49a73 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/auth-provider.model.ts @@ -0,0 +1,80 @@ +import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api'; +import type { UUIInterfaceColor, UUIInterfaceLook } from '@umbraco-cms/backoffice/external/uui'; + +/** + * Represents an authentication provider that can be used to authenticate users. + * The provider needs to be registered in the API that the authorization request is sent to in order to be used. + * + * @see {forProviderName} for the provider name that this provider is for. + */ +export interface ManifestAuthProvider extends ManifestElement { + type: 'authProvider'; + + /** + * The provider name that this provider is for. + * @examples 'Umbraco.Github' + */ + forProviderName: string; + + meta?: MetaAuthProvider; +} + +export interface MetaAuthProvider { + /** + * The label of the provider that is shown to the user. + */ + label?: string; + + /** + * The default view of the provider that is shown to the user. + * If no element is provided, then the button will be rendered as a @see {UUIButtonElement} using these options. + */ + defaultView?: { + /** + * The icon of the provider that is shown to the user. + * @examples ['icon-cloud', 'icon-github', 'icon-google', 'icon-facebook', 'icon-twitter', 'icon-x', 'icon-microsoft'] + * @default 'icon-cloud' + */ + icon?: string; + + /** + * The color of the provider that is shown to the user. + * @default 'secondary' + */ + color?: UUIInterfaceColor; + + /** + * The look of the provider that is shown to the user. + * @default 'default' + */ + look?: UUIInterfaceLook; + }; + + /** + * The behavior of the provider when it is used. + */ + behavior?: { + /** + * If true, the Umbraco backoffice login will be disabled. + * @default false + */ + denyLocalLogin?: boolean; + + /** + * If true, the user will be redirected to the provider's login page immediately. + * @default false + */ + autoRedirect?: boolean; + }; + + /** + * The linking options of the provider when it is used. + */ + linking?: { + /** + * If true, the user will be able to link the provider to an existing account. + * @default false + */ + allowManualLinking?: boolean; + }; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-action.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-action.model.ts index 1a0e2fde40..68ed28918a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-action.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-action.model.ts @@ -2,7 +2,7 @@ import type { ConditionTypes } from '../conditions/types.js'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityAction } from '@umbraco-cms/backoffice/entity-action'; import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; /** * An action to perform on an entity @@ -63,10 +63,35 @@ export interface ManifestEntityActionTrashKind extends ManifestEntityAction { + type: 'entityAction'; + kind: 'restoreFromRecycleBin'; +} + +export interface MetaEntityActionRestoreFromRecycleBinKind extends MetaEntityActionDefaultKind { + recycleBinRepositoryAlias: string; + itemRepositoryAlias: string; + pickerModal: UmbModalToken, UmbPickerModalValue> | string; +} + +// EMPTY RECYCLE BIN +export interface ManifestEntityActionEmptyRecycleBinKind + extends ManifestEntityAction { + type: 'entityAction'; + kind: 'emptyRecycleBin'; +} + +export interface MetaEntityActionEmptyRecycleBinKind extends MetaEntityActionDefaultKind { + recycleBinRepositoryAlias: string; +} + // RENAME export interface ManifestEntityActionRenameServerFileKind extends ManifestEntityAction { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts index 4eb5723b94..db8ed1e2e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts @@ -9,8 +9,6 @@ export interface ManifestEntityUserPermission extends ManifestBase { export interface MetaEntityUserPermission { verbs: Array; label?: string; - labelKey?: string; description?: string; - descriptionKey?: string; group?: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index bd329b9276..a997488da7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -1,3 +1,4 @@ +import type { ManifestAuthProvider } from './auth-provider.model.js'; import type { ManifestBlockEditorCustomView } from './block-editor-custom-view.model.js'; import type { ManifestCollection } from './collection.models.js'; import type { ManifestCollectionView } from './collection-view.model.js'; @@ -15,6 +16,8 @@ import type { ManifestEntityActionDeleteFolderKind, ManifestEntityActionDefaultKind, ManifestEntityActionTrashKind, + ManifestEntityActionRestoreFromRecycleBinKind, + ManifestEntityActionEmptyRecycleBinKind, ManifestEntityActionSortChildrenOfKind, } from './entity-action.model.js'; import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from './dynamic-root.model.js'; @@ -64,6 +67,7 @@ import type { ManifestEntryPoint, } from '@umbraco-cms/backoffice/extension-api'; +export type * from './auth-provider.model.js'; export type * from './block-editor-custom-view.model.js'; export type * from './collection.models.js'; export type * from './collection-action.model.js'; @@ -111,9 +115,11 @@ export type ManifestEntityActions = | ManifestEntityActionDeleteFolderKind | ManifestEntityActionDeleteKind | ManifestEntityActionDuplicateKind + | ManifestEntityActionEmptyRecycleBinKind | ManifestEntityActionMoveKind | ManifestEntityActionReloadTreeItemChildrenKind | ManifestEntityActionRenameServerFileKind + | ManifestEntityActionRestoreFromRecycleBinKind | ManifestEntityActionSortChildrenOfKind | ManifestEntityActionTrashKind | ManifestEntityActionUpdateFolderKind; @@ -131,12 +137,13 @@ export type ManifestWorkspaces = ManifestWorkspace | ManifestWorkspaceRoutableKi export type ManifestWorkspaceViews = ManifestWorkspaceView | ManifestWorkspaceViewContentTypeDesignEditorKind; export type ManifestTypes = + | ManifestAuthProvider | ManifestBundle - | ManifestCondition | ManifestBlockEditorCustomView | ManifestCollection | ManifestCollectionView | ManifestCollectionAction + | ManifestCondition | ManifestDashboard | ManifestDashboardCollection | ManifestDynamicRootOrigin diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/LICENSE b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/LICENSE index 4300c4cd5e..66b4696927 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/LICENSE +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/LICENSE @@ -1,8 +1,16 @@ Lucide License -ISC License +ISC License Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +Simple Icons +CC0 1.0 Universal license + +The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. +You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index d46fcbda58..99fb107023 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -1486,7 +1486,7 @@ { "name": "icon-newspaper-alt", "file": "newspaper.svg", - "legacy":true + "legacy": true }, { "name": "icon-newspaper", @@ -1737,22 +1737,22 @@ { "name": "icon-price-dollar", "file": "badge-dollar-sign.svg", - "legacy":true + "legacy": true }, { "name": "icon-price-euro", "file": "badge-euro.svg", - "legacy":true + "legacy": true }, { "name": "icon-price-pound", "file": "badge-pound-sterling.svg", - "legacy":true + "legacy": true }, { "name": "icon-price-yen", "file": "badge-japanese-yen", - "legacy":true + "legacy": true }, { "name": "icon-print", @@ -2429,10 +2429,6 @@ "name": "icon-zoom-in", "file": "zoom-in.svg" }, - - - - { "name": "icon-zoom-out", "file": "zoom-out.svg" @@ -2446,6 +2442,48 @@ "file": "database.svg" } ], + "simpleIcons": [ + { + "name": "icon-azure", + "file": "microsoftazure.svg" + }, + { + "name": "icon-facebook", + "file": "facebook.svg" + }, + { + "name": "icon-gitbook", + "file": "gitbook.svg" + }, + { + "name": "icon-github", + "file": "github.svg" + }, + { + "name": "icon-gitlab", + "file": "gitlab.svg" + }, + { + "name": "icon-google", + "file": "google.svg" + }, + { + "name": "icon-linkedin", + "file": "linkedin.svg" + }, + { + "name": "icon-mastodon", + "file": "mastodon.svg" + }, + { + "name": "icon-microsoft", + "file": "microsoft.svg" + }, + { + "name": "icon-twitter-x", + "file": "x.svg" + } + ], "umbraco": [ { "name": "icon-umbraco", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-azure.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-azure.js new file mode 100644 index 0000000000..c12ff1b97a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-azure.js @@ -0,0 +1 @@ +export default `Microsoft Azure`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-facebook.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-facebook.js new file mode 100644 index 0000000000..d3cd4e5675 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-facebook.js @@ -0,0 +1 @@ +export default `Facebook`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitbook.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitbook.js new file mode 100644 index 0000000000..3cad1567e6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitbook.js @@ -0,0 +1 @@ +export default `GitBook`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-github.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-github.js new file mode 100644 index 0000000000..e7308eddeb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-github.js @@ -0,0 +1 @@ +export default `GitHub`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitlab.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitlab.js new file mode 100644 index 0000000000..8ef7e658bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-gitlab.js @@ -0,0 +1 @@ +export default `GitLab`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-google.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-google.js new file mode 100644 index 0000000000..4e6e37bca7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-google.js @@ -0,0 +1 @@ +export default `Google`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-linkedin.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-linkedin.js new file mode 100644 index 0000000000..1fb9aeb48b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-linkedin.js @@ -0,0 +1 @@ +export default `LinkedIn`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-mastodon.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-mastodon.js new file mode 100644 index 0000000000..40c5f92c70 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-mastodon.js @@ -0,0 +1 @@ +export default `Mastodon`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-microsoft.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-microsoft.js new file mode 100644 index 0000000000..48d6ba0e9a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-microsoft.js @@ -0,0 +1 @@ +export default `Microsoft`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter-x.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter-x.js new file mode 100644 index 0000000000..34ab0e67f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter-x.js @@ -0,0 +1 @@ +export default `X`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter.js new file mode 100644 index 0000000000..3e61a109ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-twitter.js @@ -0,0 +1,6 @@ +export default ` + Twitter + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-umbraco.js b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-umbraco.js index 268d9f441c..1bcdcda9fc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-umbraco.js +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-umbraco.js @@ -1,3 +1,5 @@ -export default ` - -`; \ No newline at end of file +export default ` + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icons.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icons.json index 5cb738864a..1fe1f387f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icons.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icons.json @@ -1 +1 @@ -[{"name":"icon-activity","path":"./icons/icon-activity.js"},{"name":"icon-add","path":"./icons/icon-add.js"},{"name":"icon-addressbook","path":"./icons/icon-addressbook.js"},{"name":"icon-alarm-clock","path":"./icons/icon-alarm-clock.js"},{"name":"icon-alert-alt","path":"./icons/icon-alert-alt.js"},{"name":"icon-alert","path":"./icons/icon-alert.js"},{"name":"icon-alt","path":"./icons/icon-alt.js"},{"name":"icon-anchor","path":"./icons/icon-anchor.js"},{"name":"icon-app","path":"./icons/icon-app.js"},{"name":"icon-application-window-alt","path":"./icons/icon-application-window-alt.js"},{"name":"icon-application-window","path":"./icons/icon-application-window.js"},{"name":"icon-arrivals","path":"./icons/icon-arrivals.js"},{"name":"icon-arrow-down","path":"./icons/icon-arrow-down.js"},{"name":"icon-arrow-left","path":"./icons/icon-arrow-left.js"},{"name":"icon-arrow-right","path":"./icons/icon-arrow-right.js"},{"name":"icon-arrow-up","path":"./icons/icon-arrow-up.js"},{"name":"icon-attachment","path":"./icons/icon-attachment.js"},{"name":"icon-autofill","path":"./icons/icon-autofill.js"},{"name":"icon-award","path":"./icons/icon-award.js"},{"name":"icon-axis-rotation-2","path":"./icons/icon-axis-rotation-2.js"},{"name":"icon-axis-rotation-3","path":"./icons/icon-axis-rotation-3.js"},{"name":"icon-axis-rotation","path":"./icons/icon-axis-rotation.js"},{"name":"icon-backspace","path":"./icons/icon-backspace.js"},{"name":"icon-badge-add","path":"./icons/icon-badge-add.js"},{"name":"icon-badge-remove","path":"./icons/icon-badge-remove.js"},{"name":"icon-badge-restricted","legacy":true,"path":"./icons/icon-badge-restricted.js"},{"name":"icon-bar-chart","path":"./icons/icon-bar-chart.js"},{"name":"icon-barcode","path":"./icons/icon-barcode.js"},{"name":"icon-bars","path":"./icons/icon-bars.js"},{"name":"icon-battery-full","path":"./icons/icon-battery-full.js"},{"name":"icon-battery-low","path":"./icons/icon-battery-low.js"},{"name":"icon-beer-glass","path":"./icons/icon-beer-glass.js"},{"name":"icon-bell-off","path":"./icons/icon-bell-off.js"},{"name":"icon-bell","path":"./icons/icon-bell.js"},{"name":"icon-binarycode","path":"./icons/icon-binarycode.js"},{"name":"icon-bird","path":"./icons/icon-bird.js"},{"name":"icon-birthday-cake","path":"./icons/icon-birthday-cake.js"},{"name":"icon-block","path":"./icons/icon-block.js"},{"name":"icon-bluetooth","path":"./icons/icon-bluetooth.js"},{"name":"icon-boat-shipping","path":"./icons/icon-boat-shipping.js"},{"name":"icon-bones","path":"./icons/icon-bones.js"},{"name":"icon-book-alt-2","path":"./icons/icon-book-alt-2.js"},{"name":"icon-book-alt","path":"./icons/icon-book-alt.js"},{"name":"icon-book","path":"./icons/icon-book.js"},{"name":"icon-bookmark","path":"./icons/icon-bookmark.js"},{"name":"icon-books","path":"./icons/icon-books.js"},{"name":"icon-box-alt","path":"./icons/icon-box-alt.js"},{"name":"icon-box-open","path":"./icons/icon-box-open.js"},{"name":"icon-box","path":"./icons/icon-box.js"},{"name":"icon-brackets","path":"./icons/icon-brackets.js"},{"name":"icon-brick","path":"./icons/icon-brick.js"},{"name":"icon-briefcase","path":"./icons/icon-briefcase.js"},{"name":"icon-browser-window","path":"./icons/icon-browser-window.js"},{"name":"icon-brush-alt-2","path":"./icons/icon-brush-alt-2.js"},{"name":"icon-brush-alt","path":"./icons/icon-brush-alt.js"},{"name":"icon-brush","path":"./icons/icon-brush.js"},{"name":"icon-bug","path":"./icons/icon-bug.js"},{"name":"icon-bulleted-list","path":"./icons/icon-bulleted-list.js"},{"name":"icon-burn","path":"./icons/icon-burn.js"},{"name":"icon-bus","path":"./icons/icon-bus.js"},{"name":"icon-calculator","path":"./icons/icon-calculator.js"},{"name":"icon-calendar-alt","path":"./icons/icon-calendar-alt.js"},{"name":"icon-calendar","path":"./icons/icon-calendar.js"},{"name":"icon-camcorder","legacy":true,"path":"./icons/icon-camcorder.js"},{"name":"icon-camera-roll","path":"./icons/icon-camera-roll.js"},{"name":"icon-candy","path":"./icons/icon-candy.js"},{"name":"icon-caps-lock","path":"./icons/icon-caps-lock.js"},{"name":"icon-car","path":"./icons/icon-car.js"},{"name":"icon-categories","path":"./icons/icon-categories.js"},{"name":"icon-certificate","path":"./icons/icon-certificate.js"},{"name":"icon-chart-curve","path":"./icons/icon-chart-curve.js"},{"name":"icon-chart","path":"./icons/icon-chart.js"},{"name":"icon-chat-active","legacy":true,"path":"./icons/icon-chat-active.js"},{"name":"icon-chat","path":"./icons/icon-chat.js"},{"name":"icon-check","path":"./icons/icon-check.js"},{"name":"icon-checkbox-dotted","path":"./icons/icon-checkbox-dotted.js"},{"name":"icon-checkbox-empty","legacy":true,"path":"./icons/icon-checkbox-empty.js"},{"name":"icon-checkbox","path":"./icons/icon-checkbox.js"},{"name":"icon-chip-alt","legacy":true,"path":"./icons/icon-chip-alt.js"},{"name":"icon-chip","path":"./icons/icon-chip.js"},{"name":"icon-cinema","path":"./icons/icon-cinema.js"},{"name":"icon-circle-dotted-active","path":"./icons/icon-circle-dotted-active.js"},{"name":"icon-circle-dotted","path":"./icons/icon-circle-dotted.js"},{"name":"icon-circuits","path":"./icons/icon-circuits.js"},{"name":"icon-client","legacy":true,"path":"./icons/icon-client.js"},{"name":"icon-cloud-drive","path":"./icons/icon-cloud-drive.js"},{"name":"icon-cloud-upload","path":"./icons/icon-cloud-upload.js"},{"name":"icon-cloud","path":"./icons/icon-cloud.js"},{"name":"icon-cloudy","path":"./icons/icon-cloudy.js"},{"name":"icon-clubs","path":"./icons/icon-clubs.js"},{"name":"icon-cocktail","path":"./icons/icon-cocktail.js"},{"name":"icon-code","path":"./icons/icon-code.js"},{"name":"icon-coffee","path":"./icons/icon-coffee.js"},{"name":"icon-coin-euro","path":"./icons/icon-coin-euro.js"},{"name":"icon-coin-yen","path":"./icons/icon-coin-yen.js"},{"name":"icon-coins-alt","legacy":true,"path":"./icons/icon-coins-alt.js"},{"name":"icon-coins","path":"./icons/icon-coins.js"},{"name":"icon-color-bucket","path":"./icons/icon-color-bucket.js"},{"name":"icon-colorpicker","path":"./icons/icon-colorpicker.js"},{"name":"icon-columns","path":"./icons/icon-columns.js"},{"name":"icon-combination-lock-open","path":"./icons/icon-combination-lock-open.js"},{"name":"icon-combination-lock","path":"./icons/icon-combination-lock.js"},{"name":"icon-command","path":"./icons/icon-command.js"},{"name":"icon-company","path":"./icons/icon-company.js"},{"name":"icon-compress","path":"./icons/icon-compress.js"},{"name":"icon-connection","path":"./icons/icon-connection.js"},{"name":"icon-console","path":"./icons/icon-console.js"},{"name":"icon-contrast","path":"./icons/icon-contrast.js"},{"name":"icon-conversation-alt","path":"./icons/icon-conversation-alt.js"},{"name":"icon-conversation","legacy":true,"path":"./icons/icon-conversation.js"},{"name":"icon-coverflow","path":"./icons/icon-coverflow.js"},{"name":"icon-credit-card-alt","legacy":true,"path":"./icons/icon-credit-card-alt.js"},{"name":"icon-credit-card","path":"./icons/icon-credit-card.js"},{"name":"icon-crop","path":"./icons/icon-crop.js"},{"name":"icon-crosshair","path":"./icons/icon-crosshair.js"},{"name":"icon-crown-alt","legacy":true,"path":"./icons/icon-crown-alt.js"},{"name":"icon-crown","path":"./icons/icon-crown.js"},{"name":"icon-cupcake","legacy":true,"path":"./icons/icon-cupcake.js"},{"name":"icon-curve","path":"./icons/icon-curve.js"},{"name":"icon-cut","path":"./icons/icon-cut.js"},{"name":"icon-dashboard","path":"./icons/icon-dashboard.js"},{"name":"icon-defrag","path":"./icons/icon-defrag.js"},{"name":"icon-delete-key","path":"./icons/icon-delete-key.js"},{"name":"icon-delete","path":"./icons/icon-delete.js"},{"name":"icon-departure","path":"./icons/icon-departure.js"},{"name":"icon-desktop","legacy":true,"path":"./icons/icon-desktop.js"},{"name":"icon-diagnostics","path":"./icons/icon-diagnostics.js"},{"name":"icon-diagonal-arrow-alt","path":"./icons/icon-diagonal-arrow-alt.js"},{"name":"icon-diagonal-arrow","path":"./icons/icon-diagonal-arrow.js"},{"name":"icon-diamond","path":"./icons/icon-diamond.js"},{"name":"icon-diamonds","path":"./icons/icon-diamonds.js"},{"name":"icon-dice","path":"./icons/icon-dice.js"},{"name":"icon-diploma-alt","legacy":true,"path":"./icons/icon-diploma-alt.js"},{"name":"icon-diploma","path":"./icons/icon-diploma.js"},{"name":"icon-directions-alt","path":"./icons/icon-directions-alt.js"},{"name":"icon-directions","path":"./icons/icon-directions.js"},{"name":"icon-disc","path":"./icons/icon-disc.js"},{"name":"icon-disk-image","path":"./icons/icon-disk-image.js"},{"name":"icon-display","path":"./icons/icon-display.js"},{"name":"icon-dna","path":"./icons/icon-dna.js"},{"name":"icon-dock-connector","path":"./icons/icon-dock-connector.js"},{"name":"icon-document-dashed-line","path":"./icons/icon-document-dashed-line.js"},{"name":"icon-document","path":"./icons/icon-document.js"},{"name":"icon-documents","path":"./icons/icon-documents.js"},{"name":"icon-donate","legacy":true,"path":"./icons/icon-donate.js"},{"name":"icon-door-open-alt","legacy":true,"path":"./icons/icon-door-open-alt.js"},{"name":"icon-door-open","path":"./icons/icon-door-open.js"},{"name":"icon-download-alt","path":"./icons/icon-download-alt.js"},{"name":"icon-download","path":"./icons/icon-download.js"},{"name":"icon-drop","path":"./icons/icon-drop.js"},{"name":"icon-eco","path":"./icons/icon-eco.js"},{"name":"icon-economy","legacy":true,"path":"./icons/icon-economy.js"},{"name":"icon-edit","path":"./icons/icon-edit.js"},{"name":"icon-employee","legacy":true,"path":"./icons/icon-employee.js"},{"name":"icon-energy-saving-bulb","path":"./icons/icon-energy-saving-bulb.js"},{"name":"icon-enter","path":"./icons/icon-enter.js"},{"name":"icon-equalizer","path":"./icons/icon-equalizer.js"},{"name":"icon-escape","path":"./icons/icon-escape.js"},{"name":"icon-ethernet","path":"./icons/icon-ethernet.js"},{"name":"icon-eye","path":"./icons/icon-eye.js"},{"name":"icon-facebook-like","path":"./icons/icon-facebook-like.js"},{"name":"icon-factory","path":"./icons/icon-factory.js"},{"name":"icon-favorite","path":"./icons/icon-favorite.js"},{"name":"icon-file-cabinet","path":"./icons/icon-file-cabinet.js"},{"name":"icon-files","path":"./icons/icon-files.js"},{"name":"icon-filter","path":"./icons/icon-filter.js"},{"name":"icon-fingerprint","path":"./icons/icon-fingerprint.js"},{"name":"icon-fire","path":"./icons/icon-fire.js"},{"name":"icon-firewire","legacy":true,"path":"./icons/icon-firewire.js"},{"name":"icon-flag-alt","path":"./icons/icon-flag-alt.js"},{"name":"icon-flag","path":"./icons/icon-flag.js"},{"name":"icon-flash","path":"./icons/icon-flash.js"},{"name":"icon-flashlight","path":"./icons/icon-flashlight.js"},{"name":"icon-flowerpot","path":"./icons/icon-flowerpot.js"},{"name":"icon-folder","path":"./icons/icon-folder.js"},{"name":"icon-folders","path":"./icons/icon-folders.js"},{"name":"icon-font","path":"./icons/icon-font.js"},{"name":"icon-food","path":"./icons/icon-food.js"},{"name":"icon-footprints","path":"./icons/icon-footprints.js"},{"name":"icon-forking","path":"./icons/icon-forking.js"},{"name":"icon-frame-alt","legacy":true,"path":"./icons/icon-frame-alt.js"},{"name":"icon-frame","path":"./icons/icon-frame.js"},{"name":"icon-fullscreen-alt","path":"./icons/icon-fullscreen-alt.js"},{"name":"icon-fullscreen","path":"./icons/icon-fullscreen.js"},{"name":"icon-game","path":"./icons/icon-game.js"},{"name":"icon-geometry","legacy":true,"path":"./icons/icon-geometry.js"},{"name":"icon-gift","path":"./icons/icon-gift.js"},{"name":"icon-glasses","path":"./icons/icon-glasses.js"},{"name":"icon-globe-alt","path":"./icons/icon-globe-alt.js"},{"name":"icon-globe-asia","legacy":true,"path":"./icons/icon-globe-asia.js"},{"name":"icon-globe-europe-africa","legacy":true,"path":"./icons/icon-globe-europe-africa.js"},{"name":"icon-globe-inverted-america","legacy":true,"path":"./icons/icon-globe-inverted-america.js"},{"name":"icon-globe-inverted-asia","legacy":true,"path":"./icons/icon-globe-inverted-asia.js"},{"name":"icon-globe-inverted-europe-africa","legacy":true,"path":"./icons/icon-globe-inverted-europe-africa.js"},{"name":"icon-globe","path":"./icons/icon-globe.js"},{"name":"icon-gps","path":"./icons/icon-gps.js"},{"name":"icon-graduate","path":"./icons/icon-graduate.js"},{"name":"icon-grid","path":"./icons/icon-grid.js"},{"name":"icon-hammer","path":"./icons/icon-hammer.js"},{"name":"icon-hand-active-alt","legacy":true,"path":"./icons/icon-hand-active-alt.js"},{"name":"icon-hand-active","path":"./icons/icon-hand-active.js"},{"name":"icon-hand-pointer-alt","legacy":true,"path":"./icons/icon-hand-pointer-alt.js"},{"name":"icon-hand-pointer","path":"./icons/icon-hand-pointer.js"},{"name":"icon-handshake","path":"./icons/icon-handshake.js"},{"name":"icon-handtool-alt","legacy":true,"path":"./icons/icon-handtool-alt.js"},{"name":"icon-handtool","path":"./icons/icon-handtool.js"},{"name":"icon-hard-drive-alt","legacy":true,"path":"./icons/icon-hard-drive-alt.js"},{"name":"icon-hard-drive","legacy":true,"path":"./icons/icon-hard-drive.js"},{"name":"icon-headphones","path":"./icons/icon-headphones.js"},{"name":"icon-headset","legacy":true,"path":"./icons/icon-headset.js"},{"name":"icon-hearts","path":"./icons/icon-hearts.js"},{"name":"icon-height","path":"./icons/icon-height.js"},{"name":"icon-help-alt","path":"./icons/icon-help-alt.js"},{"name":"icon-help","path":"./icons/icon-help.js"},{"name":"icon-home","path":"./icons/icon-home.js"},{"name":"icon-hourglass","path":"./icons/icon-hourglass.js"},{"name":"icon-imac","legacy":true,"path":"./icons/icon-imac.js"},{"name":"icon-inbox-full","legacy":true,"path":"./icons/icon-inbox-full.js"},{"name":"icon-inbox","path":"./icons/icon-inbox.js"},{"name":"icon-indent","path":"./icons/icon-indent.js"},{"name":"icon-infinity","path":"./icons/icon-infinity.js"},{"name":"icon-info","path":"./icons/icon-info.js"},{"name":"icon-invoice","legacy":true,"path":"./icons/icon-invoice.js"},{"name":"icon-ipad","legacy":true,"path":"./icons/icon-ipad.js"},{"name":"icon-iphone","legacy":true,"path":"./icons/icon-iphone.js"},{"name":"icon-item-arrangement","legacy":true,"path":"./icons/icon-item-arrangement.js"},{"name":"icon-junk","path":"./icons/icon-junk.js"},{"name":"icon-key","path":"./icons/icon-key.js"},{"name":"icon-keyboard","path":"./icons/icon-keyboard.js"},{"name":"icon-lab","path":"./icons/icon-lab.js"},{"name":"icon-laptop","path":"./icons/icon-laptop.js"},{"name":"icon-layers-alt","legacy":true,"path":"./icons/icon-layers-alt.js"},{"name":"icon-layers","path":"./icons/icon-layers.js"},{"name":"icon-layout","path":"./icons/icon-layout.js"},{"name":"icon-left-double-arrow","path":"./icons/icon-left-double-arrow.js"},{"name":"icon-legal","path":"./icons/icon-legal.js"},{"name":"icon-lense","legacy":true,"path":"./icons/icon-lense.js"},{"name":"icon-library","path":"./icons/icon-library.js"},{"name":"icon-light-down","path":"./icons/icon-light-down.js"},{"name":"icon-light-up","path":"./icons/icon-light-up.js"},{"name":"icon-lightning","path":"./icons/icon-lightning.js"},{"name":"icon-link","path":"./icons/icon-link.js"},{"name":"icon-list","path":"./icons/icon-list.js"},{"name":"icon-load","legacy":true,"path":"./icons/icon-load.js"},{"name":"icon-loading","legacy":true,"path":"./icons/icon-loading.js"},{"name":"icon-location-nearby","path":"./icons/icon-location-nearby.js"},{"name":"icon-lock","path":"./icons/icon-lock.js"},{"name":"icon-log-out","path":"./icons/icon-log-out.js"},{"name":"icon-logout","legacy":true,"path":"./icons/icon-logout.js"},{"name":"icon-loupe","legacy":true,"path":"./icons/icon-loupe.js"},{"name":"icon-magnet","path":"./icons/icon-magnet.js"},{"name":"icon-mailbox","path":"./icons/icon-mailbox.js"},{"name":"icon-map-alt","path":"./icons/icon-map-alt.js"},{"name":"icon-map-location","legacy":true,"path":"./icons/icon-map-location.js"},{"name":"icon-map-marker","path":"./icons/icon-map-marker.js"},{"name":"icon-map","path":"./icons/icon-map.js"},{"name":"icon-medal","path":"./icons/icon-medal.js"},{"name":"icon-medical-emergency","path":"./icons/icon-medical-emergency.js"},{"name":"icon-medicine","path":"./icons/icon-medicine.js"},{"name":"icon-meeting","legacy":true,"path":"./icons/icon-meeting.js"},{"name":"icon-megaphone","path":"./icons/icon-megaphone.js"},{"name":"icon-merge","path":"./icons/icon-merge.js"},{"name":"icon-message-open","path":"./icons/icon-message-open.js"},{"name":"icon-message-unopened","legacy":true,"path":"./icons/icon-message-unopened.js"},{"name":"icon-message","path":"./icons/icon-message.js"},{"name":"icon-microscope","path":"./icons/icon-microscope.js"},{"name":"icon-mindmap","legacy":true,"path":"./icons/icon-mindmap.js"},{"name":"icon-mobile","path":"./icons/icon-mobile.js"},{"name":"icon-mountain","path":"./icons/icon-mountain.js"},{"name":"icon-mouse-cursor","path":"./icons/icon-mouse-cursor.js"},{"name":"icon-mouse","path":"./icons/icon-mouse.js"},{"name":"icon-movie-alt","path":"./icons/icon-movie-alt.js"},{"name":"icon-movie","path":"./icons/icon-movie.js"},{"name":"icon-multiple-credit-cards","path":"./icons/icon-multiple-credit-cards.js"},{"name":"icon-music","path":"./icons/icon-music.js"},{"name":"icon-name-badge","legacy":true,"path":"./icons/icon-name-badge.js"},{"name":"icon-navigation-bottom","legacy":true,"path":"./icons/icon-navigation-bottom.js"},{"name":"icon-navigation-down","legacy":true,"path":"./icons/icon-navigation-down.js"},{"name":"icon-navigation-first","legacy":true,"path":"./icons/icon-navigation-first.js"},{"name":"icon-navigation-horizontal","legacy":true,"path":"./icons/icon-navigation-horizontal.js"},{"name":"icon-navigation-last","legacy":true,"path":"./icons/icon-navigation-last.js"},{"name":"icon-navigation-left","legacy":true,"path":"./icons/icon-navigation-left.js"},{"name":"icon-navigation-right","legacy":true,"path":"./icons/icon-navigation-right.js"},{"name":"icon-navigation-road","legacy":true,"path":"./icons/icon-navigation-road.js"},{"name":"icon-navigation-up","legacy":true,"path":"./icons/icon-navigation-up.js"},{"name":"icon-navigation-vertical","legacy":true,"path":"./icons/icon-navigation-vertical.js"},{"name":"icon-navigation","legacy":true,"path":"./icons/icon-navigation.js"},{"name":"icon-navigational-arrow","path":"./icons/icon-navigational-arrow.js"},{"name":"icon-network-alt","path":"./icons/icon-network-alt.js"},{"name":"icon-newspaper-alt","legacy":true,"path":"./icons/icon-newspaper-alt.js"},{"name":"icon-newspaper","path":"./icons/icon-newspaper.js"},{"name":"icon-next-media","legacy":true,"path":"./icons/icon-next-media.js"},{"name":"icon-next","legacy":true,"path":"./icons/icon-next.js"},{"name":"icon-nodes","legacy":true,"path":"./icons/icon-nodes.js"},{"name":"icon-notepad-alt","legacy":true,"path":"./icons/icon-notepad-alt.js"},{"name":"icon-notepad","path":"./icons/icon-notepad.js"},{"name":"icon-old-key","path":"./icons/icon-old-key.js"},{"name":"icon-old-phone","legacy":true,"path":"./icons/icon-old-phone.js"},{"name":"icon-operator","path":"./icons/icon-operator.js"},{"name":"icon-ordered-list","path":"./icons/icon-ordered-list.js"},{"name":"icon-out","path":"./icons/icon-out.js"},{"name":"icon-outbox","legacy":true,"path":"./icons/icon-outbox.js"},{"name":"icon-outdent","path":"./icons/icon-outdent.js"},{"name":"icon-page-add","path":"./icons/icon-page-add.js"},{"name":"icon-page-down","path":"./icons/icon-page-down.js"},{"name":"icon-page-remove","path":"./icons/icon-page-remove.js"},{"name":"icon-page-restricted","path":"./icons/icon-page-restricted.js"},{"name":"icon-page-up","path":"./icons/icon-page-up.js"},{"name":"icon-paint-roller","legacy":true,"path":"./icons/icon-paint-roller.js"},{"name":"icon-palette","path":"./icons/icon-palette.js"},{"name":"icon-panel-show","path":"./icons/icon-panel-show.js"},{"name":"icon-pannel-close","path":"./icons/icon-pannel-close.js"},{"name":"icon-paper-bag","legacy":true,"path":"./icons/icon-paper-bag.js"},{"name":"icon-paper-plane-alt","path":"./icons/icon-paper-plane-alt.js"},{"name":"icon-paper-plane","path":"./icons/icon-paper-plane.js"},{"name":"icon-partly-cloudy","path":"./icons/icon-partly-cloudy.js"},{"name":"icon-paste-in","legacy":true,"path":"./icons/icon-paste-in.js"},{"name":"icon-pause","path":"./icons/icon-pause.js"},{"name":"icon-pc","legacy":true,"path":"./icons/icon-pc.js"},{"name":"icon-people-alt-2","legacy":true,"path":"./icons/icon-people-alt-2.js"},{"name":"icon-people-alt","legacy":true,"path":"./icons/icon-people-alt.js"},{"name":"icon-people-female","legacy":true,"path":"./icons/icon-people-female.js"},{"name":"icon-people","path":"./icons/icon-people.js"},{"name":"icon-phone-ring","path":"./icons/icon-phone-ring.js"},{"name":"icon-phone","path":"./icons/icon-phone.js"},{"name":"icon-photo-album","path":"./icons/icon-photo-album.js"},{"name":"icon-picture","path":"./icons/icon-picture.js"},{"name":"icon-pictures-alt-2","path":"./icons/icon-pictures-alt-2.js"},{"name":"icon-pictures-alt","legacy":true,"path":"./icons/icon-pictures-alt.js"},{"name":"icon-pictures","path":"./icons/icon-pictures.js"},{"name":"icon-pie-chart","path":"./icons/icon-pie-chart.js"},{"name":"icon-piggy-bank","path":"./icons/icon-piggy-bank.js"},{"name":"icon-pin-location","path":"./icons/icon-pin-location.js"},{"name":"icon-plane","path":"./icons/icon-plane.js"},{"name":"icon-planet","legacy":true,"path":"./icons/icon-planet.js"},{"name":"icon-play","path":"./icons/icon-play.js"},{"name":"icon-playing-cards","legacy":true,"path":"./icons/icon-playing-cards.js"},{"name":"icon-playlist","path":"./icons/icon-playlist.js"},{"name":"icon-plugin","path":"./icons/icon-plugin.js"},{"name":"icon-podcast","path":"./icons/icon-podcast.js"},{"name":"icon-poll","legacy":true,"path":"./icons/icon-poll.js"},{"name":"icon-post-it","path":"./icons/icon-post-it.js"},{"name":"icon-power-outlet","legacy":true,"path":"./icons/icon-power-outlet.js"},{"name":"icon-power","path":"./icons/icon-power.js"},{"name":"icon-presentation","path":"./icons/icon-presentation.js"},{"name":"icon-previous-media","path":"./icons/icon-previous-media.js"},{"name":"icon-previous","path":"./icons/icon-previous.js"},{"name":"icon-price-dollar","legacy":true,"path":"./icons/icon-price-dollar.js"},{"name":"icon-price-euro","legacy":true,"path":"./icons/icon-price-euro.js"},{"name":"icon-price-pound","legacy":true,"path":"./icons/icon-price-pound.js"},{"name":"icon-print","path":"./icons/icon-print.js"},{"name":"icon-printer-alt","legacy":true,"path":"./icons/icon-printer-alt.js"},{"name":"icon-projector","path":"./icons/icon-projector.js"},{"name":"icon-pulse","path":"./icons/icon-pulse.js"},{"name":"icon-pushpin","path":"./icons/icon-pushpin.js"},{"name":"icon-qr-code","path":"./icons/icon-qr-code.js"},{"name":"icon-quote","path":"./icons/icon-quote.js"},{"name":"icon-radio-alt","path":"./icons/icon-radio-alt.js"},{"name":"icon-radio-receiver","path":"./icons/icon-radio-receiver.js"},{"name":"icon-radio","path":"./icons/icon-radio.js"},{"name":"icon-rain","path":"./icons/icon-rain.js"},{"name":"icon-rate","legacy":true,"path":"./icons/icon-rate.js"},{"name":"icon-re-post","path":"./icons/icon-re-post.js"},{"name":"icon-readonly","legacy":true,"path":"./icons/icon-readonly.js"},{"name":"icon-receipt-alt","path":"./icons/icon-receipt-alt.js"},{"name":"icon-reception","path":"./icons/icon-reception.js"},{"name":"icon-record","legacy":true,"path":"./icons/icon-record.js"},{"name":"icon-redo","path":"./icons/icon-redo.js"},{"name":"icon-refresh","path":"./icons/icon-refresh.js"},{"name":"icon-remote","legacy":true,"path":"./icons/icon-remote.js"},{"name":"icon-remove","path":"./icons/icon-remove.js"},{"name":"icon-repeat-one","path":"./icons/icon-repeat-one.js"},{"name":"icon-repeat","path":"./icons/icon-repeat.js"},{"name":"icon-reply-arrow","path":"./icons/icon-reply-arrow.js"},{"name":"icon-return-to-top","legacy":true,"path":"./icons/icon-return-to-top.js"},{"name":"icon-right-double-arrow","legacy":true,"path":"./icons/icon-right-double-arrow.js"},{"name":"icon-roadsign","legacy":true,"path":"./icons/icon-roadsign.js"},{"name":"icon-rocket","path":"./icons/icon-rocket.js"},{"name":"icon-rss","path":"./icons/icon-rss.js"},{"name":"icon-ruler-alt","path":"./icons/icon-ruler-alt.js"},{"name":"icon-ruler","path":"./icons/icon-ruler.js"},{"name":"icon-satellite-dish","path":"./icons/icon-satellite-dish.js"},{"name":"icon-save","path":"./icons/icon-save.js"},{"name":"icon-scan","path":"./icons/icon-scan.js"},{"name":"icon-school","path":"./icons/icon-school.js"},{"name":"icon-screensharing","path":"./icons/icon-screensharing.js"},{"name":"icon-script-alt","legacy":true,"path":"./icons/icon-script-alt.js"},{"name":"icon-script","path":"./icons/icon-script.js"},{"name":"icon-scull","path":"./icons/icon-scull.js"},{"name":"icon-search","path":"./icons/icon-search.js"},{"name":"icon-sensor","path":"./icons/icon-sensor.js"},{"name":"icon-server-alt","legacy":true,"path":"./icons/icon-server-alt.js"},{"name":"icon-server","path":"./icons/icon-server.js"},{"name":"icon-settings-alt","legacy":true,"path":"./icons/icon-settings-alt.js"},{"name":"icon-settings","path":"./icons/icon-settings.js"},{"name":"icon-share-alt","path":"./icons/icon-share-alt.js"},{"name":"icon-share","path":"./icons/icon-share.js"},{"name":"icon-sharing-iphone","path":"./icons/icon-sharing-iphone.js"},{"name":"icon-shield","path":"./icons/icon-shield.js"},{"name":"icon-shift","path":"./icons/icon-shift.js"},{"name":"icon-shipping-box","path":"./icons/icon-shipping-box.js"},{"name":"icon-shipping","path":"./icons/icon-shipping.js"},{"name":"icon-shoe","path":"./icons/icon-shoe.js"},{"name":"icon-shopping-basket-alt-2","legacy":true,"path":"./icons/icon-shopping-basket-alt-2.js"},{"name":"icon-shopping-basket-alt","path":"./icons/icon-shopping-basket-alt.js"},{"name":"icon-shopping-basket","path":"./icons/icon-shopping-basket.js"},{"name":"icon-shuffle","path":"./icons/icon-shuffle.js"},{"name":"icon-sience","path":"./icons/icon-sience.js"},{"name":"icon-single-note","path":"./icons/icon-single-note.js"},{"name":"icon-sitemap","legacy":true,"path":"./icons/icon-sitemap.js"},{"name":"icon-sleep","path":"./icons/icon-sleep.js"},{"name":"icon-slideshow","legacy":true,"path":"./icons/icon-slideshow.js"},{"name":"icon-smiley-inverted","legacy":true,"path":"./icons/icon-smiley-inverted.js"},{"name":"icon-smiley","path":"./icons/icon-smiley.js"},{"name":"icon-snow","path":"./icons/icon-snow.js"},{"name":"icon-sound-low","path":"./icons/icon-sound-low.js"},{"name":"icon-sound-medium","legacy":true,"path":"./icons/icon-sound-medium.js"},{"name":"icon-sound-off","path":"./icons/icon-sound-off.js"},{"name":"icon-sound-waves","path":"./icons/icon-sound-waves.js"},{"name":"icon-sound","path":"./icons/icon-sound.js"},{"name":"icon-spades","path":"./icons/icon-spades.js"},{"name":"icon-speaker","path":"./icons/icon-speaker.js"},{"name":"icon-speed-gauge","path":"./icons/icon-speed-gauge.js"},{"name":"icon-split","path":"./icons/icon-split.js"},{"name":"icon-sprout","path":"./icons/icon-sprout.js"},{"name":"icon-squiggly-line","legacy":true,"path":"./icons/icon-squiggly-line.js"},{"name":"icon-ssd","legacy":true,"path":"./icons/icon-ssd.js"},{"name":"icon-stacked-disks","legacy":true,"path":"./icons/icon-stacked-disks.js"},{"name":"icon-stamp","legacy":true,"path":"./icons/icon-stamp.js"},{"name":"icon-stop-alt","path":"./icons/icon-stop-alt.js"},{"name":"icon-stop-hand","legacy":true,"path":"./icons/icon-stop-hand.js"},{"name":"icon-stop","path":"./icons/icon-stop.js"},{"name":"icon-store","path":"./icons/icon-store.js"},{"name":"icon-stream","legacy":true,"path":"./icons/icon-stream.js"},{"name":"icon-sunny","path":"./icons/icon-sunny.js"},{"name":"icon-sweatshirt","legacy":true,"path":"./icons/icon-sweatshirt.js"},{"name":"icon-sync","path":"./icons/icon-sync.js"},{"name":"icon-t-shirt","path":"./icons/icon-t-shirt.js"},{"name":"icon-tab-key","path":"./icons/icon-tab-key.js"},{"name":"icon-tag","path":"./icons/icon-tag.js"},{"name":"icon-tags","path":"./icons/icon-tags.js"},{"name":"icon-takeaway-cup","legacy":true,"path":"./icons/icon-takeaway-cup.js"},{"name":"icon-target","path":"./icons/icon-target.js"},{"name":"icon-temperatrure-alt","path":"./icons/icon-temperatrure-alt.js"},{"name":"icon-temperature","path":"./icons/icon-temperature.js"},{"name":"icon-terminal","path":"./icons/icon-terminal.js"},{"name":"icon-theater","path":"./icons/icon-theater.js"},{"name":"icon-thumb-down","path":"./icons/icon-thumb-down.js"},{"name":"icon-thumb-up","path":"./icons/icon-thumb-up.js"},{"name":"icon-thumbnail-list","path":"./icons/icon-thumbnail-list.js"},{"name":"icon-thumbnails-small","path":"./icons/icon-thumbnails-small.js"},{"name":"icon-thumbnails","path":"./icons/icon-thumbnails.js"},{"name":"icon-ticket","path":"./icons/icon-ticket.js"},{"name":"icon-time","path":"./icons/icon-time.js"},{"name":"icon-timer","path":"./icons/icon-timer.js"},{"name":"icon-tools","legacy":true,"path":"./icons/icon-tools.js"},{"name":"icon-top","legacy":true,"path":"./icons/icon-top.js"},{"name":"icon-traffic-alt","legacy":true,"path":"./icons/icon-traffic-alt.js"},{"name":"icon-trafic","path":"./icons/icon-trafic.js"},{"name":"icon-train","path":"./icons/icon-train.js"},{"name":"icon-trash-alt-2","legacy":true,"path":"./icons/icon-trash-alt-2.js"},{"name":"icon-trash-alt","legacy":true,"path":"./icons/icon-trash-alt.js"},{"name":"icon-trash","path":"./icons/icon-trash.js"},{"name":"icon-tree","path":"./icons/icon-tree.js"},{"name":"icon-trophy","path":"./icons/icon-trophy.js"},{"name":"icon-truck","path":"./icons/icon-truck.js"},{"name":"icon-tv-old","path":"./icons/icon-tv-old.js"},{"name":"icon-tv","path":"./icons/icon-tv.js"},{"name":"icon-umb-content","legacy":true,"path":"./icons/icon-umb-content.js"},{"name":"icon-umb-developer","legacy":true,"path":"./icons/icon-umb-developer.js"},{"name":"icon-umb-media","legacy":true,"path":"./icons/icon-umb-media.js"},{"name":"icon-umb-settings","legacy":true,"path":"./icons/icon-umb-settings.js"},{"name":"icon-umb-users","legacy":true,"path":"./icons/icon-umb-users.js"},{"name":"icon-umbrella","path":"./icons/icon-umbrella.js"},{"name":"icon-undo","path":"./icons/icon-undo.js"},{"name":"icon-unlocked","path":"./icons/icon-unlocked.js"},{"name":"icon-untitled","legacy":true,"path":"./icons/icon-untitled.js"},{"name":"icon-usb-connector","legacy":true,"path":"./icons/icon-usb-connector.js"},{"name":"icon-usb","path":"./icons/icon-usb.js"},{"name":"icon-user-female","legacy":true,"path":"./icons/icon-user-female.js"},{"name":"icon-user-females-alt","legacy":true,"path":"./icons/icon-user-females-alt.js"},{"name":"icon-user-females","legacy":true,"path":"./icons/icon-user-females.js"},{"name":"icon-user-glasses","legacy":true,"path":"./icons/icon-user-glasses.js"},{"name":"icon-user","path":"./icons/icon-user.js"},{"name":"icon-users-alt","legacy":true,"path":"./icons/icon-users-alt.js"},{"name":"icon-users","path":"./icons/icon-users.js"},{"name":"icon-utilities","path":"./icons/icon-utilities.js"},{"name":"icon-vcard","path":"./icons/icon-vcard.js"},{"name":"icon-video","path":"./icons/icon-video.js"},{"name":"icon-voice","path":"./icons/icon-voice.js"},{"name":"icon-wall-plug","path":"./icons/icon-wall-plug.js"},{"name":"icon-wallet","path":"./icons/icon-wallet.js"},{"name":"icon-wand","path":"./icons/icon-wand.js"},{"name":"icon-webhook","path":"./icons/icon-webhook.js"},{"name":"icon-weight","path":"./icons/icon-weight.js"},{"name":"icon-width","path":"./icons/icon-width.js"},{"name":"icon-wifi","path":"./icons/icon-wifi.js"},{"name":"icon-window-popin","path":"./icons/icon-window-popin.js"},{"name":"icon-window-sizes","path":"./icons/icon-window-sizes.js"},{"name":"icon-wine-glass","path":"./icons/icon-wine-glass.js"},{"name":"icon-wrench","path":"./icons/icon-wrench.js"},{"name":"icon-wrong","path":"./icons/icon-wrong.js"},{"name":"icon-zip","path":"./icons/icon-zip.js"},{"name":"icon-zom-out","legacy":true,"path":"./icons/icon-zom-out.js"},{"name":"icon-zoom-in","path":"./icons/icon-zoom-in.js"},{"name":"icon-zoom-out","path":"./icons/icon-zoom-out.js"},{"name":"icon-star","path":"./icons/icon-star.js"},{"name":"icon-database","path":"./icons/icon-database.js"},{"name":"icon-umbraco","path":"./icons/icon-umbraco.js"},{"name":"icon-application-error","legacy":true,"path":"./icons/icon-application-error.js"},{"name":"icon-art-easel","legacy":true,"path":"./icons/icon-art-easel.js"},{"name":"icon-article","legacy":true,"path":"./icons/icon-article.js"},{"name":"icon-auction-hammer","legacy":true,"path":"./icons/icon-auction-hammer.js"},{"name":"icon-baby-stroller","legacy":true,"path":"./icons/icon-baby-stroller.js"},{"name":"icon-badge-count","legacy":true,"path":"./icons/icon-badge-count.js"},{"name":"icon-ball","legacy":true,"path":"./icons/icon-ball.js"},{"name":"icon-band-aid","legacy":true,"path":"./icons/icon-band-aid.js"},{"name":"icon-bill-dollar","legacy":true,"path":"./icons/icon-bill-dollar.js"},{"name":"icon-bill-euro","legacy":true,"path":"./icons/icon-bill-euro.js"},{"name":"icon-bill-pound","legacy":true,"path":"./icons/icon-bill-pound.js"},{"name":"icon-bill-yen","legacy":true,"path":"./icons/icon-bill-yen.js"},{"name":"icon-bill","legacy":true,"path":"./icons/icon-bill.js"},{"name":"icon-billboard","legacy":true,"path":"./icons/icon-billboard.js"},{"name":"icon-bills-dollar","legacy":true,"path":"./icons/icon-bills-dollar.js"},{"name":"icon-bills-euro","legacy":true,"path":"./icons/icon-bills-euro.js"},{"name":"icon-bills-pound","legacy":true,"path":"./icons/icon-bills-pound.js"},{"name":"icon-bills-yen","legacy":true,"path":"./icons/icon-bills-yen.js"},{"name":"icon-bills","legacy":true,"path":"./icons/icon-bills.js"},{"name":"icon-binoculars","legacy":true,"path":"./icons/icon-binoculars.js"},{"name":"icon-blueprint","legacy":true,"path":"./icons/icon-blueprint.js"},{"name":"icon-bomb","legacy":true,"path":"./icons/icon-bomb.js"},{"name":"icon-cash-register","legacy":true,"path":"./icons/icon-cash-register.js"},{"name":"icon-checkbox-dotted-active","legacy":true,"path":"./icons/icon-checkbox-dotted-active.js"},{"name":"icon-chess","legacy":true,"path":"./icons/icon-chess.js"},{"name":"icon-circus","legacy":true,"path":"./icons/icon-circus.js"},{"name":"icon-clothes-hanger","legacy":true,"path":"./icons/icon-clothes-hanger.js"},{"name":"icon-coin-dollar","legacy":true,"path":"./icons/icon-coin-dollar.js"},{"name":"icon-coin-pound","legacy":true,"path":"./icons/icon-coin-pound.js"},{"name":"icon-coin","legacy":true,"path":"./icons/icon-coin.js"},{"name":"icon-coins-dollar-alt","legacy":true,"path":"./icons/icon-coins-dollar-alt.js"},{"name":"icon-coins-dollar","legacy":true,"path":"./icons/icon-coins-dollar.js"},{"name":"icon-coins-euro-alt","legacy":true,"path":"./icons/icon-coins-euro-alt.js"},{"name":"icon-coins-euro","legacy":true,"path":"./icons/icon-coins-euro.js"},{"name":"icon-coins-pound-alt","legacy":true,"path":"./icons/icon-coins-pound-alt.js"},{"name":"icon-coins-pound","legacy":true,"path":"./icons/icon-coins-pound.js"},{"name":"icon-coins-yen-alt","legacy":true,"path":"./icons/icon-coins-yen-alt.js"},{"name":"icon-coins-yen","legacy":true,"path":"./icons/icon-coins-yen.js"},{"name":"icon-comb","legacy":true,"path":"./icons/icon-comb.js"},{"name":"icon-desk","legacy":true,"path":"./icons/icon-desk.js"},{"name":"icon-dollar-bag","legacy":true,"path":"./icons/icon-dollar-bag.js"},{"name":"icon-eject","legacy":true,"path":"./icons/icon-eject.js"},{"name":"icon-euro-bag","legacy":true,"path":"./icons/icon-euro-bag.js"},{"name":"icon-exit-fullscreen","legacy":true,"path":"./icons/icon-exit-fullscreen.js"},{"name":"icon-female-symbol","legacy":true,"path":"./icons/icon-female-symbol.js"},{"name":"icon-filter-arrows","legacy":true,"path":"./icons/icon-filter-arrows.js"},{"name":"icon-firewall","legacy":true,"path":"./icons/icon-firewall.js"},{"name":"icon-folder-open","legacy":true,"path":"./icons/icon-folder-open.js"},{"name":"icon-folder-outline","legacy":true,"path":"./icons/icon-folder-outline.js"},{"name":"icon-handprint","legacy":true,"path":"./icons/icon-handprint.js"},{"name":"icon-hat","legacy":true,"path":"./icons/icon-hat.js"},{"name":"icon-hd","legacy":true,"path":"./icons/icon-hd.js"},{"name":"icon-inactive-line","legacy":true,"path":"./icons/icon-inactive-line.js"},{"name":"icon-keychain","legacy":true,"path":"./icons/icon-keychain.js"},{"name":"icon-keyhole","legacy":true,"path":"./icons/icon-keyhole.js"},{"name":"icon-lightbulb-active","legacy":true,"path":"./icons/icon-lightbulb-active.js"},{"name":"icon-lightbulb","legacy":true,"path":"./icons/icon-lightbulb.js"},{"name":"icon-linux-tux","legacy":true,"path":"./icons/icon-linux-tux.js"},{"name":"icon-locate","legacy":true,"path":"./icons/icon-locate.js"},{"name":"icon-location-near-me","legacy":true,"path":"./icons/icon-location-near-me.js"},{"name":"icon-male-and-female","legacy":true,"path":"./icons/icon-male-and-female.js"},{"name":"icon-male-symbol","legacy":true,"path":"./icons/icon-male-symbol.js"},{"name":"icon-molecular-network","legacy":true,"path":"./icons/icon-molecular-network.js"},{"name":"icon-molecular","legacy":true,"path":"./icons/icon-molecular.js"},{"name":"icon-multiple-windows","legacy":true,"path":"./icons/icon-multiple-windows.js"},{"name":"icon-navigation-top","legacy":true,"path":"./icons/icon-navigation-top.js"},{"name":"icon-os-x","legacy":true,"path":"./icons/icon-os-x.js"},{"name":"icon-pants","legacy":true,"path":"./icons/icon-pants.js"},{"name":"icon-parachute-drop","legacy":true,"path":"./icons/icon-parachute-drop.js"},{"name":"icon-parental-control","legacy":true,"path":"./icons/icon-parental-control.js"},{"name":"icon-path","legacy":true,"path":"./icons/icon-path.js"},{"name":"icon-piracy","legacy":true,"path":"./icons/icon-piracy.js"},{"name":"icon-poker-chip","legacy":true,"path":"./icons/icon-poker-chip.js"},{"name":"icon-pound-bag","legacy":true,"path":"./icons/icon-pound-bag.js"},{"name":"icon-price-yen","legacy":true,"path":"./icons/icon-price-yen.js"},{"name":"icon-receipt-dollar","legacy":true,"path":"./icons/icon-receipt-dollar.js"},{"name":"icon-receipt-euro","legacy":true,"path":"./icons/icon-receipt-euro.js"},{"name":"icon-receipt-pound","legacy":true,"path":"./icons/icon-receipt-pound.js"},{"name":"icon-receipt-yen","legacy":true,"path":"./icons/icon-receipt-yen.js"},{"name":"icon-resize","legacy":true,"path":"./icons/icon-resize.js"},{"name":"icon-road","legacy":true,"path":"./icons/icon-road.js"},{"name":"icon-safe","legacy":true,"path":"./icons/icon-safe.js"},{"name":"icon-safedial","legacy":true,"path":"./icons/icon-safedial.js"},{"name":"icon-sandbox-toys","legacy":true,"path":"./icons/icon-sandbox-toys.js"},{"name":"icon-security-camera","legacy":true,"path":"./icons/icon-security-camera.js"},{"name":"icon-settings-alt-2","legacy":true,"path":"./icons/icon-settings-alt-2.js"},{"name":"icon-share-alt-2","legacy":true,"path":"./icons/icon-share-alt-2.js"},{"name":"icon-shorts","legacy":true,"path":"./icons/icon-shorts.js"},{"name":"icon-simcard","legacy":true,"path":"./icons/icon-simcard.js"},{"name":"icon-split-alt","legacy":true,"path":"./icons/icon-split-alt.js"},{"name":"icon-tab","legacy":true,"path":"./icons/icon-tab.js"},{"name":"icon-tactics","legacy":true,"path":"./icons/icon-tactics.js"},{"name":"icon-theif","legacy":true,"path":"./icons/icon-theif.js"},{"name":"icon-thought-bubble","legacy":true,"path":"./icons/icon-thought-bubble.js"},{"name":"icon-umb-contour","legacy":true,"path":"./icons/icon-umb-contour.js"},{"name":"icon-umb-deploy","legacy":true,"path":"./icons/icon-umb-deploy.js"},{"name":"icon-umb-members","legacy":true,"path":"./icons/icon-umb-members.js"},{"name":"icon-universal","legacy":true,"path":"./icons/icon-universal.js"},{"name":"icon-war","legacy":true,"path":"./icons/icon-war.js"},{"name":"icon-windows","legacy":true,"path":"./icons/icon-windows.js"},{"name":"icon-yen-bag","legacy":true,"path":"./icons/icon-yen-bag.js"}] \ No newline at end of file +[{"name":"icon-activity","path":"./icons/icon-activity.js"},{"name":"icon-add","path":"./icons/icon-add.js"},{"name":"icon-addressbook","path":"./icons/icon-addressbook.js"},{"name":"icon-alarm-clock","path":"./icons/icon-alarm-clock.js"},{"name":"icon-alert-alt","path":"./icons/icon-alert-alt.js"},{"name":"icon-alert","path":"./icons/icon-alert.js"},{"name":"icon-alt","path":"./icons/icon-alt.js"},{"name":"icon-anchor","path":"./icons/icon-anchor.js"},{"name":"icon-app","path":"./icons/icon-app.js"},{"name":"icon-application-window-alt","path":"./icons/icon-application-window-alt.js"},{"name":"icon-application-window","path":"./icons/icon-application-window.js"},{"name":"icon-arrivals","path":"./icons/icon-arrivals.js"},{"name":"icon-arrow-down","path":"./icons/icon-arrow-down.js"},{"name":"icon-arrow-left","path":"./icons/icon-arrow-left.js"},{"name":"icon-arrow-right","path":"./icons/icon-arrow-right.js"},{"name":"icon-arrow-up","path":"./icons/icon-arrow-up.js"},{"name":"icon-attachment","path":"./icons/icon-attachment.js"},{"name":"icon-autofill","path":"./icons/icon-autofill.js"},{"name":"icon-award","path":"./icons/icon-award.js"},{"name":"icon-axis-rotation-2","path":"./icons/icon-axis-rotation-2.js"},{"name":"icon-axis-rotation-3","path":"./icons/icon-axis-rotation-3.js"},{"name":"icon-axis-rotation","path":"./icons/icon-axis-rotation.js"},{"name":"icon-backspace","path":"./icons/icon-backspace.js"},{"name":"icon-badge-add","path":"./icons/icon-badge-add.js"},{"name":"icon-badge-remove","path":"./icons/icon-badge-remove.js"},{"name":"icon-badge-restricted","legacy":true,"path":"./icons/icon-badge-restricted.js"},{"name":"icon-bar-chart","path":"./icons/icon-bar-chart.js"},{"name":"icon-barcode","path":"./icons/icon-barcode.js"},{"name":"icon-bars","path":"./icons/icon-bars.js"},{"name":"icon-battery-full","path":"./icons/icon-battery-full.js"},{"name":"icon-battery-low","path":"./icons/icon-battery-low.js"},{"name":"icon-beer-glass","path":"./icons/icon-beer-glass.js"},{"name":"icon-bell-off","path":"./icons/icon-bell-off.js"},{"name":"icon-bell","path":"./icons/icon-bell.js"},{"name":"icon-binarycode","path":"./icons/icon-binarycode.js"},{"name":"icon-bird","path":"./icons/icon-bird.js"},{"name":"icon-birthday-cake","path":"./icons/icon-birthday-cake.js"},{"name":"icon-block","path":"./icons/icon-block.js"},{"name":"icon-bluetooth","path":"./icons/icon-bluetooth.js"},{"name":"icon-boat-shipping","path":"./icons/icon-boat-shipping.js"},{"name":"icon-bones","path":"./icons/icon-bones.js"},{"name":"icon-book-alt-2","path":"./icons/icon-book-alt-2.js"},{"name":"icon-book-alt","path":"./icons/icon-book-alt.js"},{"name":"icon-book","path":"./icons/icon-book.js"},{"name":"icon-bookmark","path":"./icons/icon-bookmark.js"},{"name":"icon-books","path":"./icons/icon-books.js"},{"name":"icon-box-alt","path":"./icons/icon-box-alt.js"},{"name":"icon-box-open","path":"./icons/icon-box-open.js"},{"name":"icon-box","path":"./icons/icon-box.js"},{"name":"icon-brackets","path":"./icons/icon-brackets.js"},{"name":"icon-brick","path":"./icons/icon-brick.js"},{"name":"icon-briefcase","path":"./icons/icon-briefcase.js"},{"name":"icon-browser-window","path":"./icons/icon-browser-window.js"},{"name":"icon-brush-alt-2","path":"./icons/icon-brush-alt-2.js"},{"name":"icon-brush-alt","path":"./icons/icon-brush-alt.js"},{"name":"icon-brush","path":"./icons/icon-brush.js"},{"name":"icon-bug","path":"./icons/icon-bug.js"},{"name":"icon-bulleted-list","path":"./icons/icon-bulleted-list.js"},{"name":"icon-burn","path":"./icons/icon-burn.js"},{"name":"icon-bus","path":"./icons/icon-bus.js"},{"name":"icon-calculator","path":"./icons/icon-calculator.js"},{"name":"icon-calendar-alt","path":"./icons/icon-calendar-alt.js"},{"name":"icon-calendar","path":"./icons/icon-calendar.js"},{"name":"icon-camcorder","legacy":true,"path":"./icons/icon-camcorder.js"},{"name":"icon-camera-roll","path":"./icons/icon-camera-roll.js"},{"name":"icon-candy","path":"./icons/icon-candy.js"},{"name":"icon-caps-lock","path":"./icons/icon-caps-lock.js"},{"name":"icon-car","path":"./icons/icon-car.js"},{"name":"icon-categories","path":"./icons/icon-categories.js"},{"name":"icon-certificate","path":"./icons/icon-certificate.js"},{"name":"icon-chart-curve","path":"./icons/icon-chart-curve.js"},{"name":"icon-chart","path":"./icons/icon-chart.js"},{"name":"icon-chat-active","legacy":true,"path":"./icons/icon-chat-active.js"},{"name":"icon-chat","path":"./icons/icon-chat.js"},{"name":"icon-check","path":"./icons/icon-check.js"},{"name":"icon-checkbox-dotted","path":"./icons/icon-checkbox-dotted.js"},{"name":"icon-checkbox-empty","legacy":true,"path":"./icons/icon-checkbox-empty.js"},{"name":"icon-checkbox","path":"./icons/icon-checkbox.js"},{"name":"icon-chip-alt","legacy":true,"path":"./icons/icon-chip-alt.js"},{"name":"icon-chip","path":"./icons/icon-chip.js"},{"name":"icon-cinema","path":"./icons/icon-cinema.js"},{"name":"icon-circle-dotted-active","path":"./icons/icon-circle-dotted-active.js"},{"name":"icon-circle-dotted","path":"./icons/icon-circle-dotted.js"},{"name":"icon-circuits","path":"./icons/icon-circuits.js"},{"name":"icon-client","legacy":true,"path":"./icons/icon-client.js"},{"name":"icon-cloud-drive","path":"./icons/icon-cloud-drive.js"},{"name":"icon-cloud-upload","path":"./icons/icon-cloud-upload.js"},{"name":"icon-cloud","path":"./icons/icon-cloud.js"},{"name":"icon-cloudy","path":"./icons/icon-cloudy.js"},{"name":"icon-clubs","path":"./icons/icon-clubs.js"},{"name":"icon-cocktail","path":"./icons/icon-cocktail.js"},{"name":"icon-code","path":"./icons/icon-code.js"},{"name":"icon-coffee","path":"./icons/icon-coffee.js"},{"name":"icon-coin-euro","path":"./icons/icon-coin-euro.js"},{"name":"icon-coin-yen","path":"./icons/icon-coin-yen.js"},{"name":"icon-coins-alt","legacy":true,"path":"./icons/icon-coins-alt.js"},{"name":"icon-coins","path":"./icons/icon-coins.js"},{"name":"icon-color-bucket","path":"./icons/icon-color-bucket.js"},{"name":"icon-colorpicker","path":"./icons/icon-colorpicker.js"},{"name":"icon-columns","path":"./icons/icon-columns.js"},{"name":"icon-combination-lock-open","path":"./icons/icon-combination-lock-open.js"},{"name":"icon-combination-lock","path":"./icons/icon-combination-lock.js"},{"name":"icon-command","path":"./icons/icon-command.js"},{"name":"icon-company","path":"./icons/icon-company.js"},{"name":"icon-compress","path":"./icons/icon-compress.js"},{"name":"icon-connection","path":"./icons/icon-connection.js"},{"name":"icon-console","path":"./icons/icon-console.js"},{"name":"icon-contrast","path":"./icons/icon-contrast.js"},{"name":"icon-conversation-alt","path":"./icons/icon-conversation-alt.js"},{"name":"icon-conversation","legacy":true,"path":"./icons/icon-conversation.js"},{"name":"icon-coverflow","path":"./icons/icon-coverflow.js"},{"name":"icon-credit-card-alt","legacy":true,"path":"./icons/icon-credit-card-alt.js"},{"name":"icon-credit-card","path":"./icons/icon-credit-card.js"},{"name":"icon-crop","path":"./icons/icon-crop.js"},{"name":"icon-crosshair","path":"./icons/icon-crosshair.js"},{"name":"icon-crown-alt","legacy":true,"path":"./icons/icon-crown-alt.js"},{"name":"icon-crown","path":"./icons/icon-crown.js"},{"name":"icon-cupcake","legacy":true,"path":"./icons/icon-cupcake.js"},{"name":"icon-curve","path":"./icons/icon-curve.js"},{"name":"icon-cut","path":"./icons/icon-cut.js"},{"name":"icon-dashboard","path":"./icons/icon-dashboard.js"},{"name":"icon-defrag","path":"./icons/icon-defrag.js"},{"name":"icon-delete-key","path":"./icons/icon-delete-key.js"},{"name":"icon-delete","path":"./icons/icon-delete.js"},{"name":"icon-departure","path":"./icons/icon-departure.js"},{"name":"icon-desktop","legacy":true,"path":"./icons/icon-desktop.js"},{"name":"icon-diagnostics","path":"./icons/icon-diagnostics.js"},{"name":"icon-diagonal-arrow-alt","path":"./icons/icon-diagonal-arrow-alt.js"},{"name":"icon-diagonal-arrow","path":"./icons/icon-diagonal-arrow.js"},{"name":"icon-diamond","path":"./icons/icon-diamond.js"},{"name":"icon-diamonds","path":"./icons/icon-diamonds.js"},{"name":"icon-dice","path":"./icons/icon-dice.js"},{"name":"icon-diploma-alt","legacy":true,"path":"./icons/icon-diploma-alt.js"},{"name":"icon-diploma","path":"./icons/icon-diploma.js"},{"name":"icon-directions-alt","path":"./icons/icon-directions-alt.js"},{"name":"icon-directions","path":"./icons/icon-directions.js"},{"name":"icon-disc","path":"./icons/icon-disc.js"},{"name":"icon-disk-image","path":"./icons/icon-disk-image.js"},{"name":"icon-display","path":"./icons/icon-display.js"},{"name":"icon-dna","path":"./icons/icon-dna.js"},{"name":"icon-dock-connector","path":"./icons/icon-dock-connector.js"},{"name":"icon-document-dashed-line","path":"./icons/icon-document-dashed-line.js"},{"name":"icon-document","path":"./icons/icon-document.js"},{"name":"icon-documents","path":"./icons/icon-documents.js"},{"name":"icon-donate","legacy":true,"path":"./icons/icon-donate.js"},{"name":"icon-door-open-alt","legacy":true,"path":"./icons/icon-door-open-alt.js"},{"name":"icon-door-open","path":"./icons/icon-door-open.js"},{"name":"icon-download-alt","path":"./icons/icon-download-alt.js"},{"name":"icon-download","path":"./icons/icon-download.js"},{"name":"icon-drop","path":"./icons/icon-drop.js"},{"name":"icon-eco","path":"./icons/icon-eco.js"},{"name":"icon-economy","legacy":true,"path":"./icons/icon-economy.js"},{"name":"icon-edit","path":"./icons/icon-edit.js"},{"name":"icon-employee","legacy":true,"path":"./icons/icon-employee.js"},{"name":"icon-energy-saving-bulb","path":"./icons/icon-energy-saving-bulb.js"},{"name":"icon-enter","path":"./icons/icon-enter.js"},{"name":"icon-equalizer","path":"./icons/icon-equalizer.js"},{"name":"icon-escape","path":"./icons/icon-escape.js"},{"name":"icon-ethernet","path":"./icons/icon-ethernet.js"},{"name":"icon-eye","path":"./icons/icon-eye.js"},{"name":"icon-facebook-like","path":"./icons/icon-facebook-like.js"},{"name":"icon-factory","path":"./icons/icon-factory.js"},{"name":"icon-favorite","path":"./icons/icon-favorite.js"},{"name":"icon-file-cabinet","path":"./icons/icon-file-cabinet.js"},{"name":"icon-files","path":"./icons/icon-files.js"},{"name":"icon-filter","path":"./icons/icon-filter.js"},{"name":"icon-fingerprint","path":"./icons/icon-fingerprint.js"},{"name":"icon-fire","path":"./icons/icon-fire.js"},{"name":"icon-firewire","legacy":true,"path":"./icons/icon-firewire.js"},{"name":"icon-flag-alt","path":"./icons/icon-flag-alt.js"},{"name":"icon-flag","path":"./icons/icon-flag.js"},{"name":"icon-flash","path":"./icons/icon-flash.js"},{"name":"icon-flashlight","path":"./icons/icon-flashlight.js"},{"name":"icon-flowerpot","path":"./icons/icon-flowerpot.js"},{"name":"icon-folder","path":"./icons/icon-folder.js"},{"name":"icon-folders","path":"./icons/icon-folders.js"},{"name":"icon-font","path":"./icons/icon-font.js"},{"name":"icon-food","path":"./icons/icon-food.js"},{"name":"icon-footprints","path":"./icons/icon-footprints.js"},{"name":"icon-forking","path":"./icons/icon-forking.js"},{"name":"icon-frame-alt","legacy":true,"path":"./icons/icon-frame-alt.js"},{"name":"icon-frame","path":"./icons/icon-frame.js"},{"name":"icon-fullscreen-alt","path":"./icons/icon-fullscreen-alt.js"},{"name":"icon-fullscreen","path":"./icons/icon-fullscreen.js"},{"name":"icon-game","path":"./icons/icon-game.js"},{"name":"icon-geometry","legacy":true,"path":"./icons/icon-geometry.js"},{"name":"icon-gift","path":"./icons/icon-gift.js"},{"name":"icon-glasses","path":"./icons/icon-glasses.js"},{"name":"icon-globe-alt","path":"./icons/icon-globe-alt.js"},{"name":"icon-globe-asia","legacy":true,"path":"./icons/icon-globe-asia.js"},{"name":"icon-globe-europe-africa","legacy":true,"path":"./icons/icon-globe-europe-africa.js"},{"name":"icon-globe-inverted-america","legacy":true,"path":"./icons/icon-globe-inverted-america.js"},{"name":"icon-globe-inverted-asia","legacy":true,"path":"./icons/icon-globe-inverted-asia.js"},{"name":"icon-globe-inverted-europe-africa","legacy":true,"path":"./icons/icon-globe-inverted-europe-africa.js"},{"name":"icon-globe","path":"./icons/icon-globe.js"},{"name":"icon-gps","path":"./icons/icon-gps.js"},{"name":"icon-graduate","path":"./icons/icon-graduate.js"},{"name":"icon-grid","path":"./icons/icon-grid.js"},{"name":"icon-hammer","path":"./icons/icon-hammer.js"},{"name":"icon-hand-active-alt","legacy":true,"path":"./icons/icon-hand-active-alt.js"},{"name":"icon-hand-active","path":"./icons/icon-hand-active.js"},{"name":"icon-hand-pointer-alt","legacy":true,"path":"./icons/icon-hand-pointer-alt.js"},{"name":"icon-hand-pointer","path":"./icons/icon-hand-pointer.js"},{"name":"icon-handshake","path":"./icons/icon-handshake.js"},{"name":"icon-handtool-alt","legacy":true,"path":"./icons/icon-handtool-alt.js"},{"name":"icon-handtool","path":"./icons/icon-handtool.js"},{"name":"icon-hard-drive-alt","legacy":true,"path":"./icons/icon-hard-drive-alt.js"},{"name":"icon-hard-drive","legacy":true,"path":"./icons/icon-hard-drive.js"},{"name":"icon-headphones","path":"./icons/icon-headphones.js"},{"name":"icon-headset","legacy":true,"path":"./icons/icon-headset.js"},{"name":"icon-hearts","path":"./icons/icon-hearts.js"},{"name":"icon-height","path":"./icons/icon-height.js"},{"name":"icon-help-alt","path":"./icons/icon-help-alt.js"},{"name":"icon-help","path":"./icons/icon-help.js"},{"name":"icon-home","path":"./icons/icon-home.js"},{"name":"icon-hourglass","path":"./icons/icon-hourglass.js"},{"name":"icon-imac","legacy":true,"path":"./icons/icon-imac.js"},{"name":"icon-inbox-full","legacy":true,"path":"./icons/icon-inbox-full.js"},{"name":"icon-inbox","path":"./icons/icon-inbox.js"},{"name":"icon-indent","path":"./icons/icon-indent.js"},{"name":"icon-infinity","path":"./icons/icon-infinity.js"},{"name":"icon-info","path":"./icons/icon-info.js"},{"name":"icon-invoice","legacy":true,"path":"./icons/icon-invoice.js"},{"name":"icon-ipad","legacy":true,"path":"./icons/icon-ipad.js"},{"name":"icon-iphone","legacy":true,"path":"./icons/icon-iphone.js"},{"name":"icon-item-arrangement","legacy":true,"path":"./icons/icon-item-arrangement.js"},{"name":"icon-junk","path":"./icons/icon-junk.js"},{"name":"icon-key","path":"./icons/icon-key.js"},{"name":"icon-keyboard","path":"./icons/icon-keyboard.js"},{"name":"icon-lab","path":"./icons/icon-lab.js"},{"name":"icon-laptop","path":"./icons/icon-laptop.js"},{"name":"icon-layers-alt","legacy":true,"path":"./icons/icon-layers-alt.js"},{"name":"icon-layers","path":"./icons/icon-layers.js"},{"name":"icon-layout","path":"./icons/icon-layout.js"},{"name":"icon-left-double-arrow","path":"./icons/icon-left-double-arrow.js"},{"name":"icon-legal","path":"./icons/icon-legal.js"},{"name":"icon-lense","legacy":true,"path":"./icons/icon-lense.js"},{"name":"icon-library","path":"./icons/icon-library.js"},{"name":"icon-light-down","path":"./icons/icon-light-down.js"},{"name":"icon-light-up","path":"./icons/icon-light-up.js"},{"name":"icon-lightning","path":"./icons/icon-lightning.js"},{"name":"icon-link","path":"./icons/icon-link.js"},{"name":"icon-list","path":"./icons/icon-list.js"},{"name":"icon-load","legacy":true,"path":"./icons/icon-load.js"},{"name":"icon-loading","legacy":true,"path":"./icons/icon-loading.js"},{"name":"icon-location-nearby","path":"./icons/icon-location-nearby.js"},{"name":"icon-lock","path":"./icons/icon-lock.js"},{"name":"icon-log-out","path":"./icons/icon-log-out.js"},{"name":"icon-logout","legacy":true,"path":"./icons/icon-logout.js"},{"name":"icon-loupe","legacy":true,"path":"./icons/icon-loupe.js"},{"name":"icon-magnet","path":"./icons/icon-magnet.js"},{"name":"icon-mailbox","path":"./icons/icon-mailbox.js"},{"name":"icon-map-alt","path":"./icons/icon-map-alt.js"},{"name":"icon-map-location","legacy":true,"path":"./icons/icon-map-location.js"},{"name":"icon-map-marker","path":"./icons/icon-map-marker.js"},{"name":"icon-map","path":"./icons/icon-map.js"},{"name":"icon-medal","path":"./icons/icon-medal.js"},{"name":"icon-medical-emergency","path":"./icons/icon-medical-emergency.js"},{"name":"icon-medicine","path":"./icons/icon-medicine.js"},{"name":"icon-meeting","legacy":true,"path":"./icons/icon-meeting.js"},{"name":"icon-megaphone","path":"./icons/icon-megaphone.js"},{"name":"icon-merge","path":"./icons/icon-merge.js"},{"name":"icon-message-open","path":"./icons/icon-message-open.js"},{"name":"icon-message-unopened","legacy":true,"path":"./icons/icon-message-unopened.js"},{"name":"icon-message","path":"./icons/icon-message.js"},{"name":"icon-microscope","path":"./icons/icon-microscope.js"},{"name":"icon-mindmap","legacy":true,"path":"./icons/icon-mindmap.js"},{"name":"icon-mobile","path":"./icons/icon-mobile.js"},{"name":"icon-mountain","path":"./icons/icon-mountain.js"},{"name":"icon-mouse-cursor","path":"./icons/icon-mouse-cursor.js"},{"name":"icon-mouse","path":"./icons/icon-mouse.js"},{"name":"icon-movie-alt","path":"./icons/icon-movie-alt.js"},{"name":"icon-movie","path":"./icons/icon-movie.js"},{"name":"icon-multiple-credit-cards","path":"./icons/icon-multiple-credit-cards.js"},{"name":"icon-music","path":"./icons/icon-music.js"},{"name":"icon-name-badge","legacy":true,"path":"./icons/icon-name-badge.js"},{"name":"icon-navigation-bottom","legacy":true,"path":"./icons/icon-navigation-bottom.js"},{"name":"icon-navigation-down","legacy":true,"path":"./icons/icon-navigation-down.js"},{"name":"icon-navigation-first","legacy":true,"path":"./icons/icon-navigation-first.js"},{"name":"icon-navigation-horizontal","legacy":true,"path":"./icons/icon-navigation-horizontal.js"},{"name":"icon-navigation-last","legacy":true,"path":"./icons/icon-navigation-last.js"},{"name":"icon-navigation-left","legacy":true,"path":"./icons/icon-navigation-left.js"},{"name":"icon-navigation-right","legacy":true,"path":"./icons/icon-navigation-right.js"},{"name":"icon-navigation-road","legacy":true,"path":"./icons/icon-navigation-road.js"},{"name":"icon-navigation-up","legacy":true,"path":"./icons/icon-navigation-up.js"},{"name":"icon-navigation-vertical","legacy":true,"path":"./icons/icon-navigation-vertical.js"},{"name":"icon-navigation","legacy":true,"path":"./icons/icon-navigation.js"},{"name":"icon-navigational-arrow","path":"./icons/icon-navigational-arrow.js"},{"name":"icon-network-alt","path":"./icons/icon-network-alt.js"},{"name":"icon-newspaper-alt","legacy":true,"path":"./icons/icon-newspaper-alt.js"},{"name":"icon-newspaper","path":"./icons/icon-newspaper.js"},{"name":"icon-next-media","legacy":true,"path":"./icons/icon-next-media.js"},{"name":"icon-next","legacy":true,"path":"./icons/icon-next.js"},{"name":"icon-nodes","legacy":true,"path":"./icons/icon-nodes.js"},{"name":"icon-notepad-alt","legacy":true,"path":"./icons/icon-notepad-alt.js"},{"name":"icon-notepad","path":"./icons/icon-notepad.js"},{"name":"icon-old-key","path":"./icons/icon-old-key.js"},{"name":"icon-old-phone","legacy":true,"path":"./icons/icon-old-phone.js"},{"name":"icon-operator","path":"./icons/icon-operator.js"},{"name":"icon-ordered-list","path":"./icons/icon-ordered-list.js"},{"name":"icon-out","path":"./icons/icon-out.js"},{"name":"icon-outbox","legacy":true,"path":"./icons/icon-outbox.js"},{"name":"icon-outdent","path":"./icons/icon-outdent.js"},{"name":"icon-page-add","path":"./icons/icon-page-add.js"},{"name":"icon-page-down","path":"./icons/icon-page-down.js"},{"name":"icon-page-remove","path":"./icons/icon-page-remove.js"},{"name":"icon-page-restricted","path":"./icons/icon-page-restricted.js"},{"name":"icon-page-up","path":"./icons/icon-page-up.js"},{"name":"icon-paint-roller","legacy":true,"path":"./icons/icon-paint-roller.js"},{"name":"icon-palette","path":"./icons/icon-palette.js"},{"name":"icon-panel-show","path":"./icons/icon-panel-show.js"},{"name":"icon-pannel-close","path":"./icons/icon-pannel-close.js"},{"name":"icon-paper-bag","legacy":true,"path":"./icons/icon-paper-bag.js"},{"name":"icon-paper-plane-alt","path":"./icons/icon-paper-plane-alt.js"},{"name":"icon-paper-plane","path":"./icons/icon-paper-plane.js"},{"name":"icon-partly-cloudy","path":"./icons/icon-partly-cloudy.js"},{"name":"icon-paste-in","legacy":true,"path":"./icons/icon-paste-in.js"},{"name":"icon-pause","path":"./icons/icon-pause.js"},{"name":"icon-pc","legacy":true,"path":"./icons/icon-pc.js"},{"name":"icon-people-alt-2","legacy":true,"path":"./icons/icon-people-alt-2.js"},{"name":"icon-people-alt","legacy":true,"path":"./icons/icon-people-alt.js"},{"name":"icon-people-female","legacy":true,"path":"./icons/icon-people-female.js"},{"name":"icon-people","path":"./icons/icon-people.js"},{"name":"icon-phone-ring","path":"./icons/icon-phone-ring.js"},{"name":"icon-phone","path":"./icons/icon-phone.js"},{"name":"icon-photo-album","path":"./icons/icon-photo-album.js"},{"name":"icon-picture","path":"./icons/icon-picture.js"},{"name":"icon-pictures-alt-2","path":"./icons/icon-pictures-alt-2.js"},{"name":"icon-pictures-alt","legacy":true,"path":"./icons/icon-pictures-alt.js"},{"name":"icon-pictures","path":"./icons/icon-pictures.js"},{"name":"icon-pie-chart","path":"./icons/icon-pie-chart.js"},{"name":"icon-piggy-bank","path":"./icons/icon-piggy-bank.js"},{"name":"icon-pin-location","path":"./icons/icon-pin-location.js"},{"name":"icon-plane","path":"./icons/icon-plane.js"},{"name":"icon-planet","legacy":true,"path":"./icons/icon-planet.js"},{"name":"icon-play","path":"./icons/icon-play.js"},{"name":"icon-playing-cards","legacy":true,"path":"./icons/icon-playing-cards.js"},{"name":"icon-playlist","path":"./icons/icon-playlist.js"},{"name":"icon-plugin","path":"./icons/icon-plugin.js"},{"name":"icon-podcast","path":"./icons/icon-podcast.js"},{"name":"icon-poll","legacy":true,"path":"./icons/icon-poll.js"},{"name":"icon-post-it","path":"./icons/icon-post-it.js"},{"name":"icon-power-outlet","legacy":true,"path":"./icons/icon-power-outlet.js"},{"name":"icon-power","path":"./icons/icon-power.js"},{"name":"icon-presentation","path":"./icons/icon-presentation.js"},{"name":"icon-previous-media","path":"./icons/icon-previous-media.js"},{"name":"icon-previous","path":"./icons/icon-previous.js"},{"name":"icon-price-dollar","legacy":true,"path":"./icons/icon-price-dollar.js"},{"name":"icon-price-euro","legacy":true,"path":"./icons/icon-price-euro.js"},{"name":"icon-price-pound","legacy":true,"path":"./icons/icon-price-pound.js"},{"name":"icon-print","path":"./icons/icon-print.js"},{"name":"icon-printer-alt","legacy":true,"path":"./icons/icon-printer-alt.js"},{"name":"icon-projector","path":"./icons/icon-projector.js"},{"name":"icon-pulse","path":"./icons/icon-pulse.js"},{"name":"icon-pushpin","path":"./icons/icon-pushpin.js"},{"name":"icon-qr-code","path":"./icons/icon-qr-code.js"},{"name":"icon-quote","path":"./icons/icon-quote.js"},{"name":"icon-radio-alt","path":"./icons/icon-radio-alt.js"},{"name":"icon-radio-receiver","path":"./icons/icon-radio-receiver.js"},{"name":"icon-radio","path":"./icons/icon-radio.js"},{"name":"icon-rain","path":"./icons/icon-rain.js"},{"name":"icon-rate","legacy":true,"path":"./icons/icon-rate.js"},{"name":"icon-re-post","path":"./icons/icon-re-post.js"},{"name":"icon-readonly","legacy":true,"path":"./icons/icon-readonly.js"},{"name":"icon-receipt-alt","path":"./icons/icon-receipt-alt.js"},{"name":"icon-reception","path":"./icons/icon-reception.js"},{"name":"icon-record","legacy":true,"path":"./icons/icon-record.js"},{"name":"icon-redo","path":"./icons/icon-redo.js"},{"name":"icon-refresh","path":"./icons/icon-refresh.js"},{"name":"icon-remote","legacy":true,"path":"./icons/icon-remote.js"},{"name":"icon-remove","path":"./icons/icon-remove.js"},{"name":"icon-repeat-one","path":"./icons/icon-repeat-one.js"},{"name":"icon-repeat","path":"./icons/icon-repeat.js"},{"name":"icon-reply-arrow","path":"./icons/icon-reply-arrow.js"},{"name":"icon-return-to-top","legacy":true,"path":"./icons/icon-return-to-top.js"},{"name":"icon-right-double-arrow","legacy":true,"path":"./icons/icon-right-double-arrow.js"},{"name":"icon-roadsign","legacy":true,"path":"./icons/icon-roadsign.js"},{"name":"icon-rocket","path":"./icons/icon-rocket.js"},{"name":"icon-rss","path":"./icons/icon-rss.js"},{"name":"icon-ruler-alt","path":"./icons/icon-ruler-alt.js"},{"name":"icon-ruler","path":"./icons/icon-ruler.js"},{"name":"icon-satellite-dish","path":"./icons/icon-satellite-dish.js"},{"name":"icon-save","path":"./icons/icon-save.js"},{"name":"icon-scan","path":"./icons/icon-scan.js"},{"name":"icon-school","path":"./icons/icon-school.js"},{"name":"icon-screensharing","path":"./icons/icon-screensharing.js"},{"name":"icon-script-alt","legacy":true,"path":"./icons/icon-script-alt.js"},{"name":"icon-script","path":"./icons/icon-script.js"},{"name":"icon-scull","path":"./icons/icon-scull.js"},{"name":"icon-search","path":"./icons/icon-search.js"},{"name":"icon-sensor","path":"./icons/icon-sensor.js"},{"name":"icon-server-alt","legacy":true,"path":"./icons/icon-server-alt.js"},{"name":"icon-server","path":"./icons/icon-server.js"},{"name":"icon-settings-alt","legacy":true,"path":"./icons/icon-settings-alt.js"},{"name":"icon-settings","path":"./icons/icon-settings.js"},{"name":"icon-share-alt","path":"./icons/icon-share-alt.js"},{"name":"icon-share","path":"./icons/icon-share.js"},{"name":"icon-sharing-iphone","path":"./icons/icon-sharing-iphone.js"},{"name":"icon-shield","path":"./icons/icon-shield.js"},{"name":"icon-shift","path":"./icons/icon-shift.js"},{"name":"icon-shipping-box","path":"./icons/icon-shipping-box.js"},{"name":"icon-shipping","path":"./icons/icon-shipping.js"},{"name":"icon-shoe","path":"./icons/icon-shoe.js"},{"name":"icon-shopping-basket-alt-2","legacy":true,"path":"./icons/icon-shopping-basket-alt-2.js"},{"name":"icon-shopping-basket-alt","path":"./icons/icon-shopping-basket-alt.js"},{"name":"icon-shopping-basket","path":"./icons/icon-shopping-basket.js"},{"name":"icon-shuffle","path":"./icons/icon-shuffle.js"},{"name":"icon-sience","path":"./icons/icon-sience.js"},{"name":"icon-single-note","path":"./icons/icon-single-note.js"},{"name":"icon-sitemap","legacy":true,"path":"./icons/icon-sitemap.js"},{"name":"icon-sleep","path":"./icons/icon-sleep.js"},{"name":"icon-slideshow","legacy":true,"path":"./icons/icon-slideshow.js"},{"name":"icon-smiley-inverted","legacy":true,"path":"./icons/icon-smiley-inverted.js"},{"name":"icon-smiley","path":"./icons/icon-smiley.js"},{"name":"icon-snow","path":"./icons/icon-snow.js"},{"name":"icon-sound-low","path":"./icons/icon-sound-low.js"},{"name":"icon-sound-medium","legacy":true,"path":"./icons/icon-sound-medium.js"},{"name":"icon-sound-off","path":"./icons/icon-sound-off.js"},{"name":"icon-sound-waves","path":"./icons/icon-sound-waves.js"},{"name":"icon-sound","path":"./icons/icon-sound.js"},{"name":"icon-spades","path":"./icons/icon-spades.js"},{"name":"icon-speaker","path":"./icons/icon-speaker.js"},{"name":"icon-speed-gauge","path":"./icons/icon-speed-gauge.js"},{"name":"icon-split","path":"./icons/icon-split.js"},{"name":"icon-sprout","path":"./icons/icon-sprout.js"},{"name":"icon-squiggly-line","legacy":true,"path":"./icons/icon-squiggly-line.js"},{"name":"icon-ssd","legacy":true,"path":"./icons/icon-ssd.js"},{"name":"icon-stacked-disks","legacy":true,"path":"./icons/icon-stacked-disks.js"},{"name":"icon-stamp","legacy":true,"path":"./icons/icon-stamp.js"},{"name":"icon-stop-alt","path":"./icons/icon-stop-alt.js"},{"name":"icon-stop-hand","legacy":true,"path":"./icons/icon-stop-hand.js"},{"name":"icon-stop","path":"./icons/icon-stop.js"},{"name":"icon-store","path":"./icons/icon-store.js"},{"name":"icon-stream","legacy":true,"path":"./icons/icon-stream.js"},{"name":"icon-sunny","path":"./icons/icon-sunny.js"},{"name":"icon-sweatshirt","legacy":true,"path":"./icons/icon-sweatshirt.js"},{"name":"icon-sync","path":"./icons/icon-sync.js"},{"name":"icon-t-shirt","path":"./icons/icon-t-shirt.js"},{"name":"icon-tab-key","path":"./icons/icon-tab-key.js"},{"name":"icon-tag","path":"./icons/icon-tag.js"},{"name":"icon-tags","path":"./icons/icon-tags.js"},{"name":"icon-takeaway-cup","legacy":true,"path":"./icons/icon-takeaway-cup.js"},{"name":"icon-target","path":"./icons/icon-target.js"},{"name":"icon-temperatrure-alt","path":"./icons/icon-temperatrure-alt.js"},{"name":"icon-temperature","path":"./icons/icon-temperature.js"},{"name":"icon-terminal","path":"./icons/icon-terminal.js"},{"name":"icon-theater","path":"./icons/icon-theater.js"},{"name":"icon-thumb-down","path":"./icons/icon-thumb-down.js"},{"name":"icon-thumb-up","path":"./icons/icon-thumb-up.js"},{"name":"icon-thumbnail-list","path":"./icons/icon-thumbnail-list.js"},{"name":"icon-thumbnails-small","path":"./icons/icon-thumbnails-small.js"},{"name":"icon-thumbnails","path":"./icons/icon-thumbnails.js"},{"name":"icon-ticket","path":"./icons/icon-ticket.js"},{"name":"icon-time","path":"./icons/icon-time.js"},{"name":"icon-timer","path":"./icons/icon-timer.js"},{"name":"icon-tools","legacy":true,"path":"./icons/icon-tools.js"},{"name":"icon-top","legacy":true,"path":"./icons/icon-top.js"},{"name":"icon-traffic-alt","legacy":true,"path":"./icons/icon-traffic-alt.js"},{"name":"icon-trafic","path":"./icons/icon-trafic.js"},{"name":"icon-train","path":"./icons/icon-train.js"},{"name":"icon-trash-alt-2","legacy":true,"path":"./icons/icon-trash-alt-2.js"},{"name":"icon-trash-alt","legacy":true,"path":"./icons/icon-trash-alt.js"},{"name":"icon-trash","path":"./icons/icon-trash.js"},{"name":"icon-tree","path":"./icons/icon-tree.js"},{"name":"icon-trophy","path":"./icons/icon-trophy.js"},{"name":"icon-truck","path":"./icons/icon-truck.js"},{"name":"icon-tv-old","path":"./icons/icon-tv-old.js"},{"name":"icon-tv","path":"./icons/icon-tv.js"},{"name":"icon-umb-content","legacy":true,"path":"./icons/icon-umb-content.js"},{"name":"icon-umb-developer","legacy":true,"path":"./icons/icon-umb-developer.js"},{"name":"icon-umb-media","legacy":true,"path":"./icons/icon-umb-media.js"},{"name":"icon-umb-settings","legacy":true,"path":"./icons/icon-umb-settings.js"},{"name":"icon-umb-users","legacy":true,"path":"./icons/icon-umb-users.js"},{"name":"icon-umbrella","path":"./icons/icon-umbrella.js"},{"name":"icon-undo","path":"./icons/icon-undo.js"},{"name":"icon-unlocked","path":"./icons/icon-unlocked.js"},{"name":"icon-untitled","legacy":true,"path":"./icons/icon-untitled.js"},{"name":"icon-usb-connector","legacy":true,"path":"./icons/icon-usb-connector.js"},{"name":"icon-usb","path":"./icons/icon-usb.js"},{"name":"icon-user-female","legacy":true,"path":"./icons/icon-user-female.js"},{"name":"icon-user-females-alt","legacy":true,"path":"./icons/icon-user-females-alt.js"},{"name":"icon-user-females","legacy":true,"path":"./icons/icon-user-females.js"},{"name":"icon-user-glasses","legacy":true,"path":"./icons/icon-user-glasses.js"},{"name":"icon-user","path":"./icons/icon-user.js"},{"name":"icon-users-alt","legacy":true,"path":"./icons/icon-users-alt.js"},{"name":"icon-users","path":"./icons/icon-users.js"},{"name":"icon-utilities","path":"./icons/icon-utilities.js"},{"name":"icon-vcard","path":"./icons/icon-vcard.js"},{"name":"icon-video","path":"./icons/icon-video.js"},{"name":"icon-voice","path":"./icons/icon-voice.js"},{"name":"icon-wall-plug","path":"./icons/icon-wall-plug.js"},{"name":"icon-wallet","path":"./icons/icon-wallet.js"},{"name":"icon-wand","path":"./icons/icon-wand.js"},{"name":"icon-webhook","path":"./icons/icon-webhook.js"},{"name":"icon-weight","path":"./icons/icon-weight.js"},{"name":"icon-width","path":"./icons/icon-width.js"},{"name":"icon-wifi","path":"./icons/icon-wifi.js"},{"name":"icon-window-popin","path":"./icons/icon-window-popin.js"},{"name":"icon-window-sizes","path":"./icons/icon-window-sizes.js"},{"name":"icon-wine-glass","path":"./icons/icon-wine-glass.js"},{"name":"icon-wrench","path":"./icons/icon-wrench.js"},{"name":"icon-wrong","path":"./icons/icon-wrong.js"},{"name":"icon-zip","path":"./icons/icon-zip.js"},{"name":"icon-zom-out","legacy":true,"path":"./icons/icon-zom-out.js"},{"name":"icon-zoom-in","path":"./icons/icon-zoom-in.js"},{"name":"icon-zoom-out","path":"./icons/icon-zoom-out.js"},{"name":"icon-star","path":"./icons/icon-star.js"},{"name":"icon-database","path":"./icons/icon-database.js"},{"name":"icon-azure","path":"./icons/icon-azure.js"},{"name":"icon-facebook","path":"./icons/icon-facebook.js"},{"name":"icon-gitbook","path":"./icons/icon-gitbook.js"},{"name":"icon-github","path":"./icons/icon-github.js"},{"name":"icon-gitlab","path":"./icons/icon-gitlab.js"},{"name":"icon-google","path":"./icons/icon-google.js"},{"name":"icon-linkedin","path":"./icons/icon-linkedin.js"},{"name":"icon-mastodon","path":"./icons/icon-mastodon.js"},{"name":"icon-microsoft","path":"./icons/icon-microsoft.js"},{"name":"icon-twitter-x","path":"./icons/icon-twitter-x.js"},{"name":"icon-umbraco","path":"./icons/icon-umbraco.js"},{"name":"icon-application-error","legacy":true,"path":"./icons/icon-application-error.js"},{"name":"icon-art-easel","legacy":true,"path":"./icons/icon-art-easel.js"},{"name":"icon-article","legacy":true,"path":"./icons/icon-article.js"},{"name":"icon-auction-hammer","legacy":true,"path":"./icons/icon-auction-hammer.js"},{"name":"icon-baby-stroller","legacy":true,"path":"./icons/icon-baby-stroller.js"},{"name":"icon-badge-count","legacy":true,"path":"./icons/icon-badge-count.js"},{"name":"icon-ball","legacy":true,"path":"./icons/icon-ball.js"},{"name":"icon-band-aid","legacy":true,"path":"./icons/icon-band-aid.js"},{"name":"icon-bill-dollar","legacy":true,"path":"./icons/icon-bill-dollar.js"},{"name":"icon-bill-euro","legacy":true,"path":"./icons/icon-bill-euro.js"},{"name":"icon-bill-pound","legacy":true,"path":"./icons/icon-bill-pound.js"},{"name":"icon-bill-yen","legacy":true,"path":"./icons/icon-bill-yen.js"},{"name":"icon-bill","legacy":true,"path":"./icons/icon-bill.js"},{"name":"icon-billboard","legacy":true,"path":"./icons/icon-billboard.js"},{"name":"icon-bills-dollar","legacy":true,"path":"./icons/icon-bills-dollar.js"},{"name":"icon-bills-euro","legacy":true,"path":"./icons/icon-bills-euro.js"},{"name":"icon-bills-pound","legacy":true,"path":"./icons/icon-bills-pound.js"},{"name":"icon-bills-yen","legacy":true,"path":"./icons/icon-bills-yen.js"},{"name":"icon-bills","legacy":true,"path":"./icons/icon-bills.js"},{"name":"icon-binoculars","legacy":true,"path":"./icons/icon-binoculars.js"},{"name":"icon-blueprint","legacy":true,"path":"./icons/icon-blueprint.js"},{"name":"icon-bomb","legacy":true,"path":"./icons/icon-bomb.js"},{"name":"icon-cash-register","legacy":true,"path":"./icons/icon-cash-register.js"},{"name":"icon-checkbox-dotted-active","legacy":true,"path":"./icons/icon-checkbox-dotted-active.js"},{"name":"icon-chess","legacy":true,"path":"./icons/icon-chess.js"},{"name":"icon-circus","legacy":true,"path":"./icons/icon-circus.js"},{"name":"icon-clothes-hanger","legacy":true,"path":"./icons/icon-clothes-hanger.js"},{"name":"icon-coin-dollar","legacy":true,"path":"./icons/icon-coin-dollar.js"},{"name":"icon-coin-pound","legacy":true,"path":"./icons/icon-coin-pound.js"},{"name":"icon-coin","legacy":true,"path":"./icons/icon-coin.js"},{"name":"icon-coins-dollar-alt","legacy":true,"path":"./icons/icon-coins-dollar-alt.js"},{"name":"icon-coins-dollar","legacy":true,"path":"./icons/icon-coins-dollar.js"},{"name":"icon-coins-euro-alt","legacy":true,"path":"./icons/icon-coins-euro-alt.js"},{"name":"icon-coins-euro","legacy":true,"path":"./icons/icon-coins-euro.js"},{"name":"icon-coins-pound-alt","legacy":true,"path":"./icons/icon-coins-pound-alt.js"},{"name":"icon-coins-pound","legacy":true,"path":"./icons/icon-coins-pound.js"},{"name":"icon-coins-yen-alt","legacy":true,"path":"./icons/icon-coins-yen-alt.js"},{"name":"icon-coins-yen","legacy":true,"path":"./icons/icon-coins-yen.js"},{"name":"icon-comb","legacy":true,"path":"./icons/icon-comb.js"},{"name":"icon-desk","legacy":true,"path":"./icons/icon-desk.js"},{"name":"icon-dollar-bag","legacy":true,"path":"./icons/icon-dollar-bag.js"},{"name":"icon-eject","legacy":true,"path":"./icons/icon-eject.js"},{"name":"icon-euro-bag","legacy":true,"path":"./icons/icon-euro-bag.js"},{"name":"icon-exit-fullscreen","legacy":true,"path":"./icons/icon-exit-fullscreen.js"},{"name":"icon-female-symbol","legacy":true,"path":"./icons/icon-female-symbol.js"},{"name":"icon-filter-arrows","legacy":true,"path":"./icons/icon-filter-arrows.js"},{"name":"icon-firewall","legacy":true,"path":"./icons/icon-firewall.js"},{"name":"icon-folder-open","legacy":true,"path":"./icons/icon-folder-open.js"},{"name":"icon-folder-outline","legacy":true,"path":"./icons/icon-folder-outline.js"},{"name":"icon-handprint","legacy":true,"path":"./icons/icon-handprint.js"},{"name":"icon-hat","legacy":true,"path":"./icons/icon-hat.js"},{"name":"icon-hd","legacy":true,"path":"./icons/icon-hd.js"},{"name":"icon-inactive-line","legacy":true,"path":"./icons/icon-inactive-line.js"},{"name":"icon-keychain","legacy":true,"path":"./icons/icon-keychain.js"},{"name":"icon-keyhole","legacy":true,"path":"./icons/icon-keyhole.js"},{"name":"icon-lightbulb-active","legacy":true,"path":"./icons/icon-lightbulb-active.js"},{"name":"icon-lightbulb","legacy":true,"path":"./icons/icon-lightbulb.js"},{"name":"icon-linux-tux","legacy":true,"path":"./icons/icon-linux-tux.js"},{"name":"icon-locate","legacy":true,"path":"./icons/icon-locate.js"},{"name":"icon-location-near-me","legacy":true,"path":"./icons/icon-location-near-me.js"},{"name":"icon-male-and-female","legacy":true,"path":"./icons/icon-male-and-female.js"},{"name":"icon-male-symbol","legacy":true,"path":"./icons/icon-male-symbol.js"},{"name":"icon-molecular-network","legacy":true,"path":"./icons/icon-molecular-network.js"},{"name":"icon-molecular","legacy":true,"path":"./icons/icon-molecular.js"},{"name":"icon-multiple-windows","legacy":true,"path":"./icons/icon-multiple-windows.js"},{"name":"icon-navigation-top","legacy":true,"path":"./icons/icon-navigation-top.js"},{"name":"icon-os-x","legacy":true,"path":"./icons/icon-os-x.js"},{"name":"icon-pants","legacy":true,"path":"./icons/icon-pants.js"},{"name":"icon-parachute-drop","legacy":true,"path":"./icons/icon-parachute-drop.js"},{"name":"icon-parental-control","legacy":true,"path":"./icons/icon-parental-control.js"},{"name":"icon-path","legacy":true,"path":"./icons/icon-path.js"},{"name":"icon-piracy","legacy":true,"path":"./icons/icon-piracy.js"},{"name":"icon-poker-chip","legacy":true,"path":"./icons/icon-poker-chip.js"},{"name":"icon-pound-bag","legacy":true,"path":"./icons/icon-pound-bag.js"},{"name":"icon-price-yen","legacy":true,"path":"./icons/icon-price-yen.js"},{"name":"icon-receipt-dollar","legacy":true,"path":"./icons/icon-receipt-dollar.js"},{"name":"icon-receipt-euro","legacy":true,"path":"./icons/icon-receipt-euro.js"},{"name":"icon-receipt-pound","legacy":true,"path":"./icons/icon-receipt-pound.js"},{"name":"icon-receipt-yen","legacy":true,"path":"./icons/icon-receipt-yen.js"},{"name":"icon-resize","legacy":true,"path":"./icons/icon-resize.js"},{"name":"icon-road","legacy":true,"path":"./icons/icon-road.js"},{"name":"icon-safe","legacy":true,"path":"./icons/icon-safe.js"},{"name":"icon-safedial","legacy":true,"path":"./icons/icon-safedial.js"},{"name":"icon-sandbox-toys","legacy":true,"path":"./icons/icon-sandbox-toys.js"},{"name":"icon-security-camera","legacy":true,"path":"./icons/icon-security-camera.js"},{"name":"icon-settings-alt-2","legacy":true,"path":"./icons/icon-settings-alt-2.js"},{"name":"icon-share-alt-2","legacy":true,"path":"./icons/icon-share-alt-2.js"},{"name":"icon-shorts","legacy":true,"path":"./icons/icon-shorts.js"},{"name":"icon-simcard","legacy":true,"path":"./icons/icon-simcard.js"},{"name":"icon-split-alt","legacy":true,"path":"./icons/icon-split-alt.js"},{"name":"icon-tab","legacy":true,"path":"./icons/icon-tab.js"},{"name":"icon-tactics","legacy":true,"path":"./icons/icon-tactics.js"},{"name":"icon-theif","legacy":true,"path":"./icons/icon-theif.js"},{"name":"icon-thought-bubble","legacy":true,"path":"./icons/icon-thought-bubble.js"},{"name":"icon-twitter","legacy":true,"path":"./icons/icon-twitter.js"},{"name":"icon-umb-contour","legacy":true,"path":"./icons/icon-umb-contour.js"},{"name":"icon-umb-deploy","legacy":true,"path":"./icons/icon-umb-deploy.js"},{"name":"icon-umb-members","legacy":true,"path":"./icons/icon-umb-members.js"},{"name":"icon-universal","legacy":true,"path":"./icons/icon-universal.js"},{"name":"icon-war","legacy":true,"path":"./icons/icon-war.js"},{"name":"icon-windows","legacy":true,"path":"./icons/icon-windows.js"},{"name":"icon-yen-bag","legacy":true,"path":"./icons/icon-yen-bag.js"}] \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-twitter.svg b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-twitter.svg new file mode 100644 index 0000000000..d332ef9a31 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-twitter.svg @@ -0,0 +1,5 @@ + + Twitter + + diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-umbraco.svg b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-umbraco.svg index 0e121eebf3..1e69d80a4b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-umbraco.svg +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/svgs/icon-umbraco.svg @@ -1,3 +1,4 @@ - - - \ No newline at end of file + + + diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 14d59db61a..997c572bfd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -1,3 +1,4 @@ +import { manifests as authManifests } from './auth/manifests.js'; import { manifests as collectionManifests } from './collection/manifests.js'; import { manifests as contentTypeManifests } from './content-type/manifests.js'; import { manifests as cultureManifests } from './culture/manifests.js'; @@ -8,6 +9,7 @@ import { manifests as localizationManifests } from './localization/manifests.js' import { manifests as modalManifests } from './modal/common/manifests.js'; import { manifests as propertyActionManifests } from './property-action/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; +import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as sectionManifests } from './section/manifests.js'; import { manifests as serverFileSystemManifests } from './server-file-system/manifests.js'; import { manifests as settingsManifests } from './settings/manifests.js'; @@ -18,6 +20,7 @@ import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes, UmbBackofficeManifestKind } from './extension-registry/index.js'; export const manifests: Array = [ + ...authManifests, ...extensionManifests, ...cultureManifests, ...localizationManifests, @@ -34,4 +37,5 @@ export const manifests: Array = [ ...propertyActionManifests, ...serverFileSystemManifests, ...debugManifests, + ...recycleBinManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts index d923e06b0b..eb74e7deca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts @@ -12,6 +12,11 @@ export interface UmbModalConfig { key?: string; type?: UmbModalType; size?: UUIModalSidebarSize; + + /** + * Set the background property of the modal backdrop + */ + backdropBackground?: string; } export class UmbModalManagerContext extends UmbContextBase { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index a22c2dd674..05d6220b7a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -32,6 +32,7 @@ export class UmbModalContext; @@ -51,10 +52,12 @@ export class UmbModalContext { +export interface UmbPickerModalData { multiple?: boolean; - hideTreeRoot?: boolean; - filter?: (item: TreeItemType) => boolean; - pickableFilter?: (item: TreeItemType) => boolean; + hideTreeRoot?: boolean; // TODO: this should be moved to a tree picker modal data interface + filter?: (item: ItemType) => boolean; + pickableFilter?: (item: ItemType) => boolean; } export interface UmbPickerModalValue { selection: Array; @@ -11,3 +11,5 @@ export interface UmbPickerModalValue { export interface UmbTreePickerModalData extends UmbPickerModalData { treeAlias?: string; } + +export interface UmbTreePickerModalValue extends UmbPickerModalValue {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts index fe5de77170..a4602e52ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts @@ -1,10 +1,10 @@ -import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; +import { UmbLitElement } from '../../../lit-element/index.js'; +import { UMB_PROPERTY_CONTEXT } from '../../../property/property/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import type { UmbInputMultiUrlElement } from '@umbraco-cms/backoffice/components'; import type { UmbLinkPickerLink } from '@umbraco-cms/backoffice/modal'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts index b52179f9f7..1bf8e3c8d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts @@ -1,9 +1,8 @@ +import { umbExtensionsRegistry, type ManifestPropertyEditorUi } from '../../extension-registry/index.js'; import { UmbPropertyContext } from './property.context.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; -import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.kind.ts new file mode 100644 index 0000000000..de15a8826a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.kind.ts @@ -0,0 +1,21 @@ +import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../../entity-action/default/default.action.kind.js'; +import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: UmbBackofficeManifestKind = { + type: 'kind', + alias: 'Umb.Kind.EntityAction.RecycleBin.Empty', + matchKind: 'emptyRecycleBin', + matchType: 'entityAction', + manifest: { + ...UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST.manifest, + type: 'entityAction', + kind: 'emptyRecycleBin', + api: () => import('./empty-recycle-bin.action.js'), + weight: 100, + forEntityTypes: [], + meta: { + icon: 'icon-trash', + label: 'Empty Recycle Bin...', + }, + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.ts new file mode 100644 index 0000000000..acc0562252 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/empty-recycle-bin.action.ts @@ -0,0 +1,46 @@ +import { UmbEntityActionBase } from '../../../entity-action/entity-action-base.js'; +import type { UmbRecycleBinRepository } from '../../recycle-bin-repository.interface.js'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; +import { + createExtensionApiByAlias, + type MetaEntityActionEmptyRecycleBinKind, +} from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '@umbraco-cms/backoffice/tree'; + +/** + * Entity action for emptying the recycle bin. + * @export + * @class UmbEmptyRecycleBinEntityAction + * @extends {UmbEntityActionBase} + */ +export class UmbEmptyRecycleBinEntityAction extends UmbEntityActionBase { + /** + * Executes the action. + * @memberof UmbEmptyRecycleBinEntityAction + */ + async execute() { + await umbConfirmModal(this._host, { + headline: `Empty Recycle Bin`, + content: `When items are deleted from the recycle bin, they will be gone forever.`, + color: 'danger', + confirmLabel: 'Empty Recycle Bin', + }); + + const recycleBinRepository = await createExtensionApiByAlias( + this, + this.args.meta.recycleBinRepositoryAlias, + ); + await recycleBinRepository.requestEmpty(); + + const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbReloadTreeItemChildrenRequestEntityActionEvent({ + unique: this.args.unique, + entityType: this.args.entityType, + }); + + actionEventContext.dispatchEvent(event); + } +} + +export { UmbEmptyRecycleBinEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/manifests.ts new file mode 100644 index 0000000000..89241384bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/empty-recycle-bin/manifests.ts @@ -0,0 +1,3 @@ +import { manifest as emptyRecycleBinKindManifest } from './empty-recycle-bin.action.kind.js'; + +export const manifests = [emptyRecycleBinKindManifest]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/index.ts new file mode 100644 index 0000000000..df73603f45 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/index.ts @@ -0,0 +1,3 @@ +export { UmbTrashEntityAction } from './trash/trash.action.js'; +export { UmbRestoreFromRecycleBinEntityAction } from './restore-from-recycle-bin/restore-from-recycle-bin.action.js'; +export { UmbEmptyRecycleBinEntityAction } from './empty-recycle-bin/empty-recycle-bin.action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/manifests.ts new file mode 100644 index 0000000000..09766fef9c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/manifests.ts @@ -0,0 +1,11 @@ +import { manifest as kindManifest } from './restore-from-recycle-bin.action.kind.js'; + +export const manifests = [ + kindManifest, + { + type: 'modal', + alias: 'Umb.Modal.RecycleBin.Restore', + name: 'Restore From Recycle Bin Modal', + js: () => import('./modal/restore-from-recycle-bin-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/index.ts new file mode 100644 index 0000000000..b4d1511566 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/index.ts @@ -0,0 +1,2 @@ +export * from './restore-from-recycle-bin-modal.token.js'; +export * from './restore-from-recycle-bin-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.element.ts new file mode 100644 index 0000000000..6bcb071416 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.element.ts @@ -0,0 +1,192 @@ +import type { UmbRecycleBinRepository } from '../../../recycle-bin-repository.interface.js'; +import type { + UmbRestoreFromRecycleBinModalData, + UmbRestoreFromRecycleBinModalValue, +} from './restore-from-recycle-bin-modal.token.js'; +import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_MODAL_MANAGER_CONTEXT, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; + +const elementName = 'umb-restore-from-recycle-bin-modal'; + +@customElement(elementName) +export class UmbRestoreFromRecycleBinModalElement extends UmbModalBaseElement< + UmbRestoreFromRecycleBinModalData, + UmbRestoreFromRecycleBinModalValue +> { + @state() + _isAutomaticRestore = false; + + @state() + _restoreItem?: any; + + @state() + _destinationItem?: any; + + #recycleBinRepository?: UmbRecycleBinRepository; + + protected async firstUpdated(_changedProperties: PropertyValueMap | Map): Promise { + super.firstUpdated(_changedProperties); + if (!this.data?.unique) throw new Error('Cannot restore an item without a unique identifier.'); + + this._restoreItem = await this.#requestItem(this.data.unique); + const unique = await this.#requestAutomaticRestoreDestination(); + + if (unique !== undefined) { + this.setDestination(unique); + this._isAutomaticRestore = true; + } + } + + async setDestination(unique: string | null) { + // TODO: handle ROOT lookup. Currently, we can't look up the root in the item repository. + // This is a temp solution to show something in the UI. + if (unique === null) { + this._destinationItem = { + name: 'ROOT', + }; + + this.#setDestinationValue({ + unique: null, + entityType: 'unknown', + }); + } + + if (unique) { + this._destinationItem = await this.#requestItem(unique); + if (!this._destinationItem) throw new Error('Cant find destination item.'); + + this.#setDestinationValue({ + unique: this._destinationItem.unique, + entityType: this._destinationItem.entityType, + }); + } + } + + async #requestAutomaticRestoreDestination(): Promise { + if (!this.data?.unique) throw new Error('Cannot restore an item without a unique identifier.'); + if (!this.data?.recycleBinRepositoryAlias) + throw new Error('Cannot restore an item without a recycle bin repository alias.'); + + this.#recycleBinRepository = await createExtensionApiByAlias( + this, + this.data.recycleBinRepositoryAlias, + ); + + const { data } = await this.#recycleBinRepository.requestOriginalParent({ + unique: this.data.unique, + }); + + // only check for undefined because data can be null if the parent is the root + if (data !== undefined) { + return data ? data.unique : null; + } + + return undefined; + } + + async #requestItem(unique: string) { + if (!this.data?.itemRepositoryAlias) throw new Error('Cannot restore an item without an item repository alias.'); + + const itemRepository = await createExtensionApiByAlias>(this, this.data.itemRepositoryAlias); + const { data } = await itemRepository.requestItems([unique]); + + return data?.[0]; + } + + async #onSelectCustomDestination() { + if (!this.data?.pickerModal) throw new Error('Cannot select a destination without a picker modal.'); + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modal = modalManager.open(this, this.data.pickerModal, { + data: { + multiple: false, + }, + }); + + const { selection } = await modal.onSubmit(); + + if (selection.length > 0) { + const unique = selection[0]; + this.setDestination(unique); + } + } + + async #onSubmit() { + if (!this.value.destination) throw new Error('Cannot restore an item without a destination.'); + if (!this.#recycleBinRepository) throw new Error('Cannot restore an item without a destination.'); + if (!this.data?.unique) throw new Error('Cannot restore an item without a unique identifier.'); + + const { error } = await this.#recycleBinRepository.requestRestore({ + unique: this.data.unique, + destination: { unique: this.value.destination.unique }, + }); + + if (!error) { + this._submitModal(); + } + } + + #setDestinationValue(destination: UmbRestoreFromRecycleBinModalValue['destination']) { + this.updateValue({ destination }); + } + + render() { + return html` + + + ${this._isAutomaticRestore + ? html` Restore ${this._restoreItem?.name} to ${this._destinationItem?.name}` + : this.#renderCustomSelectDestination()} + + ${this.#renderActions()} + + `; + } + + #renderCustomSelectDestination() { + return html` +

Cannot automatically restore this item.

+

There is no location where this item can be automatically restored. You can select a new location below.

+
Restore to:
+ ${this._destinationItem + ? html` + + (this._destinationItem = undefined)} label="Remove" + >${this.localize.term('general_remove')} + + ` + : html` Select location`} + `; + } + + #renderActions() { + return html` + + + `; + } + + static styles = [ + UmbTextStyles, + css` + #placeholder { + width: 100%; + } + `, + ]; +} + +export default UmbRestoreFromRecycleBinModalElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbRestoreFromRecycleBinModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.token.ts new file mode 100644 index 0000000000..63b94f4451 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/modal/restore-from-recycle-bin-modal.token.ts @@ -0,0 +1,29 @@ +import type { UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbRestoreFromRecycleBinModalData { + unique: string; + entityType: string; + recycleBinRepositoryAlias: string; + itemRepositoryAlias: string; + pickerModal: UmbModalToken, UmbPickerModalValue> | string; +} + +export interface UmbRestoreFromRecycleBinModalValue { + destination: + | { + unique: string | null; + entityType: string; + } + | undefined; +} + +export const UMB_RESTORE_FROM_RECYCLE_BIN_MODAL = new UmbModalToken< + UmbRestoreFromRecycleBinModalData, + UmbRestoreFromRecycleBinModalValue +>('Umb.Modal.RecycleBin.Restore', { + modal: { + type: 'sidebar', + size: 'small', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.kind.ts new file mode 100644 index 0000000000..18edd68da0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.kind.ts @@ -0,0 +1,22 @@ +import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../../entity-action/default/default.action.kind.js'; +import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: UmbBackofficeManifestKind = { + type: 'kind', + alias: 'Umb.Kind.EntityAction.RecycleBin.Restore', + matchKind: 'restoreFromRecycleBin', + matchType: 'entityAction', + manifest: { + ...UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST.manifest, + type: 'entityAction', + kind: 'restoreFromRecycleBin', + api: () => import('./restore-from-recycle-bin.action.js'), + weight: 100, + forEntityTypes: [], + meta: { + icon: 'icon-undo', + label: 'Restore...', + pickerModal: '', + }, + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.ts new file mode 100644 index 0000000000..699c4ce03e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.ts @@ -0,0 +1,49 @@ +import { UmbEntityActionBase } from '../../../entity-action/entity-action-base.js'; +import { UMB_RESTORE_FROM_RECYCLE_BIN_MODAL } from './modal/restore-from-recycle-bin-modal.token.js'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import type { MetaEntityActionRestoreFromRecycleBinKind } from '@umbraco-cms/backoffice/extension-registry'; + +/** + * Entity action for restoring an item from the recycle bin. + * @export + * @class UmbRestoreFromRecycleBinEntityAction + * @extends {UmbEntityActionBase} + */ +export class UmbRestoreFromRecycleBinEntityAction extends UmbEntityActionBase { + /** + * Executes the action. + * @memberof UmbRestoreFromRecycleBinEntityAction + */ + async execute() { + if (!this.args.unique) throw new Error('Cannot restore an item without a unique identifier.'); + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modal = modalManager.open(this, UMB_RESTORE_FROM_RECYCLE_BIN_MODAL, { + data: { + unique: this.args.unique, + entityType: this.args.entityType, + recycleBinRepositoryAlias: this.args.meta.recycleBinRepositoryAlias, + itemRepositoryAlias: this.args.meta.itemRepositoryAlias, + pickerModal: this.args.meta.pickerModal, + }, + }); + + const { destination } = await modal.onSubmit(); + if (!destination) throw new Error('Cannot reload the structure without a destination.'); + + const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + unique: this.args.unique, + entityType: this.args.entityType, + }); + + actionEventContext.dispatchEvent(event); + + // TODO: reload destination + console.log(destination.unique, destination.entityType); + } +} + +export { UmbRestoreFromRecycleBinEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/manifests.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.kind.ts similarity index 72% rename from src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.kind.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.kind.ts index beb3103a7b..32aac57159 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/trash/trash.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.kind.ts @@ -1,4 +1,4 @@ -import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js'; +import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../../entity-action/default/default.action.kind.js'; import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifest: UmbBackofficeManifestKind = { @@ -11,13 +11,13 @@ export const manifest: UmbBackofficeManifestKind = { type: 'entityAction', kind: 'trash', api: () => import('./trash.action.js'), - weight: 900, + weight: 1150, forEntityTypes: [], meta: { icon: 'icon-trash', - label: 'Trash', + label: '#actions_trash', itemRepositoryAlias: '', - trashRepositoryAlias: '', + recycleBinRepositoryAlias: '', }, }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.ts new file mode 100644 index 0000000000..14e29f7f11 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/trash/trash.action.ts @@ -0,0 +1,59 @@ +import { UmbEntityActionBase } from '../../../entity-action/entity-action-base.js'; +import { UmbRequestReloadStructureForEntityEvent } from '../../../entity-action/request-reload-structure-for-entity.event.js'; +import type { UmbRecycleBinRepository } from '../../recycle-bin-repository.interface.js'; +import type { MetaEntityActionTrashKind } from '@umbraco-cms/backoffice/extension-registry'; +import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; +import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; +import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; + +/** + * Entity action for trashing an item. + * @export + * @class UmbTrashEntityAction + * @extends {UmbEntityActionBase} + */ +export class UmbTrashEntityAction extends UmbEntityActionBase { + /** + * Executes the action. + * @memberof UmbTrashEntityAction + */ + async execute() { + if (!this.args.unique) throw new Error('Cannot trash an item without a unique identifier.'); + + const itemRepository = await createExtensionApiByAlias>( + this, + this.args.meta.itemRepositoryAlias, + ); + + const { data } = await itemRepository.requestItems([this.args.unique]); + const item = data?.[0]; + if (!item) throw new Error('Item not found.'); + + // TODO: handle items with variants + await umbConfirmModal(this._host, { + headline: `Trash`, + content: `Are you sure you want to move ${item.name} to the recycle bin?`, + color: 'danger', + confirmLabel: 'Trash', + }); + + const recycleBinRepository = await createExtensionApiByAlias( + this, + this.args.meta.recycleBinRepositoryAlias, + ); + await recycleBinRepository.requestTrash({ unique: this.args.unique }); + + const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + unique: this.args.unique, + entityType: this.args.entityType, + }); + + actionEventContext.dispatchEvent(event); + + // TODO: reload destination + } +} + +export { UmbTrashEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/index.ts new file mode 100644 index 0000000000..4cb027b622 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/index.ts @@ -0,0 +1,14 @@ +export { UmbRecycleBinRepositoryBase } from './recycle-bin-repository-base.js'; +export { + UmbTrashEntityAction, + UmbRestoreFromRecycleBinEntityAction, + UmbEmptyRecycleBinEntityAction, +} from './entity-action/index.js'; + +export type { UmbRecycleBinDataSource } from './recycle-bin-data-source.interface.js'; +export type { UmbRecycleBinRepository } from './recycle-bin-repository.interface.js'; +export type { + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, + UmbRecycleBinOriginalParentRequestArgs, +} from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/manifests.ts new file mode 100644 index 0000000000..49e2d1fd73 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/manifests.ts @@ -0,0 +1,9 @@ +import { manifests as trashEntityActionManifests } from '../recycle-bin/entity-action/trash/manifests.js'; +import { manifests as restoreFromRecycleBinEntityActionManifests } from '../recycle-bin/entity-action/restore-from-recycle-bin/manifests.js'; +import { manifests as emptyRecycleBinEntityActionManifests } from '../recycle-bin/entity-action/empty-recycle-bin/manifests.js'; + +export const manifests = [ + ...trashEntityActionManifests, + ...restoreFromRecycleBinEntityActionManifests, + ...emptyRecycleBinEntityActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-data-source.interface.ts new file mode 100644 index 0000000000..8d381a83c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-data-source.interface.ts @@ -0,0 +1,18 @@ +import type { + UmbRecycleBinOriginalParentRequestArgs, + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, +} from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository'; + +export interface UmbRecycleBinDataSourceConstructor { + new (host: UmbControllerHost): UmbRecycleBinDataSource; +} + +export interface UmbRecycleBinDataSource { + trash(args: UmbRecycleBinTrashRequestArgs): Promise; + restore(args: UmbRecycleBinRestoreRequestArgs): Promise; + empty(): Promise; + getOriginalParent(args: UmbRecycleBinOriginalParentRequestArgs): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts new file mode 100644 index 0000000000..1dd19e0809 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts @@ -0,0 +1,99 @@ +import type { UmbRecycleBinRepository } from './recycle-bin-repository.interface.js'; +import type { + UmbRecycleBinDataSource, + UmbRecycleBinDataSourceConstructor, +} from './recycle-bin-data-source.interface.js'; +import type { + UmbRecycleBinOriginalParentRequestArgs, + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, +} from './types.js'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +/** + * Base class for recycle bin repositories. + * @export + * @abstract + * @class UmbRecycleBinRepositoryBase + * @extends {UmbRepositoryBase} + * @implements {UmbRecycleBinRepository} + */ +export abstract class UmbRecycleBinRepositoryBase extends UmbRepositoryBase implements UmbRecycleBinRepository { + #recycleBinSource: UmbRecycleBinDataSource; + + /** + * Creates an instance of UmbRecycleBinRepositoryBase. + * @param {UmbControllerHost} host + * @param {UmbRecycleBinDataSourceConstructor} recycleBinSource + * @memberof UmbRecycleBinRepositoryBase + */ + constructor(host: UmbControllerHost, recycleBinSource: UmbRecycleBinDataSourceConstructor) { + super(host); + this.#recycleBinSource = new recycleBinSource(this); + } + + /** + * Requests to trash an item. + * @param {UmbRecycleBinTrashRequestArgs} args + * @return {*} + * @memberof UmbRecycleBinRepositoryBase + */ + async requestTrash(args: UmbRecycleBinTrashRequestArgs) { + const { error } = await this.#recycleBinSource.trash(args); + + if (!error) { + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + const notification = { data: { message: `Trashed` } }; + notificationContext.peek('positive', notification); + } + + return { error }; + } + + /** + * Requests to restore an item. + * @param {UmbRecycleBinRestoreRequestArgs} args + * @return {*} + * @memberof UmbRecycleBinRepositoryBase + */ + async requestRestore(args: UmbRecycleBinRestoreRequestArgs) { + const { error } = await this.#recycleBinSource.restore(args); + + if (!error) { + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + const notification = { data: { message: `Restored` } }; + notificationContext.peek('positive', notification); + } + + return { error }; + } + + /** + * Requests to empty the recycle bin. + * @return {*} + * @memberof UmbRecycleBinRepositoryBase + */ + async requestEmpty() { + const { error } = await this.#recycleBinSource.empty(); + + if (!error) { + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + const notification = { data: { message: `Recycle Bin Emptied` } }; + notificationContext.peek('positive', notification); + } + + return this.#recycleBinSource.empty(); + } + + /** + * Requests the original parent of an item. + * @param {UmbRecycleBinOriginalParentRequestArgs} args + * @return {*} + * @memberof UmbRecycleBinRepositoryBase + */ + async requestOriginalParent(args: UmbRecycleBinOriginalParentRequestArgs) { + return this.#recycleBinSource.getOriginalParent(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository.interface.ts new file mode 100644 index 0000000000..92a6d2ba9a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository.interface.ts @@ -0,0 +1,15 @@ +import type { UmbRepositoryBase } from '../repository/repository-base.js'; +import type { + UmbRecycleBinOriginalParentRequestArgs, + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, +} from './types.js'; +import type { UmbRepositoryErrorResponse, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface UmbRecycleBinRepository extends UmbRepositoryBase, UmbApi { + requestTrash(args: UmbRecycleBinTrashRequestArgs): Promise; + requestRestore(args: UmbRecycleBinRestoreRequestArgs): Promise; + requestEmpty(): Promise; + requestOriginalParent(args: UmbRecycleBinOriginalParentRequestArgs): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts new file mode 100644 index 0000000000..3a74c7c86f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts @@ -0,0 +1,14 @@ +export interface UmbRecycleBinRestoreRequestArgs { + unique: string; + destination: { + unique: string | null; + }; +} + +export interface UmbRecycleBinTrashRequestArgs { + unique: string; +} + +export interface UmbRecycleBinOriginalParentRequestArgs { + unique: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/index.ts index 0002dac5a9..7557680428 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/index.ts @@ -7,4 +7,4 @@ export * from './detail/index.js'; export type { UmbDataSourceResponse, UmbDataSourceErrorResponse } from './data-source-response.interface.js'; export type { UmbMoveDataSource, UmbMoveRepository } from './move/index.js'; export type { UmbDuplicateDataSource, UmbDuplicateRepository } from './duplicate/index.js'; -export type { UmbPagedModel } from './types.js'; +export type { UmbPagedModel, UmbRepositoryResponse, UmbRepositoryErrorResponse } from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts index b7dc4e9992..edbefc578b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { UMB_AUTH_CONTEXT } from '../auth/index.js'; import { isApiError, isCancelError, isCancelablePromise } from './apiTypeValidators.function.js'; import { UMB_NOTIFICATION_CONTEXT, type UmbNotificationOptions } from '@umbraco-cms/backoffice/notification'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -11,6 +12,8 @@ export class UmbResourceController extends UmbControllerBase { #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE; + #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; + constructor(host: UmbControllerHost, promise: Promise, alias?: string) { super(host, alias); @@ -19,6 +22,10 @@ export class UmbResourceController extends UmbControllerBase { new UmbContextConsumerController(host, UMB_NOTIFICATION_CONTEXT, (_instance) => { this.#notificationContext = _instance; }); + + new UmbContextConsumerController(host, UMB_AUTH_CONTEXT, (_instance) => { + this.#authContext = _instance; + }); } hostConnected(): void { @@ -78,21 +85,21 @@ export class UmbResourceController extends UmbControllerBase { // Go through the error status codes and act accordingly switch (error.status ?? 0) { - case 401: - // Unauthorized - console.log('Unauthorized'); - - // TODO: Do not remove the token here but instead let whatever is listening to the event decide what to do - localStorage.removeItem('umb:userAuthTokenResponse'); - - // TODO: Show a modal dialog to login either by bubbling an event to UmbAppElement or by showing a modal directly - this.#notificationContext?.peek('warning', { - data: { - headline: 'Session Expired', - message: 'Your session has expired. Please refresh the page.', - }, - }); + case 401: { + // See if we can get the UmbAuthContext and let it know the user is timed out + if (this.#authContext) { + this.#authContext.timeOut(); + } else { + // If we can't get the auth context, show a notification + this.#notificationContext?.peek('warning', { + data: { + headline: 'Session Expired', + message: 'Your session has expired. Please refresh the page.', + }, + }); + } break; + } case 500: // Server Error diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/ref-section/ref-section.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/ref-section/ref-section.element.ts index 8b41537035..0b9892c507 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/ref-section/ref-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/ref-section/ref-section.element.ts @@ -11,7 +11,7 @@ export class UmbRefSectionElement extends UmbElementMixin(UUIRefElement) { public render() { return html`
-
${this.item?.meta.label}
+
${this.item?.meta.label ? this.localize.string(this.item.meta.label) : this.item?.name}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts index c8d639e8e7..6a7d3917b6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts @@ -97,7 +97,7 @@ export class UmbSectionMainViewElement extends UmbLitElement { }}> - ` + ` : html`${nothing}`; } @@ -107,17 +107,16 @@ export class UmbSectionMainViewElement extends UmbLitElement { ? html` ${this._dashboards.map((dashboard) => { - const dashboardName = dashboard.meta.label ?? dashboard.name; const dashboardPath = this.#constructDashboardPath(dashboard); return html` `; })} - ` + ` : ''; } @@ -127,7 +126,7 @@ export class UmbSectionMainViewElement extends UmbLitElement { ? html` ${this._views.map((view) => { - const viewName = view.meta.label ?? view.name; + const viewName = view.meta.label ? this.localize.string(view.meta.label) : view.name; const viewPath = this.#constructViewPath(view); return html` - ` + ` : ''; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/server-file-system/rename/rename-server-file.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/server-file-system/rename/rename-server-file.action.kind.ts index 7af4436044..f54bb2417c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/server-file-system/rename/rename-server-file.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/server-file-system/rename/rename-server-file.action.kind.ts @@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-edit', - label: 'Rename...', + label: '#actions_rename', renameRepositoryAlias: '', itemRepositoryAlias: '', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/settings/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/settings/manifests.ts index 73f0a117b6..803d177dbe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/settings/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/settings/manifests.ts @@ -9,7 +9,7 @@ export const manifests = [ name: 'Settings Section', weight: 400, meta: { - label: 'Settings', + label: '#sections_settings', pathname: 'settings', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/settings/welcome-dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/settings/welcome-dashboard/manifests.ts index 5865d480c2..5112dd2791 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/settings/welcome-dashboard/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/settings/welcome-dashboard/manifests.ts @@ -6,7 +6,7 @@ export const manifests = [ element: () => import('./settings-welcome-dashboard.element.js'), weight: 500, meta: { - label: 'Welcome', + label: '#dashboardTabs_settingsWelcome', pathname: 'welcome', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts index 2409617d42..7e2ca2ccdb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts @@ -28,7 +28,7 @@ export abstract class UmbTreeRepositoryBase< { protected _init: Promise; protected _treeStore?: UmbTreeStore; - #treeSource: UmbTreeDataSource; + protected _treeSource: UmbTreeDataSource; /** * Creates an instance of UmbTreeRepositoryBase. @@ -43,7 +43,7 @@ export abstract class UmbTreeRepositoryBase< treeStoreContextAlias: string | UmbContextToken, ) { super(host); - this.#treeSource = new treeSourceConstructor(this); + this._treeSource = new treeSourceConstructor(this); this._init = this.consumeContext(treeStoreContextAlias, (instance) => { this._treeStore = instance; @@ -65,7 +65,7 @@ export abstract class UmbTreeRepositoryBase< async requestRootTreeItems(args: any) { await this._init; - const { data, error } = await this.#treeSource.getRootItems(args); + const { data, error } = await this._treeSource.getRootItems(args); if (data) { this._treeStore!.appendItems(data.items); @@ -84,7 +84,7 @@ export abstract class UmbTreeRepositoryBase< if (args.parentUnique === undefined) throw new Error('Parent unique is missing'); await this._init; - const { data, error } = await this.#treeSource.getChildrenOf(args); + const { data, error } = await this._treeSource.getChildrenOf(args); if (data) { this._treeStore!.appendItems(data.items); @@ -103,7 +103,7 @@ export abstract class UmbTreeRepositoryBase< if (args.descendantUnique === undefined) throw new Error('Descendant unique is missing'); await this._init; - const { data, error } = await this.#treeSource.getAncestorsOf(args); + const { data, error } = await this._treeSource.getAncestorsOf(args); // TODO: implement observable for ancestor items in the store return { data, error }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/create-folder/create-folder.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/create-folder/create-folder.action.kind.ts index 4a03faa74f..6d3ec770bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/create-folder/create-folder.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/create-folder/create-folder.action.kind.ts @@ -16,7 +16,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-add', - label: 'Create folder...', + label: '#actions_folderCreate', }, }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.kind.ts index a737db6ff2..30677f4ba5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/delete-folder/delete-folder.action.kind.ts @@ -16,7 +16,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-trash', - label: 'Delete Folder...', + label: '#actions_folderDelete', }, }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/update-folder/update-folder.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/update-folder/update-folder.action.kind.ts index a7390e4da4..39f62fcb10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/update-folder/update-folder.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/entity-action/update-folder/update-folder.action.kind.ts @@ -16,7 +16,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-edit', - label: 'Rename Folder...', + label: '#actions_folderRename', }, }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/reload-tree-item-children/reload-tree-item-children.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/reload-tree-item-children/reload-tree-item-children.action.kind.ts index 87e817a078..56906b23a9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/reload-tree-item-children/reload-tree-item-children.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/reload-tree-item-children/reload-tree-item-children.action.kind.ts @@ -15,7 +15,7 @@ export const manifest: UmbBackofficeManifestKind = { forEntityTypes: [], meta: { icon: 'icon-refresh', - label: 'Reload children', + label: '#actions_refreshNode', }, }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts index c161336421..9ed19cce79 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts @@ -41,7 +41,6 @@ export abstract class UmbTreeItemContextBase 0); this.pagination.setTotalItems(data.total); } @@ -209,8 +207,6 @@ export abstract class UmbTreeItemContextBase { @@ -299,28 +295,6 @@ export abstract class UmbTreeItemContextBase children.length > 0), - ); - - // observe if any children will be added runtime to a tree item. Nested items/folders etc. - this.observe(hasChildrenObservable, (hasChildren) => { - /* we need to skip the first value, because it will also return false until a child is in the store - we therefor rely on the value from the tree item itself */ - if (this.#hasChildrenInitValueFlag === true) { - this.#hasChildren.setValue(hasChildren); - } - this.#hasChildrenInitValueFlag = true; - }); - } - #onReloadRequest = (event: UmbEntityActionEvent) => { if (event.getUnique() !== this.unique) return; if (event.getEntityType() !== this.entityType) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts index fff0a03aeb..82f98b8a28 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts @@ -117,7 +117,7 @@ export abstract class UmbTreeItemElementBase ${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts deleted file mode 100644 index 98bf5c8f3b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/umbraco-package.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ManifestEntryPoint } from '@umbraco-cms/backoffice/extension-api'; - -export const name = 'Umbraco.Core'; -export const version = '0.0.1'; -export const extensions: Array = [ - { - name: 'Core Entry Point', - alias: 'Umb.EntryPoint.Core', - type: 'entryPoint', - js: () => import('./entry-point.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts index b9314156d5..ce758395d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts @@ -48,7 +48,9 @@ export class UmbWorkspaceActionMenuItemElement< render() { return html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts index 3aa40821b3..8539bbda99 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts @@ -1,6 +1,6 @@ import type { UmbWorkspaceAction } from '../workspace-action.interface.js'; import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { @@ -117,7 +117,9 @@ export class UmbWorkspaceActionElement< @click=${this._onClick} look=${this.#manifest?.meta.look || 'default'} color=${this.#manifest?.meta.color || 'default'} - label=${this.#manifest?.meta.label || ''} + label=${ifDefined( + this.#manifest?.meta.label ? this.localize.string(this.#manifest.meta.label) : this.#manifest?.name, + )} .disabled=${this._isDisabled} .state=${this._buttonState}> view.alias, (view) => html` - ${view.meta.label || view.name} + ${view.meta.label ? this.localize.string(view.meta.label) : view.name} `, )} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/entity-actions/create/manifests.ts index b4ecb39f47..69a757fac4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/entity-actions/create/manifests.ts @@ -5,7 +5,7 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ { type: 'entityAction', - kind: 'delete', + kind: 'default', alias: 'Umb.EntityAction.DataType.Create', name: 'Create Data Type Entity Action', weight: 1000, @@ -13,7 +13,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DATA_TYPE_ROOT_ENTITY_TYPE, UMB_DATA_TYPE_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/data-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/data-type-tree.repository.ts index 8e1702f7ef..2c9f04fc7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/data-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/data-type-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbDataTypeTreeRepository const data: UmbDataTypeTreeRootModel = { unique: null, entityType: UMB_DATA_TYPE_ROOT_ENTITY_TYPE, - name: 'Data Types', + name: '#treeHeaders_dataTypes', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/reload-tree-item-children/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/reload-tree-item-children/manifests.ts index cf276f7dc6..c2a2ff3859 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/reload-tree-item-children/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/reload-tree-item-children/manifests.ts @@ -8,6 +8,5 @@ export const manifests: Array = [ alias: 'Umb.EntityAction.DataType.Tree.ReloadChildrenOf', name: 'Reload Data Type Tree Item Children Entity Action', forEntityTypes: [UMB_DATA_TYPE_ROOT_ENTITY_TYPE, UMB_DATA_TYPE_FOLDER_ENTITY_TYPE], - meta: {}, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts index 5aa2fda518..39b93bc9a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts @@ -26,7 +26,7 @@ const workspaceViews: Array = [ js: () => import('./views/details/data-type-details-workspace-view.element.js'), weight: 90, meta: { - label: 'Details', + label: '#general_details', pathname: 'details', icon: 'edit', }, @@ -44,7 +44,7 @@ const workspaceViews: Array = [ js: () => import('./views/info/workspace-view-data-type-info.element.js'), weight: 90, meta: { - label: 'Info', + label: '#general_info', pathname: 'info', icon: 'info', }, @@ -65,7 +65,7 @@ const workspaceActions: Array = [ name: 'Save Data Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts index 9c2bd689f9..7d8ca0164f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts @@ -8,7 +8,7 @@ const dashboards: Array = [ name: 'Dictionary Overview Dashboard', element: () => import('./dictionary-overview-dashboard.element.js'), meta: { - label: 'Dictionary overview', + label: '#dictionaryItem_overviewTitle', pathname: '', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/manifests.ts index e3503e1c88..e331dd3d01 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/entity-action/manifests.ts @@ -13,7 +13,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create', + label: '#dictionary_createNew', }, }, { @@ -36,7 +36,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE], meta: { icon: 'icon-download-alt', - label: 'Export', + label: '#actions_export', }, }, { @@ -49,7 +49,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE], meta: { icon: 'icon-page-up', - label: 'Import', + label: '#actions_import', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/section/manifests.ts index 9baccac979..2ae5954eab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/section/manifests.ts @@ -10,7 +10,7 @@ const section: ManifestSection = { name: 'Dictionary Section', weight: 100, meta: { - label: 'Dictionary', + label: '#sections_translation', pathname: 'dictionary', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/dictionary-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/dictionary-tree.repository.ts index 8e273e86ef..b772686501 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/dictionary-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/dictionary-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbDictionaryTreeRepository const data: UmbDictionaryTreeRootModel = { unique: null, entityType: UMB_DICTIONARY_ROOT_ENTITY_TYPE, - name: 'Dictionary', + name: '#treeHeaders_dictionary', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts index e889ec34da..fb241d41d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts @@ -25,7 +25,7 @@ const workspaceViews: Array = [ js: () => import('./views/workspace-view-dictionary-editor.element.js'), weight: 100, meta: { - label: 'Edit', + label: '#general_edit', pathname: 'edit', icon: 'edit', }, @@ -47,7 +47,7 @@ const workspaceActions: Array = [ weight: 90, api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/manifests.ts index 4d156accdd..5318ffcc8f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/dashboards/manifests.ts @@ -8,7 +8,7 @@ const dashboards: Array = [ js: () => import('./redirect-management/dashboard-redirect-management.element.js'), weight: 10, meta: { - label: 'Redirect Management', + label: '#dashboardTabs_contentRedirectManager', pathname: 'redirect-management', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts index e22aaa3ac6..fcea8dd858 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts @@ -21,7 +21,7 @@ const entityActions: Array = [ ], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/document-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/document-type-tree.repository.ts index 0f45c87dfa..15d2f7f74c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/document-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/document-type-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbDocumentTypeTreeRepository const data: UmbDocumentTypeTreeRootModel = { unique: null, entityType: UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - name: 'Document Types', + name: '#treeHeaders_documentTypes', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts index 7aa4387ada..deaca635a6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts @@ -26,7 +26,7 @@ const workspaceViews: Array = [ alias: 'Umb.WorkspaceView.DocumentType.Design', name: 'Document Type Workspace Design View', meta: { - label: 'Design', + label: '#general_design', pathname: 'design', icon: 'icon-document-dashed-line', compositionRepositoryAlias: UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS, @@ -45,7 +45,7 @@ const workspaceViews: Array = [ element: () => import('./views/structure/document-type-workspace-view-structure.element.js'), weight: 800, meta: { - label: 'Structure', + label: '#contentTypeEditor_structure', pathname: 'structure', icon: 'icon-mindmap', }, @@ -63,7 +63,7 @@ const workspaceViews: Array = [ element: () => import('./views/settings/document-type-workspace-view-settings.element.js'), weight: 600, meta: { - label: 'Settings', + label: '#general_settings', pathname: 'settings', icon: 'icon-settings', }, @@ -81,7 +81,7 @@ const workspaceViews: Array = [ element: () => import('./views/templates/document-type-workspace-view-templates.element.js'), weight: 400, meta: { - label: 'Templates', + label: '#treeHeaders_templates', pathname: 'templates', icon: 'icon-layout', }, @@ -102,7 +102,7 @@ const workspaceActions: Array = [ name: 'Save Document Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/manifests.ts index 844d5dd2ff..db6c6478f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/manifests.ts @@ -12,7 +12,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts index 76f0b2f269..7a74ff3ef1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts @@ -13,7 +13,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-home', - label: 'Culture and Hostnames...', + label: '#actions_assigndomain', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts index 3d5c84ca4c..5f7ade81f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts @@ -1,4 +1,4 @@ -import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UMB_DOCUMENT_PICKER_MODAL } from '../modals/index.js'; import { manifests as createManifests } from './create/manifests.js'; @@ -13,12 +13,10 @@ const entityActions: Array = [ kind: 'delete', alias: 'Umb.EntityAction.Document.Delete', name: 'Delete Document Entity Action', - weight: 1100, forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { - deleteRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, - itemRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, - pickerModalAlias: UMB_DOCUMENT_PICKER_MODAL, + itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS, + detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, }, }, { @@ -31,7 +29,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-blueprint', - label: 'Create Document Blueprint (TBD)', + label: '#actions_createblueprint', }, }, { @@ -70,7 +68,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-globe', - label: 'Publish', + label: '#actions_publish', }, }, { @@ -83,7 +81,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-globe', - label: 'Unpublish...', + label: '#actions_unpublish', }, }, { @@ -96,7 +94,7 @@ const entityActions: Array = [ api: () => import('./permissions.action.js'), meta: { icon: 'icon-name-badge', - label: 'Permissions...', + label: '#actions_setPermissions', }, }, { @@ -109,7 +107,7 @@ const entityActions: Array = [ api: () => import('./permissions.action.js'), meta: { icon: 'icon-megaphone', - label: 'Notifications...', + label: '#actions_notify', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts index 6fafa57fb6..3f7b69c528 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts @@ -7,13 +7,13 @@ const entityActions: Array = [ type: 'entityAction', kind: 'default', alias: 'Umb.EntityAction.Document.PublicAccess', - name: 'Document Permissions Entity Action', + name: 'Document Public Access Entity Action', weight: 200, api: UmbDocumentPublicAccessEntityAction, forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { icon: 'icon-lock', - label: 'Restrict Public Access...', + label: '#actions_protect', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts index 2c654126d5..0ac0cd09be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity-action/manifests.ts @@ -1,22 +1,42 @@ +import { UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; -import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UMB_DOCUMENT_PICKER_MODAL } from '../../modals/document-picker-modal.token.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests = [ +export const manifests: Array = [ { type: 'entityAction', kind: 'trash', - alias: 'Umb.EntityAction.Document.Trash', + alias: 'Umb.EntityAction.Document.RecycleBin.Trash', name: 'Trash Document Entity Action', forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS, - trashRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, + recycleBinRepositoryAlias: UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS, + }, + }, + { + type: 'entityAction', + kind: 'restoreFromRecycleBin', + alias: 'Umb.EntityAction.Document.RecycleBin.Restore', + name: 'Restore Document From Recycle Bin Entity Action', + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + meta: { + itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS, + recycleBinRepositoryAlias: UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS, + pickerModal: UMB_DOCUMENT_PICKER_MODAL, + }, + }, + { + type: 'entityAction', + kind: 'emptyRecycleBin', + alias: 'Umb.EntityAction.Document.RecycleBin.Empty', + name: 'Empty Document Recycle Bin Entity Action', + forEntityTypes: [UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE], + meta: { + recycleBinRepositoryAlias: UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS, }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission', - match: 'Umb.UserPermission.Document.Delete', - }, - ], }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity.ts index 5751458ba0..1e39a86afa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/entity.ts @@ -1,2 +1 @@ export const UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE = 'document-recycle-bin-root'; -export const UMB_DOCUMENT_RECYCLE_BIN_ENTITY_TYPE = 'document-recycle-bin'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/manifests.ts index 38ee0700be..423a1afcc2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/manifests.ts @@ -1,5 +1,6 @@ -import { manifests as treeManifests } from './tree/manifests.js'; -import { manifests as menuItemManifests } from './menu-item/manifests.js'; import { manifests as entityActionManifests } from './entity-action/manifests.js'; +import { manifests as menuItemManifests } from './menu-item/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as treeManifests } from './tree/manifests.js'; -export const manifests = [...treeManifests, ...menuItemManifests, ...entityActionManifests]; +export const manifests = [...entityActionManifests, ...menuItemManifests, ...repositoryManifests, ...treeManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/menu-item/manifests.ts index 60699a97fa..418ed9c109 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/menu-item/manifests.ts @@ -1,14 +1,15 @@ import { UMB_CONTENT_MENU_ALIAS } from '../../menu/manifests.js'; +import { UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS } from '../tree/index.js'; import type { ManifestMenuItemTreeKind } from '@umbraco-cms/backoffice/extension-registry'; const menuItem: ManifestMenuItemTreeKind = { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.DocumentRecycleBin', + alias: 'Umb.MenuItem.Document.RecycleBin', name: 'Document Recycle Bin Menu Item', weight: 100, meta: { - treeAlias: 'Umb.Tree.DocumentRecycleBin', + treeAlias: UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS, label: 'Recycle Bin', icon: 'icon-trash', menus: [UMB_CONTENT_MENU_ALIAS], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/constants.ts new file mode 100644 index 0000000000..f350dc5e9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS = 'Umb.Repository.Document.RecycleBin'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.repository.ts new file mode 100644 index 0000000000..89422926fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.repository.ts @@ -0,0 +1,11 @@ +import { UmbDocumentRecycleBinServerDataSource } from './document-recycle-bin.server.data-source.js'; +import { UmbRecycleBinRepositoryBase } from '@umbraco-cms/backoffice/recycle-bin'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDocumentRecycleBinRepository extends UmbRecycleBinRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbDocumentRecycleBinServerDataSource); + } +} + +export { UmbDocumentRecycleBinRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.server.data-source.ts new file mode 100644 index 0000000000..7c8dda0721 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/document-recycle-bin.server.data-source.ts @@ -0,0 +1,52 @@ +import type { + UmbRecycleBinDataSource, + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, + UmbRecycleBinOriginalParentRequestArgs, +} from '@umbraco-cms/backoffice/recycle-bin'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +export class UmbDocumentRecycleBinServerDataSource implements UmbRecycleBinDataSource { + #host: UmbControllerHost; + + constructor(host: UmbControllerHost) { + this.#host = host; + } + + trash(args: UmbRecycleBinTrashRequestArgs) { + return tryExecuteAndNotify(this.#host, DocumentResource.putDocumentByIdMoveToRecycleBin({ id: args.unique })); + } + + restore(args: UmbRecycleBinRestoreRequestArgs) { + return tryExecuteAndNotify( + this.#host, + DocumentResource.putRecycleBinDocumentByIdRestore({ + id: args.unique, + requestBody: { + target: args.destination.unique ? { id: args.destination.unique } : null, + }, + }), + ); + } + + empty() { + return tryExecuteAndNotify(this.#host, DocumentResource.deleteRecycleBinDocument()); + } + + async getOriginalParent(args: UmbRecycleBinOriginalParentRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + DocumentResource.getRecycleBinDocumentByIdOriginalParent({ id: args.unique }), + ); + + // only check for undefined because data can be null if the parent is the root + if (data !== undefined) { + const mappedData = data ? { unique: data.id } : null; + return { data: mappedData }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/index.ts new file mode 100644 index 0000000000..50bcc134af --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/index.ts @@ -0,0 +1 @@ +export { UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS } from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/manifests.ts new file mode 100644 index 0000000000..b2b0b2ac63 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS } from './constants.js'; +import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +const queryRepository: ManifestRepository = { + type: 'repository', + alias: UMB_DOCUMENT_RECYCLE_BIN_REPOSITORY_ALIAS, + name: 'Document Recycle Bin Repository', + api: () => import('./document-recycle-bin.repository.js'), +}; + +export const manifests = [queryRepository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/constants.ts new file mode 100644 index 0000000000..492e6f36ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/constants.ts @@ -0,0 +1,3 @@ +export const UMB_DOCUMENT_RECYCLE_BIN_TREE_REPOSITORY_ALIAS = 'Umb.Repository.Document.RecycleBin.Tree'; +export const UMB_DOCUMENT_RECYCLE_BIN_TREE_STORE_ALIAS = 'Umb.Store.Document.RecycleBin.Tree'; +export const UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS = 'Umb.Tree.Document.RecycleBin'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.repository.ts index 37ff658fb9..e11e98e92c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.repository.ts @@ -15,12 +15,15 @@ export class UmbDocumentRecycleBinTreeRepository } async requestTreeRoot() { + const { data: treeRootData } = await this._treeSource.getRootItems({ skip: 0, take: 1 }); + const hasChildren = treeRootData ? treeRootData.total > 0 : false; + const data = { unique: null, entityType: UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE, - name: 'Recycle Bin', + name: '#treeHeaders_contentRecycleBin', icon: 'icon-trash', - hasChildren: true, + hasChildren, isContainer: false, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.server.data-source.ts index d5a1ee2f36..4fa19adbd0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/document-recycle-bin-tree.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import type { UmbDocumentRecycleBinTreeItemModel } from './types.js'; import type { DocumentRecycleBinItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api'; @@ -61,9 +62,25 @@ const mapper = (item: DocumentRecycleBinItemResponseModel): UmbDocumentRecycleBi return { unique: item.id, parentUnique: item.parent ? item.parent.id : null, - entityType: 'document-recycle-bin', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + noAccess: false, + isTrashed: true, hasChildren: item.hasChildren, - isFolder: false, + isProtected: false, + documentType: { + unique: item.documentType.id, + icon: item.documentType.icon, + collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null, + }, + variants: item.variants.map((variant) => { + return { + name: variant.name, + culture: variant.culture || null, + segment: null, // TODO: add segment to the backend API? + state: variant.state, + }; + }), name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution. + isFolder: false, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/index.ts index 995f0b5ebf..1c6d9bc43c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/index.ts @@ -3,7 +3,7 @@ export { UMB_DOCUMENT_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, UMB_DOCUMENT_RECYCLE_BIN_TREE_STORE_ALIAS, UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS, -} from './manifests.js'; +} from './constants.js'; export { UMB_DOCUMENT_RECYCLE_BIN_TREE_STORE_CONTEXT } from './document-recycle-bin-tree.store.js'; export { type UmbDocumentRecycleBinTreeStore } from './document-recycle-bin-tree.store.js'; export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/manifests.ts index 0b7795f149..69c76b9235 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/manifests.ts @@ -1,4 +1,9 @@ -import { UMB_DOCUMENT_RECYCLE_BIN_ENTITY_TYPE, UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { + UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS, + UMB_DOCUMENT_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, + UMB_DOCUMENT_RECYCLE_BIN_TREE_STORE_ALIAS, +} from './constants.js'; import { UmbDocumentRecycleBinTreeRepository } from './document-recycle-bin-tree.repository.js'; import { UmbDocumentRecycleBinTreeStore } from './document-recycle-bin-tree.store.js'; import { manifests as reloadTreeItemChildrenManifests } from './reload-tree-item-children/manifests.js'; @@ -9,10 +14,6 @@ import type { ManifestTreeStore, } from '@umbraco-cms/backoffice/extension-registry'; -export const UMB_DOCUMENT_RECYCLE_BIN_TREE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentRecycleBin.Tree'; -export const UMB_DOCUMENT_RECYCLE_BIN_TREE_STORE_ALIAS = 'Umb.Store.DocumentRecycleBin.Tree'; -export const UMB_DOCUMENT_RECYCLE_BIN_TREE_ALIAS = 'Umb.Tree.DocumentRecycleBin'; - const treeRepository: ManifestRepository = { type: 'repository', alias: UMB_DOCUMENT_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, @@ -40,9 +41,9 @@ const tree: ManifestTree = { const treeItem: ManifestTreeItem = { type: 'treeItem', kind: 'default', - alias: 'Umb.TreeItem.DocumentRecycleBin', - name: 'DocumentRecycleBin Tree Item', - forEntityTypes: [UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE, UMB_DOCUMENT_RECYCLE_BIN_ENTITY_TYPE], + alias: 'Umb.TreeItem.Document.RecycleBin', + name: 'Document Recycle Bin Tree Item', + forEntityTypes: [UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE], }; export const manifests = [treeRepository, treeStore, tree, treeItem, ...reloadTreeItemChildrenManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/reload-tree-item-children/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/reload-tree-item-children/manifests.ts index 9b90625f81..35463faee4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/reload-tree-item-children/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/reload-tree-item-children/manifests.ts @@ -1,4 +1,4 @@ -import { UMB_DOCUMENT_RECYCLE_BIN_ENTITY_TYPE, UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../../entity.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -7,6 +7,6 @@ export const manifests: Array = [ kind: 'reloadTreeItemChildren', alias: 'Umb.EntityAction.DocumentRecycleBin.Tree.ReloadChildrenOf', name: 'Reload Document Recycle Bin Tree Item Children Entity Action', - forEntityTypes: [UMB_DOCUMENT_RECYCLE_BIN_ENTITY_TYPE, UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE], + forEntityTypes: [UMB_DOCUMENT_RECYCLE_BIN_ROOT_ENTITY_TYPE], }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts index 5c67491e8e..d84c7821f1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts @@ -1,5 +1,6 @@ -import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree'; +import type { UmbDocumentTreeItemModel } from '../../tree/index.js'; +import type { UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree'; -export interface UmbDocumentRecycleBinTreeItemModel extends UmbUniqueTreeItemModel {} +export interface UmbDocumentRecycleBinTreeItemModel extends UmbDocumentTreeItemModel {} export interface UmbDocumentRecycleBinTreeRootModel extends UmbUniqueTreeRootModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index e8fb1638a3..aa8516e2b5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -199,8 +199,6 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource import('./tree-item/document-tree-item.element.js'), api: UmbDocumentTreeItemContext, - forEntityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_ENTITY_TYPE], + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], }; -export const manifests = [...reloadTreeItemChildrenManifests, tree, treeItem, treeRepository, treeStore]; +const rootTreeItem: ManifestTreeItem = { + type: 'treeItem', + kind: 'default', + alias: 'Umb.TreeItem.Document.Root', + name: 'Document Tree Item', + forEntityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE], +}; + +export const manifests = [...reloadTreeItemChildrenManifests, tree, treeItem, rootTreeItem, treeRepository, treeStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts index 7eb0367957..661cbed611 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts @@ -218,14 +218,7 @@ export class UmbInputDocumentGranularUserPermissionElement extends FormControlMi ) .map((m) => { const manifest = m as ManifestEntityUserPermission; - - if (manifest.meta.labelKey) { - return this.localize.term(manifest.meta.labelKey); - } else if (manifest.meta.label) { - return manifest.meta.label; - } else { - return manifest.name; - } + return manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name; }) .join(', '); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts index 1fc1880cb9..dd05ad4153 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts @@ -30,8 +30,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Read'], - labelKey: 'actions_browse', - descriptionKey: 'actionDescriptions_browse', + label: '#actions_browse', + description: '#actionDescriptions_browse', }, }, { @@ -41,8 +41,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.CreateBlueprint'], - labelKey: 'actions_createblueprint', - descriptionKey: 'actionDescriptions_createblueprint', + label: '#actions_createblueprint', + description: '#actionDescriptions_createblueprint', }, }, { @@ -52,8 +52,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Delete'], - labelKey: 'actions_delete', - descriptionKey: 'actionDescriptions_delete', + label: '#actions_delete', + description: '#actionDescriptions_delete', }, }, { @@ -63,8 +63,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Create'], - labelKey: 'actions_create', - descriptionKey: 'actionDescriptions_create', + label: '#actions_create', + description: '#actionDescriptions_create', }, }, { @@ -74,8 +74,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Notifications'], - labelKey: 'actions_notify', - descriptionKey: 'actionDescriptions_notify', + label: '#actions_notify', + description: '#actionDescriptions_notify', }, }, { @@ -85,8 +85,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Publish'], - labelKey: 'actions_publish', - descriptionKey: 'actionDescriptions_publish', + label: '#actions_publish', + description: '#actionDescriptions_publish', }, }, { @@ -96,8 +96,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Permissions'], - labelKey: 'actions_setPermissions', - descriptionKey: 'actionDescriptions_rights', + label: '#actions_setPermissions', + description: '#actionDescriptions_rights', }, }, { @@ -107,8 +107,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Unpublish'], - labelKey: 'actions_unpublish', - descriptionKey: 'actionDescriptions_unpublish', + label: '#actions_unpublish', + description: '#actionDescriptions_unpublish', }, }, { @@ -118,8 +118,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Update'], - labelKey: 'actions_update', - descriptionKey: 'actionDescriptions_update', + label: '#actions_update', + description: '#actionDescriptions_update', }, }, { @@ -129,8 +129,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Duplicate'], - labelKey: 'actions_copy', - descriptionKey: 'actionDescriptions_copy', + label: '#actions_copy', + description: '#actionDescriptions_copy', group: 'structure', }, }, @@ -141,8 +141,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Move'], - labelKey: 'actions_move', - descriptionKey: 'actionDescriptions_move', + label: '#actions_move', + description: '#actionDescriptions_move', group: 'structure', }, }, @@ -153,8 +153,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Sort'], - labelKey: 'actions_sort', - descriptionKey: 'actionDescriptions_sort', + label: '#actions_sort', + description: '#actionDescriptions_sort', group: 'structure', }, }, @@ -165,8 +165,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.CultureAndHostnames'], - labelKey: 'actions_assigndomain', - descriptionKey: 'actionDescriptions_assignDomain', + label: '#actions_assigndomain', + description: '#actionDescriptions_assignDomain', group: 'administration', }, }, @@ -177,8 +177,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.PublicAccess'], - labelKey: 'actions_protect', - descriptionKey: 'actionDescriptions_protect', + label: '#actions_protect', + description: '#actionDescriptions_protect', group: 'administration', }, }, @@ -189,8 +189,8 @@ const permissions: Array = [ forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], meta: { verbs: ['Umb.Document.Rollback'], - labelKey: 'actions_rollback', - descriptionKey: 'actionDescriptions_rollback', + label: '#actions_rollback', + description: '#actionDescriptions_rollback', group: 'administration', }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts index a46f146d5c..70284ccffd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts @@ -52,7 +52,7 @@ const workspaceViews: Array = [ element: () => import('./views/edit/document-workspace-view-edit.element.js'), weight: 200, meta: { - label: 'Content', + label: '#general_content', pathname: 'content', icon: 'document', }, @@ -70,7 +70,7 @@ const workspaceViews: Array = [ element: () => import('./views/info/document-workspace-view-info.element.js'), weight: 100, meta: { - label: 'Info', + label: '#general_info', pathname: 'info', icon: 'info', }, @@ -92,7 +92,7 @@ const workspaceActions: Array = [ weight: 70, api: UmbDocumentSaveAndPublishWorkspaceAction, meta: { - label: 'Save And Publish', + label: '#buttons_saveAndPublish', look: 'primary', color: 'positive', }, @@ -111,7 +111,7 @@ const workspaceActions: Array = [ weight: 80, api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'secondary', color: 'positive', }, @@ -152,7 +152,7 @@ const workspaceActionMenuItems: Array = [ api: UmbDocumentUnpublishWorkspaceAction, forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', meta: { - label: 'Unpublish...', + label: '#actions_unpublish', icon: 'icon-globe', }, }, @@ -165,7 +165,7 @@ const workspaceActionMenuItems: Array = [ api: UmbDocumentPublishWithDescendantsWorkspaceAction, forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', meta: { - label: 'Publish with descendants...', + label: '#buttons_publishDescendants', icon: 'icon-globe', }, }, @@ -178,7 +178,7 @@ const workspaceActionMenuItems: Array = [ api: UmbDocumentSaveAndScheduleWorkspaceAction, forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', meta: { - label: 'Schedule...', + label: '#buttons_schedulePublish', icon: 'icon-globe', }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/section.manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/section.manifests.ts index 81a84ad864..bba5937c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/section.manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/section.manifests.ts @@ -12,7 +12,7 @@ const section: ManifestSection = { name: 'Content Section', weight: 600, meta: { - label: 'Content', + label: '#sections_content', pathname: 'content', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts index f40347abb5..c630937f90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts @@ -7,7 +7,7 @@ export const manifests = [ js: () => import('./dashboard-health-check.element.js'), weight: 102, meta: { - label: 'Health Check', + label: '#dashboardTabs_settingsHealthCheck', pathname: 'health-check', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/language/entity-actions/manifests.ts index e1f0f005cb..cfb8a2c75e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/entity-actions/manifests.ts @@ -25,7 +25,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_LANGUAGE_ROOT_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create', + label: '#actions_create', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/manifests.ts index 96711c9deb..ce50e15990 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/manifests.ts @@ -24,7 +24,7 @@ const workspaceViews: Array = [ js: () => import('./views/language-details-workspace-view.element.js'), weight: 90, meta: { - label: 'Details', + label: '#general_details', pathname: 'details', icon: 'edit', }, @@ -47,7 +47,7 @@ const workspaceActions: Array = [ meta: { look: 'primary', color: 'positive', - label: 'Save', + label: '#buttons_save', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/manifests.ts index 22154a19b6..b1076a052c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/manifests.ts @@ -44,7 +44,7 @@ const workspaceViews: Array = [ element: () => import('./views/search/index.js'), weight: 200, meta: { - label: 'Search', + label: '#general_search', pathname: 'search', icon: 'icon-search', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts index 950ba4c98f..e47997e8ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts @@ -17,7 +17,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts index ae20ab7e9c..deac728c26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbMediaTypeTreeRepository const data: UmbMediaTypeTreeRootModel = { unique: null, entityType: UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, - name: 'Media Types', + name: '#treeHeaders_mediaTypes', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts index 9826f39746..62bd3c02b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts @@ -26,7 +26,7 @@ const workspaceViews: Array = [ alias: 'Umb.WorkspaceView.MediaType.Design', name: 'Media Type Workspace Design View', meta: { - label: 'Design', + label: '#general_design', pathname: 'design', icon: 'icon-document-dashed-line', }, @@ -44,7 +44,7 @@ const workspaceViews: Array = [ js: () => import('./views/structure/media-type-workspace-view-structure.element.js'), weight: 800, meta: { - label: 'Structure', + label: '#contentTypeEditor_structure', pathname: 'structure', icon: 'icon-mindmap', }, @@ -65,7 +65,7 @@ const workspaceActions: Array = [ name: 'Save Media Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/manifests.ts index 01adf09894..6d961309f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/manifests.ts @@ -13,7 +13,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_MEDIA_ROOT_ENTITY_TYPE, UMB_MEDIA_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create', + label: '#actions_create', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts index dba85d8ce2..cbdd5167a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts @@ -3,6 +3,7 @@ import { manifests as entityActionsManifests } from './entity-actions/manifests. import { manifests as entityBulkActionsManifests } from './entity-bulk-actions/manifests.js'; import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as propertyEditorsManifests } from './property-editors/manifests.js'; +import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as sectionViewManifests } from './section-view/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; @@ -14,6 +15,7 @@ export const manifests = [ ...entityBulkActionsManifests, ...menuManifests, ...propertyEditorsManifests, + ...recycleBinManifests, ...repositoryManifests, ...sectionViewManifests, ...treeManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts new file mode 100644 index 0000000000..d3631a29c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity-action/manifests.ts @@ -0,0 +1,42 @@ +import { UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS } from '../repository/index.js'; +import { UMB_MEDIA_ENTITY_TYPE } from '../../entity.js'; +import { UMB_MEDIA_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'trash', + alias: 'Umb.EntityAction.Media.RecycleBin.Trash', + name: 'Trash Media Entity Action', + forEntityTypes: [UMB_MEDIA_ENTITY_TYPE], + meta: { + itemRepositoryAlias: UMB_MEDIA_ITEM_REPOSITORY_ALIAS, + recycleBinRepositoryAlias: UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS, + }, + }, + { + type: 'entityAction', + kind: 'restoreFromRecycleBin', + alias: 'Umb.EntityAction.Media.RecycleBin.Restore', + name: 'Restore Media From Recycle Bin Entity Action', + forEntityTypes: [UMB_MEDIA_ENTITY_TYPE], + meta: { + itemRepositoryAlias: UMB_MEDIA_ITEM_REPOSITORY_ALIAS, + recycleBinRepositoryAlias: UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS, + pickerModal: UMB_MEDIA_TREE_PICKER_MODAL, + }, + }, + { + type: 'entityAction', + kind: 'emptyRecycleBin', + alias: 'Umb.EntityAction.Media.RecycleBin.Empty', + name: 'Empty Media Recycle Bin Entity Action', + forEntityTypes: [UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE], + meta: { + recycleBinRepositoryAlias: UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity.ts new file mode 100644 index 0000000000..6237cb94c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/entity.ts @@ -0,0 +1 @@ +export const UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE = 'media-recycle-bin-root'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/index.ts new file mode 100644 index 0000000000..f04892ceea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/index.ts @@ -0,0 +1,2 @@ +export * from './tree/index.js'; +export * from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/manifests.ts new file mode 100644 index 0000000000..423a1afcc2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/manifests.ts @@ -0,0 +1,6 @@ +import { manifests as entityActionManifests } from './entity-action/manifests.js'; +import { manifests as menuItemManifests } from './menu-item/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as treeManifests } from './tree/manifests.js'; + +export const manifests = [...entityActionManifests, ...menuItemManifests, ...repositoryManifests, ...treeManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/menu-item/manifests.ts new file mode 100644 index 0000000000..3c6b76f1eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/menu-item/manifests.ts @@ -0,0 +1,19 @@ +import { UMB_MEDIA_MENU_ALIAS } from '../../menu/manifests.js'; +import { UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS } from '../tree/index.js'; +import type { ManifestMenuItemTreeKind } from '@umbraco-cms/backoffice/extension-registry'; + +const menuItem: ManifestMenuItemTreeKind = { + type: 'menuItem', + kind: 'tree', + alias: 'Umb.MenuItem.Media.RecycleBin', + name: 'Media Recycle Bin Menu Item', + weight: 100, + meta: { + treeAlias: UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS, + label: 'Recycle Bin', + icon: 'icon-trash', + menus: [UMB_MEDIA_MENU_ALIAS], + }, +}; + +export const manifests = [menuItem]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/constants.ts new file mode 100644 index 0000000000..e71d4eb514 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/constants.ts @@ -0,0 +1 @@ +export const UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS = 'Umb.Repository.Media.RecycleBin'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/index.ts new file mode 100644 index 0000000000..b62f9e3b1d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/index.ts @@ -0,0 +1 @@ +export { UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS } from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/manifests.ts new file mode 100644 index 0000000000..d645ed6c0e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS } from './constants.js'; +import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_MEDIA_RECYCLE_BIN_REPOSITORY_ALIAS, + name: 'Media Recycle Bin Repository', + api: () => import('./media-recycle-bin.repository.js'), +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.repository.ts new file mode 100644 index 0000000000..867693f2c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.repository.ts @@ -0,0 +1,11 @@ +import { UmbMediaRecycleBinServerDataSource } from './media-recycle-bin.server.data-source.js'; +import { UmbRecycleBinRepositoryBase } from '@umbraco-cms/backoffice/recycle-bin'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbMediaRecycleBinRepository extends UmbRecycleBinRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbMediaRecycleBinServerDataSource); + } +} + +export { UmbMediaRecycleBinRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.server.data-source.ts new file mode 100644 index 0000000000..91eedae21f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/repository/media-recycle-bin.server.data-source.ts @@ -0,0 +1,52 @@ +import type { + UmbRecycleBinDataSource, + UmbRecycleBinRestoreRequestArgs, + UmbRecycleBinTrashRequestArgs, + UmbRecycleBinOriginalParentRequestArgs, +} from '@umbraco-cms/backoffice/recycle-bin'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { MediaResource } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +export class UmbMediaRecycleBinServerDataSource implements UmbRecycleBinDataSource { + #host: UmbControllerHost; + + constructor(host: UmbControllerHost) { + this.#host = host; + } + + trash(args: UmbRecycleBinTrashRequestArgs) { + return tryExecuteAndNotify(this.#host, MediaResource.putMediaByIdMoveToRecycleBin({ id: args.unique })); + } + + restore(args: UmbRecycleBinRestoreRequestArgs) { + return tryExecuteAndNotify( + this.#host, + MediaResource.putRecycleBinMediaByIdRestore({ + id: args.unique, + requestBody: { + target: args.destination.unique ? { id: args.destination.unique } : null, + }, + }), + ); + } + + empty() { + return tryExecuteAndNotify(this.#host, MediaResource.deleteRecycleBinMedia()); + } + + async getOriginalParent(args: UmbRecycleBinOriginalParentRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + MediaResource.getRecycleBinMediaByIdOriginalParent({ id: args.unique }), + ); + + // only check for undefined because data can be null if the parent is the root + if (data !== undefined) { + const mappedData = data ? { unique: data.id } : null; + return { data: mappedData }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/constants.ts new file mode 100644 index 0000000000..161a713575 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/constants.ts @@ -0,0 +1,3 @@ +export const UMB_MEDIA_RECYCLE_BIN_TREE_REPOSITORY_ALIAS = 'Umb.Repository.Media.RecycleBin.Tree'; +export const UMB_MEDIA_RECYCLE_BIN_TREE_STORE_ALIAS = 'Umb.Store.Media.RecycleBin.Tree'; +export const UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS = 'Umb.Tree.Media.RecycleBin'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/index.ts new file mode 100644 index 0000000000..2a6044c805 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/index.ts @@ -0,0 +1,9 @@ +export { UmbMediaRecycleBinTreeRepository } from './media-recycle-bin-tree.repository.js'; +export { + UMB_MEDIA_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, + UMB_MEDIA_RECYCLE_BIN_TREE_STORE_ALIAS, + UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS, +} from './constants.js'; +export { UMB_MEDIA_RECYCLE_BIN_TREE_STORE_CONTEXT } from './media-recycle-bin-tree.store.js'; +export { type UmbMediaRecycleBinTreeStore } from './media-recycle-bin-tree.store.js'; +export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/manifests.ts new file mode 100644 index 0000000000..d3557a6293 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/manifests.ts @@ -0,0 +1,49 @@ +import { UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { + UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS, + UMB_MEDIA_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, + UMB_MEDIA_RECYCLE_BIN_TREE_STORE_ALIAS, +} from './constants.js'; +import { UmbMediaRecycleBinTreeRepository } from './media-recycle-bin-tree.repository.js'; +import { UmbMediaRecycleBinTreeStore } from './media-recycle-bin-tree.store.js'; +import { manifests as reloadTreeItemChildrenManifests } from './reload-tree-item-children/manifests.js'; +import type { + ManifestRepository, + ManifestTree, + ManifestTreeItem, + ManifestTreeStore, +} from '@umbraco-cms/backoffice/extension-registry'; + +const treeRepository: ManifestRepository = { + type: 'repository', + alias: UMB_MEDIA_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, + name: 'Media Recycle Bin Tree Repository', + api: UmbMediaRecycleBinTreeRepository, +}; + +const treeStore: ManifestTreeStore = { + type: 'treeStore', + alias: UMB_MEDIA_RECYCLE_BIN_TREE_STORE_ALIAS, + name: 'Media Recycle Bin Tree Store', + api: UmbMediaRecycleBinTreeStore, +}; + +const tree: ManifestTree = { + type: 'tree', + kind: 'default', + alias: UMB_MEDIA_RECYCLE_BIN_TREE_ALIAS, + name: 'Media Recycle Bin Tree', + meta: { + repositoryAlias: UMB_MEDIA_RECYCLE_BIN_TREE_REPOSITORY_ALIAS, + }, +}; + +const treeItem: ManifestTreeItem = { + type: 'treeItem', + kind: 'default', + alias: 'Umb.TreeItem.Media.RecycleBin', + name: 'Media Recycle Bin Tree Item', + forEntityTypes: [UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE], +}; + +export const manifests = [treeRepository, treeStore, tree, treeItem, ...reloadTreeItemChildrenManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.repository.ts new file mode 100644 index 0000000000..7d3f2eb88d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.repository.ts @@ -0,0 +1,33 @@ +import { UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../entity.js'; +import { UmbMediaRecycleBinTreeServerDataSource } from './media-recycle-bin-tree.server.data-source.js'; +import type { UmbMediaRecycleBinTreeItemModel, UmbMediaRecycleBinTreeRootModel } from './types.js'; +import { UMB_MEDIA_RECYCLE_BIN_TREE_STORE_CONTEXT } from './media-recycle-bin-tree.store.js'; +import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMediaRecycleBinTreeRepository + extends UmbTreeRepositoryBase + implements UmbApi +{ + constructor(host: UmbControllerHost) { + super(host, UmbMediaRecycleBinTreeServerDataSource, UMB_MEDIA_RECYCLE_BIN_TREE_STORE_CONTEXT); + } + + async requestTreeRoot() { + const { data: treeRootData } = await this._treeSource.getRootItems({ skip: 0, take: 1 }); + const hasChildren = treeRootData ? treeRootData.total > 0 : false; + + const data = { + unique: null, + entityType: UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE, + name: 'Recycle Bin', + icon: 'icon-trash', + hasChildren, + isContainer: false, + isFolder: true, + }; + + return { data }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.server.data-source.ts new file mode 100644 index 0000000000..a9fa6bdf6c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.server.data-source.ts @@ -0,0 +1,84 @@ +import { UMB_MEDIA_ENTITY_TYPE } from '../../entity.js'; +import type { UmbMediaRecycleBinTreeItemModel } from './types.js'; +import type { MediaRecycleBinItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { MediaResource } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { + UmbTreeAncestorsOfRequestArgs, + UmbTreeChildrenOfRequestArgs, + UmbTreeRootItemsRequestArgs, +} from '@umbraco-cms/backoffice/tree'; +import { UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree'; + +/** + * A data source for the Media Recycle Bin tree that fetches data from the server + * @export + * @class UmbMediaRecycleBinTreeServerDataSource + * @implements {UmbTreeDataSource} + */ +export class UmbMediaRecycleBinTreeServerDataSource extends UmbTreeServerDataSourceBase< + MediaRecycleBinItemResponseModel, + UmbMediaRecycleBinTreeItemModel +> { + /** + * Creates an instance of UmbMediaRecycleBinTreeServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMediaRecycleBinTreeServerDataSource + */ + constructor(host: UmbControllerHost) { + super(host, { + getRootItems, + getChildrenOf, + getAncestorsOf, + mapper, + }); + } +} + +const getRootItems = (args: UmbTreeRootItemsRequestArgs) => + // eslint-disable-next-line local-rules/no-direct-api-import + MediaResource.getRecycleBinMediaRoot({ skip: args.skip, take: args.take }); + +const getChildrenOf = (args: UmbTreeChildrenOfRequestArgs) => { + if (args.parentUnique === null) { + return getRootItems(args); + } else { + // eslint-disable-next-line local-rules/no-direct-api-import + return MediaResource.getRecycleBinMediaChildren({ + parentId: args.parentUnique, + skip: args.skip, + take: args.take, + }); + } +}; + +const getAncestorsOf = (args: UmbTreeAncestorsOfRequestArgs) => + // eslint-disable-next-line local-rules/no-direct-api-import + MediaResource.getTreeMediaAncestors({ + descendantId: args.descendantUnique, + }); + +const mapper = (item: MediaRecycleBinItemResponseModel): UmbMediaRecycleBinTreeItemModel => { + return { + unique: item.id, + parentUnique: item.parent ? item.parent.id : null, + entityType: UMB_MEDIA_ENTITY_TYPE, + noAccess: false, + isTrashed: true, + hasChildren: item.hasChildren, + mediaType: { + unique: item.mediaType.id, + icon: item.mediaType.icon, + collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null, + }, + variants: item.variants.map((variant) => { + return { + name: variant.name, + culture: variant.culture || null, + segment: null, // TODO: add segment to the backend API? + }; + }), + name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution. + isFolder: false, + }; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.store.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.store.ts new file mode 100644 index 0000000000..6ccf7e34dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/media-recycle-bin-tree.store.ts @@ -0,0 +1,24 @@ +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbUniqueTreeStore } from '@umbraco-cms/backoffice/tree'; + +/** + * @export + * @class UmbMediaRecycleBinTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Media Recycle Bin Tree Items + */ +export class UmbMediaRecycleBinTreeStore extends UmbUniqueTreeStore { + /** + * Creates an instance of UmbMediaRecycleBinTreeStore. + * @param {UmbControllerHost} host + * @memberof UmbMediaRecycleBinTreeStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_MEDIA_RECYCLE_BIN_TREE_STORE_CONTEXT.toString()); + } +} + +export const UMB_MEDIA_RECYCLE_BIN_TREE_STORE_CONTEXT = new UmbContextToken( + 'UmbMediaRecycleBinTreeStore', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/reload-tree-item-children/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/reload-tree-item-children/manifests.ts new file mode 100644 index 0000000000..e4977c9214 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/reload-tree-item-children/manifests.ts @@ -0,0 +1,12 @@ +import { UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE } from '../../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'reloadTreeItemChildren', + alias: 'Umb.EntityAction.MediaRecycleBin.Tree.ReloadChildrenOf', + name: 'Reload Media Recycle Bin Tree Item Children Entity Action', + forEntityTypes: [UMB_MEDIA_RECYCLE_BIN_ROOT_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/types.ts new file mode 100644 index 0000000000..b09498103f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/recycle-bin/tree/types.ts @@ -0,0 +1,6 @@ +import type { UmbMediaTreeItemModel } from '../../tree/index.js'; +import type { UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree'; + +export interface UmbMediaRecycleBinTreeItemModel extends UmbMediaTreeItemModel {} + +export interface UmbMediaRecycleBinTreeRootModel extends UmbUniqueTreeRootModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts index a6f7acda64..bcb5010371 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts @@ -8,7 +8,7 @@ const sectionsViews: Array = [ element: () => import('./media-section-view.element.js'), weight: 200, meta: { - label: 'Media', + label: '#general_media', pathname: 'media', icon: 'icon-user', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.repository.ts index c9b9893e53..7fea19f1ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbMediaTreeRepository const data: UmbMediaTreeRootModel = { unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE, - name: 'Media', + name: '#treeHeaders_media', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts index bd158e586e..00808ce9c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts @@ -44,7 +44,7 @@ const workspaceViews: Array = [ js: () => import('./views/edit/media-workspace-view-edit.element.js'), weight: 200, meta: { - label: 'Media', + label: '#general_details', pathname: 'media', icon: 'icon-picture', }, @@ -62,7 +62,7 @@ const workspaceViews: Array = [ js: () => import('./views/info/media-workspace-view-info.element.js'), weight: 100, meta: { - label: 'Info', + label: '#general_info', pathname: 'info', icon: 'info', }, @@ -83,7 +83,7 @@ const workspaceActions: Array = [ name: 'Save Media Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/section.manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/section.manifests.ts index e06de6f026..3981df871d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/section.manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/section.manifests.ts @@ -9,7 +9,7 @@ const section: ManifestSection = { name: 'Media Section', weight: 500, meta: { - label: 'Media', + label: '#sections_media', pathname: 'media', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/workspace/manifests.ts index f17abf1cf3..7732091c9b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/workspace/manifests.ts @@ -27,7 +27,7 @@ const workspaceActions: Array = [ name: 'Save Member Group Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, @@ -48,7 +48,7 @@ export const workspaceViews: Array = [ js: () => import('./views/info/member-type-workspace-view-info.element.js'), weight: 300, meta: { - label: 'Info', + label: '#general_info', pathname: 'info', icon: 'icon-document', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-section/manifests.ts index 9b9ffe225e..368ad7f0d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-section/manifests.ts @@ -6,7 +6,7 @@ const section: ManifestSection = { name: 'Members Section', weight: 300, meta: { - label: 'Members', + label: '#sections_member', pathname: 'member-management', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/entity-actions/manifests.ts index 177b69c006..1af70b700e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/entity-actions/manifests.ts @@ -15,7 +15,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/member-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/member-type-tree.repository.ts index 80d19d5ef5..fa6123d7d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/member-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/member-type-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbMemberTypeTreeRepository const data: UmbMemberTypeTreeRootModel = { unique: null, entityType: UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, - name: 'Member Types', + name: '#treeHeaders_memberTypes', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/manifests.ts index 336c403747..8acc0d3acd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/manifests.ts @@ -25,7 +25,7 @@ const workspaceViews: Array = [ alias: 'Umb.WorkspaceView.MemberType.Design', name: 'Member Type Workspace Design View', meta: { - label: 'Design', + label: '#general_design', pathname: 'design', icon: 'icon-member-dashed-line', }, @@ -46,7 +46,7 @@ const workspaceActions: Array = [ name: 'Save Member Type Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/manifests.ts index 9198c2ef10..98312be30d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/manifests.ts @@ -27,7 +27,7 @@ const workspaceActions: Array = [ name: 'Save Member Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, @@ -48,7 +48,7 @@ export const workspaceViews: Array = [ js: () => import('./views/content/member-workspace-view-content.element.js'), weight: 100, meta: { - label: 'Content', + label: '#general_details', pathname: 'content', icon: 'icon-document', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts index d3b793e074..9f20431ef3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts @@ -6,7 +6,7 @@ export const manifests = [ element: () => import('./models-builder-dashboard.element.js'), weight: 300, meta: { - label: 'Models Builder', + label: '#dashboardTabs_settingsModelsBuilder', pathname: 'models-builder', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts index 6667d5cbd2..fb2c6018c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts @@ -8,7 +8,7 @@ const section: ManifestSection = { name: 'Packages Section', weight: 200, meta: { - label: 'Packages', + label: '#sections_packages', pathname: 'packages', }, conditions: [ @@ -45,7 +45,7 @@ const sectionsViews: Array = [ element: () => import('./views/installed/installed-packages-section-view.element.js'), weight: 200, meta: { - label: 'Installed', + label: '#packager_installed', pathname: 'installed', icon: 'icon-box', }, @@ -63,7 +63,7 @@ const sectionsViews: Array = [ element: () => import('./views/created/created-packages-section-view.element.js'), weight: 100, meta: { - label: 'Created', + label: '#packager_created', pathname: 'created', icon: 'icon-files', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/sources/package.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/sources/package.server.data.ts index 3b70e8e1b5..a3e1aa7974 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/sources/package.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package/repository/sources/package.server.data.ts @@ -1,5 +1,5 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -import { PackageResource } from '@umbraco-cms/backoffice/external/backend-api'; +import { ManifestResource, PackageResource } from '@umbraco-cms/backoffice/external/backend-api'; import type { CreatePackageRequestModel, UpdatePackageRequestModel, @@ -34,7 +34,7 @@ export class UmbPackageServerDataSource { * @memberof UmbPackageServerDataSource */ getRootItems() { - return tryExecuteAndNotify(this.host, PackageResource.getPackageManifest()); + return tryExecuteAndNotify(this.host, ManifestResource.getManifestManifest()); } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/types.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/types.ts index 774cd13211..b3eb720c52 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/types.ts @@ -1,6 +1,6 @@ -import type { PackageManifestResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { ManifestResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -export type UmbPackage = PackageManifestResponseModel; +export type UmbPackage = ManifestResponseModel; export type PackageManifestResponse = UmbPackage[]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/relation-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/relation-type/manifests.ts index 49fc7fad40..88485c6869 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/relation-type/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/relation-type/manifests.ts @@ -19,7 +19,7 @@ const workspaceViews: Array = [ js: () => import('./views/relation-type-detail-workspace-view.element.js'), weight: 20, meta: { - label: 'Details', + label: '#general_details', pathname: 'details', icon: 'icon-trafic', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts index 6842024300..74f7473793 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts @@ -27,7 +27,7 @@ export const manifests: Array = [ js: () => import('./examine-management-dashboard/dashboard-examine-management.element.js'), weight: 400, meta: { - label: 'Examine Management', + label: '#dashboardTabs_settingsExamine', pathname: 'examine-management', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/manifests.ts index a406ba57df..c1f82d6790 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/manifests.ts @@ -11,7 +11,7 @@ const dashboards: Array = [ js: () => import('./published-status/dashboard-published-status.element.js'), weight: 200, meta: { - label: 'Published Status', + label: '#dashboardTabs_settingsPublishedStatus', pathname: 'published-status', }, conditions: [ @@ -29,7 +29,7 @@ const dashboards: Array = [ js: () => import('./performance-profiling/dashboard-performance-profiling.element.js'), weight: 101, meta: { - label: 'Profiling', + label: '#dashboardTabs_settingsProfiler', pathname: 'profiling', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/entity-actions/create/manifests.ts index da6fef2722..91977b42b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/entity-actions/create/manifests.ts @@ -13,7 +13,7 @@ export const manifests: Array = [ forEntityTypes: [UMB_PARTIAL_VIEW_ROOT_ENTITY_TYPE, UMB_PARTIAL_VIEW_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/partial-view-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/partial-view-tree.repository.ts index d24813369e..27cd6feb94 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/partial-view-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/partial-view-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbPartialViewTreeRepository const data: UmbPartialViewTreeRootModel = { unique: null, entityType: UMB_PARTIAL_VIEW_ROOT_ENTITY_TYPE, - name: 'Partial Views', + name: '#treeHeaders_partialViews', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/manifests.ts index 734cd8e35d..e390ab43c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/manifests.ts @@ -22,7 +22,7 @@ const workspaceActions: Array = [ name: 'Save Partial View', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/entity-actions/create/manifests.ts index 1c8c5aa5da..4b9b260cf4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/entity-actions/create/manifests.ts @@ -13,7 +13,7 @@ export const manifests: Array = [ forEntityTypes: [UMB_SCRIPT_ROOT_ENTITY_TYPE, UMB_SCRIPT_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/script-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/script-tree.repository.ts index 7e12203803..2dec0c4450 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/script-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/script-tree.repository.ts @@ -15,7 +15,7 @@ export class UmbScriptTreeRepository extends UmbTreeRepositoryBase = [ name: 'Save Script Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/entity-actions/create/manifests.ts index df096e95f5..b0b1c1903f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/entity-actions/create/manifests.ts @@ -13,7 +13,7 @@ export const manifests: Array = [ forEntityTypes: [UMB_STYLESHEET_ROOT_ENTITY_TYPE, UMB_STYLESHEET_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create...', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/stylesheet-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/stylesheet-tree.repository.ts index db32e1cd83..3de3703eca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/stylesheet-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/stylesheet-tree.repository.ts @@ -17,7 +17,7 @@ export class UmbStylesheetTreeRepository extends UmbTreeRepositoryBase< const data: UmbStylesheetTreeRootModel = { unique: null, entityType: UMB_STYLESHEET_ROOT_ENTITY_TYPE, - name: 'Stylesheets', + name: '#treeHeaders_stylesheets', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/manifests.ts index ffdb1016de..576b32f168 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/manifests.ts @@ -26,7 +26,7 @@ const workspaceViews: Array = [ js: () => import('./views/code-editor/stylesheet-code-editor-workspace-view.element.js'), weight: 700, meta: { - label: 'Code', + label: '#stylesheet_tabCode', pathname: 'code', icon: 'icon-brackets', }, @@ -44,7 +44,7 @@ const workspaceViews: Array = [ js: () => import('./views/rich-text-rule/stylesheet-rich-text-rule-workspace-view.element.js'), weight: 800, meta: { - label: 'Rich Text Editor', + label: '#stylesheet_tabRule', pathname: 'rich-text-editor', icon: 'icon-font', }, @@ -64,7 +64,7 @@ const workspaceActions: Array = [ name: 'Save Stylesheet Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/entity-actions/manifests.ts index 701e5606e7..f7ec206706 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/entity-actions/manifests.ts @@ -13,7 +13,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_TEMPLATE_ENTITY_TYPE, UMB_TEMPLATE_ROOT_ENTITY_TYPE], meta: { icon: 'icon-add', - label: 'Create', + label: '#actions_create', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/tree/template-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/tree/template-tree.repository.ts index 096aeee850..d4c5dbbcb4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/tree/template-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/tree/template-tree.repository.ts @@ -18,7 +18,7 @@ export class UmbTemplateTreeRepository const data: UmbTemplateTreeRootModel = { unique: null, entityType: UMB_TEMPLATE_ROOT_ENTITY_TYPE, - name: 'Templates', + name: '#treeHeaders_templates', hasChildren: true, isFolder: true, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/manifests.ts index 13a5f9b71b..9648fdcc09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/manifests.ts @@ -31,7 +31,7 @@ const workspaceActions: Array = [ meta: { look: 'primary', color: 'positive', - label: 'Save', + label: '#buttons_save', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts index 7ccc036d14..4c85e03a05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts @@ -1,3 +1,4 @@ +import { loadManifestApi } from '../../../../libs/extension-api/functions/load-manifest-api.function.js'; import { pastePreProcessHandler } from './input-tiny-mce.handlers.js'; import { defaultFallbackConfig } from './input-tiny-mce.defaults.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; @@ -6,7 +7,6 @@ import type { TinyMcePluginArguments, UmbTinyMcePluginBase } from './tiny-mce-pl import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import type { EditorEvent, Editor, RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; -import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; import { type ManifestTinyMcePlugin, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { css, customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts index 62024e27f5..fea4b1dd92 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts @@ -7,7 +7,7 @@ export const dashboard: ManifestDashboard = { js: () => import('./umbraco-news-dashboard.element.js'), weight: 20, meta: { - label: 'Welcome', + label: '#dashboardTabs_contentIntro', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user/current-user-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user/current-user-modal.element.ts index 9ad8bd483b..1e89d04f19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user/current-user-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user/current-user-modal.element.ts @@ -1,12 +1,12 @@ import { UMB_CURRENT_USER_CONTEXT } from '../../current-user.context.js'; import type { UmbCurrentUserModel } from '../../types.js'; -import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit'; import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbModalContext } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-current-user-modal') export class UmbCurrentUserModalElement extends UmbLitElement { @@ -16,6 +16,9 @@ export class UmbCurrentUserModalElement extends UmbLitElement { @state() private _currentUser?: UmbCurrentUserModel; + @state() + private _logOutButtonState?: UUIButtonState; + #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; @@ -50,7 +53,9 @@ export class UmbCurrentUserModalElement extends UmbLitElement { private async _logout() { if (!this.#authContext) return; - this.#authContext.signOut(); + this._logOutButtonState = 'waiting'; + await this.#authContext.signOut(); + this._logOutButtonState = 'success'; } render() { @@ -65,6 +70,7 @@ export class UmbCurrentUserModalElement extends UmbLitElement { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts index 684c9d266d..657fc4355a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts @@ -24,7 +24,9 @@ export class UmbUserGroupTableSectionsColumnLayoutElement extends UmbLitElement this.observe( umbExtensionsRegistry.byType('section'), (sections) => { - this._sectionsNames = sections.filter((x) => this.value.includes(x.alias)).map((x) => x.meta.label || x.name); + this._sectionsNames = sections + .filter((x) => this.value.includes(x.alias)) + .map((x) => (x.meta.label ? this.localize.string(x.meta.label) : x.name)); }, 'umbUserGroupTableSectionsColumnLayoutObserver', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts index 79d6c5adf5..1c2b3c03a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts @@ -40,7 +40,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) { #setUserPermissionLabels(manifests: Array) { this.#userPermissionLabels = manifests.map((manifest) => - manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label ?? '', + manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name, ); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/manifests.ts index 9a24209b37..cae9d05cd7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/manifests.ts @@ -25,7 +25,7 @@ const workspaceActions: Array = [ name: 'Save User Group Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts index 490b97b45e..5b19e06dd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts @@ -94,10 +94,8 @@ export class UmbInputEntityUserPermissionElement extends FormControlMixin(UmbLit #renderPermission(manifest: ManifestEntityUserPermission) { return html` this.#onChangeUserPermission(event, manifest.meta.verbs)}>`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-section/manifests.ts index 72f444e0b0..2a5b10ef93 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-section/manifests.ts @@ -8,7 +8,7 @@ const section: ManifestSection = { name: 'User Management Section', weight: 100, meta: { - label: 'Users', + label: '#sections_users', pathname: 'user-management', }, conditions: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts index a8200fddfc..6e6b8d2f63 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts @@ -34,7 +34,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-check', - label: 'Enable', + label: '#actions_enable', }, conditions: [ { @@ -52,7 +52,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-block', - label: 'Disable', + label: '#actions_disable', }, conditions: [ { @@ -70,7 +70,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-key', - label: 'Change Password', + label: '#user_changePassword', }, }, { @@ -83,7 +83,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-unlocked', - label: 'Unlock', + label: '#actions_unlock', }, conditions: [ { @@ -101,7 +101,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-settings', - label: 'Configure Two-Factor', + label: '#user_configureMfa', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/invite/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/invite/entity-action/manifests.ts index bfce6da496..f2b7db505e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/invite/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/invite/entity-action/manifests.ts @@ -14,7 +14,7 @@ const entityActions: Array = [ forEntityTypes: [UMB_USER_ENTITY_TYPE], meta: { icon: 'icon-message', - label: 'Resend Invite', + label: '#actions_resendInvite', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/manifests.ts index f54a0593a2..9e4423458c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/manifests.ts @@ -28,7 +28,7 @@ const workspaceActions: Array = [ name: 'Save User Workspace Action', api: UmbSubmitWorkspaceAction, meta: { - label: 'Save', + label: '#buttons_save', look: 'primary', color: 'positive', }, diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 96e5b296bc..eee2db6359 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -80,6 +80,7 @@ "@umbraco-cms/backoffice/property-action": ["./src/packages/core/property-action/index.ts"], "@umbraco-cms/backoffice/property-editor": ["./src/packages/core/property-editor/index.ts"], "@umbraco-cms/backoffice/property": ["./src/packages/core/property/index.ts"], + "@umbraco-cms/backoffice/recycle-bin": ["./src/packages/core/recycle-bin/index.ts"], "@umbraco-cms/backoffice/relation-type": ["./src/packages/relations/relation-types/index.ts"], "@umbraco-cms/backoffice/relations": ["./src/packages/relations/relations/index.ts"], "@umbraco-cms/backoffice/repository": ["./src/packages/core/repository/index.ts"], @@ -107,6 +108,7 @@ "@umbraco-cms/backoffice/webhook": ["./src/packages/webhook/index.ts"], "@umbraco-cms/backoffice/workspace": ["./src/packages/core/workspace/index.ts"], "@umbraco-cms/backoffice/external/backend-api": ["./src/external/backend-api/index.ts"], + "@umbraco-cms/backoffice/external/base64-js": ["./src/external/base64-js/index.ts"], "@umbraco-cms/backoffice/external/diff": ["./src/external/diff/index.ts"], "@umbraco-cms/backoffice/external/dompurify": ["./src/external/dompurify/index.ts"], "@umbraco-cms/backoffice/external/lit": ["./src/external/lit/index.ts"], diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index f058a79d6f..4ecc0d2ab2 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -22,7 +22,7 @@ export const plugins: PluginOption[] = [ dest: 'umbraco/backoffice/css', }, { - src: 'src/assets/*.svg', + src: 'src/assets/*', dest: 'umbraco/backoffice/assets', }, { diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 3da2a3dc6f..23006d64e2 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -22,7 +22,6 @@ export default { reporters: ['lcovonly', 'text-summary'], }, plugins: [ - esbuildPlugin({ ts: true, tsconfig: './tsconfig.json', target: 'auto', json: true }), importMapsPlugin({ inject: { importMap: createImportMap({ @@ -35,8 +34,9 @@ export default { }, }), commonjs({ - include: ['node_modules/**', 'src/external/**'], + include: ['node_modules/base64-js/**/*', 'node_modules/tinymce/**/*'] }), + esbuildPlugin({ ts: true, tsconfig: './tsconfig.json', target: 'auto', json: true }), ], testRunnerHtml: (testFramework, devMode) => `