Merge branch 'master' into trim_behavior
This commit is contained in:
@@ -9,6 +9,7 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/T
|
||||
import BaseCharAtlas from './atlas/BaseCharAtlas';
|
||||
import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
||||
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
import { is256Color } from './atlas/CharAtlasUtils';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -298,7 +299,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (fg < 256) {
|
||||
} else if (is256Color(fg)) {
|
||||
// 256 color support
|
||||
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } f
|
||||
import { IColorSet, IRenderDimensions } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { is256Color } from './atlas/CharAtlasUtils';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkHoverEvent = null;
|
||||
@@ -42,7 +43,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _onLinkHover(e: ILinkHoverEvent): void {
|
||||
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (e.fg < 256) {
|
||||
} else if (is256Color(e.fg)) {
|
||||
// 256 color support
|
||||
this._ctx.fillStyle = this._colors.ansi[e.fg].css;
|
||||
} else {
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer';
|
||||
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types';
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types';
|
||||
import { GridCache } from './GridCache';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { is256Color } from './atlas/CharAtlasUtils';
|
||||
|
||||
/**
|
||||
* This CharData looks like a null character, which will forc a clear and render
|
||||
@@ -143,10 +144,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
const temp = bg;
|
||||
bg = fg;
|
||||
fg = temp;
|
||||
if (fg === 256) {
|
||||
if (fg === DEFAULT_COLOR) {
|
||||
fg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
if (bg === 257) {
|
||||
if (bg === DEFAULT_COLOR) {
|
||||
bg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
}
|
||||
@@ -186,7 +187,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
let nextFillStyle = null; // null represents default background color
|
||||
if (bg === INVERTED_DEFAULT_COLOR) {
|
||||
nextFillStyle = this._colors.foreground.css;
|
||||
} else if (bg < 256) {
|
||||
} else if (is256Color(bg)) {
|
||||
nextFillStyle = this._colors.ansi[bg].css;
|
||||
}
|
||||
|
||||
@@ -230,7 +231,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
this._ctx.save();
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (fg < 256) {
|
||||
} else if (is256Color(fg)) {
|
||||
// 256 color support
|
||||
this._ctx.fillStyle = this._colors.ansi[fg].css;
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { ITerminal } from '../../Types';
|
||||
import { IColorSet } from '../Types';
|
||||
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||
import { DEFAULT_COLOR } from './Types';
|
||||
|
||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
||||
// null out some fields that don't matter
|
||||
@@ -51,3 +52,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
|
||||
export function is256Color(colorCode: number): boolean {
|
||||
return colorCode < DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ interface IGlyphCacheValue {
|
||||
inBitmap: boolean;
|
||||
}
|
||||
|
||||
function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
|
||||
export function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
|
||||
// Note that this only returns a valid key when code < 256
|
||||
// Layout:
|
||||
// 0b00000000000000000000000000000001: italic (1)
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { DIM_OPACITY, IGlyphIdentifier } from './Types';
|
||||
import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR } from './Types';
|
||||
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||
import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator';
|
||||
import BaseCharAtlas from './BaseCharAtlas';
|
||||
import { is256Color } from './CharAtlasUtils';
|
||||
|
||||
export default class StaticCharAtlas extends BaseCharAtlas {
|
||||
private _texture: HTMLCanvasElement | ImageBitmap;
|
||||
@@ -41,8 +42,8 @@ export default class StaticCharAtlas extends BaseCharAtlas {
|
||||
const isAscii = glyph.code < 256;
|
||||
// A color is basic if it is one of the 4 bit ANSI colors.
|
||||
const isBasicColor = glyph.fg < 16;
|
||||
const isDefaultColor = glyph.fg >= 256;
|
||||
const isDefaultBackground = glyph.bg >= 256;
|
||||
const isDefaultColor = glyph.fg === DEFAULT_COLOR;
|
||||
const isDefaultBackground = glyph.bg === DEFAULT_COLOR;
|
||||
return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic;
|
||||
}
|
||||
|
||||
@@ -58,9 +59,9 @@ export default class StaticCharAtlas extends BaseCharAtlas {
|
||||
}
|
||||
|
||||
let colorIndex = 0;
|
||||
if (glyph.fg < 256) {
|
||||
if (is256Color(glyph.fg)) {
|
||||
colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0);
|
||||
} else {
|
||||
} else if (glyph.fg === DEFAULT_COLOR) {
|
||||
// If default color and bold
|
||||
if (glyph.bold) {
|
||||
colorIndex = 1;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const INVERTED_DEFAULT_COLOR = -1;
|
||||
export const DEFAULT_COLOR = 256;
|
||||
export const INVERTED_DEFAULT_COLOR = 257;
|
||||
export const DIM_OPACITY = 0.5;
|
||||
|
||||
export interface IGlyphIdentifier {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EventEmitter } from '../../common/EventEmitter';
|
||||
import { ColorManager } from '../ColorManager';
|
||||
import { RenderDebouncer } from '../../ui/RenderDebouncer';
|
||||
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
|
||||
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
|
||||
|
||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||
const ROW_CONTAINER_CLASS = 'xterm-rows';
|
||||
@@ -111,6 +112,8 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
element.style.height = `${this.dimensions.actualCellHeight}px`;
|
||||
element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
|
||||
// Make sure rows don't overflow onto following row
|
||||
element.style.overflow = 'hidden';
|
||||
});
|
||||
|
||||
if (!this._dimensionsStyleElement) {
|
||||
@@ -123,16 +126,14 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
` display: inline-block;` +
|
||||
` height: 100%;` +
|
||||
` vertical-align: top;` +
|
||||
` width: ${this._terminal.charMeasure.width}px` +
|
||||
` width: ${this.dimensions.actualCellWidth}px` +
|
||||
`}`;
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
|
||||
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
||||
this._rowContainer.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._rowContainer.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._terminal.screenElement.style.width = '';
|
||||
this._terminal.screenElement.style.height = '';
|
||||
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
}
|
||||
|
||||
public setTheme(theme: ITheme | undefined): IColorSet {
|
||||
@@ -199,6 +200,9 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
||||
});
|
||||
styles +=
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this.colorManager.colors.background.css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this.colorManager.colors.foreground.css}; }`;
|
||||
|
||||
this._themeStyleElement.innerHTML = styles;
|
||||
return this.colorManager.colors;
|
||||
@@ -332,7 +336,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
const row = y + terminal.buffer.ydisp;
|
||||
const lineData = terminal.buffer.lines.get(row);
|
||||
const cursorStyle = terminal.options.cursorStyle;
|
||||
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, terminal.charMeasure.width, terminal.cols));
|
||||
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols));
|
||||
}
|
||||
|
||||
this._terminal.emit('refresh', {start, end});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '.
|
||||
import { FLAGS } from '../Types';
|
||||
import { BufferLine } from '../../BufferLine';
|
||||
import { IBufferLine } from '../../Types';
|
||||
import { DEFAULT_COLOR } from '../atlas/Types';
|
||||
|
||||
describe('DomRendererRowFactory', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
@@ -23,11 +24,10 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
describe('createRow', () => {
|
||||
it('should create an element for every character in the row', () => {
|
||||
it('should not create anything for an empty row', () => {
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span> </span>' +
|
||||
'<span> </span>'
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
@@ -45,8 +45,7 @@ describe('DomRendererRowFactory', () => {
|
||||
for (const style of ['block', 'bar', 'underline']) {
|
||||
const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-cursor xterm-cursor-${style}"> </span>` +
|
||||
'<span> </span>'
|
||||
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -65,8 +64,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-bold">a</span>' +
|
||||
'<span> </span>'
|
||||
'<span class="xterm-bold">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -74,31 +72,28 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-italic">a</span>' +
|
||||
'<span> </span>'
|
||||
'<span class="xterm-italic">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add classes for 256 foreground colors', () => {
|
||||
const defaultAttrNoFgColor = (0 << 9) | (256 << 0);
|
||||
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-fg-${i}">a</span>` +
|
||||
'<span> </span>'
|
||||
`<span class="xterm-fg-${i}">a</span>`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should add classes for 256 background colors', () => {
|
||||
const defaultAttrNoBgColor = (257 << 9) | (0 << 0);
|
||||
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-bg-${i}">a</span>` +
|
||||
'<span> </span>'
|
||||
`<span class="xterm-bg-${i}">a</span>`
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -107,36 +102,32 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>' +
|
||||
'<span> </span>'
|
||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly invert default fg color', () => {
|
||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-15">a</span>' +
|
||||
'<span> </span>'
|
||||
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly invert default bg color', () => {
|
||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-0 xterm-bg-1">a</span>' +
|
||||
'<span> </span>'
|
||||
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should turn bold fg text bright', () => {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
|
||||
const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>` +
|
||||
'<span> </span>'
|
||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer';
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from '../../Buffer';
|
||||
import { FLAGS } from '../Types';
|
||||
import { IBufferLine } from '../../Types';
|
||||
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
|
||||
|
||||
export const BOLD_CLASS = 'xterm-bold';
|
||||
export const ITALIC_CLASS = 'xterm-italic';
|
||||
@@ -22,14 +23,23 @@ export class DomRendererRowFactory {
|
||||
|
||||
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
|
||||
const fragment = this._document.createDocumentFragment();
|
||||
let colCount = 0;
|
||||
|
||||
for (let x = 0; x < lineData.length; x++) {
|
||||
// Don't allow any buffer to the right to be displayed
|
||||
if (colCount >= cols) {
|
||||
continue;
|
||||
// Find the line length first, this prevents the need to output a bunch of
|
||||
// empty cells at the end. This cannot easily be integrated into the main
|
||||
// loop below because of the colCount feature (which can be removed after we
|
||||
// properly support reflow and disallow data to go beyond the right-side of
|
||||
// the viewport).
|
||||
let lineLength = 0;
|
||||
for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {
|
||||
const charData = lineData.get(x);
|
||||
const code = charData[CHAR_DATA_CODE_INDEX];
|
||||
if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
|
||||
lineLength = x + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < lineLength; x++) {
|
||||
const charData = lineData.get(x);
|
||||
const char: string = charData[CHAR_DATA_CHAR_INDEX] || ' ';
|
||||
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
|
||||
@@ -70,16 +80,17 @@ export class DomRendererRowFactory {
|
||||
const temp = bg;
|
||||
bg = fg;
|
||||
fg = temp;
|
||||
if (fg === 256) {
|
||||
fg = 0;
|
||||
if (fg === DEFAULT_COLOR) {
|
||||
fg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
if (bg === 257) {
|
||||
bg = 15;
|
||||
if (bg === DEFAULT_COLOR) {
|
||||
bg = INVERTED_DEFAULT_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FLAGS.BOLD) {
|
||||
// Convert the FG color to the bold variant
|
||||
// Convert the FG color to the bold variant. This should not happen when
|
||||
// the fg is the inverse default color as there is no bold variant.
|
||||
if (fg < 8) {
|
||||
fg += 8;
|
||||
}
|
||||
@@ -91,14 +102,13 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
|
||||
charElement.textContent = char;
|
||||
if (fg !== 257) {
|
||||
if (fg !== DEFAULT_COLOR) {
|
||||
charElement.classList.add(`xterm-fg-${fg}`);
|
||||
}
|
||||
if (bg !== 256) {
|
||||
if (bg !== DEFAULT_COLOR) {
|
||||
charElement.classList.add(`xterm-bg-${bg}`);
|
||||
}
|
||||
fragment.appendChild(charElement);
|
||||
colCount += width;
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user