Add React wrapper for Vue

This commit is contained in:
Samuel Elliott 2018-05-14 16:55:18 +01:00
parent 9eb8eaa906
commit 581c94f6b3
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
4 changed files with 63 additions and 157 deletions

View File

@ -92,17 +92,7 @@ export default new class EmoteModule {
return this._searchCache || (this._searchCache = {});
}
get React() {
return WebpackModules.getModuleByName('React');
}
get ReactDOM() {
return WebpackModules.getModuleByName('ReactDOM');
}
processMarkup(markup, timestamp) {
if (!this.enabledSetting.value) return markup;
timestamp = timestamp.valueOf();
const allowNoWrapper = timestamp < enforceWrapperFrom;
@ -126,12 +116,13 @@ export default new class EmoteModule {
newMarkup.push(text);
text = null;
}
newMarkup.push(this.React.createElement('span', {
className: 'bd-emote-outer',
'data-bdemote-name': emote.name,
'data-bdemote-src': emote.src,
'data-has-wrapper': /;[\w]+;/gmi.test(word)
newMarkup.push(VueInjector.createReactElement({
components: { EmoteComponent },
data: { emote, hasWrapper: /;[\w]+;/gmi.test(word) },
template: '<EmoteComponent :src="emote.src" :name="emote.name" :hasWrapper="hasWrapper" />'
}));
continue;
}
if (text === null) {
@ -151,16 +142,6 @@ export default new class EmoteModule {
return !/;[\w]+;/gmi.test(word);
}
injectAll() {
if (!this.enabledSetting.value) return;
const all = document.getElementsByClassName('bd-emote-outer');
for (const ec of all) {
if (ec.children.length) continue;
this.injectEmote(ec);
}
}
findByProp(obj, what, value) {
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
@ -179,7 +160,7 @@ export default new class EmoteModule {
try {
// First child has all the actual text content, second is the edited timestamp
const markup = this.findByProp(retVal, 'className', 'markup');
if (!markup) return;
if (!markup || !this.enabledSetting.value) return;
markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp);
} catch (err) {
Logger.err('EmoteModule', err);
@ -188,39 +169,6 @@ export default new class EmoteModule {
for (const message of document.querySelectorAll('.message')) {
Reflection(message).forceUpdate();
}
this.injectAll();
this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
}
injectEmote(root) {
if (!this.enabledSetting.value) return;
while (root.firstChild) {
root.removeChild(root.firstChild);
}
const { bdemoteName, bdemoteSrc, hasWrapper } = root.dataset;
if (!bdemoteName || !bdemoteSrc) return;
VueInjector.inject(root, {
components: { EmoteComponent },
data: { src: bdemoteSrc, name: bdemoteName, hasWrapper },
template: '<EmoteComponent :src="src" :name="name" :hasWrapper="hasWrapper" />'
}, DOM.createElement('span'));
root.classList.add('bd-is-emote');
}
injectEmotes(element) {
if (!this.enabledSetting.value || !element) return;
for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo);
}
getEmote(word) {

View File

@ -9,7 +9,7 @@
}
.bd-emotewrapper {
display: flex;
display: inline-flex;
max-height: 32px;
img {

View File

@ -30,8 +30,7 @@ export default class extends Module {
}
/**
* Patches Message to use the extended NameTag.
* This is because NameTag is also used in places we don't really want any badges.
* Patches Message to render profile badges.
*/
async patchMessage() {
const Message = await ReactComponents.getComponent('Message');
@ -48,24 +47,14 @@ export default class extends Module {
const username = ReactHelpers.findByProp(retVal, 'type', 'h2');
if (!username) return;
username.props.children.splice(1, 0, ReactHelpers.React.createElement('span', {
className: 'bd-badge-outer',
'data-userid': user.id
username.props.children.splice(1, 0, VueInjector.createReactElement({
components: { BdMessageBadge },
data: { c },
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
}));
});
this.unpatchMessageMount = MonkeyPatch('ProfileBadges', Message.component.prototype).after('componentDidMount', component => {
const element = ReactHelpers.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectMessageBadges(element);
});
this.unpatchMessageUpdate = MonkeyPatch('ProfileBadges', Message.component.prototype).after('componentDidUpdate', component => {
const element = ReactHelpers.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectMessageBadges(element);
});
// Rerender all messages
for (const message of document.querySelectorAll('.message')) {
Reflection(message).forceUpdate();
@ -122,27 +111,16 @@ export default class extends Module {
const c = contributors.find(c => c.id === user.id);
if (!c) return;
retVal.props.children.splice(1, 0, ReactHelpers.React.createElement('span', {
className: 'bd-badge-outer',
'data-userid': user.id
retVal.props.children.splice(1, 0, VueInjector.createReactElement({
components: { BdMessageBadge },
data: { c },
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
}));
} catch (err) {
Logger.err('ProfileBadges', ['Error thrown while rendering a NameTag', err]);
}
return retVal;
}
componentDidMount() {
const element = ReactHelpers.ReactDOM.findDOMNode(this);
if (!element) return;
ProfileBadges.injectMessageBadges(element);
}
componentDidUpdate() {
const element = ReactHelpers.ReactDOM.findDOMNode(this);
if (!element) return;
ProfileBadges.injectMessageBadges(element);
}
};
// Rerender all channel members
@ -157,45 +135,22 @@ export default class extends Module {
return this.PatchedNameTag;
}
injectMessageBadges(element) {
for (const beo of element.getElementsByClassName('bd-badge-outer'))
this.injectMessageBadge(beo);
}
injectMessageBadge(root) {
while (root.firstChild) {
root.removeChild(root.firstChild);
}
const { userid } = root.dataset;
if (!userid) return;
const c = contributors.find(c => c.id === userid);
if (!c) return;
VueInjector.inject(root, {
components: { BdMessageBadge },
data: { c },
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
}, DOM.createElement('span'));
root.classList.add('bd-has-badge');
}
/**
* Patches UserProfileModal to render profile badges.
*/
async patchUserProfileModal() {
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal');
this.unpatchUserProfileModal = MonkeyPatch('ProfileBadges', this.UserProfileModal.component.prototype).after('renderBadges', (component, args, retVal, setRetVal) => {
this.unpatchUserProfileModal = MonkeyPatch('ProfileBadges', this.UserProfileModal.component.prototype).after('renderBadges', (component, args, retVal, setRetVal) => {
const user = ReactHelpers.findProp(component, 'user');
if (!user) return;
const c = contributors.find(c => c.id === user.id);
if (!c) return;
const element = ReactHelpers.React.createElement('div', {
className: 'bd-profile-badges-outer',
'data-userid': user.id
const element = VueInjector.createReactElement({
components: { BdBadge },
data: { c },
template: '<BdBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />',
});
if (!retVal) {
@ -205,43 +160,6 @@ export default class extends Module {
}));
} else retVal.props.children.splice(0, 0, element);
});
this.UserProfileModal.component.prototype.componentDidMount = this.UserProfileModal.component.prototype.componentDidMount || (() => {});
this.unpatchUserProfileModalMount = MonkeyPatch('ProfileBadges', this.UserProfileModal.component.prototype).after('componentDidMount', component => {
const element = ReactHelpers.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectProfileBadges(element);
});
this.UserProfileModal.component.prototype.componentDidUpdate = this.UserProfileModal.component.prototype.componentDidUpdate || (() => {});
this.unpatchUserProfileModalUpdate = MonkeyPatch('ProfileBadges', this.UserProfileModal.component.prototype).after('componentDidUpdate', component => {
const element = ReactHelpers.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectProfileBadges(element);
});
}
injectProfileBadges(element) {
for (const beo of element.getElementsByClassName('bd-profile-badges-outer'))
this.injectProfileBadge(beo);
}
injectProfileBadge(root) {
while (root.firstChild) {
root.removeChild(root.firstChild);
}
const { userid } = root.dataset;
if (!userid) return;
const c = contributors.find(c => c.id == userid);
if (!c) return;
VueInjector.inject(root, {
components: { BdBadge },
data: { c },
template: '<BdBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />',
}, DOM.createElement('span'));
}
}

View File

@ -8,6 +8,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { WebpackModules } from 'modules';
import Vue from './vue';
export default class {
@ -28,4 +29,43 @@ export default class {
return vue;
}
/**
* Returns a React element that will render a Vue component.
* @param {Object} options Options to pass to Vue
* @return {React.Element}
*/
static createReactElement(options) {
const React = WebpackModules.getModuleByName('React');
return React.createElement(this.ReactCompatibility, {options});
}
static get ReactCompatibility() {
if (this._ReactCompatibility) return this._ReactCompatibility;
const React = WebpackModules.getModuleByName('React');
const ReactDOM = WebpackModules.getModuleByName('ReactDOM');
return this._ReactCompatibility = class VueComponent extends React.Component {
render() {
return React.createElement('span');
}
componentDidMount() {
const element = ReactDOM.findDOMNode(this);
if (!element) return;
this.vueInstance.$mount(element);
}
componentDidUpdate() {
const element = ReactDOM.findDOMNode(this);
if (!element) return;
this.vueInstance.$mount(element);
}
get vueInstance() {
return this._vueInstance || (this._vueInstance = new Vue(this.props.options));
}
}
}
}