mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-04-29 23:43:55 -04:00
215 lines
5.1 KiB
JavaScript
215 lines
5.1 KiB
JavaScript
import { WidgetClickHook, WidgetClickOutsideHook } from 'discourse/widgets/click-hook';
|
|
import { h } from 'virtual-dom';
|
|
|
|
function emptyContent() { }
|
|
|
|
const _registry = {};
|
|
const _dirty = {};
|
|
|
|
export function keyDirty(key) {
|
|
_dirty[key] = true;
|
|
}
|
|
|
|
function drawWidget(builder, attrs, state) {
|
|
const properties = {};
|
|
|
|
if (this.buildClasses) {
|
|
let classes = this.buildClasses(attrs, state) || [];
|
|
if (!Array.isArray(classes)) { classes = [classes]; }
|
|
if (classes.length) {
|
|
properties.className = classes.join(' ');
|
|
}
|
|
}
|
|
if (this.buildId) {
|
|
properties.id = this.buildId(attrs);
|
|
}
|
|
|
|
if (this.buildAttributes) {
|
|
properties.attributes = this.buildAttributes(attrs);
|
|
}
|
|
if (this.clickOutside) {
|
|
properties['widget-click-outside'] = new WidgetClickOutsideHook(this);
|
|
}
|
|
if (this.click) {
|
|
properties['widget-click'] = new WidgetClickHook(this);
|
|
}
|
|
|
|
const attributes = properties['attributes'] || {};
|
|
properties.attributes = attributes;
|
|
if (this.title) {
|
|
attributes.title = I18n.t(this.title);
|
|
}
|
|
|
|
return h(this.tagName || 'div', properties, this.html(attrs, state));
|
|
}
|
|
|
|
export function createWidget(name, opts) {
|
|
const result = class CustomWidget extends Widget {};
|
|
|
|
if (name) {
|
|
_registry[name] = result;
|
|
}
|
|
|
|
opts.html = opts.html || emptyContent;
|
|
opts.draw = drawWidget;
|
|
|
|
Object.keys(opts).forEach(k => result.prototype[k] = opts[k]);
|
|
return result;
|
|
}
|
|
|
|
export default class Widget {
|
|
constructor(attrs, container, opts) {
|
|
opts = opts || {};
|
|
this.attrs = attrs || {};
|
|
this.mergeState = opts.state;
|
|
this.container = container;
|
|
this.model = opts.model;
|
|
|
|
this.key = this.buildKey ? this.buildKey(attrs) : null;
|
|
|
|
this.site = container.lookup('site:main');
|
|
this.siteSettings = container.lookup('site-settings:main');
|
|
this.currentUser = container.lookup('current-user:main');
|
|
this.store = container.lookup('store:main');
|
|
}
|
|
|
|
defaultState() {
|
|
return {};
|
|
}
|
|
|
|
destroy() {
|
|
console.log('destroy called');
|
|
}
|
|
|
|
render(prev) {
|
|
if (prev && prev.state) {
|
|
this.state = prev.state;
|
|
} else {
|
|
this.state = this.defaultState();
|
|
}
|
|
|
|
// Sometimes we pass state down from the parent
|
|
if (this.mergeState) {
|
|
this.state = _.merge(this.state, this.mergeState);
|
|
}
|
|
|
|
if (prev && prev.shadowTree) {
|
|
this.shadowTree = true;
|
|
if (!_dirty[prev.key]) { return prev.vnode; }
|
|
}
|
|
|
|
return this.draw(h, this.attrs, this.state);
|
|
}
|
|
|
|
_findAncestorWithProperty(property) {
|
|
let widget = this;
|
|
while (widget) {
|
|
const value = widget[property];
|
|
if (value) {
|
|
return widget;
|
|
}
|
|
widget = widget.parentWidget;
|
|
}
|
|
}
|
|
|
|
_findView() {
|
|
const widget = this._findAncestorWithProperty('_emberView');
|
|
if (widget) {
|
|
return widget._emberView;
|
|
}
|
|
}
|
|
|
|
attach(widgetName, attrs, opts) {
|
|
let WidgetClass = _registry[widgetName];
|
|
|
|
if (!WidgetClass) {
|
|
if (!this.container) {
|
|
console.error("couldn't find container");
|
|
return;
|
|
}
|
|
WidgetClass = this.container.lookupFactory(`widget:${widgetName}`);
|
|
}
|
|
|
|
if (WidgetClass) {
|
|
const result = new WidgetClass(attrs, this.container, opts);
|
|
result.parentWidget = this;
|
|
return result;
|
|
} else {
|
|
throw `Couldn't find ${widgetName} factory`;
|
|
}
|
|
}
|
|
|
|
scheduleRerender() {
|
|
let widget = this;
|
|
while (widget) {
|
|
if (widget.shadowTree) {
|
|
keyDirty(widget.key);
|
|
}
|
|
|
|
const emberView = widget._emberView;
|
|
if (emberView) {
|
|
return emberView.queueRerender();
|
|
}
|
|
widget = widget.parentWidget;
|
|
}
|
|
}
|
|
|
|
sendComponentAction(name, param) {
|
|
const view = this._findAncestorWithProperty('_emberView');
|
|
|
|
let promise;
|
|
if (view) {
|
|
// Peek into ember internals to allow us to return promises from actions
|
|
const ev = view._emberView;
|
|
const target = ev.get('targetObject');
|
|
|
|
const actionName = ev.get(name);
|
|
if (!actionName) {
|
|
Ember.warn(`${name} not found`);
|
|
return;
|
|
}
|
|
|
|
if (target) {
|
|
// TODO: Use ember closure actions
|
|
const actions = target._actions || target.actionHooks;
|
|
const method = actions[actionName];
|
|
if (method) {
|
|
promise = method.call(target, param);
|
|
if (!promise || !promise.then) {
|
|
promise = Ember.RSVP.resolve(promise);
|
|
}
|
|
} else {
|
|
return ev.sendAction(name, param);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (promise) {
|
|
return promise.then(() => this.scheduleRerender());
|
|
}
|
|
}
|
|
|
|
findAncestorModel() {
|
|
const modelWidget = this._findAncestorWithProperty('model');
|
|
if (modelWidget) {
|
|
return modelWidget.model;
|
|
}
|
|
}
|
|
|
|
sendWidgetAction(name, param) {
|
|
const widget = this._findAncestorWithProperty(name);
|
|
if (widget) {
|
|
const result = widget[name](param);
|
|
if (result && result.then) {
|
|
return result.then(() => this.scheduleRerender());
|
|
} else {
|
|
this.scheduleRerender();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return this.sendComponentAction(name, param || this.findAncestorModel());
|
|
}
|
|
}
|
|
|
|
Widget.prototype.type = 'Thunk';
|