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;