diff --git a/examples/themes.css b/examples/themes.css
new file mode 100644
index 0000000..15ac533
--- /dev/null
+++ b/examples/themes.css
@@ -0,0 +1,14 @@
+.dark {
+ background: #000;
+ color: #fff
+}
+
+.dark {
+ background: #fff;
+ color: #000;
+}
+
+.tan {
+ background: tan;
+ color: #ccc;
+}
diff --git a/examples/themes.html b/examples/themes.html
new file mode 100644
index 0000000..553bf1c
--- /dev/null
+++ b/examples/themes.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+ EPUB.js Spreads Example
+
+
+
+
+
+
+
+
+
+
+ ‹
+ ›
+
+
+
+
+
diff --git a/src/contents.js b/src/contents.js
index 430db6f..4955877 100644
--- a/src/contents.js
+++ b/src/contents.js
@@ -426,6 +426,13 @@ Contents.prototype.addStylesheet = function(src) {
return;
}
+ // Check if link already exists
+ $stylesheet = this.document.querySelector('link[href="'+src+'"]');
+ if ($stylesheet) {
+ resolve(true);
+ return; // already present
+ }
+
$stylesheet = this.document.createElement('link');
$stylesheet.type = 'text/css';
$stylesheet.rel = "stylesheet";
@@ -449,10 +456,16 @@ Contents.prototype.addStylesheet = function(src) {
Contents.prototype.addStylesheetRules = function(rules) {
var styleEl;
var styleSheet;
+ var key = "epubjs-inserted-css";
if(!this.document) return;
- styleEl = this.document.createElement('style');
+ // Check if link already exists
+ styleEl = this.document.getElementById("#"+key);
+ if (!styleEl) {
+ styleEl = this.document.createElement('style');
+ styleEl.id = key;
+ }
// Append style element to head
this.document.head.appendChild(styleEl);
@@ -507,6 +520,28 @@ Contents.prototype.addScript = function(src) {
}.bind(this));
};
+Contents.prototype.addClass = function(className) {
+ var content;
+
+ if(!this.document) return;
+
+ content = this.content || this.document.body;
+
+ content.classList.add(className);
+
+};
+
+Contents.prototype.removeClass = function(className) {
+ var content;
+
+ if(!this.document) return;
+
+ content = this.content || this.document.body;
+
+ content.classList.remove(className);
+
+};
+
Contents.prototype.addEventListeners = function(){
if(!this.document) {
return;
diff --git a/src/managers/default/index.js b/src/managers/default/index.js
index fd80774..678bae0 100644
--- a/src/managers/default/index.js
+++ b/src/managers/default/index.js
@@ -559,6 +559,14 @@ DefaultViewManager.prototype.updateFlow = function(flow){
};
+DefaultViewManager.prototype.getContents = function(){
+ var contents = [];
+ this.views.each(function(view){
+ contents.push(view.contents);
+ });
+ return contents;
+};
+
//-- Enable binding events to Manager
EventEmitter(DefaultViewManager.prototype);
diff --git a/src/rendition.js b/src/rendition.js
index 9f1b3c6..acd205e 100644
--- a/src/rendition.js
+++ b/src/rendition.js
@@ -7,6 +7,7 @@ var EpubCFI = require('./epubcfi');
var Queue = require('./queue');
var Layout = require('./layout');
var Mapping = require('./mapping');
+var Themes = require('./themes');
var Path = require('./core').Path;
/**
@@ -71,6 +72,7 @@ function Rendition(book, options) {
this.hooks.content.register(this.passViewEvents.bind(this));
// this.hooks.display.register(this.afterDisplay.bind(this));
+ this.themes = new Themes(this);
this.epubcfi = new EpubCFI();
@@ -587,6 +589,10 @@ Rendition.prototype.adjustImages = function(view) {
});
};
+Rendition.prototype.getContents = function () {
+ return this.manager ? this.manager.getContents() : [];
+};
+
//-- Enable binding events to Renderer
EventEmitter(Rendition.prototype);
diff --git a/src/themes.js b/src/themes.js
new file mode 100644
index 0000000..d30300f
--- /dev/null
+++ b/src/themes.js
@@ -0,0 +1,152 @@
+var Url = require('./core').Url;
+
+function Themes(rendition) {
+ this.rendition = rendition;
+ this._themes = {
+ "default" : {
+ "rules" : [],
+ "url" : "",
+ "serialized" : ''
+ }
+ };
+ this._overrides = {};
+ this._current = "default";
+ this._injected = [];
+ this.rendition.hooks.content.register(this.inject.bind(this));
+ this.rendition.hooks.content.register(this.overrides.bind(this));
+
+}
+
+Themes.prototype.register = function () {
+ if (arguments.length === 0) {
+ return;
+ }
+ if (arguments.length === 1 && typeof(arguments[0]) === "object") {
+ return this.registerThemes(arguments[0]);
+ }
+ if (arguments.length === 2 && typeof(arguments[1]) === "string") {
+ return this.registerUrl(arguments[0], arguments[1]);
+ }
+ if (arguments.length === 2 && typeof(arguments[1]) === "object") {
+ return this.registerRules(arguments[0], arguments[1]);
+ }
+};
+
+Themes.prototype.default = function (theme) {
+ if (!theme) {
+ return;
+ }
+ if (typeof(theme) === "string") {
+ return this.registerUrl("default", theme);
+ }
+ if (typeof(theme) === "object") {
+ return this.registerRules("default", theme);
+ }
+};
+
+Themes.prototype.registerThemes = function (themes) {
+ for (var theme in themes) {
+ if (themes.hasOwnProperty(theme)) {
+ if (typeof(themes[theme]) === "string") {
+ this.registerUrl(theme, themes[theme]);
+ } else {
+ this.registerRules(theme, themes[theme]);
+ }
+ }
+ }
+};
+
+Themes.prototype.registerUrl = function (name, input) {
+ var url = new Url(input);
+ this._themes[name] = { "url": url.toString() };
+};
+
+Themes.prototype.registerRules = function (name, rules) {
+ this._themes[name] = { "rules": rules };
+ // TODO: serialize css rules
+};
+
+Themes.prototype.apply = function (name) {
+ var prev = this._current;
+ var contents;
+
+ this._current = name;
+ this.update(name);
+
+ contents = this.rendition.getContents();
+ contents.forEach(function (content) {
+ content.removeClass(prev);
+ content.addClass(name);
+ }.bind(this));
+};
+
+Themes.prototype.update = function (name) {
+ var contents = this.rendition.getContents();
+ contents.forEach(function (content) {
+ this.add(name, content);
+ }.bind(this));
+};
+
+Themes.prototype.inject = function (view) {
+ var links = [];
+ var themes = this._themes;
+ var theme;
+
+ for (var name in themes) {
+ if (themes.hasOwnProperty(name)) {
+ theme = themes[name];
+ if(theme.rules || (theme.url && links.indexOf(theme.url) === -1)) {
+ this.add(name, view.contents);
+ }
+ }
+ }
+
+ if(this._current) {
+ view.contents.addClass(this._current);
+ }
+};
+
+Themes.prototype.add = function (name, contents) {
+ var theme = this._themes[name];
+
+ if (!theme) {
+ return;
+ }
+
+ if (theme.url) {
+ contents.addStylesheet(theme.url);
+ } else if (theme.serialized) {
+ // TODO: handle serialized
+ } else if (theme.rules && theme.rules.length) {
+ contents.addStylesheetRules(theme.rules);
+ theme.injected = true;
+ }
+};
+
+Themes.prototype.override = function (name, value) {
+ var contents = this.rendition.getContents();
+
+ this._overrides[name] = value;
+
+ contents.forEach(function (content) {
+ content.css(name, this._overrides[name]);
+ }.bind(this));
+};
+
+Themes.prototype.overrides = function (view) {
+ var contents = view.contents;
+ var overrides = this._overrides;
+ var rules = [];
+
+ for (var rule in overrides) {
+ if (overrides.hasOwnProperty(rule)) {
+ contents.css(rule, overrides[rule]);
+ }
+ }
+};
+
+Themes.prototype.fontSize = function (size) {
+ this.override("font-size", size);
+};
+
+module.exports = Themes;