variable customization

pull/7748/head
Guilherme Gazzo 8 years ago
parent b7a527b6ad
commit 36bba3bcfd
  1. 1
      packages/rocketchat-theme/package.js
  2. 10
      packages/rocketchat-theme/server/variables.js
  3. 6
      packages/rocketchat-ui-master/client/main.js
  4. 160
      packages/rocketchat-ui-master/server/dynamic-css.js
  5. 16
      packages/rocketchat-ui-master/server/inject.js
  6. 2
      packages/rocketchat-ui-sidenav/client/sidebarItem.js

@ -29,6 +29,7 @@ Package.onUse(function(api) {
// Photoswipe
api.addFiles('client/vendor/photoswipe.css', 'client');
api.addAssets('client/imports/general/variables.css', 'server');
// Fontello
api.addFiles('client/vendor/fontello/css/fontello.css', 'client');
api.addAssets('client/vendor/fontello/font/fontello.eot', 'client');

@ -76,3 +76,13 @@ RocketChat.settings.add('theme-custom-css', '', {
section: 'Custom CSS',
public: true
});
RocketChat.settings.add('theme-custom-variables', Assets.getText('client/imports/general/variables.css'), {
group: 'Layout',
type: 'code',
code: 'text/css',
multiline: true,
section: 'Customize Theme',
public: true
});

@ -1,6 +1,10 @@
/* globals toolbarSearch, menu, isRtl, fireGlobalEvent, CachedChatSubscription, DynamicCss */
import Clipboard from 'clipboard';
RocketChat.settings.collection.find({_id:/theme/}, {fields:{ value: 1, properties: 1, type: 1 }}).observe({changed: () => { DynamicCss.run(); }});
if (!DynamicCss.test()) {
RocketChat.settings.collection.find({_id:'theme-custom-variables'}, {fields:{ value: 1 }}).observe({changed: () => { DynamicCss.run(true); }});
}
Template.body.onRendered(function() {
new Clipboard('.clipboard');

@ -2,6 +2,7 @@
'use strict';
export default () => {
const debounce = (func, wait, immediate) => {
let timeout;
return function(...args) {
@ -15,46 +16,143 @@ export default () => {
callNow && func.apply(this, args);
};
};
const cssVarPoly = {
init() {
console.time('cssVarPoly');
cssVarPoly.ratifiedVars = {};
cssVarPoly.varsByBlock = [];
cssVarPoly.oldCSS = [];
const style = document.createElement('style');
style.type = 'text/css';
style.id = 'rocketchat-dynamic-css';
cssVarPoly.findCSS();
cssVarPoly.updateCSS();
console.timeEnd('cssVarPoly');
},
findCSS() {
const styleBlocks = Array.prototype.concat.apply([], document.querySelectorAll('#css-variables, link[type="text/css"].__meteor-css__'));
DynamicCss = typeof DynamicCss !=='undefined'? DynamicCss : {list:[]};
// we need to track the order of the style/link elements when we save off the CSS, set a counter
let counter = 1;
const colors = setting => {
if (setting._id.indexOf('theme-color') > -1) {
return setting.properties.map(property => {
const temp = setting._id.replace('theme-color-', '').split(':');
const settingName = temp[0];
const pseudo = temp[1] || '';
const propertyName = property.replace('color', '').replace('-', '');
const className = propertyName ? `${ settingName }-${ propertyName }` : settingName;
// loop through all CSS blocks looking for CSS variables being set
styleBlocks.map(block => {
// console.log(block.nodeName);
if (block.nodeName === 'STYLE') {
const theCSS = block.innerHTML;
cssVarPoly.findSetters(theCSS, counter);
cssVarPoly.oldCSS[counter++] = theCSS;
} else if (block.nodeName === 'LINK') {
const url = block.getAttribute('href');
cssVarPoly.oldCSS[counter] = '';
cssVarPoly.getLink(url, counter, function(counter, request) {
cssVarPoly.findSetters(request.responseText, counter);
cssVarPoly.oldCSS[counter++] = request.responseText;
cssVarPoly.updateCSS();
});
}
});
},
return `.${ className }${ pseudo ? ':' : '' }${ pseudo }{${ property }:${ setting.value }}`;
}).join('\n');
}
// find all the "--variable: value" matches in a provided block of CSS and add them to the master list
findSetters(theCSS, counter) {
// console.log(theCSS);
cssVarPoly.varsByBlock[counter] = theCSS.match(/(--[^:; ]+:..*?;)/g);
},
return '';
};
// run through all the CSS blocks to update the variables and then inject on the page
updateCSS: debounce(() => {
// first lets loop through all the variables to make sure later vars trump earlier vars
cssVarPoly.ratifySetters(cssVarPoly.varsByBlock);
// loop through the css blocks (styles and links)
cssVarPoly.oldCSS.filter(e => e).forEach((css, id) => {
const newCSS = cssVarPoly.replaceGetters(css, cssVarPoly.ratifiedVars);
const el = document.querySelector(`#inserted${ id }`);
if (el) {
// console.log("updating")
el.innerHTML = newCSS;
} else {
// console.log("adding");
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = newCSS;
style.classList.add('inserted');
style.id = `inserted${ id }`;
document.getElementsByTagName('head')[0].appendChild(style);
}
});
}, 100),
// parse a provided block of CSS looking for a provided list of variables and replace the --var-name with the correct value
replaceGetters(oldCSS, varList) {
return oldCSS.replace(/var\((--.*?)\)/gm, (all, variable) => varList[variable]);
},
// determine the css variable name value pair and track the latest
ratifySetters(varList) {
// loop through each block in order, to maintain order specificity
varList.filter(curVars => curVars).forEach(curVars => {
// const curVars = varList[curBlock] || [];
curVars.forEach(function(theVar) {
// console.log(theVar);
// split on the name value pair separator
const matches = theVar.split(/:\s*/);
// console.log(matches);
// put it in an object based on the varName. Each time we do this it will override a previous use and so will always have the last set be the winner
// 0 = the name, 1 = the value, strip off the ; if it is there
cssVarPoly.ratifiedVars[matches[0]] = matches[1].replace(/;/, '');
});
});
Object.keys(cssVarPoly.ratifiedVars).filter(key => {
return cssVarPoly.ratifiedVars[key].indexOf('var') > -1;
}).forEach(key => {
cssVarPoly.ratifiedVars[key] = cssVarPoly.ratifiedVars[key].replace(/var\((--.*?)\)/gm, function(all, variable) {
return cssVarPoly.ratifiedVars[variable];
});
});
},
// get the CSS file (same domain for now)
getLink(url, counter, success) {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.overrideMimeType('text/css;');
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success!
// console.log(request.responseText);
if (typeof success === 'function') {
success(counter, request);
}
} else {
// We reached our target server, but it returned an error
console.warn('an error was returned from:', url);
}
};
request.onerror = function() {
// There was a connection error of some sort
console.warn('we could not get anything from:', url);
};
const customCss = setting => setting._id.indexOf('theme-custom-css') > -1 ? setting.value : '';
const fontFamily = setting => setting._id.indexOf('theme-font-body-font-family') > -1 ? `body{${ setting.value }}` : '';
request.send();
}
const properties = [fontFamily, colors, customCss];
const run = list => {
return list.filter(setting => setting.value && setting.properties || setting.type !== 'color')
.sort((a, b) => a._id.length - b._id.length)
.map(setting => properties.reduce((ret, f) => ret || f(setting), ''))
.join('\n');
};
const stateCheck = setInterval(() => {
if (document.readyState === 'complete' && typeof Meteor !== 'undefined') {
clearInterval(stateCheck);
// document ready
cssVarPoly.init();
}
}, 100);
DynamicCss.run = debounce(() => {
const list = typeof RocketChat !== 'undefined' ? RocketChat.settings.collection.find({_id:/theme/}).fetch() : [];
return style.innerHTML = run(list.length && list || DynamicCss.list);
}, 1000);
DynamicCss = typeof DynamicCss !=='undefined'? DynamicCss : {};
DynamicCss.test = () => window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)');
DynamicCss.run = debounce((replace = false) => {
document.head.appendChild(style);
DynamicCss.run();
if (replace) {
document.querySelector('#css-variables').innerHTML = RocketChat.settings.get('theme-custom-variables');
}
cssVarPoly.init();
}, 1000);
};

@ -1,21 +1,19 @@
/* globals Inject */
const renderDynamicCssList = () => {
const variables = RocketChat.models.Settings.find({_id:/theme-/}, {fields: { value: 1, properties: 1, type: 1 }}).fetch();
Inject.rawHead('dynamic-variables',
`<script>
DynamicCss = typeof DynamicCss !== 'undefined' ? DynamicCss : { };
DynamicCss.list = ${ JSON.stringify(variables) };
</script>`);
const variables = RocketChat.models.Settings.findOne({_id:'theme-custom-variables'}, {fields: { value: 1}});
if (!variables || !variables.value) {
return;
}
Inject.rawBody('dynamic-variables', `<style id='css-variables'>${ variables.value }</style>`);
};
renderDynamicCssList();
RocketChat.models.Settings.find({_id:/theme-/}, {fields: { value: 1, properties: 1, type: 1 }}).observe({
RocketChat.models.Settings.find({_id:'theme-custom-variables'}, {fields: { value: 1}}).observe({
changed: renderDynamicCssList
});
Inject.rawHead('dynamic', `<script>(${ require('./dynamic-css.js').default.toString() })()</script>`);
Inject.rawHead('dynamic', `<script>(${ require('./dynamic-css.js').default.toString().replace(/\/\/.*?\n/g, '') })()</script>`);
Inject.rawHead('page-loading', `<style>${ Assets.getText('public/loading.css') }</style>`);

@ -21,5 +21,5 @@ Template.sidebarItem.events({
});
Template.sidebarItem.onCreated(function() {
console.log('sidebarItem', this.data);
// console.log('sidebarItem', this.data);
});

Loading…
Cancel
Save