mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 16:16:41 +08:00
feat(config): add option to disable custom html functionality (#26956)
This commit is contained in:
24
core/src/components.d.ts
vendored
24
core/src/components.d.ts
vendored
@ -192,7 +192,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -1039,7 +1039,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"loadingSpinner"?: SpinnerTypes | null;
|
"loadingSpinner"?: SpinnerTypes | null;
|
||||||
/**
|
/**
|
||||||
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"loadingText"?: string | IonicSafeString;
|
"loadingText"?: string | IonicSafeString;
|
||||||
}
|
}
|
||||||
@ -1422,7 +1422,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* Optional text content to display in the loading indicator.
|
* Optional text content to display in the loading indicator. This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -2221,7 +2221,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"pullingIcon"?: SpinnerTypes | string | null;
|
"pullingIcon"?: SpinnerTypes | string | null;
|
||||||
/**
|
/**
|
||||||
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"pullingText"?: string | IonicSafeString;
|
"pullingText"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -2229,7 +2229,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"refreshingSpinner"?: SpinnerTypes | null;
|
"refreshingSpinner"?: SpinnerTypes | null;
|
||||||
/**
|
/**
|
||||||
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"refreshingText"?: string | IonicSafeString;
|
"refreshingText"?: string | IonicSafeString;
|
||||||
}
|
}
|
||||||
@ -2990,7 +2990,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* Message to be shown in the toast.
|
* Message to be shown in the toast. This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -4147,7 +4147,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -5045,7 +5045,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"loadingSpinner"?: SpinnerTypes | null;
|
"loadingSpinner"?: SpinnerTypes | null;
|
||||||
/**
|
/**
|
||||||
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"loadingText"?: string | IonicSafeString;
|
"loadingText"?: string | IonicSafeString;
|
||||||
}
|
}
|
||||||
@ -5420,7 +5420,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* Optional text content to display in the loading indicator.
|
* Optional text content to display in the loading indicator. This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -6188,7 +6188,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"pullingIcon"?: SpinnerTypes | string | null;
|
"pullingIcon"?: SpinnerTypes | string | null;
|
||||||
/**
|
/**
|
||||||
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"pullingText"?: string | IonicSafeString;
|
"pullingText"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
@ -6196,7 +6196,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"refreshingSpinner"?: SpinnerTypes | null;
|
"refreshingSpinner"?: SpinnerTypes | null;
|
||||||
/**
|
/**
|
||||||
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"refreshingText"?: string | IonicSafeString;
|
"refreshingText"?: string | IonicSafeString;
|
||||||
}
|
}
|
||||||
@ -6999,7 +6999,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"leaveAnimation"?: AnimationBuilder;
|
"leaveAnimation"?: AnimationBuilder;
|
||||||
/**
|
/**
|
||||||
* Message to be shown in the toast.
|
* Message to be shown in the toast. This property accepts custom HTML as a string. Developers who only want to pass plain text can disable the custom HTML functionality by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
"message"?: string | IonicSafeString;
|
"message"?: string | IonicSafeString;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||||
import { Component, Element, Event, Host, Listen, Method, Prop, Watch, forceUpdate, h } from '@stencil/core';
|
import { Component, Element, Event, Host, Listen, Method, Prop, Watch, forceUpdate, h } from '@stencil/core';
|
||||||
|
|
||||||
|
import { config } from '../../global/config';
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import type {
|
import type {
|
||||||
AlertButton,
|
AlertButton,
|
||||||
@ -12,6 +13,7 @@ import type {
|
|||||||
OverlayEventDetail,
|
OverlayEventDetail,
|
||||||
OverlayInterface,
|
OverlayInterface,
|
||||||
} from '../../interface';
|
} from '../../interface';
|
||||||
|
import { ENABLE_HTML_CONTENT_DEFAULT } from '../../utils/config';
|
||||||
import type { Gesture } from '../../utils/gesture';
|
import type { Gesture } from '../../utils/gesture';
|
||||||
import { createButtonActiveGesture } from '../../utils/gesture/button-active';
|
import { createButtonActiveGesture } from '../../utils/gesture/button-active';
|
||||||
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
|
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
|
||||||
@ -39,6 +41,7 @@ import { mdLeaveAnimation } from './animations/md.leave';
|
|||||||
scoped: true,
|
scoped: true,
|
||||||
})
|
})
|
||||||
export class Alert implements ComponentInterface, OverlayInterface {
|
export class Alert implements ComponentInterface, OverlayInterface {
|
||||||
|
private customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
||||||
private activeId?: string;
|
private activeId?: string;
|
||||||
private inputType?: string;
|
private inputType?: string;
|
||||||
private processedInputs: AlertInput[] = [];
|
private processedInputs: AlertInput[] = [];
|
||||||
@ -93,6 +96,11 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
* `<Ionic>`
|
* `<Ionic>`
|
||||||
*
|
*
|
||||||
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
||||||
|
*
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() message?: string | IonicSafeString;
|
@Prop() message?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -579,6 +587,19 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderAlertMessage(msgId: string) {
|
||||||
|
const { customHTMLEnabled, message } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(message)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={msgId} class="alert-message">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
|
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
@ -631,7 +652,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(message)}></div>
|
{this.renderAlertMessage(msgId)}
|
||||||
|
|
||||||
{this.renderAlertInputs()}
|
{this.renderAlertInputs()}
|
||||||
{this.renderAlertButtons()}
|
{this.renderAlertButtons()}
|
||||||
|
|||||||
40
core/src/components/alert/test/alert.spec.ts
Normal file
40
core/src/components/alert/test/alert.spec.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
import { Alert } from '../alert';
|
||||||
|
import { config } from '../../../global/config';
|
||||||
|
|
||||||
|
describe('alert: custom html', () => {
|
||||||
|
it('should allow for custom html by default', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Alert],
|
||||||
|
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.alert-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: true });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Alert],
|
||||||
|
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.alert-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: false });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Alert],
|
||||||
|
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.alert-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,6 +4,7 @@ import { Component, Host, Prop, h } from '@stencil/core';
|
|||||||
import { config } from '../../global/config';
|
import { config } from '../../global/config';
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import type { SpinnerTypes } from '../../interface';
|
import type { SpinnerTypes } from '../../interface';
|
||||||
|
import { ENABLE_HTML_CONTENT_DEFAULT } from '../../utils/config';
|
||||||
import type { IonicSafeString } from '../../utils/sanitization';
|
import type { IonicSafeString } from '../../utils/sanitization';
|
||||||
import { sanitizeDOMString } from '../../utils/sanitization';
|
import { sanitizeDOMString } from '../../utils/sanitization';
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ import { sanitizeDOMString } from '../../utils/sanitization';
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export class InfiniteScrollContent implements ComponentInterface {
|
export class InfiniteScrollContent implements ComponentInterface {
|
||||||
|
private customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An animated SVG spinner that shows while loading.
|
* An animated SVG spinner that shows while loading.
|
||||||
*/
|
*/
|
||||||
@ -28,6 +31,11 @@ export class InfiniteScrollContent implements ComponentInterface {
|
|||||||
* `<Ionic>`
|
* `<Ionic>`
|
||||||
*
|
*
|
||||||
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
||||||
|
*
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() loadingText?: string | IonicSafeString;
|
@Prop() loadingText?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -41,6 +49,15 @@ export class InfiniteScrollContent implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderLoadingText() {
|
||||||
|
const { customHTMLEnabled, loadingText } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div class="infinite-loading-text" innerHTML={sanitizeDOMString(loadingText)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class="infinite-loading-text">{this.loadingText}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
return (
|
return (
|
||||||
@ -58,9 +75,7 @@ export class InfiniteScrollContent implements ComponentInterface {
|
|||||||
<ion-spinner name={this.loadingSpinner} />
|
<ion-spinner name={this.loadingSpinner} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.loadingText !== undefined && (
|
{this.loadingText !== undefined && this.renderLoadingText()}
|
||||||
<div class="infinite-loading-text" innerHTML={sanitizeDOMString(this.loadingText)} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Host>
|
</Host>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
import { InfiniteScrollContent } from '../infinite-scroll-content';
|
||||||
|
import { config } from '../../../global/config';
|
||||||
|
|
||||||
|
describe('infinite-scroll-content: custom html', () => {
|
||||||
|
it('should allow for custom html by default', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [InfiniteScrollContent],
|
||||||
|
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.infinite-loading-text');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: true });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [InfiniteScrollContent],
|
||||||
|
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.infinite-loading-text');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: false });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [InfiniteScrollContent],
|
||||||
|
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text2</button>"></ion-infinite-scroll-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.infinite-loading-text');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
OverlayInterface,
|
OverlayInterface,
|
||||||
SpinnerTypes,
|
SpinnerTypes,
|
||||||
} from '../../interface';
|
} from '../../interface';
|
||||||
|
import { ENABLE_HTML_CONTENT_DEFAULT } from '../../utils/config';
|
||||||
import { BACKDROP, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
|
import { BACKDROP, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
|
||||||
import type { IonicSafeString } from '../../utils/sanitization';
|
import type { IonicSafeString } from '../../utils/sanitization';
|
||||||
import { sanitizeDOMString } from '../../utils/sanitization';
|
import { sanitizeDOMString } from '../../utils/sanitization';
|
||||||
@ -34,6 +35,7 @@ import { mdLeaveAnimation } from './animations/md.leave';
|
|||||||
scoped: true,
|
scoped: true,
|
||||||
})
|
})
|
||||||
export class Loading implements ComponentInterface, OverlayInterface {
|
export class Loading implements ComponentInterface, OverlayInterface {
|
||||||
|
private customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
||||||
private durationTimeout?: ReturnType<typeof setTimeout>;
|
private durationTimeout?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
presented = false;
|
presented = false;
|
||||||
@ -61,6 +63,11 @@ export class Loading implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional text content to display in the loading indicator.
|
* Optional text content to display in the loading indicator.
|
||||||
|
*
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() message?: string | IonicSafeString;
|
@Prop() message?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -187,6 +194,19 @@ export class Loading implements ComponentInterface, OverlayInterface {
|
|||||||
this.dismiss(undefined, BACKDROP);
|
this.dismiss(undefined, BACKDROP);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private renderLoadingMessage(msgId: string) {
|
||||||
|
const { customHTMLEnabled, message } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div class="loading-content" id={msgId} innerHTML={sanitizeDOMString(message)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="loading-content" id={msgId}>
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, spinner, htmlAttributes, overlayIndex } = this;
|
const { message, spinner, htmlAttributes, overlayIndex } = this;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
@ -226,9 +246,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message !== undefined && (
|
{message !== undefined && this.renderLoadingMessage(msgId)}
|
||||||
<div class="loading-content" id={msgId} innerHTML={sanitizeDOMString(message)}></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div tabindex="0"></div>
|
<div tabindex="0"></div>
|
||||||
|
|||||||
40
core/src/components/loading/test/loading.spec.ts
Normal file
40
core/src/components/loading/test/loading.spec.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
import { Loading } from '../loading';
|
||||||
|
import { config } from '../../../global/config';
|
||||||
|
|
||||||
|
describe('alert: custom html', () => {
|
||||||
|
it('should allow for custom html by default', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Loading],
|
||||||
|
html: `<ion-loading message="<button class='custom-html'>Custom Text</button>"></ion-loading>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.loading-content');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: true });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Loading],
|
||||||
|
html: `<ion-loading message="<button class='custom-html'>Custom Text</button>"></ion-loading>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.loading-content');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: false });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Loading],
|
||||||
|
html: `<ion-loading message="<button class='custom-html'>Custom Text</button>"></ion-loading>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = page.body.querySelector('.loading-content');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -5,6 +5,7 @@ import { arrowDown, caretBackSharp } from 'ionicons/icons';
|
|||||||
import { config } from '../../global/config';
|
import { config } from '../../global/config';
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import type { SpinnerTypes } from '../../interface';
|
import type { SpinnerTypes } from '../../interface';
|
||||||
|
import { ENABLE_HTML_CONTENT_DEFAULT } from '../../utils/config';
|
||||||
import { isPlatform } from '../../utils/platform';
|
import { isPlatform } from '../../utils/platform';
|
||||||
import type { IonicSafeString } from '../../utils/sanitization';
|
import type { IonicSafeString } from '../../utils/sanitization';
|
||||||
import { sanitizeDOMString } from '../../utils/sanitization';
|
import { sanitizeDOMString } from '../../utils/sanitization';
|
||||||
@ -14,6 +15,8 @@ import { SPINNERS } from '../spinner/spinner-configs';
|
|||||||
tag: 'ion-refresher-content',
|
tag: 'ion-refresher-content',
|
||||||
})
|
})
|
||||||
export class RefresherContent implements ComponentInterface {
|
export class RefresherContent implements ComponentInterface {
|
||||||
|
private customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
||||||
|
|
||||||
@Element() el!: HTMLIonRefresherContentElement;
|
@Element() el!: HTMLIonRefresherContentElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +34,11 @@ export class RefresherContent implements ComponentInterface {
|
|||||||
* `<Ionic>`
|
* `<Ionic>`
|
||||||
*
|
*
|
||||||
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
||||||
|
*
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() pullingText?: string | IonicSafeString;
|
@Prop() pullingText?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -47,6 +55,11 @@ export class RefresherContent implements ComponentInterface {
|
|||||||
* `<Ionic>`
|
* `<Ionic>`
|
||||||
*
|
*
|
||||||
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
|
||||||
|
*
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() refreshingText?: string | IonicSafeString;
|
@Prop() refreshingText?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -68,6 +81,24 @@ export class RefresherContent implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderPullingText() {
|
||||||
|
const { customHTMLEnabled, pullingText } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div class="refresher-pulling-text" innerHTML={sanitizeDOMString(pullingText)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class="refresher-pulling-text">{pullingText}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderRefreshingText() {
|
||||||
|
const { customHTMLEnabled, refreshingText } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div class="refresher-refreshing-text" innerHTML={sanitizeDOMString(refreshingText)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class="refresher-refreshing-text">{refreshingText}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const pullingIcon = this.pullingIcon;
|
const pullingIcon = this.pullingIcon;
|
||||||
const hasSpinner = pullingIcon != null && (SPINNERS[pullingIcon] as any) !== undefined;
|
const hasSpinner = pullingIcon != null && (SPINNERS[pullingIcon] as any) !== undefined;
|
||||||
@ -93,9 +124,7 @@ export class RefresherContent implements ComponentInterface {
|
|||||||
<ion-icon icon={this.pullingIcon} lazy={false}></ion-icon>
|
<ion-icon icon={this.pullingIcon} lazy={false}></ion-icon>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.pullingText !== undefined && (
|
{this.pullingText !== undefined && this.renderPullingText()}
|
||||||
<div class="refresher-pulling-text" innerHTML={sanitizeDOMString(this.pullingText)}></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="refresher-refreshing">
|
<div class="refresher-refreshing">
|
||||||
{this.refreshingSpinner && (
|
{this.refreshingSpinner && (
|
||||||
@ -103,9 +132,7 @@ export class RefresherContent implements ComponentInterface {
|
|||||||
<ion-spinner name={this.refreshingSpinner}></ion-spinner>
|
<ion-spinner name={this.refreshingSpinner}></ion-spinner>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.refreshingText !== undefined && (
|
{this.refreshingText !== undefined && this.renderRefreshingText()}
|
||||||
<div class="refresher-refreshing-text" innerHTML={sanitizeDOMString(this.refreshingText)}></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Host>
|
</Host>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
import { RefresherContent } from '../refresher-content';
|
||||||
|
import { config } from '../../../global/config';
|
||||||
|
|
||||||
|
describe('refresher-content: custom html', () => {
|
||||||
|
it('should allow for custom html by default', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [RefresherContent],
|
||||||
|
html: `<ion-refresher-content pulling-text="<button class='custom-pulling-html'>Custom Pulling Text</button>" refreshing-text="<button class='custom-refreshing-html'>Custom Refreshing Text</button>"></ion-refresher-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pullingContent = page.body.querySelector('.refresher-pulling-text');
|
||||||
|
expect(pullingContent.textContent).toContain('Custom Pulling Text');
|
||||||
|
expect(pullingContent.querySelector('button.custom-pulling-html')).not.toBe(null);
|
||||||
|
|
||||||
|
const refreshingContent = page.body.querySelector('.refresher-refreshing-text');
|
||||||
|
expect(refreshingContent.textContent).toContain('Custom Refreshing Text');
|
||||||
|
expect(refreshingContent.querySelector('button.custom-refreshing-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: true });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [RefresherContent],
|
||||||
|
html: `<ion-refresher-content pulling-text="<button class='custom-pulling-html'>Custom Pulling Text</button>" refreshing-text="<button class='custom-refreshing-html'>Custom Refreshing Text</button>"></ion-refresher-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pullingContent = page.body.querySelector('.refresher-pulling-text');
|
||||||
|
expect(pullingContent.textContent).toContain('Custom Pulling Text');
|
||||||
|
expect(pullingContent.querySelector('button.custom-pulling-html')).not.toBe(null);
|
||||||
|
|
||||||
|
const refreshingContent = page.body.querySelector('.refresher-refreshing-text');
|
||||||
|
expect(refreshingContent.textContent).toContain('Custom Refreshing Text');
|
||||||
|
expect(refreshingContent.querySelector('button.custom-refreshing-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: false });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [RefresherContent],
|
||||||
|
html: `<ion-refresher-content pulling-text="<button class='custom-pulling-html'>Custom Pulling Text</button>" refreshing-text="<button class='custom-html'>Custom Refreshing Text</button>"></ion-refresher-content>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pullingContent = page.body.querySelector('.refresher-pulling-text');
|
||||||
|
expect(pullingContent.textContent).toContain('Custom Pulling Text');
|
||||||
|
expect(pullingContent.querySelector('button.custom-pulling-html')).toBe(null);
|
||||||
|
|
||||||
|
const refreshingContent = page.body.querySelector('.refresher-refreshing-text');
|
||||||
|
expect(refreshingContent.textContent).toContain('Custom Refreshing Text');
|
||||||
|
expect(refreshingContent.querySelector('button.custom-refreshing-html')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
43
core/src/components/toast/test/toast.spec.ts
Normal file
43
core/src/components/toast/test/toast.spec.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
import { Toast } from '../toast';
|
||||||
|
import { config } from '../../../global/config';
|
||||||
|
|
||||||
|
describe('alert: custom html', () => {
|
||||||
|
it('should allow for custom html by default', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Toast],
|
||||||
|
html: `<ion-toast message="<button class='custom-html'>Custom Text</button>"></ion-toast>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = page.body.querySelector('ion-toast');
|
||||||
|
const content = toast.shadowRoot.querySelector('.toast-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: true });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Toast],
|
||||||
|
html: `<ion-toast message="<button class='custom-html'>Custom Text</button>"></ion-toast>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = page.body.querySelector('ion-toast');
|
||||||
|
const content = toast.shadowRoot.querySelector('.toast-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow for custom html', async () => {
|
||||||
|
config.reset({ innerHTMLTemplatesEnabled: false });
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Toast],
|
||||||
|
html: `<ion-toast message="<button class='custom-html'>Custom Text</button>"></ion-toast>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = page.body.querySelector('ion-toast');
|
||||||
|
const content = toast.shadowRoot.querySelector('.toast-message');
|
||||||
|
expect(content.textContent).toContain('Custom Text');
|
||||||
|
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -11,6 +11,7 @@ import type {
|
|||||||
OverlayInterface,
|
OverlayInterface,
|
||||||
ToastButton,
|
ToastButton,
|
||||||
} from '../../interface';
|
} from '../../interface';
|
||||||
|
import { ENABLE_HTML_CONTENT_DEFAULT } from '../../utils/config';
|
||||||
import { printIonWarning } from '../../utils/logging';
|
import { printIonWarning } from '../../utils/logging';
|
||||||
import { dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
|
import { dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
|
||||||
import type { IonicSafeString } from '../../utils/sanitization';
|
import type { IonicSafeString } from '../../utils/sanitization';
|
||||||
@ -43,6 +44,7 @@ import type { ToastAttributes, ToastPosition, ToastLayout } from './toast-interf
|
|||||||
shadow: true,
|
shadow: true,
|
||||||
})
|
})
|
||||||
export class Toast implements ComponentInterface, OverlayInterface {
|
export class Toast implements ComponentInterface, OverlayInterface {
|
||||||
|
private customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
||||||
private durationTimeout?: ReturnType<typeof setTimeout>;
|
private durationTimeout?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
presented = false;
|
presented = false;
|
||||||
@ -99,6 +101,10 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to be shown in the toast.
|
* Message to be shown in the toast.
|
||||||
|
* This property accepts custom HTML as a string.
|
||||||
|
* Developers who only want to pass plain text
|
||||||
|
* can disable the custom HTML functionality
|
||||||
|
* by setting `innerHTMLTemplatesEnabled: false` in the Ionic config.
|
||||||
*/
|
*/
|
||||||
@Prop() message?: string | IonicSafeString;
|
@Prop() message?: string | IonicSafeString;
|
||||||
|
|
||||||
@ -299,6 +305,19 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderToastMessage() {
|
||||||
|
const { customHTMLEnabled, message } = this;
|
||||||
|
if (customHTMLEnabled) {
|
||||||
|
return <div class="toast-message" part="message" innerHTML={sanitizeDOMString(message)}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="toast-message" part="message">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { layout, el } = this;
|
const { layout, el } = this;
|
||||||
const allButtons = this.getButtons();
|
const allButtons = this.getButtons();
|
||||||
@ -355,9 +374,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
|||||||
{this.header}
|
{this.header}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.message !== undefined && (
|
{this.message !== undefined && this.renderToastMessage()}
|
||||||
<div class="toast-message" part="message" innerHTML={sanitizeDOMString(this.message)}></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.renderButtons(endButtons, 'end')}
|
{this.renderButtons(endButtons, 'end')}
|
||||||
|
|||||||
@ -187,6 +187,14 @@ export interface IonicConfig {
|
|||||||
*/
|
*/
|
||||||
sanitizerEnabled?: boolean;
|
sanitizerEnabled?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relevant Components: ion-alert, ion-infinite-scroll-content, ion-loading, ion-refresher-content, ion-toast
|
||||||
|
* If `false`, all `innerHTML` usage will be disabled in Ionic, and
|
||||||
|
* custom HTML will not be usable in the relevant components.
|
||||||
|
* `innerHTML` usage is enabled by default.
|
||||||
|
*/
|
||||||
|
innerHTMLTemplatesEnabled?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the default platform detection methods.
|
* Overrides the default platform detection methods.
|
||||||
*/
|
*/
|
||||||
@ -238,3 +246,5 @@ export const getMode = (): Mode => {
|
|||||||
}
|
}
|
||||||
return 'md';
|
return 'md';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ENABLE_HTML_CONTENT_DEFAULT = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user