summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts')
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts132
1 files changed, 91 insertions, 41 deletions
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index 2812b47cd1..c283876223 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -15,11 +15,11 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-linked-text_html';
import {GrLinkTextParser, LinkTextParserConfig} from './link-text-parser';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property, observe} from '@polymer/decorators';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {assertIsDefined} from '../../../utils/common-util';
declare global {
interface HTMLElementTagNameMap {
@@ -27,80 +27,105 @@ declare global {
}
}
-export interface GrLinkedText {
- $: {
- output: HTMLSpanElement;
- };
-}
-
@customElement('gr-linked-text')
-export class GrLinkedText extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrLinkedText extends LitElement {
+ private outputElement?: HTMLSpanElement;
@property({type: Boolean})
removeZeroWidthSpace?: boolean;
- // content default is null, because this.$.output.textContent is string|null
@property({type: String})
- content: string | null = null;
+ content = '';
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, attribute: true})
pre = false;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, attribute: true})
disabled = false;
+ @property({type: Boolean, attribute: true})
+ inline = false;
+
@property({type: Object})
config?: LinkTextParserConfig;
- @observe('content')
- _contentChanged(content: string | null) {
- // In the case where the config may not be set (perhaps due to the
- // request for it still being in flight), set the content anyway to
- // prevent waiting on the config to display the text.
- if (!this.config) {
- return;
+ static override get styles() {
+ return css`
+ :host {
+ display: block;
+ }
+ :host([inline]) {
+ display: inline;
+ }
+ :host([pre]) ::slotted(span) {
+ white-space: var(--linked-text-white-space, pre-wrap);
+ word-wrap: var(--linked-text-word-wrap, break-word);
+ }
+ `;
+ }
+
+ override render() {
+ return html`<slot name="insert"></slot>`;
+ }
+
+ // NOTE: LinkTextParser dynamically creates HTML fragments based on backend
+ // configuration commentLinks. These commentLinks can contain arbitrary HTML
+ // fragments. This means that arbitrary HTML needs to be injected into the
+ // DOM-tree, where this HTML is is controlled on the server-side in the
+ // server-configuration rather than by arbitrary users.
+ // To enable this injection of 'unsafe' HTML, LinkTextParser generates
+ // HTML fragments. Lit does not support inserting html fragments directly
+ // into its DOM-tree as it controls the DOM-tree that it generates.
+ // Therefore, to get around this we create a single element that we slot into
+ // the Lit-owned DOM. This element will not be part of this LitElement as
+ // it's slotted in and thus can be modified on the fly by handleParseResult.
+ override firstUpdated(_changedProperties: PropertyValues): void {
+ this.outputElement = document.createElement('span');
+ this.outputElement.id = 'output';
+ this.outputElement.slot = 'insert';
+ this.append(this.outputElement);
+ }
+
+ override updated(changedProperties: PropertyValues): void {
+ if (changedProperties.has('content') || changedProperties.has('config')) {
+ this._contentOrConfigChanged();
+ } else if (changedProperties.has('disabled')) {
+ this.styleLinks();
}
- this.$.output.textContent = content;
}
/**
* Because either the source text or the linkification config has changed,
* the content should be re-parsed.
+ * Private but used in tests.
*
* @param content The raw, un-linkified source string to parse.
* @param config The server config specifying commentLink patterns
*/
- @observe('content', 'config')
- _contentOrConfigChanged(
- content: string | null,
- config?: LinkTextParserConfig
- ) {
- if (!config) {
+ _contentOrConfigChanged() {
+ if (!this.config) {
+ assertIsDefined(this.outputElement);
+ this.outputElement.textContent = this.content;
return;
}
- // TODO(TS): mapCommentlinks always has value, remove
- if (!GerritNav.mapCommentlinks) return;
- config = GerritNav.mapCommentlinks(config);
- const output = this.$.output;
- output.textContent = '';
+ const config = GerritNav.mapCommentlinks(this.config);
+ assertIsDefined(this.outputElement);
+ this.outputElement.textContent = '';
const parser = new GrLinkTextParser(
config,
(text: string | null, href: string | null, fragment?: DocumentFragment) =>
- this._handleParseResult(text, href, fragment),
+ this.handleParseResult(text, href, fragment),
this.removeZeroWidthSpace
);
- parser.parse(content);
+ parser.parse(this.content);
// Ensure that external links originating from HTML commentlink configs
// open in a new tab. @see Issue 5567
// Ensure links to the same host originating from commentlink configs
// open in the same tab. When target is not set - default is _self
// @see Issue 4616
- output.querySelectorAll('a').forEach(anchor => {
+ this.outputElement.querySelectorAll('a').forEach(anchor => {
if (anchor.hostname === window.location.hostname) {
anchor.removeAttribute('target');
} else {
@@ -108,6 +133,30 @@ export class GrLinkedText extends PolymerElement {
}
anchor.setAttribute('rel', 'noopener');
});
+
+ this.styleLinks();
+ }
+
+ /**
+ * Styles the links based on whether gr-linked-text is disabled or not
+ */
+ private styleLinks() {
+ assertIsDefined(this.outputElement);
+ this.outputElement.querySelectorAll('a').forEach(anchor => {
+ anchor.setAttribute('style', this.computeLinkStyle());
+ });
+ }
+
+ private computeLinkStyle() {
+ if (this.disabled) {
+ return `
+ color: inherit;
+ text-decoration: none;
+ pointer-events: none;
+ `;
+ } else {
+ return 'color: var(--link-color)';
+ }
}
/**
@@ -119,12 +168,13 @@ export class GrLinkedText extends PolymerElement {
* - To attach an arbitrary fragment: when called with only the `fragment`
* argument, the fragment should be attached to the resulting DOM as is.
*/
- private _handleParseResult(
+ private handleParseResult(
text: string | null,
href: string | null,
fragment?: DocumentFragment
) {
- const output = this.$.output;
+ assertIsDefined(this.outputElement);
+ const output = this.outputElement;
if (href) {
const a = document.createElement('a');
a.setAttribute('href', href);