refactor(render): create and store render ProtoViewRef in every app ProtoView
Needed to change Renderer.mergeChildComponentProtoViews to not create new ProtoViews to be able to deal with cyclic references. This commit is part of using the new render layer in Angular.
This commit is contained in:
12
modules/angular2/src/core/application.js
vendored
12
modules/angular2/src/core/application.js
vendored
@ -3,7 +3,6 @@ import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, strin
|
||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Compiler, CompilerCache} from './compiler/compiler';
|
||||
import {ProtoView} from './compiler/view';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
|
||||
import {ExceptionHandler} from './exception_handler';
|
||||
@ -72,12 +71,11 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
throw new BaseException(`Only Components can be bootstrapped; ` +
|
||||
`Directive of ${stringify(type)} is not a Component`);
|
||||
}
|
||||
return compiler.compile(appComponentAnnotatedType.type).then(
|
||||
(protoView) => {
|
||||
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
|
||||
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation),
|
||||
changeDetection.createProtoChangeDetector('root'),
|
||||
strategy);
|
||||
return compiler.compileRoot(
|
||||
appElement,
|
||||
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation)
|
||||
).then(
|
||||
(appProtoView) => {
|
||||
// The light Dom of the app element is not considered part of
|
||||
// the angular application. Thus the context and lightDomInjector are
|
||||
// empty.
|
||||
|
124
modules/angular2/src/core/compiler/compiler.js
vendored
124
modules/angular2/src/core/compiler/compiler.js
vendored
@ -85,6 +85,17 @@ export class NewCompiler {
|
||||
return DirectiveBinding.createFromType(meta.type, meta.annotation);
|
||||
}
|
||||
|
||||
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>.
|
||||
// Used for bootstrapping.
|
||||
compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise<ProtoView> {
|
||||
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
|
||||
return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true)
|
||||
}).then( (rootProtoView) => {
|
||||
rootProtoView.instantiateInPlace = true;
|
||||
return rootProtoView;
|
||||
});
|
||||
}
|
||||
|
||||
compile(component: Type):Promise<ProtoView> {
|
||||
var protoView = this._compile(this._bindDirective(component));
|
||||
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
|
||||
@ -96,7 +107,8 @@ export class NewCompiler {
|
||||
var protoView = this._compilerCache.get(component);
|
||||
if (isPresent(protoView)) {
|
||||
// The component has already been compiled into a ProtoView,
|
||||
// returns a resolved Promise.
|
||||
// returns a plain ProtoView, not wrapped inside of a Promise.
|
||||
// Needed for recursive components.
|
||||
return protoView;
|
||||
}
|
||||
|
||||
@ -113,31 +125,72 @@ export class NewCompiler {
|
||||
this._flattenDirectives(template),
|
||||
(directive) => this._bindDirective(directive)
|
||||
);
|
||||
|
||||
pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => {
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
|
||||
// Compile all the components from the template
|
||||
var nestedPVPromises = this._compileNestedComponents(protoView);
|
||||
if (nestedPVPromises.length > 0) {
|
||||
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
|
||||
// The promise will resolved after nested ProtoViews are compiled.
|
||||
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
|
||||
(_) => protoView,
|
||||
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); }
|
||||
);
|
||||
}
|
||||
return protoView;
|
||||
var renderTemplate = this._buildRenderTemplate(component, template, directives);
|
||||
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
|
||||
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
|
||||
});
|
||||
|
||||
MapWrapper.set(this._compiling, component, pvPromise);
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> {
|
||||
var component = componentBinding.key.token;
|
||||
// TODO(tbosch): union type return ProtoView or Promise<ProtoView>
|
||||
_compileNestedProtoViews(componentBinding, renderPv, directives, isComponentRootView) {
|
||||
var nestedPVPromises = [];
|
||||
var protoView = this._protoViewFactory.createProtoView(componentBinding, renderPv, directives);
|
||||
if (isComponentRootView && isPresent(componentBinding)) {
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
var component = componentBinding.key.token;
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
}
|
||||
|
||||
var binderIndex = 0;
|
||||
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView;
|
||||
var elementBinderDone = (nestedPv) => {
|
||||
elementBinder.nestedProtoView = nestedPv;
|
||||
// Can't set the parentProtoView for components,
|
||||
// as their ProtoView might be used in multiple other components.
|
||||
nestedPv.parentProtoView = isPresent(nestedComponent) ? null : protoView;
|
||||
};
|
||||
var nestedCall = null;
|
||||
if (isPresent(nestedComponent)) {
|
||||
if (!(nestedComponent.annotation instanceof DynamicComponent)) {
|
||||
nestedCall = this._compile(nestedComponent);
|
||||
}
|
||||
} else if (isPresent(nestedRenderProtoView)) {
|
||||
nestedCall = this._compileNestedProtoViews(componentBinding, nestedRenderProtoView, directives, false);
|
||||
}
|
||||
if (PromiseWrapper.isPromise(nestedCall)) {
|
||||
ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone));
|
||||
} else if (isPresent(nestedCall)) {
|
||||
elementBinderDone(nestedCall);
|
||||
}
|
||||
binderIndex++;
|
||||
});
|
||||
|
||||
var protoViewDone = (_) => {
|
||||
var childComponentRenderPvRefs = [];
|
||||
ListWrapper.forEach(protoView.elementBinders, (eb) => {
|
||||
if (isPresent(eb.componentDirective)) {
|
||||
var componentPv = eb.nestedProtoView;
|
||||
ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null);
|
||||
}
|
||||
});
|
||||
this._renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
|
||||
return protoView;
|
||||
};
|
||||
if (nestedPVPromises.length > 0) {
|
||||
return PromiseWrapper.all(nestedPVPromises).then(protoViewDone);
|
||||
} else {
|
||||
return protoViewDone(null);
|
||||
}
|
||||
}
|
||||
|
||||
_buildRenderTemplate(component, template, directives) {
|
||||
var componentUrl = this._urlResolver.resolve(
|
||||
this._appUrl, this._componentUrlMapper.getUrl(component)
|
||||
);
|
||||
@ -150,37 +203,12 @@ export class NewCompiler {
|
||||
// is able to resolve urls in stylesheets.
|
||||
templateAbsUrl = componentUrl;
|
||||
}
|
||||
var renderTemplate = new renderApi.Template({
|
||||
return new renderApi.Template({
|
||||
componentId: stringify(component),
|
||||
absUrl: templateAbsUrl,
|
||||
inline: template.inline,
|
||||
directives: ListWrapper.map(directives, this._buildRenderDirective)
|
||||
});
|
||||
return this._renderer.compile(renderTemplate).then( (renderPv) => {
|
||||
return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives);
|
||||
});
|
||||
}
|
||||
|
||||
_compileNestedComponents(protoView, nestedPVPromises = null):List<Promise> {
|
||||
if (isBlank(nestedPVPromises)) {
|
||||
nestedPVPromises = [];
|
||||
}
|
||||
ListWrapper.map(protoView.elementBinders, (elementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) {
|
||||
var nestedCall = this._compile(nestedComponent);
|
||||
if (PromiseWrapper.isPromise(nestedCall)) {
|
||||
ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => {
|
||||
elementBinder.nestedProtoView = nestedPv;
|
||||
}));
|
||||
} else {
|
||||
elementBinder.nestedProtoView = nestedCall;
|
||||
}
|
||||
} else if (isPresent(elementBinder.nestedProtoView)) {
|
||||
this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises);
|
||||
}
|
||||
});
|
||||
return nestedPVPromises;
|
||||
}
|
||||
|
||||
_buildRenderDirective(directiveBinding) {
|
||||
@ -269,7 +297,7 @@ export class Compiler extends NewCompiler {
|
||||
new DefaultStepFactory(parser, shadowDomStrategy.render),
|
||||
templateLoader
|
||||
),
|
||||
null, null
|
||||
null, shadowDomStrategy.render
|
||||
),
|
||||
new ProtoViewFactory(changeDetection, shadowDomStrategy)
|
||||
);
|
||||
|
@ -20,16 +20,19 @@ export class ProtoViewFactory {
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
}
|
||||
|
||||
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
|
||||
return this._createProtoView(null, componentAnnotation, renderProtoView, directives);
|
||||
}
|
||||
|
||||
_createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
|
||||
var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy',
|
||||
componentAnnotation.changeDetection);
|
||||
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
|
||||
var protoChangeDetector;
|
||||
if (isBlank(componentBinding)) {
|
||||
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
|
||||
} else {
|
||||
var componentAnnotation:Component = componentBinding.annotation;
|
||||
protoChangeDetector = this._changeDetection.createProtoChangeDetector(
|
||||
'dummy', componentAnnotation.changeDetection
|
||||
);
|
||||
}
|
||||
var domProtoView = this._getDomProtoView(renderProtoView.render);
|
||||
var protoView = new ProtoView(domProtoView.element, protoChangeDetector,
|
||||
this._shadowDomStrategy, parent);
|
||||
var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector,
|
||||
this._shadowDomStrategy, null);
|
||||
|
||||
for (var i=0; i<renderProtoView.elementBinders.length; i++) {
|
||||
var renderElementBinder = renderProtoView.elementBinders[i];
|
||||
@ -42,13 +45,10 @@ export class ProtoViewFactory {
|
||||
i, parentPeiWithDistance,
|
||||
sortedDirectives, renderElementBinder
|
||||
);
|
||||
var elementBinder = this._createElementBinder(
|
||||
this._createElementBinder(
|
||||
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
|
||||
);
|
||||
this._createDirectiveBinders(protoView, sortedDirectives);
|
||||
if (isPresent(renderElementBinder.nestedProtoView)) {
|
||||
elementBinder.nestedProtoView = this._createProtoView(protoView, componentAnnotation, renderElementBinder.nestedProtoView, directives);
|
||||
}
|
||||
}
|
||||
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
|
||||
protoView.bindVariable(varName, mappedName);
|
||||
|
26
modules/angular2/src/core/compiler/view.js
vendored
26
modules/angular2/src/core/compiler/view.js
vendored
@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {ViewPool} from './view_pool';
|
||||
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||
@ -283,11 +284,14 @@ export class ProtoView {
|
||||
|
||||
_directiveMementosMap:Map;
|
||||
_directiveMementos:List;
|
||||
render:renderApi.ProtoViewRef;
|
||||
|
||||
constructor(
|
||||
render:renderApi.ProtoViewRef,
|
||||
template,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
|
||||
this.render = render;
|
||||
this.element = template;
|
||||
this.elementBinders = [];
|
||||
this.variableBindings = MapWrapper.create();
|
||||
@ -642,28 +646,6 @@ export class ProtoView {
|
||||
|
||||
return MapWrapper.get(this._directiveMementosMap, id);
|
||||
}
|
||||
|
||||
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
||||
// and the component template is already compiled into protoView.
|
||||
// Used for bootstrapping.
|
||||
static createRootProtoView(protoView: ProtoView,
|
||||
insertionElement,
|
||||
rootComponentBinding: DirectiveBinding,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy: ShadowDomStrategy
|
||||
): ProtoView {
|
||||
|
||||
DOM.addClass(insertionElement, NG_BINDING_CLASS);
|
||||
var cmpType = rootComponentBinding.key.token;
|
||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
||||
rootProtoView.instantiateInPlace = true;
|
||||
var binder = rootProtoView.bindElement(null, 0,
|
||||
new ProtoElementInjector(null, 0, [cmpType], true));
|
||||
binder.componentDirective = rootComponentBinding;
|
||||
binder.nestedProtoView = protoView;
|
||||
shadowDomStrategy.shimAppElement(cmpType, insertionElement);
|
||||
return rootProtoView;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
10
modules/angular2/src/render/api.js
vendored
10
modules/angular2/src/render/api.js
vendored
@ -141,20 +141,20 @@ export class Renderer {
|
||||
compile(template:Template):Promise<ProtoView> { return null; }
|
||||
|
||||
/**
|
||||
* Creates a new ProtoView with preset nested components,
|
||||
* Sets the preset nested components,
|
||||
* which will be instantiated when this protoView is instantiated.
|
||||
* Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components.
|
||||
* @param {List<ProtoViewRef>} protoViewRefs
|
||||
* ProtoView for every element with a component in this protoView or in a view container's protoView
|
||||
* @return {List<ProtoViewRef>}
|
||||
* new ProtoViewRef for the given protoView and all of its view container's protoViews
|
||||
*/
|
||||
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, protoViewRefs:List<ProtoViewRef>):List<ProtoViewRef> { return null; }
|
||||
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List<ProtoViewRef>) { return null; }
|
||||
|
||||
/**
|
||||
* Creats a ProtoView that will create a root view for the given element,
|
||||
* i.e. it will not clone the element but only attach other proto views to it.
|
||||
* Contains a single nested component with the given componentId.
|
||||
*/
|
||||
createRootProtoView(selectorOrElement):ProtoViewRef { return null; }
|
||||
createRootProtoView(selectorOrElement, componentId):Promise<ProtoView> { return null; }
|
||||
|
||||
/**
|
||||
* Creates a view and all of its nested child components.
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
@ -26,10 +26,6 @@ function _wrapView(view:View) {
|
||||
return new _DirectDomViewRef(view);
|
||||
}
|
||||
|
||||
function _wrapProtoView(protoView:ProtoView) {
|
||||
return new DirectDomProtoViewRef(protoView);
|
||||
}
|
||||
|
||||
function _collectComponentChildViewRefs(view, target = null) {
|
||||
if (isBlank(target)) {
|
||||
target = [];
|
||||
@ -83,22 +79,22 @@ export class DirectDomRenderer extends api.Renderer {
|
||||
return this._compiler.compile(template);
|
||||
}
|
||||
|
||||
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>):List<api.ProtoViewRef> {
|
||||
var protoViews = [];
|
||||
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {
|
||||
_resolveProtoView(protoViewRef).mergeChildComponentProtoViews(
|
||||
ListWrapper.map(protoViewRefs, _resolveProtoView),
|
||||
protoViews
|
||||
ListWrapper.map(protoViewRefs, _resolveProtoView)
|
||||
);
|
||||
return ListWrapper.map(protoViews, _wrapProtoView);
|
||||
}
|
||||
|
||||
createRootProtoView(selectorOrElement):api.ProtoViewRef {
|
||||
createRootProtoView(selectorOrElement, componentId):Promise<api.ProtoView> {
|
||||
var element = selectorOrElement; // TODO: select the element if it is not a real element...
|
||||
var rootProtoViewBuilder = new ProtoViewBuilder(element);
|
||||
rootProtoViewBuilder.setIsRootView(true);
|
||||
rootProtoViewBuilder.bindElement(element, 'root element').setComponentId('root');
|
||||
this._shadowDomStrategy.processElement(null, 'root', element);
|
||||
return rootProtoViewBuilder.build().render;
|
||||
var elBinder = rootProtoViewBuilder.bindElement(element, 'root element');
|
||||
elBinder.setComponentId(componentId);
|
||||
elBinder.bindDirective(0);
|
||||
|
||||
this._shadowDomStrategy.processElement(null, componentId, element);
|
||||
return PromiseWrapper.resolve(rootProtoViewBuilder.build());
|
||||
}
|
||||
|
||||
createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> {
|
||||
|
@ -1,12 +1,8 @@
|
||||
import {AST} from 'angular2/change_detection';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import * as protoViewModule from './proto_view';
|
||||
|
||||
/**
|
||||
* Note: Code that uses this class assumes that is immutable!
|
||||
*/
|
||||
export class ElementBinder {
|
||||
contentTagSelector: string;
|
||||
textNodeIndices: List<number>;
|
||||
@ -39,24 +35,4 @@ export class ElementBinder {
|
||||
this.distanceToParent = distanceToParent;
|
||||
this.propertySetters = propertySetters;
|
||||
}
|
||||
|
||||
mergeChildComponentProtoViews(protoViews:List<protoViewModule.ProtoView>, target:List<protoViewModule.ProtoView>):ElementBinder {
|
||||
var nestedProtoView;
|
||||
if (isPresent(this.componentId)) {
|
||||
nestedProtoView = ListWrapper.removeAt(protoViews, 0);
|
||||
} else if (isPresent(this.nestedProtoView)) {
|
||||
nestedProtoView = this.nestedProtoView.mergeChildComponentProtoViews(protoViews, target);
|
||||
}
|
||||
return new ElementBinder({
|
||||
parentIndex: this.parentIndex,
|
||||
textNodeIndices: this.textNodeIndices,
|
||||
contentTagSelector: this.contentTagSelector,
|
||||
nestedProtoView: nestedProtoView,
|
||||
componentId: this.componentId,
|
||||
eventLocals: this.eventLocals,
|
||||
eventNames: this.eventNames,
|
||||
distanceToParent: this.distanceToParent,
|
||||
propertySetters: this.propertySetters
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,6 @@ import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {NG_BINDING_CLASS} from '../util';
|
||||
|
||||
/**
|
||||
* Note: Code that uses this class assumes that is immutable!
|
||||
*/
|
||||
export class ProtoView {
|
||||
element;
|
||||
elementBinders:List<ElementBinder>;
|
||||
@ -28,22 +25,14 @@ export class ProtoView {
|
||||
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
|
||||
}
|
||||
|
||||
mergeChildComponentProtoViews(protoViews:List<ProtoView>, target:List<ProtoView>):ProtoView {
|
||||
var elementBinders = ListWrapper.createFixedSize(this.elementBinders.length);
|
||||
mergeChildComponentProtoViews(componentProtoViews:List<ProtoView>) {
|
||||
var componentProtoViewIndex = 0;
|
||||
for (var i=0; i<this.elementBinders.length; i++) {
|
||||
var eb = this.elementBinders[i];
|
||||
if (isPresent(eb.componentId) || isPresent(eb.nestedProtoView)) {
|
||||
elementBinders[i] = eb.mergeChildComponentProtoViews(protoViews, target);
|
||||
} else {
|
||||
elementBinders[i] = eb;
|
||||
if (isPresent(eb.componentId)) {
|
||||
eb.nestedProtoView = componentProtoViews[componentProtoViewIndex];
|
||||
componentProtoViewIndex++;
|
||||
}
|
||||
}
|
||||
var result = new ProtoView({
|
||||
elementBinders: elementBinders,
|
||||
element: this.element,
|
||||
isRootView: this.isRootView
|
||||
});
|
||||
ListWrapper.insert(target, 0, result);
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user