handle rich text rules on the client

This commit is contained in:
Mads Rasmussen
2023-12-19 12:51:25 +01:00
parent a2f30c27ac
commit e46072ba27
10 changed files with 86 additions and 270 deletions

View File

@@ -52,25 +52,7 @@ export class UmbStylesheetRuleInputElement extends FormControlMixin(UmbLitElemen
return true;
}
setRules(rules: UmbSortableStylesheetRule[]) {
/*
const newRules = rules.map((r, i) => ({ ...r, sortOrder: i }));
this.#rules.next(newRules);
this.sendRulesGetContent();
*/
}
#onChange(event: UUIComboboxEvent) {
event.stopPropagation();
const target = event.target as UUIComboboxElement;
if (typeof target?.value === 'string') {
this.value = target.value;
this.dispatchEvent(new UmbChangeEvent());
}
}
addRule = (rule: UmbSortableStylesheetRule | null = null) => {
#openRuleSettings = (rule: UmbSortableStylesheetRule | null = null) => {
if (!this.#modalManager) throw new Error('Modal context not found');
const modalContext = this.#modalManager.open(UMB_STYLESHEET_RULE_SETTINGS_MODAL, {
value: {
@@ -79,18 +61,15 @@ export class UmbStylesheetRuleInputElement extends FormControlMixin(UmbLitElemen
});
modalContext?.onSubmit().then((value) => {
console.log(value);
/*
if (result.rule) {
console.log(result.rule);
//this.#context?.setRules([...this._rules, { ...result.rule, sortOrder: this._rules.length }]);
}
*/
const newRule: UmbSortableStylesheetRule = { ...value.rule, sortOrder: this.rules.length };
this.rules = [...this.rules, newRule];
this.dispatchEvent(new UmbChangeEvent());
});
};
removeRule = (rule: UmbSortableStylesheetRule) => {
//const rules = this._rules?.filter((r) => r.name !== rule.name);
#removeRule = (rule: UmbSortableStylesheetRule) => {
this.rules = this.rules.filter((r) => r.name !== rule.name);
this.dispatchEvent(new UmbChangeEvent());
};
render() {
@@ -103,11 +82,15 @@ export class UmbStylesheetRuleInputElement extends FormControlMixin(UmbLitElemen
<umb-stylesheet-rule-ref
name=${rule.name}
detail=${rule.selector}
data-umb-rule-name="${ifDefined(rule?.name)}"></umb-stylesheet-rule-ref>
data-umb-rule-name="${ifDefined(rule?.name)}">
<uui-action-bar slot="actions">
<uui-button @click=${() => this.#removeRule(rule)} label="Remove ${rule.name}">Remove</uui-button>
</uui-action-bar>
</umb-stylesheet-rule-ref>
`,
)}
</uui-ref-list>
<uui-button label="Add rule" look="placeholder" @click=${() => this.addRule(null)}>Add</uui-button>
<uui-button label="Add rule" look="placeholder" @click=${() => this.#openRuleSettings(null)}>Add</uui-button>
`;
}

View File

@@ -1,4 +1,3 @@
export * from './rich-text-rule/index.js';
export * from './item/index.js';
export * from './stylesheet-detail.repository.js';

View File

@@ -1 +0,0 @@
export { UmbStylesheetRichTextRuleRepository } from './stylesheet-rich-text-rule.repository.js';

View File

@@ -1,43 +0,0 @@
import { UmbStylesheetRichTextRuleServerDataSource } from './stylesheet-rich-text-rule.server.data-source.js';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import {
ExtractRichTextStylesheetRulesRequestModel,
InterpolateRichTextStylesheetRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
export class UmbStylesheetRichTextRuleRepository extends UmbRepositoryBase {
#dataSource;
constructor(host: UmbControllerHost) {
super(host);
this.#dataSource = new UmbStylesheetRichTextRuleServerDataSource(host);
}
requestStylesheetRules(unique: string) {
return this.#dataSource.getStylesheetRichTextRules(unique);
}
/**
* Existing content + array of rules => new content string
*
* @param {InterpolateRichTextStylesheetRequestModel} data
* @return {*} {Promise<DataSourceResponse<InterpolateRichTextStylesheetResponseModel>>}
* @memberof UmbStylesheetRepository
*/
interpolateStylesheetRules(data: InterpolateRichTextStylesheetRequestModel) {
return this.#dataSource.interpolateStylesheetRules(data);
}
/**
* content string => array of rules
*
* @param {ExtractRichTextStylesheetRulesRequestModel} data
* @return {*}
* @memberof UmbStylesheetRepository
*/
extractStylesheetRules(data: ExtractRichTextStylesheetRulesRequestModel) {
return this.#dataSource.extractStylesheetRules(data);
}
}

View File

@@ -1,67 +0,0 @@
import { UmbServerPathUniqueSerializer } from '../../../utils/index.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import {
ExtractRichTextStylesheetRulesRequestModel,
InterpolateRichTextStylesheetRequestModel,
StylesheetResource,
} from '@umbraco-cms/backoffice/backend-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
/**
* A data source for the Stylesheet rich text rules that fetches data from the server
* @export
* @class UmbStylesheetRichTextRuleServerDataSource
*/
export class UmbStylesheetRichTextRuleServerDataSource {
#host: UmbControllerHost;
#serverPathUniqueSerializer = new UmbServerPathUniqueSerializer();
/**
* Creates an instance of UmbStylesheetRichTextRuleServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbStylesheetRichTextRuleServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Get's the rich text rules for a stylesheet
*
* @param {string} unique
* @return {*}
* @memberof UmbStylesheetRichTextRuleServerDataSource
*/
getStylesheetRichTextRules(unique: string) {
const path = this.#serverPathUniqueSerializer.toServerPath(unique);
return tryExecuteAndNotify(this.#host, StylesheetResource.getStylesheetRichTextRules({ path }));
}
/**
* Extracts the rich text rules from a stylesheet string. In simple words: takes a stylesheet string and returns a array of rules.
*
* @param {ExtractRichTextStylesheetRulesRequestModel} data
* @return {*}
* @memberof UmbStylesheetRichTextRuleServerDataSource
*/
extractStylesheetRules(data: ExtractRichTextStylesheetRulesRequestModel) {
return tryExecuteAndNotify(
this.#host,
StylesheetResource.postStylesheetRichTextExtractRules({ requestBody: data }),
);
}
/**
* Interpolates the rich text rules from a stylesheet string. In simple words: takes a array of rules and existing content. Returns new content with the rules applied.
*
* @param {InterpolateRichTextStylesheetRequestModel} data
* @return {*}
* @memberof UmbStylesheetRichTextRuleServerDataSource
*/
interpolateStylesheetRules(data: InterpolateRichTextStylesheetRequestModel) {
return tryExecuteAndNotify(
this.#host,
StylesheetResource.postStylesheetRichTextInterpolateRules({ requestBody: data }),
);
}
}

View File

@@ -0,0 +1 @@
export { UmbStylesheetRuleManager } from './stylesheet-rule-manager.js';

View File

@@ -0,0 +1,47 @@
import { RichTextRuleModel } from '@umbraco-cms/backoffice/backend-api';
export class UmbStylesheetRuleManager {
#umbRuleRegex = /\/\*\*\s*umb_name:\s*(?<name>[^*\r\n]*?)\s*\*\/\s*(?<selector>[^,{]*?)\s*{\s*(?<styles>.*?)\s*}/gis;
/**
* Extracts umbraco rules from a stylesheet content
* @param {string} stylesheetContent
* @return {*}
* @memberof UmbStylesheetRuleManager
*/
extractRules(stylesheetContent: string) {
const regex = this.#umbRuleRegex;
if (!stylesheetContent) throw Error('No Stylesheet content');
return [...stylesheetContent.matchAll(regex)].map((match) => match.groups);
}
/**
* Inserts umbraco rules into stylesheet content
* @param {string} stylesheetContent
* @param {UmbStylesheetRule[]} rules
* @return {*}
* @memberof UmbStylesheetRuleManager
*/
insertRules(stylesheetContent: string, rules: Array<RichTextRuleModel>) {
const regex = this.#umbRuleRegex;
if (!stylesheetContent) throw Error('No Stylesheet content');
if (!stylesheetContent && !rules) return { content: '' };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const cleanedContent = stylesheetContent?.replaceAll(regex, '');
const newContent = `
${cleanedContent.replace(/[\r\n]+$/, '')}
${rules
?.map(
(rule) => `
/**umb_name:${rule.name}*/
${rule.selector} {
${rule.styles}
}
`,
)
.join('')}`;
return newContent;
}
}

View File

@@ -99,34 +99,6 @@ export class UmbStylesheetWorkspaceContext
}
}
/*
async sendRulesGetContent() {
const requestBody = {
content: this.getData()?.content,
rules: this.getRules(),
};
const { data } = await this.repository.interpolateStylesheetRules(requestBody);
this.setContent(data?.content ?? '');
}
async sendContentGetRules() {
const content = this.getData()?.content;
if (!content) throw Error('No content');
const { data } = await this.repository.extractStylesheetRules({ content });
this.setRules(data?.rules ?? []);
}
getRules() {
return this.#rules.getValue();
}
updateRule(unique: string, rule: RichTextRuleModelSortable) {
this.#rules.updateOne(unique, rule);
this.sendRulesGetContent();
}
*/
public destroy(): void {
this.#data.destroy();
}

View File

@@ -1,10 +1,12 @@
import { UmbStylesheetWorkspaceContext } from '../../stylesheet-workspace.context.js';
import { UmbStylesheetRichTextRuleRepository } from '../../../repository/index.js';
import { UmbSortableStylesheetRule } from '../../../types.js';
import { UmbStylesheetRuleInputElement } from '../../../components/index.js';
import { UmbStylesheetRuleManager } from '../../../utils/index.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-stylesheet-rich-text-rule-workspace-view')
export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement {
@@ -12,24 +14,36 @@ export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement
_rules: UmbSortableStylesheetRule[] = [];
#context?: UmbStylesheetWorkspaceContext;
#stylesheetRichTextRuleRepository = new UmbStylesheetRichTextRuleRepository(this);
#stylesheetRuleManager = new UmbStylesheetRuleManager();
#stylesheetContent = '';
constructor() {
super();
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
this.#context = workspaceContext as UmbStylesheetWorkspaceContext;
const unique = this.#context?.getEntityId();
this.#setRules(unique);
this.#observeContent();
});
}
async #setRules(unique: string) {
const { data } = await this.#stylesheetRichTextRuleRepository.requestStylesheetRules(unique);
#observeContent() {
if (!this.#context?.content) return;
this.observe(
this.#context.content,
(content) => {
this.#stylesheetContent = content;
this.#extractRules(content);
},
'umbStylesheetContentObserver',
);
}
if (data) {
this._rules = data.rules ?? [];
#extractRules(content: string | undefined) {
if (content) {
const rules = this.#stylesheetRuleManager.extractRules(content);
this._rules = [...rules];
} else {
this._rules = [];
}
}
@@ -37,9 +51,8 @@ export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement
event.stopPropagation();
const target = event.target as UmbStylesheetRuleInputElement;
const rules = target.rules;
console.log(rules);
console.log(event);
debugger;
const newContent = this.#stylesheetRuleManager.insertRules(this.#stylesheetContent, rules);
this.#context?.setContent(newContent);
}
render() {

View File

@@ -1,88 +0,0 @@
import { UMB_STYLESHEET_WORKSPACE_CONTEXT, UmbStylesheetWorkspaceContext } from '../../stylesheet-workspace.context.js';
import { UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR_MODAL } from './stylesheet-rich-text-rule-workspace-view.element.js';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { RichTextRuleModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@customElement('umb-stylesheet-rich-text-editor-rule')
export default class UmbStylesheetRichTextEditorRuleElement extends UmbLitElement {
@property({ attribute: false })
private rule: RichTextRuleModel | null = null;
#context?: UmbStylesheetWorkspaceContext;
private _modalContext?: UmbModalManagerContext;
constructor() {
super();
this.consumeContext(UMB_STYLESHEET_WORKSPACE_CONTEXT, (workspaceContext) => {
this.#context = workspaceContext;
});
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this._modalContext = instance;
});
}
#openModal() {
if (!this._modalContext) throw new Error('Modal context not found');
const modal = this._modalContext.open(UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR_MODAL, {
value: {
rule: this.rule,
},
});
modal?.onSubmit().then((value) => {
if (value.rule && this.rule?.name) {
this.#context?.updateRule(this.rule?.name, value.rule);
}
});
}
#removeRule() {
//TODO: SPORTER BREAKS THAT - rules are removed from the data but not from the DOM
if (!this.#context) throw new Error('Context not found');
this.#context.setRules(this.#context.getRules().filter((r) => r.name !== this.rule?.name));
}
render() {
return html`
<div class="rule-name"><uui-icon name="icon-navigation"></uui-icon>${this.rule?.name}</div>
<div class="rule-actions">
<uui-button label="Edit" look="secondary" @click=${this.#openModal}>Edit</uui-button
><uui-button label="Remove" look="secondary" color="danger" @click=${this.#removeRule}>Remove</uui-button>
</div>
`;
}
static styles = [
UmbTextStyles,
css`
:host {
display: flex;
width: 100%;
justify-content: space-between;
padding: var(--uui-size-2);
align-items: center;
border-radius: var(--uui-border-radius);
background-color: var(--uui-color-surface-alt);
margin-bottom: var(--uui-size-space-4);
}
.rule-name {
display: flex;
align-items: center;
gap: var(--uui-size-2);
padding-left: var(--uui-size-2);
font-weight: bold;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
'umb-stylesheet-rich-text-editor-rule': UmbStylesheetRichTextEditorRuleElement;
}
}