From 4d6524b8916955bf5fa9086042917244751dc03d Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 16 May 2015 16:29:20 +0200 Subject: allow preview style replacements --- lib/exe/css.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/exe/css.php b/lib/exe/css.php index 701cebaed..dc4d7d75c 100644 --- a/lib/exe/css.php +++ b/lib/exe/css.php @@ -37,7 +37,7 @@ function css_out(){ if(!$tpl) $tpl = $conf['template']; // load style.ini - $styleini = css_styleini($tpl); + $styleini = css_styleini($tpl, $INPUT->bool('preview')); // find mediatypes if ($INPUT->str('s') == 'feed') { @@ -49,7 +49,7 @@ function css_out(){ } // The generated script depends on some dynamic options - $cache = new cache('styles'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].DOKU_BASE.$tpl.$type,'.css'); + $cache = new cache('styles'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].$INPUT->int('preview').DOKU_BASE.$tpl.$type,'.css'); // if old 'default' userstyle setting exists, make it 'screen' userstyle for backwards compatibility if (isset($config_cascade['userstyle']['default'])) { @@ -63,6 +63,7 @@ function css_out(){ $cache_files[] = $tplinc.'style.local.ini'; // @deprecated $cache_files[] = DOKU_CONF."tpl/$tpl/style.ini"; $cache_files[] = __FILE__; + if($INPUT->bool('preview')) $cache_files[] = $conf['cachedir'].'/preview.ini'; // Array of needed files and their web locations, the latter ones // are needed to fix relative paths in the stylesheets @@ -262,9 +263,12 @@ function css_applystyle($css, $replacements) { * @author Andreas Gohr * * @param string $tpl the used template + * @param bool $preview load preview replacements * @return array with keys 'stylesheets' and 'replacements' */ -function css_styleini($tpl) { +function css_styleini($tpl, $preview=false) { + global $conf; + $stylesheets = array(); // mode, file => base $replacements = array(); // placeholder => value @@ -321,6 +325,19 @@ function css_styleini($tpl) { } } + // allow replacement overwrites in preview mode + if($preview) { + $webbase = DOKU_BASE; + $ini = $conf['cachedir'].'/preview.ini'; + if(file_exists($ini)) { + $data = parse_ini_file($ini, true); + // replacements + if(is_array($data['replacements'])) { + $replacements = array_merge($replacements, css_fixreplacementurls($data['replacements'], $webbase)); + } + } + } + return array( 'stylesheets' => $stylesheets, 'replacements' => $replacements -- cgit v1.2.3 From 6ea007c9f8c6830ea4f21ad880e91279e2f4bf10 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 16 May 2015 18:40:21 +0200 Subject: a first very basic implementation of the preview mechanism --- .gitignore | 1 + lib/plugins/styler/.travis.yml | 13 +++++ lib/plugins/styler/README | 27 ++++++++++ lib/plugins/styler/_test/general.test.php | 33 ++++++++++++ lib/plugins/styler/action.php | 84 +++++++++++++++++++++++++++++++ lib/plugins/styler/admin.php | 74 +++++++++++++++++++++++++++ lib/plugins/styler/lang/en/lang.php | 16 ++++++ lib/plugins/styler/plugin.info.txt | 7 +++ lib/plugins/styler/script.js | 40 +++++++++++++++ 9 files changed, 295 insertions(+) create mode 100644 lib/plugins/styler/.travis.yml create mode 100644 lib/plugins/styler/README create mode 100644 lib/plugins/styler/_test/general.test.php create mode 100644 lib/plugins/styler/action.php create mode 100644 lib/plugins/styler/admin.php create mode 100644 lib/plugins/styler/lang/en/lang.php create mode 100644 lib/plugins/styler/plugin.info.txt create mode 100644 lib/plugins/styler/script.js diff --git a/.gitignore b/.gitignore index acb26dd90..cf1a37300 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ !/lib/plugins/popularity !/lib/plugins/revert !/lib/plugins/safefnrecode +!/lib/plugins/styler !/lib/plugins/testing !/lib/plugins/usermanager !/lib/plugins/action.php diff --git a/lib/plugins/styler/.travis.yml b/lib/plugins/styler/.travis.yml new file mode 100644 index 000000000..d80c0691f --- /dev/null +++ b/lib/plugins/styler/.travis.yml @@ -0,0 +1,13 @@ +# Config file for travis-ci.org + +language: php +php: + - "5.5" + - "5.4" + - "5.3" +env: + - DOKUWIKI=master + - DOKUWIKI=stable +before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh +install: sh travis.sh +script: cd _test && phpunit --stderr --group plugin_styler \ No newline at end of file diff --git a/lib/plugins/styler/README b/lib/plugins/styler/README new file mode 100644 index 000000000..37a966352 --- /dev/null +++ b/lib/plugins/styler/README @@ -0,0 +1,27 @@ +styler Plugin for DokuWiki + +Allows to edit style.ini replacements + +All documentation for this plugin can be found at +https://www.dokuwiki.org/plugin:styler + +If you install this plugin manually, make sure it is installed in +lib/plugins/styler/ - if the folder is called different it +will not work! + +Please refer to http://www.dokuwiki.org/plugins for additional info +on how to install plugins in DokuWiki. + +---- +Copyright (C) Andreas Gohr + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +See the COPYING file in your DokuWiki folder for details diff --git a/lib/plugins/styler/_test/general.test.php b/lib/plugins/styler/_test/general.test.php new file mode 100644 index 000000000..8b0712a20 --- /dev/null +++ b/lib/plugins/styler/_test/general.test.php @@ -0,0 +1,33 @@ +assertFileExists($file); + + $info = confToHash($file); + + $this->assertArrayHasKey('base', $info); + $this->assertArrayHasKey('author', $info); + $this->assertArrayHasKey('email', $info); + $this->assertArrayHasKey('date', $info); + $this->assertArrayHasKey('name', $info); + $this->assertArrayHasKey('desc', $info); + $this->assertArrayHasKey('url', $info); + + $this->assertEquals('styler', $info['base']); + $this->assertRegExp('/^https?:\/\//', $info['url']); + $this->assertTrue(mail_isvalid($info['email'])); + $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']); + $this->assertTrue(false !== strtotime($info['date'])); + } +} diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php new file mode 100644 index 000000000..648190a2a --- /dev/null +++ b/lib/plugins/styler/action.php @@ -0,0 +1,84 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +class action_plugin_styler extends DokuWiki_Action_Plugin { + + /** + * Registers a callback function for a given event + * + * @param Doku_Event_Handler $controller DokuWiki's event controller object + * @return void + */ + public function register(Doku_Event_Handler $controller) { + + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); + $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); + + } + + /** + * [Custom event handler which performs action] + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + public function handle_action(Doku_Event &$event, $param) { + $event->data = act_clean($event->data); + if($event->data === 'styler_plugin_preview') { + msg('handle') ; + $event->data = 'show'; + $this->preview(); + } elseif ($event->data === 'styler_plugin_save') { + $event->data = 'show'; + } + } + + protected function preview(){ + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, $this->makeini()); + } + + protected function makeini() { + global $INPUT; + + $ini = "[replacements]\n"; + foreach($INPUT->arr('tpl') as $key => $val) { + $ini .= $key .' = "'.addslashes($val).'"'."\n"; + } + + return $ini; + } + + /** + * [Custom event handler which performs action] + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + + public function handle_ajax(Doku_Event &$event, $param) { + if($event->data != 'plugin_styler') return; + $event->preventDefault(); + $event->stopPropagation(); + + /** @var admin_plugin_styler $hlp */ + $hlp = plugin_load('admin', 'styler'); + $hlp->html(); + } + +} + +// vim:ts=4:sw=4:et: diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php new file mode 100644 index 000000000..8ecbd22a0 --- /dev/null +++ b/lib/plugins/styler/admin.php @@ -0,0 +1,74 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +class admin_plugin_styler extends DokuWiki_Admin_Plugin { + + /** + * @return int sort number in admin menu + */ + public function getMenuSort() { + return 1000; + } + + /** + * @return bool true if only access for superuser, false is for superusers and moderators + */ + public function forAdminOnly() { + return true; + } + + /** + * Should carry out any processing required by the plugin. + */ + public function handle() { + set_doku_pref('styler_plugin', 1); + } + + /** + * Render HTML output, e.g. helpful text and a form + */ + public function html() { + global $conf; + $tpl = $conf['template']; + define('SIMPLE_TEST',1); // hack, ideally certain functions should be moved out of css.php + require_once(DOKU_INC.'lib/exe/css.php'); + $styleini = css_styleini($conf['template'], true); + $replacements = $styleini['replacements']; + + ptln('

'.$this->getLang('menu').'

'); + + if (empty($replacements)) { + echo '

Sorry, this template does not support this functionality.

'; + } else { + echo '

Intro blah... for the currently active template ("'.$tpl.'")... not all variables preview...

'; + + echo '
'; + echo '

Template variables

'; + echo ''; + foreach($replacements as $key => $value){ + echo ''; + echo ''; + echo ''; + } + echo '
'.$key.''; + echo '
'; + echo ''; + echo '
'; + } + + + + } + + +} + +// vim:ts=4:sw=4:et: \ No newline at end of file diff --git a/lib/plugins/styler/lang/en/lang.php b/lib/plugins/styler/lang/en/lang.php new file mode 100644 index 000000000..dfb472f11 --- /dev/null +++ b/lib/plugins/styler/lang/en/lang.php @@ -0,0 +1,16 @@ + + */ + +// menu entry for admin plugins +// $lang['menu'] = 'Your menu entry'; + +// custom language strings for the plugin +// $lang['fixme'] = 'FIXME'; + + + +//Setup VIM: ex: et ts=4 : diff --git a/lib/plugins/styler/plugin.info.txt b/lib/plugins/styler/plugin.info.txt new file mode 100644 index 000000000..51f2f72f6 --- /dev/null +++ b/lib/plugins/styler/plugin.info.txt @@ -0,0 +1,7 @@ +base styler +author Andreas Gohr +email andi@splitbrain.org +date 2015-05-16 +name styler plugin +desc Allows to edit style.ini replacements +url https://www.dokuwiki.org/plugin:styler diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js new file mode 100644 index 000000000..d09a8b8da --- /dev/null +++ b/lib/plugins/styler/script.js @@ -0,0 +1,40 @@ +jQuery(function () { + + + if (DokuCookie.getValue('styler_plugin') == 1) { + + + + // load dialog + var $dialog = jQuery(document.createElement('div')); + jQuery('body').append($dialog); + $dialog.load( + DOKU_BASE + '/lib/exe/ajax.php', + { + 'call': 'plugin_styler' + }, + function () { + // load the preview template + var now = new Date().getTime(); + var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + + // open the dialog + $dialog.dialog({ + 'title': 'Template Variables', + 'width': 500, + 'top': 50, + 'position': { 'my': 'left top', 'at': 'left top', 'of': window }, + // bring everything back to normal + 'close': function (event, ui) { + // disable the styler plugin again + DokuCookie.setValue('styler_plugin', 0); + // reload + document.location.reload() + } + }); + } + ); + + } +}); \ No newline at end of file -- cgit v1.2.3 From 0f5e7090beb4c8b8e99aa87454c89b53ed11cc66 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 16 May 2015 19:27:43 +0200 Subject: we have working save and revert buttons --- lib/plugins/styler/action.php | 73 ++++++++++++++++++++++++++++++++++++++----- lib/plugins/styler/admin.php | 3 ++ 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index 648190a2a..e2385e739 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -19,8 +19,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { */ public function register(Doku_Event_Handler $controller) { - $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); - $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); + $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); } @@ -35,31 +35,90 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { public function handle_action(Doku_Event &$event, $param) { $event->data = act_clean($event->data); if($event->data === 'styler_plugin_preview') { - msg('handle') ; $event->data = 'show'; $this->preview(); - } elseif ($event->data === 'styler_plugin_save') { + } elseif($event->data === 'styler_plugin_reset') { $event->data = 'show'; + $this->reset(); + } elseif($event->data === 'styler_plugin_revert') { + $event->data = 'show'; + $this->revert(); + } elseif($event->data === 'styler_plugin_save') { + $event->data = 'show'; + $this->save(); } } - protected function preview(){ + /** + * saves the preview.ini + */ + protected function preview() { global $conf; - $ini = $conf['cachedir'].'/preview.ini'; + $ini = $conf['cachedir'].'/preview.ini'; io_saveFile($ini, $this->makeini()); } + /** + * deletes the preview.ini + */ + protected function reset() { + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, ''); + } + + /** + * deletes the local style.ini replacements + */ + protected function revert() { + $this->replaceini(''); + $this->reset(); + } + + /** + * save the local style.ini replacements + */ + protected function save() { + $this->replaceini($this->makeini()); + $this->reset(); + } + + /** + * create the replacement part of a style.ini from submitted data + * + * @return string + */ protected function makeini() { global $INPUT; $ini = "[replacements]\n"; foreach($INPUT->arr('tpl') as $key => $val) { - $ini .= $key .' = "'.addslashes($val).'"'."\n"; + $ini .= $key.' = "'.addslashes($val).'"'."\n"; } return $ini; } + /** + * replaces the replacement parts in the local ini + * + * @param string $new the new ini contents + */ + protected function replaceini($new) { + global $conf; + $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; + if(file_exists($ini)) { + $old = io_readFile($ini); + $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); + $old = trim($old); + } else { + $old = ''; + } + + io_makeFileDir($ini); + io_saveFile($ini, "$old\n\n$new"); + } + /** * [Custom event handler which performs action] * diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 8ecbd22a0..323bf61ec 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -61,6 +61,9 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { } echo ''; echo ''; + echo ''; #FIXME only if preview.ini exists + echo ''; #FIXME only if local.ini exists + echo ''; echo ''; } -- cgit v1.2.3 From fb7685fb53589ebe099201297c0e8dbf20d89c59 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 16 May 2015 19:52:26 +0200 Subject: more improvements --- lib/plugins/styler/action.php | 50 ++++++++++++++++++++++++++----------------- lib/plugins/styler/admin.php | 16 ++++++++------ lib/plugins/styler/script.js | 11 +++++----- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index e2385e739..88e2d0912 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -9,6 +9,14 @@ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); +/** + * Class action_plugin_styler + * + * This handles all the save actions and loading the interface + * + * All this usually would be done within an admin plugin, but we want to have this available outside + * the admin interface using our floating dialog. + */ class action_plugin_styler extends DokuWiki_Action_Plugin { /** @@ -18,10 +26,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { * @return void */ public function register(Doku_Event_Handler $controller) { - $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); - } /** @@ -33,6 +39,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { * @return void */ public function handle_action(Doku_Event &$event, $param) { + if(!auth_isadmin()) return; + $event->data = act_clean($event->data); if($event->data === 'styler_plugin_preview') { $event->data = 'show'; @@ -49,6 +57,26 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { } } + /** + * [Custom event handler which performs action] + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + + public function handle_ajax(Doku_Event &$event, $param) { + if(!auth_isadmin()) return; + if($event->data != 'plugin_styler') return; + $event->preventDefault(); + $event->stopPropagation(); + + /** @var admin_plugin_styler $hlp */ + $hlp = plugin_load('admin', 'styler'); + $hlp->form(); + } + /** * saves the preview.ini */ @@ -119,24 +147,6 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { io_saveFile($ini, "$old\n\n$new"); } - /** - * [Custom event handler which performs action] - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - - public function handle_ajax(Doku_Event &$event, $param) { - if($event->data != 'plugin_styler') return; - $event->preventDefault(); - $event->stopPropagation(); - - /** @var admin_plugin_styler $hlp */ - $hlp = plugin_load('admin', 'styler'); - $hlp->html(); - } } diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 323bf61ec..4be2153ab 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -29,13 +29,21 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { * Should carry out any processing required by the plugin. */ public function handle() { - set_doku_pref('styler_plugin', 1); } /** * Render HTML output, e.g. helpful text and a form */ public function html() { + echo '
'; + $this->form(); + echo '
'; + } + + /** + * Create the actual editing form + */ + public function form() { global $conf; $tpl = $conf['template']; define('SIMPLE_TEST',1); // hack, ideally certain functions should be moved out of css.php @@ -50,7 +58,7 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { } else { echo '

Intro blah... for the currently active template ("'.$tpl.'")... not all variables preview...

'; - echo '
'; + echo ''; echo '

Template variables

'; echo ''; foreach($replacements as $key => $value){ @@ -66,12 +74,8 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { echo ''; echo ''; } - - - } - } // vim:ts=4:sw=4:et: \ No newline at end of file diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index d09a8b8da..b3cadfd5e 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -1,10 +1,12 @@ jQuery(function () { + // user openend the admin page, set cookie and redirect + if(jQuery('#plugin__styler').length) { + DokuCookie.setValue('styler_plugin', 1); + document.location.href = DOKU_BASE; + } - + // The Styler Dialog is currently enabled, display it here and apply the preview styles if (DokuCookie.getValue('styler_plugin') == 1) { - - - // load dialog var $dialog = jQuery(document.createElement('div')); jQuery('body').append($dialog); @@ -35,6 +37,5 @@ jQuery(function () { }); } ); - } }); \ No newline at end of file -- cgit v1.2.3 From aae321f2598674fe5c509cdd2a07327249358938 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 08:15:32 +0200 Subject: make styler plugin work without javascript as well (partly done) --- lib/plugins/styler/action.php | 94 ++++--------------------------------- lib/plugins/styler/admin.php | 106 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index 88e2d0912..b7dd27cc4 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -28,6 +28,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { public function register(Doku_Event_Handler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); + + // FIXME load preview style when on admin page } /** @@ -40,21 +42,12 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { */ public function handle_action(Doku_Event &$event, $param) { if(!auth_isadmin()) return; + if($event->data != 'styler_plugin') return; + $event->data = 'show'; - $event->data = act_clean($event->data); - if($event->data === 'styler_plugin_preview') { - $event->data = 'show'; - $this->preview(); - } elseif($event->data === 'styler_plugin_reset') { - $event->data = 'show'; - $this->reset(); - } elseif($event->data === 'styler_plugin_revert') { - $event->data = 'show'; - $this->revert(); - } elseif($event->data === 'styler_plugin_save') { - $event->data = 'show'; - $this->save(); - } + /** @var admin_plugin_styler $hlp */ + $hlp = plugin_load('admin', 'styler'); + $hlp->handle(); } /** @@ -74,80 +67,9 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { /** @var admin_plugin_styler $hlp */ $hlp = plugin_load('admin', 'styler'); - $hlp->form(); + $hlp->form(true); } - /** - * saves the preview.ini - */ - protected function preview() { - global $conf; - $ini = $conf['cachedir'].'/preview.ini'; - io_saveFile($ini, $this->makeini()); - } - - /** - * deletes the preview.ini - */ - protected function reset() { - global $conf; - $ini = $conf['cachedir'].'/preview.ini'; - io_saveFile($ini, ''); - } - - /** - * deletes the local style.ini replacements - */ - protected function revert() { - $this->replaceini(''); - $this->reset(); - } - - /** - * save the local style.ini replacements - */ - protected function save() { - $this->replaceini($this->makeini()); - $this->reset(); - } - - /** - * create the replacement part of a style.ini from submitted data - * - * @return string - */ - protected function makeini() { - global $INPUT; - - $ini = "[replacements]\n"; - foreach($INPUT->arr('tpl') as $key => $val) { - $ini .= $key.' = "'.addslashes($val).'"'."\n"; - } - - return $ini; - } - - /** - * replaces the replacement parts in the local ini - * - * @param string $new the new ini contents - */ - protected function replaceini($new) { - global $conf; - $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; - if(file_exists($ini)) { - $old = io_readFile($ini); - $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); - $old = trim($old); - } else { - $old = ''; - } - - io_makeFileDir($ini); - io_saveFile($ini, "$old\n\n$new"); - } - - } // vim:ts=4:sw=4:et: diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 4be2153ab..dfc72373c 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -29,6 +29,11 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { * Should carry out any processing required by the plugin. */ public function handle() { + global $INPUT; + $run = $INPUT->extract('run')->str('run'); + if(!$run) return; + $run = "run_$run"; + $this->$run(); } /** @@ -36,46 +41,125 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { */ public function html() { echo '
'; - $this->form(); + $this->form(false); echo '
'; } /** * Create the actual editing form */ - public function form() { + public function form($isajax) { global $conf; + global $ID; $tpl = $conf['template']; - define('SIMPLE_TEST',1); // hack, ideally certain functions should be moved out of css.php + define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php require_once(DOKU_INC.'lib/exe/css.php'); - $styleini = css_styleini($conf['template'], true); + $styleini = css_styleini($conf['template'], true); $replacements = $styleini['replacements']; + if($isajax) { + $target = wl($ID, array('do' => 'styler_plugin')); + } else { + $target = wl($ID, array('do' => 'admin', 'page' => 'styler')); + } + ptln('

'.$this->getLang('menu').'

'); - if (empty($replacements)) { + if(empty($replacements)) { echo '

Sorry, this template does not support this functionality.

'; } else { echo '

Intro blah... for the currently active template ("'.$tpl.'")... not all variables preview...

'; - echo '
'; + echo ''; echo '

Template variables

'; echo '
'; - foreach($replacements as $key => $value){ + foreach($replacements as $key => $value) { echo ''; echo ''; echo ''; } echo '
'.$key.''; echo '
'; - echo ''; - echo ''; #FIXME only if preview.ini exists - echo ''; #FIXME only if local.ini exists - echo ''; + echo ''; + echo ''; #FIXME only if preview.ini exists + echo ''; #FIXME only if local.ini exists + echo ''; echo ''; } } + /** + * saves the preview.ini + */ + protected function run_preview() { + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, $this->makeini()); + } + + /** + * deletes the preview.ini + */ + protected function run_reset() { + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, ''); + } + + /** + * deletes the local style.ini replacements + */ + protected function run_revert() { + $this->replaceini(''); + $this->run_reset(); + } + + /** + * save the local style.ini replacements + */ + protected function run_save() { + $this->replaceini($this->makeini()); + $this->run_reset(); + } + + /** + * create the replacement part of a style.ini from submitted data + * + * @return string + */ + protected function makeini() { + global $INPUT; + + $ini = "[replacements]\n"; + $ini .= ";These overwrites have been generated from the Template Styler Admin interface\n"; + $ini .= ";Any values in this section will be overwritten by that tool again\n"; + foreach($INPUT->arr('tpl') as $key => $val) { + $ini .= $key.' = "'.addslashes($val).'"'."\n"; + } + + return $ini; + } + + /** + * replaces the replacement parts in the local ini + * + * @param string $new the new ini contents + */ + protected function replaceini($new) { + global $conf; + $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; + if(file_exists($ini)) { + $old = io_readFile($ini); + $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); + $old = trim($old); + } else { + $old = ''; + } + + io_makeFileDir($ini); + io_saveFile($ini, "$old\n\n$new"); + } + } // vim:ts=4:sw=4:et: \ No newline at end of file -- cgit v1.2.3 From 831864db51ec2c0cd8b86aaee1f1c160df91087d Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 10:53:22 +0200 Subject: make styler work without JS as well --- lib/plugins/styler/action.php | 37 +++++++++++++++++++++++++++++++------ lib/plugins/styler/admin.php | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index b7dd27cc4..b63cf3ec1 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -20,7 +20,7 @@ if(!defined('DOKU_INC')) die(); class action_plugin_styler extends DokuWiki_Action_Plugin { /** - * Registers a callback function for a given event + * Registers a callback functions * * @param Doku_Event_Handler $controller DokuWiki's event controller object * @return void @@ -28,12 +28,37 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { public function register(Doku_Event_Handler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); + $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_header'); + } + + /** + * Adds the preview parameter to the stylesheet loading in non-js mode + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + public function handle_header(Doku_Event &$event, $param) { + global $ACT; + global $INPUT; + if($ACT != 'admin' || $INPUT->str('page') != 'styler') return; + if(!auth_isadmin()) return; - // FIXME load preview style when on admin page + // set preview + $len = count($event->data['link']); + for($i = 0; $i < $len; $i++) { + if( + $event->data['link'][$i]['rel'] == 'stylesheet' && + strpos($event->data['link'][$i]['href'], 'lib/exe/css.php') !== false + ) { + $event->data['link'][$i]['href'] .= '&preview=1&tseed='.time(); + } + } } /** - * [Custom event handler which performs action] + * Updates the style.ini settings by passing it on to handle() of the admin component * * @param Doku_Event $event event object by reference * @param mixed $param [the parameters passed as fifth argument to register_hook() when this @@ -41,8 +66,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { * @return void */ public function handle_action(Doku_Event &$event, $param) { - if(!auth_isadmin()) return; if($event->data != 'styler_plugin') return; + if(!auth_isadmin()) return; $event->data = 'show'; /** @var admin_plugin_styler $hlp */ @@ -51,7 +76,7 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { } /** - * [Custom event handler which performs action] + * Create the style form in the floating Dialog * * @param Doku_Event $event event object by reference * @param mixed $param [the parameters passed as fifth argument to register_hook() when this @@ -60,8 +85,8 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { */ public function handle_ajax(Doku_Event &$event, $param) { - if(!auth_isadmin()) return; if($event->data != 'plugin_styler') return; + if(!auth_isadmin()) return; $event->preventDefault(); $event->stopPropagation(); diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index dfc72373c..bb9426e12 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -26,7 +26,7 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { } /** - * Should carry out any processing required by the plugin. + * handle the different actions (also called from ajax) */ public function handle() { global $INPUT; -- cgit v1.2.3 From 82f9aff6a65b1acaeec6cfde9a2848cb9cb92e03 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 11:16:17 +0200 Subject: started to localize the styler plugin --- lib/plugins/styler/admin.php | 37 +++++++++++++++++++++++++++--------- lib/plugins/styler/lang/en/intro.txt | 2 ++ lib/plugins/styler/lang/en/lang.php | 9 +++++++-- lib/plugins/styler/script.js | 2 +- 4 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 lib/plugins/styler/lang/en/intro.txt diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index bb9426e12..bfc6cd32b 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -25,6 +25,15 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { return true; } + /** + * @param string $language + * @return string + */ + public function getMenuText($language) { + $js = $this->getLang('js'); + return $js['menu']; + } + /** * handle the different actions (also called from ajax) */ @@ -41,6 +50,7 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { */ public function html() { echo '
'; + ptln('

'.$this->getMenuText('').'

'); $this->form(false); echo '
'; } @@ -63,15 +73,13 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { $target = wl($ID, array('do' => 'admin', 'page' => 'styler')); } - ptln('

'.$this->getLang('menu').'

'); - if(empty($replacements)) { - echo '

Sorry, this template does not support this functionality.

'; + echo '

'.$this->getLang('error').'

'; } else { - echo '

Intro blah... for the currently active template ("'.$tpl.'")... not all variables preview...

'; + echo $this->locale_xhtml('intro'); echo '
'; - echo '

Template variables

'; + echo ''; foreach($replacements as $key => $value) { echo ''; @@ -80,11 +88,22 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { echo ''; } echo '
'; - echo ''; - echo ''; #FIXME only if preview.ini exists - echo ''; #FIXME only if local.ini exists - echo ''; + + echo '

'; + echo ''; + echo ''; #FIXME only if preview.ini exists + echo '

'; + + echo '

'; + echo ''; + echo '

'; + + echo '

'; + echo ''; #FIXME only if local.ini exists + echo '

'; + echo '
'; + } } diff --git a/lib/plugins/styler/lang/en/intro.txt b/lib/plugins/styler/lang/en/intro.txt new file mode 100644 index 000000000..bb9e3ff33 --- /dev/null +++ b/lib/plugins/styler/lang/en/intro.txt @@ -0,0 +1,2 @@ +This tool allows you to change certain style settings of your currently selected template +all changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file diff --git a/lib/plugins/styler/lang/en/lang.php b/lib/plugins/styler/lang/en/lang.php index dfb472f11..2dd7921b0 100644 --- a/lib/plugins/styler/lang/en/lang.php +++ b/lib/plugins/styler/lang/en/lang.php @@ -6,10 +6,15 @@ */ // menu entry for admin plugins -// $lang['menu'] = 'Your menu entry'; +$lang['js']['menu'] = 'Template Style Settings'; // custom language strings for the plugin -// $lang['fixme'] = 'FIXME'; +$lang['error'] = 'Sorry, this template does not support this functionality.'; + +$lang['btn_preview'] = 'Preview your changes'; +$lang['btn_save'] = 'Save your changes'; +$lang['btn_reset'] = 'Reset your current changes'; +$lang['btn_revert'] = 'Revert all styles back to the template\'s default'; diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index b3cadfd5e..d472c4fe7 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -23,7 +23,7 @@ jQuery(function () { // open the dialog $dialog.dialog({ - 'title': 'Template Variables', + 'title': LANG.plugins.styler.menu, 'width': 500, 'top': 50, 'position': { 'my': 'left top', 'at': 'left top', 'of': window }, -- cgit v1.2.3 From e422a60a9672c993b6872e856b9172dbd8135b56 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 15:37:50 +0200 Subject: added color picker support I'm not too hapy with it --- lib/plugins/styler/admin.php | 28 +- lib/plugins/styler/iris.js | 1488 ++++++++++++++++++++++++++++++++++++++++++ lib/plugins/styler/script.js | 13 +- 3 files changed, 1525 insertions(+), 4 deletions(-) create mode 100644 lib/plugins/styler/iris.js diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index bfc6cd32b..8d44ac6f5 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -84,7 +84,7 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { foreach($replacements as $key => $value) { echo ''; echo ''.$key.''; - echo ''; + echo 'colorClass($key).' />'; echo ''; } echo ''; @@ -107,6 +107,32 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { } } + /** + * set the color class attribute + */ + protected function colorClass($key) { + static $colors = array( + 'text', + 'background', + 'text_alt', + 'background_alt', + 'text_neu', + 'background_neu', + 'border', + 'highlight', + 'background_site', + 'link', + 'existing', + 'missing', + ); + + if(preg_match('/colou?r/', $key) || in_array(trim($key,'_'), $colors)) { + return 'class="color"'; + } else { + return ''; + } + } + /** * saves the preview.ini */ diff --git a/lib/plugins/styler/iris.js b/lib/plugins/styler/iris.js new file mode 100644 index 000000000..4eda5022e --- /dev/null +++ b/lib/plugins/styler/iris.js @@ -0,0 +1,1488 @@ +/*! Iris Color Picker - v1.0.7 - 2014-11-28 +* https://github.com/Automattic/Iris +* Copyright (c) 2014 Matt Wiebe; Licensed GPLv2 */ +(function( $, undef ){ + var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion; + + _html = '
'; + _css = '.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}'; + + // Even IE9 dosen't support gradients. Elaborate sigh. + UA = navigator.userAgent.toLowerCase(); + isIE = navigator.appName === 'Microsoft Internet Explorer'; + IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; + nonGradientIE = ( isIE && IEVersion < 10 ); + gradientType = false; + + // we don't bother with an unprefixed version, as it has a different syntax + vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ]; + + // Bail for IE <= 7 + if ( nonGradientIE && IEVersion <= 7 ) { + $.fn.iris = $.noop; + $.support.iris = false; + return; + } + + $.support.iris = true; + + function testGradientType() { + var el, base, + bgImageString = 'backgroundImage'; + + if ( nonGradientIE ) { + gradientType = 'filter'; + } + else { + el = $( '
' ); + base = 'linear-gradient(top,#fff,#000)'; + $.each( vendorPrefixes, function( i, val ){ + el.css( bgImageString, val + base ); + if ( el.css( bgImageString ).match( 'gradient' ) ) { + gradientType = i; + return false; + } + }); + // check for legacy webkit gradient syntax + if ( gradientType === false ) { + el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' ); + if ( el.css( bgImageString ).match( 'gradient' ) ) { + gradientType = 'webkit'; + } + } + el.remove(); + } + + } + + /** + * Only for CSS3 gradients. oldIE will use a separate function. + * + * Accepts as many color stops as necessary from 2nd arg on, or 2nd + * arg can be an array of color stops + * + * @param {string} origin Gradient origin - top or left, defaults to left. + * @return {string} Appropriate CSS3 gradient string for use in + */ + function createGradient( origin, stops ) { + origin = ( origin === 'top' ) ? 'top' : 'left'; + stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); + if ( gradientType === 'webkit' ) { + return legacyWebkitGradient( origin, stops ); + } else { + return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')'; + } + } + + /** + * Stupid gradients for a stupid browser. + */ + function stupidIEGradient( origin, stops ) { + var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html; + + origin = ( origin === 'top' ) ? 'top' : 'left'; + stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); + // 8 hex: AARRGGBB + // GradientType: 0 vertical, 1 horizontal + type = ( origin === 'top' ) ? 0 : 1; + self = $( this ); + lastIndex = stops.length - 1; + filter = 'filter'; + startPosProp = ( type === 1 ) ? 'left' : 'top'; + endPosProp = ( type === 1 ) ? 'right' : 'bottom'; + dimensionProp = ( type === 1 ) ? 'height' : 'width'; + template = '
'; + html = ''; + // need a positioning context + if ( self.css('position') === 'static' ) { + self.css( {position: 'relative' } ); + } + + stops = fillColorStops( stops ); + $.each(stops, function( i, startColor ) { + var endColor, endStop, filterVal; + + // we want two at a time. if we're on the last pair, bail. + if ( i === lastIndex ) { + return false; + } + + endColor = stops[ i + 1 ]; + //if our pairs are at the same color stop, moving along. + if ( startColor.stop === endColor.stop ) { + return; + } + + endStop = 100 - parseFloat( endColor.stop ) + '%'; + startColor.octoHex = new Color( startColor.color ).toIEOctoHex(); + endColor.octoHex = new Color( endColor.color ).toIEOctoHex(); + + filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')'; + html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal ); + }); + self.find( '.iris-ie-gradient-shim' ).remove(); + $( html ).prependTo( self ); + } + + function legacyWebkitGradient( origin, colorList ) { + var stops = []; + origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,'; + colorList = fillColorStops( colorList ); + $.each( colorList, function( i, val ){ + stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' ); + }); + return '-webkit-gradient(linear,' + origin + stops.join(',') + ')'; + } + + function fillColorStops( colorList ) { + var colors = [], + percs = [], + newColorList = [], + lastIndex = colorList.length - 1; + + $.each( colorList, function( index, val ) { + var color = val, + perc = false, + match = val.match( /1?[0-9]{1,2}%$/ ); + + if ( match ) { + color = val.replace( /\s?1?[0-9]{1,2}%$/, '' ); + perc = match.shift(); + } + colors.push( color ); + percs.push( perc ); + }); + + // back fill first and last + if ( percs[0] === false ) { + percs[0] = '0%'; + } + + if ( percs[lastIndex] === false ) { + percs[lastIndex] = '100%'; + } + + percs = backFillColorStops( percs ); + + $.each( percs, function( i ){ + newColorList[i] = { color: colors[i], stop: percs[i] }; + }); + return newColorList; + } + + function backFillColorStops( stops ) { + var first = 0, + last = stops.length - 1, + i = 0, + foundFirst = false, + incr, + steps, + step, + firstVal; + + if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) { + return stops; + } + while ( i < stops.length - 1 ) { + if ( ! foundFirst && stops[i] === false ) { + first = i - 1; + foundFirst = true; + } else if ( foundFirst && stops[i] !== false ) { + last = i; + i = stops.length; + } + i++; + } + steps = last - first; + firstVal = parseInt( stops[first].replace('%'), 10 ); + incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps; + i = first + 1; + step = 1; + while ( i < last ) { + stops[i] = ( firstVal + ( step * incr ) ) + '%'; + step++; + i++; + } + return backFillColorStops( stops ); + } + + $.fn.gradient = function() { + var args = arguments; + return this.each( function() { + // this'll be oldishIE + if ( nonGradientIE ) { + stupidIEGradient.apply( this, args ); + } else { + // new hotness + $( this ).css( 'backgroundImage', createGradient.apply( this, args ) ); + } + }); + }; + + $.fn.raninbowGradient = function( origin, args ) { + var opts, template, i, steps; + + origin = origin || 'top'; + opts = $.extend( {}, { s: 100, l: 50 }, args ); + template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)'; + i = 0; + steps = []; + while ( i <= 360 ) { + steps.push( template.replace('%h%', i) ); + i += 30; + } + return this.each(function() { + $(this).gradient( origin, steps ); + }); + }; + + // the colorpicker widget def. + Iris = { + options: { + color: false, + mode: 'hsl', + controls: { + horiz: 's', // horizontal defaults to saturation + vert: 'l', // vertical defaults to lightness + strip: 'h' // right strip defaults to hue + }, + hide: true, // hide the color picker by default + border: true, // draw a border around the collection of UI elements + target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. + width: 200, // the width of the collection of UI elements + palettes: false // show a palette of basic colors beneath the square. + }, + _color: '', + _palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ], + _inited: false, + _defaultHSLControls: { + horiz: 's', + vert: 'l', + strip: 'h' + }, + _defaultHSVControls: { + horiz: 'h', + vert: 'v', + strip: 's' + }, + _scale: { + h: 360, + s: 100, + l: 100, + v: 100 + }, + _create: function() { + var self = this, + el = self.element, + color = self.options.color || el.val(); + + if ( gradientType === false ) { + testGradientType(); + } + + if ( el.is( 'input' ) ) { + if ( self.options.target ) { + self.picker = $( _html ).appendTo( self.options.target ); + } else { + self.picker = $( _html ).insertAfter( el ); + } + + self._addInputListeners( el ); + } else { + el.append( _html ); + self.picker = el.find( '.iris-picker' ); + } + + // Browsers / Versions + // Feature detection doesn't work for these, and $.browser is deprecated + if ( isIE ) { + if ( IEVersion === 9 ) { + self.picker.addClass( 'iris-ie-9' ); + } else if ( IEVersion <= 8 ) { + self.picker.addClass( 'iris-ie-lt9' ); + } + } else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { + self.picker.addClass( 'iris-mozilla' ); + } + + if ( self.options.palettes ) { + self._addPalettes(); + } + + self._color = new Color( color ).setHSpace( self.options.mode ); + self.options.color = self._color.toString(); + + // prep 'em for re-use + self.controls = { + square: self.picker.find( '.iris-square' ), + squareDrag: self.picker.find( '.iris-square-value' ), + horiz: self.picker.find( '.iris-square-horiz' ), + vert: self.picker.find( '.iris-square-vert' ), + strip: self.picker.find( '.iris-strip' ), + stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' ) + }; + + // small sanity check - if we chose hsv, change default controls away from hsl + if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { + self.options.controls = self._defaultHSVControls; + } else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { + self.options.controls = self._defaultHSLControls; + } + + // store it. HSL gets squirrely + self.hue = self._color.h(); + + if ( self.options.hide ) { + self.picker.hide(); + } + + if ( self.options.border ) { + self.picker.addClass( 'iris-border' ); + } + + self._initControls(); + self.active = 'external'; + self._dimensions(); + self._change(); + }, + _has: function(needle, haystack) { + var ret = false; + $.each(haystack, function(i,v){ + if ( needle === v ) { + ret = true; + // exit the loop + return false; + } + }); + return ret; + }, + _addPalettes: function () { + var container = $( '
' ), + palette = $( '' ), + colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes; + + // do we have an existing container? Empty and reuse it. + if ( this.picker.find( '.iris-palette-container' ).length ) { + container = this.picker.find( '.iris-palette-container' ).detach().html( '' ); + } + + $.each(colors, function(index, val) { + palette.clone().data( 'color', val ) + .css( 'backgroundColor', val ).appendTo( container ) + .height( 10 ).width( 10 ); + }); + + this.picker.append(container); + }, + _paint: function() { + var self = this; + self._paintDimension( 'top', 'strip' ); + self._paintDimension( 'top', 'vert' ); + self._paintDimension( 'left', 'horiz' ); + }, + _paintDimension: function( origin, control ) { + var self = this, + c = self._color, + mode = self.options.mode, + color = self._getHSpaceColor(), + target = self.controls[ control ], + controlOpts = self.options.controls, + stops; + + // don't paint the active control + if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) { + return; + } + + switch ( controlOpts[ control ] ) { + case 'h': + if ( mode === 'hsv' ) { + color = c.clone(); + switch ( control ) { + case 'horiz': + color[controlOpts.vert](100); + break; + case 'vert': + color[controlOpts.horiz](100); + break; + case 'strip': + color.setHSpace('hsl'); + break; + } + stops = color.toHsl(); + } else { + if ( control === 'strip' ) { + stops = { s: color.s, l: color.l }; + } else { + stops = { s: 100, l: color.l }; + } + } + + target.raninbowGradient( origin, stops ); + break; + case 's': + if ( mode === 'hsv' ) { + if ( control === 'vert' ) { + stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ]; + } else if ( control === 'strip' ) { + stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ]; + } else if ( control === 'horiz' ) { + stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ]; + } + } else { // implicit mode === 'hsl' + if ( control === 'vert' && self.options.controls.horiz === 'h' ) { + stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)']; + } else { + stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)']; + } + } + + + target.gradient( origin, stops ); + break; + case 'l': + if ( control === 'strip' ) { + stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)']; + } else { + stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)']; + } + target.gradient( origin, stops ); + break; + case 'v': + if ( control === 'strip' ) { + stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ]; + } else { + stops = ['rgba(0,0,0,0)', '#000']; + } + target.gradient( origin, stops ); + break; + default: + break; + } + }, + + _getHSpaceColor: function() { + return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl(); + }, + + _dimensions: function( reset ) { + // whatever size + var self = this, + opts = self.options, + controls = self.controls, + square = controls.square, + strip = self.picker.find( '.iris-strip' ), + squareWidth = '77.5%', + stripWidth = '12%', + totalPadding = 20, + innerWidth = opts.border ? opts.width - totalPadding : opts.width, + controlsHeight, + paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length, + paletteMargin, paletteWidth, paletteContainerWidth; + + if ( reset ) { + square.css( 'width', '' ); + strip.css( 'width', '' ); + self.picker.css( {width: '', height: ''} ); + } + + squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 ); + stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 ); + controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth; + + square.width( squareWidth ).height( squareWidth ); + strip.height( squareWidth ).width( stripWidth ); + self.picker.css( { width: opts.width, height: controlsHeight } ); + + if ( ! opts.palettes ) { + return self.picker.css( 'paddingBottom', '' ); + } + + // single margin at 2% + paletteMargin = squareWidth * 2 / 100; + paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin ); + paletteWidth = paletteContainerWidth / paletteCount; + self.picker.find('.iris-palette').each( function( i ) { + var margin = i === 0 ? 0 : paletteMargin; + $( this ).css({ + width: paletteWidth, + height: paletteWidth, + marginLeft: margin + }); + }); + self.picker.css( 'paddingBottom', paletteWidth + paletteMargin ); + strip.height( paletteWidth + paletteMargin + squareWidth ); + }, + + _addInputListeners: function( input ) { + var self = this, + debounceTimeout = 100, + callback = function( event ){ + var color = new Color( input.val() ), + val = input.val().replace( /^#/, '' ); + + input.removeClass( 'iris-error' ); + // we gave a bad color + if ( color.error ) { + // don't error on an empty input - we want those allowed + if ( val !== '' ) { + input.addClass( 'iris-error' ); + } + } else { + if ( color.toString() !== self._color.toString() ) { + // let's not do this on keyup for hex shortcodes + if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { + self._setOption( 'color', color.toString() ); + } + } + } + }; + + input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); + + // If we initialized hidden, show on first focus. The rest is up to you. + if ( self.options.hide ) { + input.one( 'focus', function() { + self.show(); + }); + } + }, + + _initControls: function() { + var self = this, + controls = self.controls, + square = controls.square, + controlOpts = self.options.controls, + stripScale = self._scale[controlOpts.strip]; + + controls.stripSlider.slider({ + orientation: 'vertical', + max: stripScale, + slide: function( event, ui ) { + self.active = 'strip'; + // "reverse" for hue. + if ( controlOpts.strip === 'h' ) { + ui.value = stripScale - ui.value; + } + + self._color[controlOpts.strip]( ui.value ); + self._change.apply( self, arguments ); + } + }); + + controls.squareDrag.draggable({ + containment: controls.square.find( '.iris-square-inner' ), + zIndex: 1000, + cursor: 'move', + drag: function( event, ui ) { + self._squareDrag( event, ui ); + }, + start: function() { + square.addClass( 'iris-dragging' ); + $(this).addClass( 'ui-state-focus' ); + }, + stop: function() { + square.removeClass( 'iris-dragging' ); + $(this).removeClass( 'ui-state-focus' ); + } + }).on( 'mousedown mouseup', function( event ) { + var focusClass = 'ui-state-focus'; + event.preventDefault(); + if (event.type === 'mousedown' ) { + self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur(); + $(this).addClass( focusClass ).focus(); + } else { + $(this).removeClass( focusClass ); + } + }).on( 'keydown', function( event ) { + var container = controls.square, + draggable = controls.squareDrag, + position = draggable.position(), + distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop" + + // make alt key go "10" + if ( event.altKey ) { + distance *= 10; + } + + // Reposition if one of the directional keys is pressed + switch ( event.keyCode ) { + case 37: position.left -= distance; break; // Left + case 38: position.top -= distance; break; // Up + case 39: position.left += distance; break; // Right + case 40: position.top += distance; break; // Down + default: return true; // Exit and bubble + } + + // Keep draggable within container + position.left = Math.max( 0, Math.min( position.left, container.width() ) ); + position.top = Math.max( 0, Math.min( position.top, container.height() ) ); + + draggable.css(position); + self._squareDrag( event, { position: position }); + event.preventDefault(); + }); + + // allow clicking on the square to move there and keep dragging + square.mousedown( function( event ) { + var squareOffset, pos; + // only left click + if ( event.which !== 1 ) { + return; + } + + // prevent bubbling from the handle: no infinite loops + if ( ! $( event.target ).is( 'div' ) ) { + return; + } + + squareOffset = self.controls.square.offset(); + pos = { + top: event.pageY - squareOffset.top, + left: event.pageX - squareOffset.left + }; + event.preventDefault(); + self._squareDrag( event, { position: pos } ); + event.target = self.controls.squareDrag.get(0); + self.controls.squareDrag.css( pos ).trigger( event ); + }); + + // palettes + if ( self.options.palettes ) { + self._paletteListeners(); + } + }, + + _paletteListeners: function() { + var self = this; + self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() { + self._color.fromCSS( $(this).data('color') ); + self.active = 'external'; + self._change(); + }).on( 'keydown.palette', '.iris-palette', function( event ) { + if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) { + return true; + } + event.stopPropagation(); + $( this ).click(); + }); + }, + + _squareDrag: function( event, ui ) { + var self = this, + controlOpts = self.options.controls, + dimensions = self._squareDimensions(), + vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ), + horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] ); + + self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal ); + + self.active = 'square'; + self._change.apply( self, arguments ); + }, + + _setOption: function( key, value ) { + var self = this, + oldValue = self.options[key], + doDimensions = false, + hexLessColor, + newColor, + method; + + // ensure the new value is set. We can reset to oldValue if some check wasn't met. + self.options[key] = value; + + switch(key) { + case 'color': + // cast to string in case we have a number + value = '' + value; + hexLessColor = value.replace( /^#/, '' ); + newColor = new Color( value ).setHSpace( self.options.mode ); + if ( newColor.error ) { + self.options[key] = oldValue; + } else { + self._color = newColor; + self.options.color = self.options[key] = self._color.toString(); + self.active = 'external'; + self._change(); + } + break; + case 'palettes': + doDimensions = true; + + if ( value ) { + self._addPalettes(); + } else { + self.picker.find('.iris-palette-container').remove(); + } + + // do we need to add events? + if ( ! oldValue ) { + self._paletteListeners(); + } + break; + case 'width': + doDimensions = true; + break; + case 'border': + doDimensions = true; + method = value ? 'addClass' : 'removeClass'; + self.picker[method]('iris-border'); + break; + case 'mode': + case 'controls': + // if nothing's changed, let's bail, since this causes re-rendering the whole widget + if ( oldValue === value ) { + return; + } + + // we're using these poorly named variables because they're already scoped. + // method is the element that Iris was called on. oldValue will be the options + method = self.element; + oldValue = self.options; + oldValue.hide = ! self.picker.is( ':visible' ); + self.destroy(); + self.picker.remove(); + return $(self.element).iris(oldValue); + } + + // Do we need to recalc dimensions? + if ( doDimensions ) { + self._dimensions(true); + } + }, + + _squareDimensions: function( forceRefresh ) { + var square = this.controls.square, + dimensions, + control; + + if ( forceRefresh !== undef && square.data('dimensions') ) { + return square.data('dimensions'); + } + + control = this.controls.squareDrag; + dimensions = { + w: square.width(), + h: square.height() + }; + square.data( 'dimensions', dimensions ); + return dimensions; + }, + + _isNonHueControl: function( active, type ) { + if ( active === 'square' && this.options.controls.strip === 'h' ) { + return true; + } else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) { + return false; + } + + return true; + }, + + _change: function() { + var self = this, + controls = self.controls, + color = self._getHSpaceColor(), + actions = [ 'square', 'strip' ], + controlOpts = self.options.controls, + type = controlOpts[self.active] || 'external', + oldHue = self.hue; + + if ( self.active === 'strip' ) { + // take no action on any of the square sliders if we adjusted the strip + actions = []; + } else if ( self.active !== 'external' ) { + // for non-strip, non-external, strip should never change + actions.pop(); // conveniently the last item + } + + $.each( actions, function(index, item) { + var value, dimensions, cssObj; + if ( item !== self.active ) { + switch ( item ) { + case 'strip': + // reverse for hue + value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; + controls.stripSlider.slider( 'value', value ); + break; + case 'square': + dimensions = self._squareDimensions(); + cssObj = { + left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, + top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) + }; + + self.controls.squareDrag.css( cssObj ); + break; + } + } + }); + + // Ensure that we don't change hue if we triggered a hue reset + if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { + self._color.h(oldHue); + } + + // store hue for repeating above check next time + self.hue = self._color.h(); + + self.options.color = self._color.toString(); + + // only run after the first time + if ( self._inited ) { + self._trigger( 'change', { type: self.active }, { color: self._color } ); + } + + if ( self.element.is( ':input' ) && ! self._color.error ) { + self.element.removeClass( 'iris-error' ); + if ( self.element.val() !== self._color.toString() ) { + self.element.val( self._color.toString() ); + } + } + + self._paint(); + self._inited = true; + self.active = false; + }, + // taken from underscore.js _.debounce method + _debounce: function( func, wait, immediate ) { + var timeout, result; + return function() { + var context = this, + args = arguments, + later, + callNow; + + later = function() { + timeout = null; + if ( ! immediate) { + result = func.apply( context, args ); + } + }; + + callNow = immediate && !timeout; + clearTimeout( timeout ); + timeout = setTimeout( later, wait ); + if ( callNow ) { + result = func.apply( context, args ); + } + return result; + }; + }, + show: function() { + this.picker.show(); + }, + hide: function() { + this.picker.hide(); + }, + toggle: function() { + this.picker.toggle(); + }, + color: function(newColor) { + if ( newColor === true ) { + return this._color.clone(); + } else if ( newColor === undef ) { + return this._color.toString(); + } + this.option('color', newColor); + } + }; + // initialize the widget + $.widget( 'a8c.iris', Iris ); + // add CSS + $( '' ).appendTo( 'head' ); + +}( jQuery )); +/*! Color.js - v0.9.11 - 2013-08-09 +* https://github.com/Automattic/Color.js +* Copyright (c) 2013 Matt Wiebe; Licensed GPLv2 */ +(function(global, undef) { + + var Color = function( color, type ) { + if ( ! ( this instanceof Color ) ) + return new Color( color, type ); + + return this._init( color, type ); + }; + + Color.fn = Color.prototype = { + _color: 0, + _alpha: 1, + error: false, + // for preserving hue/sat in fromHsl().toHsl() flows + _hsl: { h: 0, s: 0, l: 0 }, + // for preserving hue/sat in fromHsv().toHsv() flows + _hsv: { h: 0, s: 0, v: 0 }, + // for setting hsl or hsv space - needed for .h() & .s() functions to function properly + _hSpace: 'hsl', + _init: function( color ) { + var func = 'noop'; + switch ( typeof color ) { + case 'object': + // alpha? + if ( color.a !== undef ) + this.a( color.a ); + func = ( color.r !== undef ) ? 'fromRgb' : + ( color.l !== undef ) ? 'fromHsl' : + ( color.v !== undef ) ? 'fromHsv' : func; + return this[func]( color ); + case 'string': + return this.fromCSS( color ); + case 'number': + return this.fromInt( parseInt( color, 10 ) ); + } + return this; + }, + + _error: function() { + this.error = true; + return this; + }, + + clone: function() { + var newColor = new Color( this.toInt() ), + copy = ['_alpha', '_hSpace', '_hsl', '_hsv', 'error']; + for ( var i = copy.length - 1; i >= 0; i-- ) { + newColor[ copy[i] ] = this[ copy[i] ]; + } + return newColor; + }, + + setHSpace: function( space ) { + this._hSpace = ( space === 'hsv' ) ? space : 'hsl'; + return this; + }, + + noop: function() { + return this; + }, + + fromCSS: function( color ) { + var list, + leadingRE = /^(rgb|hs(l|v))a?\(/; + this.error = false; + + // whitespace and semicolon trim + color = color.replace(/^\s+/, '').replace(/\s+$/, '').replace(/;$/, ''); + + if ( color.match(leadingRE) && color.match(/\)$/) ) { + list = color.replace(/(\s|%)/g, '').replace(leadingRE, '').replace(/,?\);?$/, '').split(','); + + if ( list.length < 3 ) + return this._error(); + + if ( list.length === 4 ) { + this.a( parseFloat( list.pop() ) ); + // error state has been set to true in .a() if we passed NaN + if ( this.error ) + return this; + } + + for (var i = list.length - 1; i >= 0; i--) { + list[i] = parseInt(list[i], 10); + if ( isNaN( list[i] ) ) + return this._error(); + } + + if ( color.match(/^rgb/) ) { + return this.fromRgb( { + r: list[0], + g: list[1], + b: list[2] + } ); + } else if ( color.match(/^hsv/) ) { + return this.fromHsv( { + h: list[0], + s: list[1], + v: list[2] + } ); + } else { + return this.fromHsl( { + h: list[0], + s: list[1], + l: list[2] + } ); + } + } else { + // must be hex amirite? + return this.fromHex( color ); + } + }, + + fromRgb: function( rgb, preserve ) { + if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef ) + return this._error(); + + this.error = false; + return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve ); + }, + + fromHex: function( color ) { + color = color.replace(/^#/, '').replace(/^0x/, ''); + if ( color.length === 3 ) { + color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; + } + + // rough error checking - this is where things go squirrely the most + this.error = ! /^[0-9A-F]{6}$/i.test( color ); + return this.fromInt( parseInt( color, 16 ) ); + }, + + fromHsl: function( hsl ) { + var r, g, b, q, p, h, s, l; + + if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef ) + return this._error(); + + this._hsl = hsl; // store it + this._hSpace = 'hsl'; // implicit + h = hsl.h / 360; s = hsl.s / 100; l = hsl.l / 100; + if ( s === 0 ) { + r = g = b = l; // achromatic + } + else { + q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; + p = 2 * l - q; + r = this.hue2rgb( p, q, h + 1/3 ); + g = this.hue2rgb( p, q, h ); + b = this.hue2rgb( p, q, h - 1/3 ); + } + return this.fromRgb( { + r: r * 255, + g: g * 255, + b: b * 255 + }, true ); // true preserves hue/sat + }, + + fromHsv: function( hsv ) { + var h, s, v, r, g, b, i, f, p, q, t; + if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef ) + return this._error(); + + this._hsv = hsv; // store it + this._hSpace = 'hsv'; // implicit + + h = hsv.h / 360; s = hsv.s / 100; v = hsv.v / 100; + i = Math.floor( h * 6 ); + f = h * 6 - i; + p = v * ( 1 - s ); + q = v * ( 1 - f * s ); + t = v * ( 1 - ( 1 - f ) * s ); + + switch( i % 6 ) { + case 0: + r = v; g = t; b = p; + break; + case 1: + r = q; g = v; b = p; + break; + case 2: + r = p; g = v; b = t; + break; + case 3: + r = p; g = q; b = v; + break; + case 4: + r = t; g = p; b = v; + break; + case 5: + r = v; g = p; b = q; + break; + } + + return this.fromRgb( { + r: r * 255, + g: g * 255, + b: b * 255 + }, true ); // true preserves hue/sat + + }, + // everything comes down to fromInt + fromInt: function( color, preserve ) { + this._color = parseInt( color, 10 ); + + if ( isNaN( this._color ) ) + this._color = 0; + + // let's coerce things + if ( this._color > 16777215 ) + this._color = 16777215; + else if ( this._color < 0 ) + this._color = 0; + + // let's not do weird things + if ( preserve === undef ) { + this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0; + } + // EVENT GOES HERE + return this; + }, + + hue2rgb: function( p, q, t ) { + if ( t < 0 ) { + t += 1; + } + if ( t > 1 ) { + t -= 1; + } + if ( t < 1/6 ) { + return p + ( q - p ) * 6 * t; + } + if ( t < 1/2 ) { + return q; + } + if ( t < 2/3 ) { + return p + ( q - p ) * ( 2/3 - t ) * 6; + } + return p; + }, + + toString: function() { + var hex = parseInt( this._color, 10 ).toString( 16 ); + if ( this.error ) + return ''; + // maybe left pad it + if ( hex.length < 6 ) { + for (var i = 6 - hex.length - 1; i >= 0; i--) { + hex = '0' + hex; + } + } + return '#' + hex; + }, + + toCSS: function( type, alpha ) { + type = type || 'hex'; + alpha = parseFloat( alpha || this._alpha ); + switch ( type ) { + case 'rgb': + case 'rgba': + var rgb = this.toRgb(); + if ( alpha < 1 ) { + return "rgba( " + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + alpha + " )"; + } + else { + return "rgb( " + rgb.r + ", " + rgb.g + ", " + rgb.b + " )"; + } + break; + case 'hsl': + case 'hsla': + var hsl = this.toHsl(); + if ( alpha < 1 ) { + return "hsla( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%, " + alpha + " )"; + } + else { + return "hsl( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "% )"; + } + break; + default: + return this.toString(); + } + }, + + toRgb: function() { + return { + r: 255 & ( this._color >> 16 ), + g: 255 & ( this._color >> 8 ), + b: 255 & ( this._color ) + }; + }, + + toHsl: function() { + var rgb = this.toRgb(); + var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; + var max = Math.max( r, g, b ), min = Math.min( r, g, b ); + var h, s, l = ( max + min ) / 2; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); + switch ( max ) { + case r: h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: h = ( b - r ) / d + 2; + break; + case b: h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSL space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsl.h !== h ) { + h = this._hsl.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsl.s ) { + s = this._hsl.s; + } + + return { + h: h, + s: s, + l: Math.round( l * 100 ) + }; + + }, + + toHsv: function() { + var rgb = this.toRgb(); + var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; + var max = Math.max( r, g, b ), min = Math.min( r, g, b ); + var h, s, v = max; + var d = max - min; + s = max === 0 ? 0 : d / max; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + switch( max ){ + case r: + h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: + h = ( b - r ) / d + 2; + break; + case b: + h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSV space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsv.h !== h ) { + h = this._hsv.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsv.s ) { + s = this._hsv.s; + } + + return { + h: h, + s: s, + v: Math.round( v * 100 ) + }; + }, + + toInt: function() { + return this._color; + }, + + toIEOctoHex: function() { + // AARRBBGG + var hex = this.toString(); + var AA = parseInt( 255 * this._alpha, 10 ).toString(16); + if ( AA.length === 1 ) { + AA = '0' + AA; + } + return '#' + AA + hex.replace(/^#/, '' ); + }, + + toLuminosity: function() { + var rgb = this.toRgb(); + return 0.2126 * Math.pow( rgb.r / 255, 2.2 ) + 0.7152 * Math.pow( rgb.g / 255, 2.2 ) + 0.0722 * Math.pow( rgb.b / 255, 2.2); + }, + + getDistanceLuminosityFrom: function( color ) { + if ( ! ( color instanceof Color ) ) { + throw 'getDistanceLuminosityFrom requires a Color object'; + } + var lum1 = this.toLuminosity(); + var lum2 = color.toLuminosity(); + if ( lum1 > lum2 ) { + return ( lum1 + 0.05 ) / ( lum2 + 0.05 ); + } + else { + return ( lum2 + 0.05 ) / ( lum1 + 0.05 ); + } + }, + + getMaxContrastColor: function() { + var lum = this.toLuminosity(); + var hex = ( lum >= 0.5 ) ? '000000' : 'ffffff'; + return new Color( hex ); + }, + + getReadableContrastingColor: function( bgColor, minContrast ) { + if ( ! bgColor instanceof Color ) { + return this; + } + + // you shouldn't use less than 5, but you might want to. + var targetContrast = ( minContrast === undef ) ? 5 : minContrast; + // working things + var contrast = bgColor.getDistanceLuminosityFrom( this ); + var maxContrastColor = bgColor.getMaxContrastColor(); + var maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor ); + + // if current max contrast is less than the target contrast, we had wishful thinking. + // still, go max + if ( maxContrast <= targetContrast ) { + return maxContrastColor; + } + // or, we might already have sufficient contrast + else if ( contrast >= targetContrast ) { + return this; + } + + var incr = ( 0 === maxContrastColor.toInt() ) ? -1 : 1; + while ( contrast < targetContrast ) { + this.l( incr, true ); // 2nd arg turns this into an incrementer + contrast = this.getDistanceLuminosityFrom( bgColor ); + // infininite loop prevention: you never know. + if ( this._color === 0 || this._color === 16777215 ) { + break; + } + } + + return this; + + }, + + a: function( val ) { + if ( val === undef ) + return this._alpha; + + var a = parseFloat( val ); + + if ( isNaN( a ) ) + return this._error(); + + this._alpha = a; + return this; + }, + + // TRANSFORMS + + darken: function( amount ) { + amount = amount || 5; + return this.l( - amount, true ); + }, + + lighten: function( amount ) { + amount = amount || 5; + return this.l( amount, true ); + }, + + saturate: function( amount ) { + amount = amount || 15; + return this.s( amount, true ); + }, + + desaturate: function( amount ) { + amount = amount || 15; + return this.s( - amount, true ); + }, + + toGrayscale: function() { + return this.setHSpace('hsl').s( 0 ); + }, + + getComplement: function() { + return this.h( 180, true ); + }, + + getSplitComplement: function( step ) { + step = step || 1; + var incr = 180 + ( step * 30 ); + return this.h( incr, true ); + }, + + getAnalog: function( step ) { + step = step || 1; + var incr = step * 30; + return this.h( incr, true ); + }, + + getTetrad: function( step ) { + step = step || 1; + var incr = step * 60; + return this.h( incr, true ); + }, + + getTriad: function( step ) { + step = step || 1; + var incr = step * 120; + return this.h( incr, true ); + }, + + _partial: function( key ) { + var prop = shortProps[key]; + return function( val, incr ) { + var color = this._spaceFunc('to', prop.space); + + // GETTER + if ( val === undef ) + return color[key]; + + // INCREMENT + if ( incr === true ) + val = color[key] + val; + + // MOD & RANGE + if ( prop.mod ) + val = val % prop.mod; + if ( prop.range ) + val = ( val < prop.range[0] ) ? prop.range[0] : ( val > prop.range[1] ) ? prop.range[1] : val; + + // NEW VALUE + color[key] = val; + + return this._spaceFunc('from', prop.space, color); + }; + }, + + _spaceFunc: function( dir, s, val ) { + var space = s || this._hSpace, + funcName = dir + space.charAt(0).toUpperCase() + space.substr(1); + return this[funcName](val); + } + }; + + var shortProps = { + h: { + mod: 360 + }, + s: { + range: [0,100] + }, + l: { + space: 'hsl', + range: [0,100] + }, + v: { + space: 'hsv', + range: [0,100] + }, + r: { + space: 'rgb', + range: [0,255] + }, + g: { + space: 'rgb', + range: [0,255] + }, + b: { + space: 'rgb', + range: [0,255] + } + }; + + for ( var key in shortProps ) { + if ( shortProps.hasOwnProperty( key ) ) + Color.fn[key] = Color.fn._partial(key); + } + + // play nicely with Node + browser + if ( typeof exports === 'object' ) + module.exports = Color; + else + global.Color = Color; + +}(this)); diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index d472c4fe7..b71e46802 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -1,6 +1,8 @@ +/* DOKUWIKI:include_once iris.js */ + jQuery(function () { // user openend the admin page, set cookie and redirect - if(jQuery('#plugin__styler').length) { + if (jQuery('#plugin__styler').length) { DokuCookie.setValue('styler_plugin', 1); document.location.href = DOKU_BASE; } @@ -22,11 +24,12 @@ jQuery(function () { $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); // open the dialog - $dialog.dialog({ + var $dlg = $dialog.dialog({ 'title': LANG.plugins.styler.menu, 'width': 500, 'top': 50, - 'position': { 'my': 'left top', 'at': 'left top', 'of': window }, + 'maxHeight': 500, + 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, // bring everything back to normal 'close': function (event, ui) { // disable the styler plugin again @@ -35,6 +38,10 @@ jQuery(function () { document.location.reload() } }); + + jQuery('.styler .color').iris({ + }); + } ); } -- cgit v1.2.3 From 9fb9909916c8ce7fbca17d536942c0166afe487c Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 17:58:50 +0200 Subject: added more localization --- lib/plugins/styler/admin.php | 8 +++++++- lib/plugins/styler/lang/en/intro.txt | 4 ++-- lib/plugins/styler/lang/en/lang.php | 11 +++++++++++ lib/tpl/dokuwiki/lang/en/lang.php | 12 ++++++++++++ lib/tpl/dokuwiki/lang/en/style.txt | 4 ++++ lib/tpl/dokuwiki/pagefooter.html | 4 ++++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 lib/tpl/dokuwiki/lang/en/lang.php create mode 100644 lib/tpl/dokuwiki/lang/en/style.txt create mode 100644 lib/tpl/dokuwiki/pagefooter.html diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 8d44ac6f5..4269a0ee7 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -82,8 +82,12 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { echo ''; foreach($replacements as $key => $value) { + $name = tpl_getLang($key); + if(empty($name)) $name = $this->getLang($key); + if(empty($name)) $name = $key; + echo ''; - echo ''; + echo ''; echo ''; } @@ -104,6 +108,8 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { echo ''; + echo tpl_locale_xhtml('style'); + } } diff --git a/lib/plugins/styler/lang/en/intro.txt b/lib/plugins/styler/lang/en/intro.txt index bb9e3ff33..4ea55172f 100644 --- a/lib/plugins/styler/lang/en/intro.txt +++ b/lib/plugins/styler/lang/en/intro.txt @@ -1,2 +1,2 @@ -This tool allows you to change certain style settings of your currently selected template -all changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file +This tool allows you to change certain style settings of your currently selected template. +All changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file diff --git a/lib/plugins/styler/lang/en/lang.php b/lib/plugins/styler/lang/en/lang.php index 2dd7921b0..1bf8efa2c 100644 --- a/lib/plugins/styler/lang/en/lang.php +++ b/lib/plugins/styler/lang/en/lang.php @@ -16,6 +16,17 @@ $lang['btn_save'] = 'Save your changes'; $lang['btn_reset'] = 'Reset your current changes'; $lang['btn_revert'] = 'Revert all styles back to the template\'s default'; +// default guaranteed placeholders +$lang['__text__'] = 'Main text color'; +$lang['__background__'] = 'Main text background color'; +$lang['__text_alt__'] = 'Alternative text color'; +$lang['__background_alt__'] = 'Alternative text background color'; +$lang['__text_neu__'] = 'Neutral text color'; +$lang['__background_neu__'] = 'Neutral text background color'; +$lang['__border__'] = 'Border color'; +$lang['__highlight__'] = 'Highlight color (for search results mainly)'; + + //Setup VIM: ex: et ts=4 : diff --git a/lib/tpl/dokuwiki/lang/en/lang.php b/lib/tpl/dokuwiki/lang/en/lang.php new file mode 100644 index 000000000..82a720e21 --- /dev/null +++ b/lib/tpl/dokuwiki/lang/en/lang.php @@ -0,0 +1,12 @@ +tpl(); -- cgit v1.2.3 From b1a864fe66dcf7f538e4d304cc01ea9ec736b786 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 19:45:15 +0200 Subject: fix keeping the current page --- lib/plugins/styler/action.php | 3 +++ lib/plugins/styler/admin.php | 1 - lib/plugins/styler/script.js | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index b63cf3ec1..35e2f8f3c 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -90,6 +90,9 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { $event->preventDefault(); $event->stopPropagation(); + global $ID; + $ID = getID(); + /** @var admin_plugin_styler $hlp */ $hlp = plugin_load('admin', 'styler'); $hlp->form(true); diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 4269a0ee7..432b22279 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -61,7 +61,6 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { public function form($isajax) { global $conf; global $ID; - $tpl = $conf['template']; define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php require_once(DOKU_INC.'lib/exe/css.php'); $styleini = css_styleini($conf['template'], true); diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index b71e46802..1c53987a4 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -4,7 +4,7 @@ jQuery(function () { // user openend the admin page, set cookie and redirect if (jQuery('#plugin__styler').length) { DokuCookie.setValue('styler_plugin', 1); - document.location.href = DOKU_BASE; + document.location.href = document.location.href.replace(/do=admin/, ''); } // The Styler Dialog is currently enabled, display it here and apply the preview styles @@ -15,7 +15,8 @@ jQuery(function () { $dialog.load( DOKU_BASE + '/lib/exe/ajax.php', { - 'call': 'plugin_styler' + 'call': 'plugin_styler', + 'id': JSINFO.id }, function () { // load the preview template -- cgit v1.2.3 From a93f9a7aab0ddcc00415f5b4e0c6876751a4d4c1 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 19:48:18 +0200 Subject: allow resizing the dialog --- lib/plugins/styler/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index 1c53987a4..79df8a88b 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -16,7 +16,7 @@ jQuery(function () { DOKU_BASE + '/lib/exe/ajax.php', { 'call': 'plugin_styler', - 'id': JSINFO.id + 'id': JSINFO.id }, function () { // load the preview template @@ -28,8 +28,8 @@ jQuery(function () { var $dlg = $dialog.dialog({ 'title': LANG.plugins.styler.menu, 'width': 500, + 'height': 500, 'top': 50, - 'maxHeight': 500, 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, // bring everything back to normal 'close': function (event, ui) { -- cgit v1.2.3 From d071b66c820e5b1ecc60efc27a15e113c88832af Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 17 May 2015 20:40:33 +0200 Subject: autopreview in styler plugin --- lib/plugins/styler/action.php | 7 ++- lib/plugins/styler/admin.php | 8 +-- lib/plugins/styler/script.js | 119 ++++++++++++++++++++++++++++-------------- 3 files changed, 91 insertions(+), 43 deletions(-) diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php index 35e2f8f3c..1cc712a15 100644 --- a/lib/plugins/styler/action.php +++ b/lib/plugins/styler/action.php @@ -91,11 +91,16 @@ class action_plugin_styler extends DokuWiki_Action_Plugin { $event->stopPropagation(); global $ID; + global $INPUT; $ID = getID(); /** @var admin_plugin_styler $hlp */ $hlp = plugin_load('admin', 'styler'); - $hlp->form(true); + if($INPUT->str('run') == 'preview') { + $hlp->run_preview(); + } else { + $hlp->form(true); + } } } diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php index 432b22279..a66dfbb11 100644 --- a/lib/plugins/styler/admin.php +++ b/lib/plugins/styler/admin.php @@ -87,13 +87,13 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { echo ''; echo ''; - echo ''; } echo '
'.$key.''.$name.'colorClass($key).' />'; echo '
'.$name.'colorClass($key).' />'; + echo 'colorClass($key).' />'; echo '
'; echo '

'; - echo ''; + echo ''; echo ''; #FIXME only if preview.ini exists echo '

'; @@ -139,9 +139,9 @@ class admin_plugin_styler extends DokuWiki_Admin_Plugin { } /** - * saves the preview.ini + * saves the preview.ini (alos called from ajax directly) */ - protected function run_preview() { + public function run_preview() { global $conf; $ini = $conf['cachedir'].'/preview.ini'; io_saveFile($ini, $this->makeini()); diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js index 79df8a88b..a4fb601c8 100644 --- a/lib/plugins/styler/script.js +++ b/lib/plugins/styler/script.js @@ -7,43 +7,86 @@ jQuery(function () { document.location.href = document.location.href.replace(/do=admin/, ''); } - // The Styler Dialog is currently enabled, display it here and apply the preview styles - if (DokuCookie.getValue('styler_plugin') == 1) { - // load dialog - var $dialog = jQuery(document.createElement('div')); - jQuery('body').append($dialog); - $dialog.load( - DOKU_BASE + '/lib/exe/ajax.php', - { - 'call': 'plugin_styler', - 'id': JSINFO.id - }, - function () { - // load the preview template - var now = new Date().getTime(); - var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); - - // open the dialog - var $dlg = $dialog.dialog({ - 'title': LANG.plugins.styler.menu, - 'width': 500, - 'height': 500, - 'top': 50, - 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, - // bring everything back to normal - 'close': function (event, ui) { - // disable the styler plugin again - DokuCookie.setValue('styler_plugin', 0); - // reload - document.location.reload() - } - }); - - jQuery('.styler .color').iris({ - }); - - } - ); + // continue only if the Styler Dialog is currently enabled + if (DokuCookie.getValue('styler_plugin') != 1) return; + + var styler_timeout = null; + + // create dialog element + var $dialog = jQuery(document.createElement('div')); + jQuery('body').append($dialog); + + + /** + * updates the current CSS with a new preview one + */ + function styler_updateCSS() { + var now = new Date().getTime(); + var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + } + + /** + * save current values and reload preview (debounced) + */ + function styler_saveAndUpdate() { + if (styler_timeout) window.clearTimeout(styler_timeout); + styler_timeout = window.setTimeout(function () { + styler_timeout = null; + + var params = $dialog.find('input[type=text]').serializeArray(); + params[params.length] = { name: 'call', value: 'plugin_styler'}; + params[params.length] = {name: 'run', value: 'preview'}; + + jQuery.post( + DOKU_BASE + '/lib/exe/ajax.php', + params, + styler_updateCSS + ); + }, 500); } + + // load the dialog content and apply listeners + $dialog.load( + DOKU_BASE + '/lib/exe/ajax.php', + { + 'call': 'plugin_styler', + 'run': 'html', + 'id': JSINFO.id + }, + function () { + // load the preview template + styler_updateCSS(); + + // open the dialog + $dialog.dialog({ + 'title': LANG.plugins.styler.menu, + 'width': 500, + 'height': 500, + 'top': 50, + 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, + // bring everything back to normal + 'close': function (event, ui) { + // disable the styler plugin again + DokuCookie.setValue('styler_plugin', 0); + // reload + document.location.reload() + } + }); + + // we don't need the manual preview in JS mode + $dialog.find('.btn_preview').hide(); + + // add the color picker FIXME add saveAndUpdate to correct event + $dialog.find('.color').iris({ }); + + // listen to keyup events + $dialog.find('input[type=text]').keyup(function () { + console.log('change'); + styler_saveAndUpdate(); + }); + + } + ); + }); \ No newline at end of file -- cgit v1.2.3 From 123bc813fd93ab5d8dab3cc4a66a09e613a10aa2 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 23 May 2015 15:29:33 +0200 Subject: renamed plugin from styler to styling styler was already taken --- lib/plugins/styler/.travis.yml | 13 - lib/plugins/styler/README | 27 - lib/plugins/styler/_test/general.test.php | 33 - lib/plugins/styler/action.php | 108 -- lib/plugins/styler/admin.php | 215 ---- lib/plugins/styler/iris.js | 1488 ---------------------------- lib/plugins/styler/lang/en/intro.txt | 2 - lib/plugins/styler/lang/en/lang.php | 32 - lib/plugins/styler/plugin.info.txt | 7 - lib/plugins/styler/script.js | 92 -- lib/plugins/styling/.travis.yml | 13 + lib/plugins/styling/README | 27 + lib/plugins/styling/_test/general.test.php | 33 + lib/plugins/styling/action.php | 108 ++ lib/plugins/styling/admin.php | 215 ++++ lib/plugins/styling/iris.js | 1488 ++++++++++++++++++++++++++++ lib/plugins/styling/lang/en/intro.txt | 2 + lib/plugins/styling/lang/en/lang.php | 32 + lib/plugins/styling/plugin.info.txt | 7 + lib/plugins/styling/script.js | 92 ++ 20 files changed, 2017 insertions(+), 2017 deletions(-) delete mode 100644 lib/plugins/styler/.travis.yml delete mode 100644 lib/plugins/styler/README delete mode 100644 lib/plugins/styler/_test/general.test.php delete mode 100644 lib/plugins/styler/action.php delete mode 100644 lib/plugins/styler/admin.php delete mode 100644 lib/plugins/styler/iris.js delete mode 100644 lib/plugins/styler/lang/en/intro.txt delete mode 100644 lib/plugins/styler/lang/en/lang.php delete mode 100644 lib/plugins/styler/plugin.info.txt delete mode 100644 lib/plugins/styler/script.js create mode 100644 lib/plugins/styling/.travis.yml create mode 100644 lib/plugins/styling/README create mode 100644 lib/plugins/styling/_test/general.test.php create mode 100644 lib/plugins/styling/action.php create mode 100644 lib/plugins/styling/admin.php create mode 100644 lib/plugins/styling/iris.js create mode 100644 lib/plugins/styling/lang/en/intro.txt create mode 100644 lib/plugins/styling/lang/en/lang.php create mode 100644 lib/plugins/styling/plugin.info.txt create mode 100644 lib/plugins/styling/script.js diff --git a/lib/plugins/styler/.travis.yml b/lib/plugins/styler/.travis.yml deleted file mode 100644 index d80c0691f..000000000 --- a/lib/plugins/styler/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Config file for travis-ci.org - -language: php -php: - - "5.5" - - "5.4" - - "5.3" -env: - - DOKUWIKI=master - - DOKUWIKI=stable -before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh -install: sh travis.sh -script: cd _test && phpunit --stderr --group plugin_styler \ No newline at end of file diff --git a/lib/plugins/styler/README b/lib/plugins/styler/README deleted file mode 100644 index 37a966352..000000000 --- a/lib/plugins/styler/README +++ /dev/null @@ -1,27 +0,0 @@ -styler Plugin for DokuWiki - -Allows to edit style.ini replacements - -All documentation for this plugin can be found at -https://www.dokuwiki.org/plugin:styler - -If you install this plugin manually, make sure it is installed in -lib/plugins/styler/ - if the folder is called different it -will not work! - -Please refer to http://www.dokuwiki.org/plugins for additional info -on how to install plugins in DokuWiki. - ----- -Copyright (C) Andreas Gohr - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -See the COPYING file in your DokuWiki folder for details diff --git a/lib/plugins/styler/_test/general.test.php b/lib/plugins/styler/_test/general.test.php deleted file mode 100644 index 8b0712a20..000000000 --- a/lib/plugins/styler/_test/general.test.php +++ /dev/null @@ -1,33 +0,0 @@ -assertFileExists($file); - - $info = confToHash($file); - - $this->assertArrayHasKey('base', $info); - $this->assertArrayHasKey('author', $info); - $this->assertArrayHasKey('email', $info); - $this->assertArrayHasKey('date', $info); - $this->assertArrayHasKey('name', $info); - $this->assertArrayHasKey('desc', $info); - $this->assertArrayHasKey('url', $info); - - $this->assertEquals('styler', $info['base']); - $this->assertRegExp('/^https?:\/\//', $info['url']); - $this->assertTrue(mail_isvalid($info['email'])); - $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']); - $this->assertTrue(false !== strtotime($info['date'])); - } -} diff --git a/lib/plugins/styler/action.php b/lib/plugins/styler/action.php deleted file mode 100644 index 1cc712a15..000000000 --- a/lib/plugins/styler/action.php +++ /dev/null @@ -1,108 +0,0 @@ - - */ - -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * Class action_plugin_styler - * - * This handles all the save actions and loading the interface - * - * All this usually would be done within an admin plugin, but we want to have this available outside - * the admin interface using our floating dialog. - */ -class action_plugin_styler extends DokuWiki_Action_Plugin { - - /** - * Registers a callback functions - * - * @param Doku_Event_Handler $controller DokuWiki's event controller object - * @return void - */ - public function register(Doku_Event_Handler $controller) { - $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); - $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); - $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_header'); - } - - /** - * Adds the preview parameter to the stylesheet loading in non-js mode - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - public function handle_header(Doku_Event &$event, $param) { - global $ACT; - global $INPUT; - if($ACT != 'admin' || $INPUT->str('page') != 'styler') return; - if(!auth_isadmin()) return; - - // set preview - $len = count($event->data['link']); - for($i = 0; $i < $len; $i++) { - if( - $event->data['link'][$i]['rel'] == 'stylesheet' && - strpos($event->data['link'][$i]['href'], 'lib/exe/css.php') !== false - ) { - $event->data['link'][$i]['href'] .= '&preview=1&tseed='.time(); - } - } - } - - /** - * Updates the style.ini settings by passing it on to handle() of the admin component - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - public function handle_action(Doku_Event &$event, $param) { - if($event->data != 'styler_plugin') return; - if(!auth_isadmin()) return; - $event->data = 'show'; - - /** @var admin_plugin_styler $hlp */ - $hlp = plugin_load('admin', 'styler'); - $hlp->handle(); - } - - /** - * Create the style form in the floating Dialog - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - - public function handle_ajax(Doku_Event &$event, $param) { - if($event->data != 'plugin_styler') return; - if(!auth_isadmin()) return; - $event->preventDefault(); - $event->stopPropagation(); - - global $ID; - global $INPUT; - $ID = getID(); - - /** @var admin_plugin_styler $hlp */ - $hlp = plugin_load('admin', 'styler'); - if($INPUT->str('run') == 'preview') { - $hlp->run_preview(); - } else { - $hlp->form(true); - } - } - -} - -// vim:ts=4:sw=4:et: diff --git a/lib/plugins/styler/admin.php b/lib/plugins/styler/admin.php deleted file mode 100644 index a66dfbb11..000000000 --- a/lib/plugins/styler/admin.php +++ /dev/null @@ -1,215 +0,0 @@ - - */ - -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -class admin_plugin_styler extends DokuWiki_Admin_Plugin { - - /** - * @return int sort number in admin menu - */ - public function getMenuSort() { - return 1000; - } - - /** - * @return bool true if only access for superuser, false is for superusers and moderators - */ - public function forAdminOnly() { - return true; - } - - /** - * @param string $language - * @return string - */ - public function getMenuText($language) { - $js = $this->getLang('js'); - return $js['menu']; - } - - /** - * handle the different actions (also called from ajax) - */ - public function handle() { - global $INPUT; - $run = $INPUT->extract('run')->str('run'); - if(!$run) return; - $run = "run_$run"; - $this->$run(); - } - - /** - * Render HTML output, e.g. helpful text and a form - */ - public function html() { - echo '
'; - ptln('

'.$this->getMenuText('').'

'); - $this->form(false); - echo '
'; - } - - /** - * Create the actual editing form - */ - public function form($isajax) { - global $conf; - global $ID; - define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php - require_once(DOKU_INC.'lib/exe/css.php'); - $styleini = css_styleini($conf['template'], true); - $replacements = $styleini['replacements']; - - if($isajax) { - $target = wl($ID, array('do' => 'styler_plugin')); - } else { - $target = wl($ID, array('do' => 'admin', 'page' => 'styler')); - } - - if(empty($replacements)) { - echo '

'.$this->getLang('error').'

'; - } else { - echo $this->locale_xhtml('intro'); - - echo '
'; - - echo ''; - foreach($replacements as $key => $value) { - $name = tpl_getLang($key); - if(empty($name)) $name = $this->getLang($key); - if(empty($name)) $name = $key; - - echo ''; - echo ''; - echo ''; - } - echo '
'.$name.'colorClass($key).' />'; - echo '
'; - - echo '

'; - echo ''; - echo ''; #FIXME only if preview.ini exists - echo '

'; - - echo '

'; - echo ''; - echo '

'; - - echo '

'; - echo ''; #FIXME only if local.ini exists - echo '

'; - - echo '
'; - - echo tpl_locale_xhtml('style'); - - } - } - - /** - * set the color class attribute - */ - protected function colorClass($key) { - static $colors = array( - 'text', - 'background', - 'text_alt', - 'background_alt', - 'text_neu', - 'background_neu', - 'border', - 'highlight', - 'background_site', - 'link', - 'existing', - 'missing', - ); - - if(preg_match('/colou?r/', $key) || in_array(trim($key,'_'), $colors)) { - return 'class="color"'; - } else { - return ''; - } - } - - /** - * saves the preview.ini (alos called from ajax directly) - */ - public function run_preview() { - global $conf; - $ini = $conf['cachedir'].'/preview.ini'; - io_saveFile($ini, $this->makeini()); - } - - /** - * deletes the preview.ini - */ - protected function run_reset() { - global $conf; - $ini = $conf['cachedir'].'/preview.ini'; - io_saveFile($ini, ''); - } - - /** - * deletes the local style.ini replacements - */ - protected function run_revert() { - $this->replaceini(''); - $this->run_reset(); - } - - /** - * save the local style.ini replacements - */ - protected function run_save() { - $this->replaceini($this->makeini()); - $this->run_reset(); - } - - /** - * create the replacement part of a style.ini from submitted data - * - * @return string - */ - protected function makeini() { - global $INPUT; - - $ini = "[replacements]\n"; - $ini .= ";These overwrites have been generated from the Template Styler Admin interface\n"; - $ini .= ";Any values in this section will be overwritten by that tool again\n"; - foreach($INPUT->arr('tpl') as $key => $val) { - $ini .= $key.' = "'.addslashes($val).'"'."\n"; - } - - return $ini; - } - - /** - * replaces the replacement parts in the local ini - * - * @param string $new the new ini contents - */ - protected function replaceini($new) { - global $conf; - $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; - if(file_exists($ini)) { - $old = io_readFile($ini); - $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); - $old = trim($old); - } else { - $old = ''; - } - - io_makeFileDir($ini); - io_saveFile($ini, "$old\n\n$new"); - } - -} - -// vim:ts=4:sw=4:et: \ No newline at end of file diff --git a/lib/plugins/styler/iris.js b/lib/plugins/styler/iris.js deleted file mode 100644 index 4eda5022e..000000000 --- a/lib/plugins/styler/iris.js +++ /dev/null @@ -1,1488 +0,0 @@ -/*! Iris Color Picker - v1.0.7 - 2014-11-28 -* https://github.com/Automattic/Iris -* Copyright (c) 2014 Matt Wiebe; Licensed GPLv2 */ -(function( $, undef ){ - var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion; - - _html = '
'; - _css = '.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}'; - - // Even IE9 dosen't support gradients. Elaborate sigh. - UA = navigator.userAgent.toLowerCase(); - isIE = navigator.appName === 'Microsoft Internet Explorer'; - IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; - nonGradientIE = ( isIE && IEVersion < 10 ); - gradientType = false; - - // we don't bother with an unprefixed version, as it has a different syntax - vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ]; - - // Bail for IE <= 7 - if ( nonGradientIE && IEVersion <= 7 ) { - $.fn.iris = $.noop; - $.support.iris = false; - return; - } - - $.support.iris = true; - - function testGradientType() { - var el, base, - bgImageString = 'backgroundImage'; - - if ( nonGradientIE ) { - gradientType = 'filter'; - } - else { - el = $( '
' ); - base = 'linear-gradient(top,#fff,#000)'; - $.each( vendorPrefixes, function( i, val ){ - el.css( bgImageString, val + base ); - if ( el.css( bgImageString ).match( 'gradient' ) ) { - gradientType = i; - return false; - } - }); - // check for legacy webkit gradient syntax - if ( gradientType === false ) { - el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' ); - if ( el.css( bgImageString ).match( 'gradient' ) ) { - gradientType = 'webkit'; - } - } - el.remove(); - } - - } - - /** - * Only for CSS3 gradients. oldIE will use a separate function. - * - * Accepts as many color stops as necessary from 2nd arg on, or 2nd - * arg can be an array of color stops - * - * @param {string} origin Gradient origin - top or left, defaults to left. - * @return {string} Appropriate CSS3 gradient string for use in - */ - function createGradient( origin, stops ) { - origin = ( origin === 'top' ) ? 'top' : 'left'; - stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); - if ( gradientType === 'webkit' ) { - return legacyWebkitGradient( origin, stops ); - } else { - return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')'; - } - } - - /** - * Stupid gradients for a stupid browser. - */ - function stupidIEGradient( origin, stops ) { - var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html; - - origin = ( origin === 'top' ) ? 'top' : 'left'; - stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); - // 8 hex: AARRGGBB - // GradientType: 0 vertical, 1 horizontal - type = ( origin === 'top' ) ? 0 : 1; - self = $( this ); - lastIndex = stops.length - 1; - filter = 'filter'; - startPosProp = ( type === 1 ) ? 'left' : 'top'; - endPosProp = ( type === 1 ) ? 'right' : 'bottom'; - dimensionProp = ( type === 1 ) ? 'height' : 'width'; - template = '
'; - html = ''; - // need a positioning context - if ( self.css('position') === 'static' ) { - self.css( {position: 'relative' } ); - } - - stops = fillColorStops( stops ); - $.each(stops, function( i, startColor ) { - var endColor, endStop, filterVal; - - // we want two at a time. if we're on the last pair, bail. - if ( i === lastIndex ) { - return false; - } - - endColor = stops[ i + 1 ]; - //if our pairs are at the same color stop, moving along. - if ( startColor.stop === endColor.stop ) { - return; - } - - endStop = 100 - parseFloat( endColor.stop ) + '%'; - startColor.octoHex = new Color( startColor.color ).toIEOctoHex(); - endColor.octoHex = new Color( endColor.color ).toIEOctoHex(); - - filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')'; - html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal ); - }); - self.find( '.iris-ie-gradient-shim' ).remove(); - $( html ).prependTo( self ); - } - - function legacyWebkitGradient( origin, colorList ) { - var stops = []; - origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,'; - colorList = fillColorStops( colorList ); - $.each( colorList, function( i, val ){ - stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' ); - }); - return '-webkit-gradient(linear,' + origin + stops.join(',') + ')'; - } - - function fillColorStops( colorList ) { - var colors = [], - percs = [], - newColorList = [], - lastIndex = colorList.length - 1; - - $.each( colorList, function( index, val ) { - var color = val, - perc = false, - match = val.match( /1?[0-9]{1,2}%$/ ); - - if ( match ) { - color = val.replace( /\s?1?[0-9]{1,2}%$/, '' ); - perc = match.shift(); - } - colors.push( color ); - percs.push( perc ); - }); - - // back fill first and last - if ( percs[0] === false ) { - percs[0] = '0%'; - } - - if ( percs[lastIndex] === false ) { - percs[lastIndex] = '100%'; - } - - percs = backFillColorStops( percs ); - - $.each( percs, function( i ){ - newColorList[i] = { color: colors[i], stop: percs[i] }; - }); - return newColorList; - } - - function backFillColorStops( stops ) { - var first = 0, - last = stops.length - 1, - i = 0, - foundFirst = false, - incr, - steps, - step, - firstVal; - - if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) { - return stops; - } - while ( i < stops.length - 1 ) { - if ( ! foundFirst && stops[i] === false ) { - first = i - 1; - foundFirst = true; - } else if ( foundFirst && stops[i] !== false ) { - last = i; - i = stops.length; - } - i++; - } - steps = last - first; - firstVal = parseInt( stops[first].replace('%'), 10 ); - incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps; - i = first + 1; - step = 1; - while ( i < last ) { - stops[i] = ( firstVal + ( step * incr ) ) + '%'; - step++; - i++; - } - return backFillColorStops( stops ); - } - - $.fn.gradient = function() { - var args = arguments; - return this.each( function() { - // this'll be oldishIE - if ( nonGradientIE ) { - stupidIEGradient.apply( this, args ); - } else { - // new hotness - $( this ).css( 'backgroundImage', createGradient.apply( this, args ) ); - } - }); - }; - - $.fn.raninbowGradient = function( origin, args ) { - var opts, template, i, steps; - - origin = origin || 'top'; - opts = $.extend( {}, { s: 100, l: 50 }, args ); - template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)'; - i = 0; - steps = []; - while ( i <= 360 ) { - steps.push( template.replace('%h%', i) ); - i += 30; - } - return this.each(function() { - $(this).gradient( origin, steps ); - }); - }; - - // the colorpicker widget def. - Iris = { - options: { - color: false, - mode: 'hsl', - controls: { - horiz: 's', // horizontal defaults to saturation - vert: 'l', // vertical defaults to lightness - strip: 'h' // right strip defaults to hue - }, - hide: true, // hide the color picker by default - border: true, // draw a border around the collection of UI elements - target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. - width: 200, // the width of the collection of UI elements - palettes: false // show a palette of basic colors beneath the square. - }, - _color: '', - _palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ], - _inited: false, - _defaultHSLControls: { - horiz: 's', - vert: 'l', - strip: 'h' - }, - _defaultHSVControls: { - horiz: 'h', - vert: 'v', - strip: 's' - }, - _scale: { - h: 360, - s: 100, - l: 100, - v: 100 - }, - _create: function() { - var self = this, - el = self.element, - color = self.options.color || el.val(); - - if ( gradientType === false ) { - testGradientType(); - } - - if ( el.is( 'input' ) ) { - if ( self.options.target ) { - self.picker = $( _html ).appendTo( self.options.target ); - } else { - self.picker = $( _html ).insertAfter( el ); - } - - self._addInputListeners( el ); - } else { - el.append( _html ); - self.picker = el.find( '.iris-picker' ); - } - - // Browsers / Versions - // Feature detection doesn't work for these, and $.browser is deprecated - if ( isIE ) { - if ( IEVersion === 9 ) { - self.picker.addClass( 'iris-ie-9' ); - } else if ( IEVersion <= 8 ) { - self.picker.addClass( 'iris-ie-lt9' ); - } - } else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { - self.picker.addClass( 'iris-mozilla' ); - } - - if ( self.options.palettes ) { - self._addPalettes(); - } - - self._color = new Color( color ).setHSpace( self.options.mode ); - self.options.color = self._color.toString(); - - // prep 'em for re-use - self.controls = { - square: self.picker.find( '.iris-square' ), - squareDrag: self.picker.find( '.iris-square-value' ), - horiz: self.picker.find( '.iris-square-horiz' ), - vert: self.picker.find( '.iris-square-vert' ), - strip: self.picker.find( '.iris-strip' ), - stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' ) - }; - - // small sanity check - if we chose hsv, change default controls away from hsl - if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { - self.options.controls = self._defaultHSVControls; - } else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { - self.options.controls = self._defaultHSLControls; - } - - // store it. HSL gets squirrely - self.hue = self._color.h(); - - if ( self.options.hide ) { - self.picker.hide(); - } - - if ( self.options.border ) { - self.picker.addClass( 'iris-border' ); - } - - self._initControls(); - self.active = 'external'; - self._dimensions(); - self._change(); - }, - _has: function(needle, haystack) { - var ret = false; - $.each(haystack, function(i,v){ - if ( needle === v ) { - ret = true; - // exit the loop - return false; - } - }); - return ret; - }, - _addPalettes: function () { - var container = $( '
' ), - palette = $( '' ), - colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes; - - // do we have an existing container? Empty and reuse it. - if ( this.picker.find( '.iris-palette-container' ).length ) { - container = this.picker.find( '.iris-palette-container' ).detach().html( '' ); - } - - $.each(colors, function(index, val) { - palette.clone().data( 'color', val ) - .css( 'backgroundColor', val ).appendTo( container ) - .height( 10 ).width( 10 ); - }); - - this.picker.append(container); - }, - _paint: function() { - var self = this; - self._paintDimension( 'top', 'strip' ); - self._paintDimension( 'top', 'vert' ); - self._paintDimension( 'left', 'horiz' ); - }, - _paintDimension: function( origin, control ) { - var self = this, - c = self._color, - mode = self.options.mode, - color = self._getHSpaceColor(), - target = self.controls[ control ], - controlOpts = self.options.controls, - stops; - - // don't paint the active control - if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) { - return; - } - - switch ( controlOpts[ control ] ) { - case 'h': - if ( mode === 'hsv' ) { - color = c.clone(); - switch ( control ) { - case 'horiz': - color[controlOpts.vert](100); - break; - case 'vert': - color[controlOpts.horiz](100); - break; - case 'strip': - color.setHSpace('hsl'); - break; - } - stops = color.toHsl(); - } else { - if ( control === 'strip' ) { - stops = { s: color.s, l: color.l }; - } else { - stops = { s: 100, l: color.l }; - } - } - - target.raninbowGradient( origin, stops ); - break; - case 's': - if ( mode === 'hsv' ) { - if ( control === 'vert' ) { - stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ]; - } else if ( control === 'strip' ) { - stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ]; - } else if ( control === 'horiz' ) { - stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ]; - } - } else { // implicit mode === 'hsl' - if ( control === 'vert' && self.options.controls.horiz === 'h' ) { - stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)']; - } else { - stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)']; - } - } - - - target.gradient( origin, stops ); - break; - case 'l': - if ( control === 'strip' ) { - stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)']; - } else { - stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)']; - } - target.gradient( origin, stops ); - break; - case 'v': - if ( control === 'strip' ) { - stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ]; - } else { - stops = ['rgba(0,0,0,0)', '#000']; - } - target.gradient( origin, stops ); - break; - default: - break; - } - }, - - _getHSpaceColor: function() { - return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl(); - }, - - _dimensions: function( reset ) { - // whatever size - var self = this, - opts = self.options, - controls = self.controls, - square = controls.square, - strip = self.picker.find( '.iris-strip' ), - squareWidth = '77.5%', - stripWidth = '12%', - totalPadding = 20, - innerWidth = opts.border ? opts.width - totalPadding : opts.width, - controlsHeight, - paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length, - paletteMargin, paletteWidth, paletteContainerWidth; - - if ( reset ) { - square.css( 'width', '' ); - strip.css( 'width', '' ); - self.picker.css( {width: '', height: ''} ); - } - - squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 ); - stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 ); - controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth; - - square.width( squareWidth ).height( squareWidth ); - strip.height( squareWidth ).width( stripWidth ); - self.picker.css( { width: opts.width, height: controlsHeight } ); - - if ( ! opts.palettes ) { - return self.picker.css( 'paddingBottom', '' ); - } - - // single margin at 2% - paletteMargin = squareWidth * 2 / 100; - paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin ); - paletteWidth = paletteContainerWidth / paletteCount; - self.picker.find('.iris-palette').each( function( i ) { - var margin = i === 0 ? 0 : paletteMargin; - $( this ).css({ - width: paletteWidth, - height: paletteWidth, - marginLeft: margin - }); - }); - self.picker.css( 'paddingBottom', paletteWidth + paletteMargin ); - strip.height( paletteWidth + paletteMargin + squareWidth ); - }, - - _addInputListeners: function( input ) { - var self = this, - debounceTimeout = 100, - callback = function( event ){ - var color = new Color( input.val() ), - val = input.val().replace( /^#/, '' ); - - input.removeClass( 'iris-error' ); - // we gave a bad color - if ( color.error ) { - // don't error on an empty input - we want those allowed - if ( val !== '' ) { - input.addClass( 'iris-error' ); - } - } else { - if ( color.toString() !== self._color.toString() ) { - // let's not do this on keyup for hex shortcodes - if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { - self._setOption( 'color', color.toString() ); - } - } - } - }; - - input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); - - // If we initialized hidden, show on first focus. The rest is up to you. - if ( self.options.hide ) { - input.one( 'focus', function() { - self.show(); - }); - } - }, - - _initControls: function() { - var self = this, - controls = self.controls, - square = controls.square, - controlOpts = self.options.controls, - stripScale = self._scale[controlOpts.strip]; - - controls.stripSlider.slider({ - orientation: 'vertical', - max: stripScale, - slide: function( event, ui ) { - self.active = 'strip'; - // "reverse" for hue. - if ( controlOpts.strip === 'h' ) { - ui.value = stripScale - ui.value; - } - - self._color[controlOpts.strip]( ui.value ); - self._change.apply( self, arguments ); - } - }); - - controls.squareDrag.draggable({ - containment: controls.square.find( '.iris-square-inner' ), - zIndex: 1000, - cursor: 'move', - drag: function( event, ui ) { - self._squareDrag( event, ui ); - }, - start: function() { - square.addClass( 'iris-dragging' ); - $(this).addClass( 'ui-state-focus' ); - }, - stop: function() { - square.removeClass( 'iris-dragging' ); - $(this).removeClass( 'ui-state-focus' ); - } - }).on( 'mousedown mouseup', function( event ) { - var focusClass = 'ui-state-focus'; - event.preventDefault(); - if (event.type === 'mousedown' ) { - self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur(); - $(this).addClass( focusClass ).focus(); - } else { - $(this).removeClass( focusClass ); - } - }).on( 'keydown', function( event ) { - var container = controls.square, - draggable = controls.squareDrag, - position = draggable.position(), - distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop" - - // make alt key go "10" - if ( event.altKey ) { - distance *= 10; - } - - // Reposition if one of the directional keys is pressed - switch ( event.keyCode ) { - case 37: position.left -= distance; break; // Left - case 38: position.top -= distance; break; // Up - case 39: position.left += distance; break; // Right - case 40: position.top += distance; break; // Down - default: return true; // Exit and bubble - } - - // Keep draggable within container - position.left = Math.max( 0, Math.min( position.left, container.width() ) ); - position.top = Math.max( 0, Math.min( position.top, container.height() ) ); - - draggable.css(position); - self._squareDrag( event, { position: position }); - event.preventDefault(); - }); - - // allow clicking on the square to move there and keep dragging - square.mousedown( function( event ) { - var squareOffset, pos; - // only left click - if ( event.which !== 1 ) { - return; - } - - // prevent bubbling from the handle: no infinite loops - if ( ! $( event.target ).is( 'div' ) ) { - return; - } - - squareOffset = self.controls.square.offset(); - pos = { - top: event.pageY - squareOffset.top, - left: event.pageX - squareOffset.left - }; - event.preventDefault(); - self._squareDrag( event, { position: pos } ); - event.target = self.controls.squareDrag.get(0); - self.controls.squareDrag.css( pos ).trigger( event ); - }); - - // palettes - if ( self.options.palettes ) { - self._paletteListeners(); - } - }, - - _paletteListeners: function() { - var self = this; - self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() { - self._color.fromCSS( $(this).data('color') ); - self.active = 'external'; - self._change(); - }).on( 'keydown.palette', '.iris-palette', function( event ) { - if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) { - return true; - } - event.stopPropagation(); - $( this ).click(); - }); - }, - - _squareDrag: function( event, ui ) { - var self = this, - controlOpts = self.options.controls, - dimensions = self._squareDimensions(), - vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ), - horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] ); - - self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal ); - - self.active = 'square'; - self._change.apply( self, arguments ); - }, - - _setOption: function( key, value ) { - var self = this, - oldValue = self.options[key], - doDimensions = false, - hexLessColor, - newColor, - method; - - // ensure the new value is set. We can reset to oldValue if some check wasn't met. - self.options[key] = value; - - switch(key) { - case 'color': - // cast to string in case we have a number - value = '' + value; - hexLessColor = value.replace( /^#/, '' ); - newColor = new Color( value ).setHSpace( self.options.mode ); - if ( newColor.error ) { - self.options[key] = oldValue; - } else { - self._color = newColor; - self.options.color = self.options[key] = self._color.toString(); - self.active = 'external'; - self._change(); - } - break; - case 'palettes': - doDimensions = true; - - if ( value ) { - self._addPalettes(); - } else { - self.picker.find('.iris-palette-container').remove(); - } - - // do we need to add events? - if ( ! oldValue ) { - self._paletteListeners(); - } - break; - case 'width': - doDimensions = true; - break; - case 'border': - doDimensions = true; - method = value ? 'addClass' : 'removeClass'; - self.picker[method]('iris-border'); - break; - case 'mode': - case 'controls': - // if nothing's changed, let's bail, since this causes re-rendering the whole widget - if ( oldValue === value ) { - return; - } - - // we're using these poorly named variables because they're already scoped. - // method is the element that Iris was called on. oldValue will be the options - method = self.element; - oldValue = self.options; - oldValue.hide = ! self.picker.is( ':visible' ); - self.destroy(); - self.picker.remove(); - return $(self.element).iris(oldValue); - } - - // Do we need to recalc dimensions? - if ( doDimensions ) { - self._dimensions(true); - } - }, - - _squareDimensions: function( forceRefresh ) { - var square = this.controls.square, - dimensions, - control; - - if ( forceRefresh !== undef && square.data('dimensions') ) { - return square.data('dimensions'); - } - - control = this.controls.squareDrag; - dimensions = { - w: square.width(), - h: square.height() - }; - square.data( 'dimensions', dimensions ); - return dimensions; - }, - - _isNonHueControl: function( active, type ) { - if ( active === 'square' && this.options.controls.strip === 'h' ) { - return true; - } else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) { - return false; - } - - return true; - }, - - _change: function() { - var self = this, - controls = self.controls, - color = self._getHSpaceColor(), - actions = [ 'square', 'strip' ], - controlOpts = self.options.controls, - type = controlOpts[self.active] || 'external', - oldHue = self.hue; - - if ( self.active === 'strip' ) { - // take no action on any of the square sliders if we adjusted the strip - actions = []; - } else if ( self.active !== 'external' ) { - // for non-strip, non-external, strip should never change - actions.pop(); // conveniently the last item - } - - $.each( actions, function(index, item) { - var value, dimensions, cssObj; - if ( item !== self.active ) { - switch ( item ) { - case 'strip': - // reverse for hue - value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; - controls.stripSlider.slider( 'value', value ); - break; - case 'square': - dimensions = self._squareDimensions(); - cssObj = { - left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, - top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) - }; - - self.controls.squareDrag.css( cssObj ); - break; - } - } - }); - - // Ensure that we don't change hue if we triggered a hue reset - if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { - self._color.h(oldHue); - } - - // store hue for repeating above check next time - self.hue = self._color.h(); - - self.options.color = self._color.toString(); - - // only run after the first time - if ( self._inited ) { - self._trigger( 'change', { type: self.active }, { color: self._color } ); - } - - if ( self.element.is( ':input' ) && ! self._color.error ) { - self.element.removeClass( 'iris-error' ); - if ( self.element.val() !== self._color.toString() ) { - self.element.val( self._color.toString() ); - } - } - - self._paint(); - self._inited = true; - self.active = false; - }, - // taken from underscore.js _.debounce method - _debounce: function( func, wait, immediate ) { - var timeout, result; - return function() { - var context = this, - args = arguments, - later, - callNow; - - later = function() { - timeout = null; - if ( ! immediate) { - result = func.apply( context, args ); - } - }; - - callNow = immediate && !timeout; - clearTimeout( timeout ); - timeout = setTimeout( later, wait ); - if ( callNow ) { - result = func.apply( context, args ); - } - return result; - }; - }, - show: function() { - this.picker.show(); - }, - hide: function() { - this.picker.hide(); - }, - toggle: function() { - this.picker.toggle(); - }, - color: function(newColor) { - if ( newColor === true ) { - return this._color.clone(); - } else if ( newColor === undef ) { - return this._color.toString(); - } - this.option('color', newColor); - } - }; - // initialize the widget - $.widget( 'a8c.iris', Iris ); - // add CSS - $( '' ).appendTo( 'head' ); - -}( jQuery )); -/*! Color.js - v0.9.11 - 2013-08-09 -* https://github.com/Automattic/Color.js -* Copyright (c) 2013 Matt Wiebe; Licensed GPLv2 */ -(function(global, undef) { - - var Color = function( color, type ) { - if ( ! ( this instanceof Color ) ) - return new Color( color, type ); - - return this._init( color, type ); - }; - - Color.fn = Color.prototype = { - _color: 0, - _alpha: 1, - error: false, - // for preserving hue/sat in fromHsl().toHsl() flows - _hsl: { h: 0, s: 0, l: 0 }, - // for preserving hue/sat in fromHsv().toHsv() flows - _hsv: { h: 0, s: 0, v: 0 }, - // for setting hsl or hsv space - needed for .h() & .s() functions to function properly - _hSpace: 'hsl', - _init: function( color ) { - var func = 'noop'; - switch ( typeof color ) { - case 'object': - // alpha? - if ( color.a !== undef ) - this.a( color.a ); - func = ( color.r !== undef ) ? 'fromRgb' : - ( color.l !== undef ) ? 'fromHsl' : - ( color.v !== undef ) ? 'fromHsv' : func; - return this[func]( color ); - case 'string': - return this.fromCSS( color ); - case 'number': - return this.fromInt( parseInt( color, 10 ) ); - } - return this; - }, - - _error: function() { - this.error = true; - return this; - }, - - clone: function() { - var newColor = new Color( this.toInt() ), - copy = ['_alpha', '_hSpace', '_hsl', '_hsv', 'error']; - for ( var i = copy.length - 1; i >= 0; i-- ) { - newColor[ copy[i] ] = this[ copy[i] ]; - } - return newColor; - }, - - setHSpace: function( space ) { - this._hSpace = ( space === 'hsv' ) ? space : 'hsl'; - return this; - }, - - noop: function() { - return this; - }, - - fromCSS: function( color ) { - var list, - leadingRE = /^(rgb|hs(l|v))a?\(/; - this.error = false; - - // whitespace and semicolon trim - color = color.replace(/^\s+/, '').replace(/\s+$/, '').replace(/;$/, ''); - - if ( color.match(leadingRE) && color.match(/\)$/) ) { - list = color.replace(/(\s|%)/g, '').replace(leadingRE, '').replace(/,?\);?$/, '').split(','); - - if ( list.length < 3 ) - return this._error(); - - if ( list.length === 4 ) { - this.a( parseFloat( list.pop() ) ); - // error state has been set to true in .a() if we passed NaN - if ( this.error ) - return this; - } - - for (var i = list.length - 1; i >= 0; i--) { - list[i] = parseInt(list[i], 10); - if ( isNaN( list[i] ) ) - return this._error(); - } - - if ( color.match(/^rgb/) ) { - return this.fromRgb( { - r: list[0], - g: list[1], - b: list[2] - } ); - } else if ( color.match(/^hsv/) ) { - return this.fromHsv( { - h: list[0], - s: list[1], - v: list[2] - } ); - } else { - return this.fromHsl( { - h: list[0], - s: list[1], - l: list[2] - } ); - } - } else { - // must be hex amirite? - return this.fromHex( color ); - } - }, - - fromRgb: function( rgb, preserve ) { - if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef ) - return this._error(); - - this.error = false; - return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve ); - }, - - fromHex: function( color ) { - color = color.replace(/^#/, '').replace(/^0x/, ''); - if ( color.length === 3 ) { - color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; - } - - // rough error checking - this is where things go squirrely the most - this.error = ! /^[0-9A-F]{6}$/i.test( color ); - return this.fromInt( parseInt( color, 16 ) ); - }, - - fromHsl: function( hsl ) { - var r, g, b, q, p, h, s, l; - - if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef ) - return this._error(); - - this._hsl = hsl; // store it - this._hSpace = 'hsl'; // implicit - h = hsl.h / 360; s = hsl.s / 100; l = hsl.l / 100; - if ( s === 0 ) { - r = g = b = l; // achromatic - } - else { - q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; - p = 2 * l - q; - r = this.hue2rgb( p, q, h + 1/3 ); - g = this.hue2rgb( p, q, h ); - b = this.hue2rgb( p, q, h - 1/3 ); - } - return this.fromRgb( { - r: r * 255, - g: g * 255, - b: b * 255 - }, true ); // true preserves hue/sat - }, - - fromHsv: function( hsv ) { - var h, s, v, r, g, b, i, f, p, q, t; - if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef ) - return this._error(); - - this._hsv = hsv; // store it - this._hSpace = 'hsv'; // implicit - - h = hsv.h / 360; s = hsv.s / 100; v = hsv.v / 100; - i = Math.floor( h * 6 ); - f = h * 6 - i; - p = v * ( 1 - s ); - q = v * ( 1 - f * s ); - t = v * ( 1 - ( 1 - f ) * s ); - - switch( i % 6 ) { - case 0: - r = v; g = t; b = p; - break; - case 1: - r = q; g = v; b = p; - break; - case 2: - r = p; g = v; b = t; - break; - case 3: - r = p; g = q; b = v; - break; - case 4: - r = t; g = p; b = v; - break; - case 5: - r = v; g = p; b = q; - break; - } - - return this.fromRgb( { - r: r * 255, - g: g * 255, - b: b * 255 - }, true ); // true preserves hue/sat - - }, - // everything comes down to fromInt - fromInt: function( color, preserve ) { - this._color = parseInt( color, 10 ); - - if ( isNaN( this._color ) ) - this._color = 0; - - // let's coerce things - if ( this._color > 16777215 ) - this._color = 16777215; - else if ( this._color < 0 ) - this._color = 0; - - // let's not do weird things - if ( preserve === undef ) { - this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0; - } - // EVENT GOES HERE - return this; - }, - - hue2rgb: function( p, q, t ) { - if ( t < 0 ) { - t += 1; - } - if ( t > 1 ) { - t -= 1; - } - if ( t < 1/6 ) { - return p + ( q - p ) * 6 * t; - } - if ( t < 1/2 ) { - return q; - } - if ( t < 2/3 ) { - return p + ( q - p ) * ( 2/3 - t ) * 6; - } - return p; - }, - - toString: function() { - var hex = parseInt( this._color, 10 ).toString( 16 ); - if ( this.error ) - return ''; - // maybe left pad it - if ( hex.length < 6 ) { - for (var i = 6 - hex.length - 1; i >= 0; i--) { - hex = '0' + hex; - } - } - return '#' + hex; - }, - - toCSS: function( type, alpha ) { - type = type || 'hex'; - alpha = parseFloat( alpha || this._alpha ); - switch ( type ) { - case 'rgb': - case 'rgba': - var rgb = this.toRgb(); - if ( alpha < 1 ) { - return "rgba( " + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + alpha + " )"; - } - else { - return "rgb( " + rgb.r + ", " + rgb.g + ", " + rgb.b + " )"; - } - break; - case 'hsl': - case 'hsla': - var hsl = this.toHsl(); - if ( alpha < 1 ) { - return "hsla( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%, " + alpha + " )"; - } - else { - return "hsl( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "% )"; - } - break; - default: - return this.toString(); - } - }, - - toRgb: function() { - return { - r: 255 & ( this._color >> 16 ), - g: 255 & ( this._color >> 8 ), - b: 255 & ( this._color ) - }; - }, - - toHsl: function() { - var rgb = this.toRgb(); - var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; - var max = Math.max( r, g, b ), min = Math.min( r, g, b ); - var h, s, l = ( max + min ) / 2; - - if ( max === min ) { - h = s = 0; // achromatic - } else { - var d = max - min; - s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); - switch ( max ) { - case r: h = ( g - b ) / d + ( g < b ? 6 : 0 ); - break; - case g: h = ( b - r ) / d + 2; - break; - case b: h = ( r - g ) / d + 4; - break; - } - h /= 6; - } - - // maintain hue & sat if we've been manipulating things in the HSL space. - h = Math.round( h * 360 ); - if ( h === 0 && this._hsl.h !== h ) { - h = this._hsl.h; - } - s = Math.round( s * 100 ); - if ( s === 0 && this._hsl.s ) { - s = this._hsl.s; - } - - return { - h: h, - s: s, - l: Math.round( l * 100 ) - }; - - }, - - toHsv: function() { - var rgb = this.toRgb(); - var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; - var max = Math.max( r, g, b ), min = Math.min( r, g, b ); - var h, s, v = max; - var d = max - min; - s = max === 0 ? 0 : d / max; - - if ( max === min ) { - h = s = 0; // achromatic - } else { - switch( max ){ - case r: - h = ( g - b ) / d + ( g < b ? 6 : 0 ); - break; - case g: - h = ( b - r ) / d + 2; - break; - case b: - h = ( r - g ) / d + 4; - break; - } - h /= 6; - } - - // maintain hue & sat if we've been manipulating things in the HSV space. - h = Math.round( h * 360 ); - if ( h === 0 && this._hsv.h !== h ) { - h = this._hsv.h; - } - s = Math.round( s * 100 ); - if ( s === 0 && this._hsv.s ) { - s = this._hsv.s; - } - - return { - h: h, - s: s, - v: Math.round( v * 100 ) - }; - }, - - toInt: function() { - return this._color; - }, - - toIEOctoHex: function() { - // AARRBBGG - var hex = this.toString(); - var AA = parseInt( 255 * this._alpha, 10 ).toString(16); - if ( AA.length === 1 ) { - AA = '0' + AA; - } - return '#' + AA + hex.replace(/^#/, '' ); - }, - - toLuminosity: function() { - var rgb = this.toRgb(); - return 0.2126 * Math.pow( rgb.r / 255, 2.2 ) + 0.7152 * Math.pow( rgb.g / 255, 2.2 ) + 0.0722 * Math.pow( rgb.b / 255, 2.2); - }, - - getDistanceLuminosityFrom: function( color ) { - if ( ! ( color instanceof Color ) ) { - throw 'getDistanceLuminosityFrom requires a Color object'; - } - var lum1 = this.toLuminosity(); - var lum2 = color.toLuminosity(); - if ( lum1 > lum2 ) { - return ( lum1 + 0.05 ) / ( lum2 + 0.05 ); - } - else { - return ( lum2 + 0.05 ) / ( lum1 + 0.05 ); - } - }, - - getMaxContrastColor: function() { - var lum = this.toLuminosity(); - var hex = ( lum >= 0.5 ) ? '000000' : 'ffffff'; - return new Color( hex ); - }, - - getReadableContrastingColor: function( bgColor, minContrast ) { - if ( ! bgColor instanceof Color ) { - return this; - } - - // you shouldn't use less than 5, but you might want to. - var targetContrast = ( minContrast === undef ) ? 5 : minContrast; - // working things - var contrast = bgColor.getDistanceLuminosityFrom( this ); - var maxContrastColor = bgColor.getMaxContrastColor(); - var maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor ); - - // if current max contrast is less than the target contrast, we had wishful thinking. - // still, go max - if ( maxContrast <= targetContrast ) { - return maxContrastColor; - } - // or, we might already have sufficient contrast - else if ( contrast >= targetContrast ) { - return this; - } - - var incr = ( 0 === maxContrastColor.toInt() ) ? -1 : 1; - while ( contrast < targetContrast ) { - this.l( incr, true ); // 2nd arg turns this into an incrementer - contrast = this.getDistanceLuminosityFrom( bgColor ); - // infininite loop prevention: you never know. - if ( this._color === 0 || this._color === 16777215 ) { - break; - } - } - - return this; - - }, - - a: function( val ) { - if ( val === undef ) - return this._alpha; - - var a = parseFloat( val ); - - if ( isNaN( a ) ) - return this._error(); - - this._alpha = a; - return this; - }, - - // TRANSFORMS - - darken: function( amount ) { - amount = amount || 5; - return this.l( - amount, true ); - }, - - lighten: function( amount ) { - amount = amount || 5; - return this.l( amount, true ); - }, - - saturate: function( amount ) { - amount = amount || 15; - return this.s( amount, true ); - }, - - desaturate: function( amount ) { - amount = amount || 15; - return this.s( - amount, true ); - }, - - toGrayscale: function() { - return this.setHSpace('hsl').s( 0 ); - }, - - getComplement: function() { - return this.h( 180, true ); - }, - - getSplitComplement: function( step ) { - step = step || 1; - var incr = 180 + ( step * 30 ); - return this.h( incr, true ); - }, - - getAnalog: function( step ) { - step = step || 1; - var incr = step * 30; - return this.h( incr, true ); - }, - - getTetrad: function( step ) { - step = step || 1; - var incr = step * 60; - return this.h( incr, true ); - }, - - getTriad: function( step ) { - step = step || 1; - var incr = step * 120; - return this.h( incr, true ); - }, - - _partial: function( key ) { - var prop = shortProps[key]; - return function( val, incr ) { - var color = this._spaceFunc('to', prop.space); - - // GETTER - if ( val === undef ) - return color[key]; - - // INCREMENT - if ( incr === true ) - val = color[key] + val; - - // MOD & RANGE - if ( prop.mod ) - val = val % prop.mod; - if ( prop.range ) - val = ( val < prop.range[0] ) ? prop.range[0] : ( val > prop.range[1] ) ? prop.range[1] : val; - - // NEW VALUE - color[key] = val; - - return this._spaceFunc('from', prop.space, color); - }; - }, - - _spaceFunc: function( dir, s, val ) { - var space = s || this._hSpace, - funcName = dir + space.charAt(0).toUpperCase() + space.substr(1); - return this[funcName](val); - } - }; - - var shortProps = { - h: { - mod: 360 - }, - s: { - range: [0,100] - }, - l: { - space: 'hsl', - range: [0,100] - }, - v: { - space: 'hsv', - range: [0,100] - }, - r: { - space: 'rgb', - range: [0,255] - }, - g: { - space: 'rgb', - range: [0,255] - }, - b: { - space: 'rgb', - range: [0,255] - } - }; - - for ( var key in shortProps ) { - if ( shortProps.hasOwnProperty( key ) ) - Color.fn[key] = Color.fn._partial(key); - } - - // play nicely with Node + browser - if ( typeof exports === 'object' ) - module.exports = Color; - else - global.Color = Color; - -}(this)); diff --git a/lib/plugins/styler/lang/en/intro.txt b/lib/plugins/styler/lang/en/intro.txt deleted file mode 100644 index 4ea55172f..000000000 --- a/lib/plugins/styler/lang/en/intro.txt +++ /dev/null @@ -1,2 +0,0 @@ -This tool allows you to change certain style settings of your currently selected template. -All changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file diff --git a/lib/plugins/styler/lang/en/lang.php b/lib/plugins/styler/lang/en/lang.php deleted file mode 100644 index 1bf8efa2c..000000000 --- a/lib/plugins/styler/lang/en/lang.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ - -// menu entry for admin plugins -$lang['js']['menu'] = 'Template Style Settings'; - -// custom language strings for the plugin -$lang['error'] = 'Sorry, this template does not support this functionality.'; - -$lang['btn_preview'] = 'Preview your changes'; -$lang['btn_save'] = 'Save your changes'; -$lang['btn_reset'] = 'Reset your current changes'; -$lang['btn_revert'] = 'Revert all styles back to the template\'s default'; - -// default guaranteed placeholders -$lang['__text__'] = 'Main text color'; -$lang['__background__'] = 'Main text background color'; -$lang['__text_alt__'] = 'Alternative text color'; -$lang['__background_alt__'] = 'Alternative text background color'; -$lang['__text_neu__'] = 'Neutral text color'; -$lang['__background_neu__'] = 'Neutral text background color'; -$lang['__border__'] = 'Border color'; -$lang['__highlight__'] = 'Highlight color (for search results mainly)'; - - - - -//Setup VIM: ex: et ts=4 : diff --git a/lib/plugins/styler/plugin.info.txt b/lib/plugins/styler/plugin.info.txt deleted file mode 100644 index 51f2f72f6..000000000 --- a/lib/plugins/styler/plugin.info.txt +++ /dev/null @@ -1,7 +0,0 @@ -base styler -author Andreas Gohr -email andi@splitbrain.org -date 2015-05-16 -name styler plugin -desc Allows to edit style.ini replacements -url https://www.dokuwiki.org/plugin:styler diff --git a/lib/plugins/styler/script.js b/lib/plugins/styler/script.js deleted file mode 100644 index a4fb601c8..000000000 --- a/lib/plugins/styler/script.js +++ /dev/null @@ -1,92 +0,0 @@ -/* DOKUWIKI:include_once iris.js */ - -jQuery(function () { - // user openend the admin page, set cookie and redirect - if (jQuery('#plugin__styler').length) { - DokuCookie.setValue('styler_plugin', 1); - document.location.href = document.location.href.replace(/do=admin/, ''); - } - - // continue only if the Styler Dialog is currently enabled - if (DokuCookie.getValue('styler_plugin') != 1) return; - - var styler_timeout = null; - - // create dialog element - var $dialog = jQuery(document.createElement('div')); - jQuery('body').append($dialog); - - - /** - * updates the current CSS with a new preview one - */ - function styler_updateCSS() { - var now = new Date().getTime(); - var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); - } - - /** - * save current values and reload preview (debounced) - */ - function styler_saveAndUpdate() { - if (styler_timeout) window.clearTimeout(styler_timeout); - styler_timeout = window.setTimeout(function () { - styler_timeout = null; - - var params = $dialog.find('input[type=text]').serializeArray(); - params[params.length] = { name: 'call', value: 'plugin_styler'}; - params[params.length] = {name: 'run', value: 'preview'}; - - jQuery.post( - DOKU_BASE + '/lib/exe/ajax.php', - params, - styler_updateCSS - ); - }, 500); - } - - // load the dialog content and apply listeners - $dialog.load( - DOKU_BASE + '/lib/exe/ajax.php', - { - 'call': 'plugin_styler', - 'run': 'html', - 'id': JSINFO.id - }, - function () { - // load the preview template - styler_updateCSS(); - - // open the dialog - $dialog.dialog({ - 'title': LANG.plugins.styler.menu, - 'width': 500, - 'height': 500, - 'top': 50, - 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, - // bring everything back to normal - 'close': function (event, ui) { - // disable the styler plugin again - DokuCookie.setValue('styler_plugin', 0); - // reload - document.location.reload() - } - }); - - // we don't need the manual preview in JS mode - $dialog.find('.btn_preview').hide(); - - // add the color picker FIXME add saveAndUpdate to correct event - $dialog.find('.color').iris({ }); - - // listen to keyup events - $dialog.find('input[type=text]').keyup(function () { - console.log('change'); - styler_saveAndUpdate(); - }); - - } - ); - -}); \ No newline at end of file diff --git a/lib/plugins/styling/.travis.yml b/lib/plugins/styling/.travis.yml new file mode 100644 index 000000000..75ee0b152 --- /dev/null +++ b/lib/plugins/styling/.travis.yml @@ -0,0 +1,13 @@ +# Config file for travis-ci.org + +language: php +php: + - "5.5" + - "5.4" + - "5.3" +env: + - DOKUWIKI=master + - DOKUWIKI=stable +before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh +install: sh travis.sh +script: cd _test && phpunit --stderr --group plugin_styling diff --git a/lib/plugins/styling/README b/lib/plugins/styling/README new file mode 100644 index 000000000..a1a5e890c --- /dev/null +++ b/lib/plugins/styling/README @@ -0,0 +1,27 @@ +styling Plugin for DokuWiki + +Allows to edit style.ini replacements + +All documentation for this plugin can be found at +https://www.dokuwiki.org/plugin:styling + +If you install this plugin manually, make sure it is installed in +lib/plugins/styling/ - if the folder is called different it +will not work! + +Please refer to http://www.dokuwiki.org/plugins for additional info +on how to install plugins in DokuWiki. + +---- +Copyright (C) Andreas Gohr + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +See the COPYING file in your DokuWiki folder for details diff --git a/lib/plugins/styling/_test/general.test.php b/lib/plugins/styling/_test/general.test.php new file mode 100644 index 000000000..1337f6f75 --- /dev/null +++ b/lib/plugins/styling/_test/general.test.php @@ -0,0 +1,33 @@ +assertFileExists($file); + + $info = confToHash($file); + + $this->assertArrayHasKey('base', $info); + $this->assertArrayHasKey('author', $info); + $this->assertArrayHasKey('email', $info); + $this->assertArrayHasKey('date', $info); + $this->assertArrayHasKey('name', $info); + $this->assertArrayHasKey('desc', $info); + $this->assertArrayHasKey('url', $info); + + $this->assertEquals('styling', $info['base']); + $this->assertRegExp('/^https?:\/\//', $info['url']); + $this->assertTrue(mail_isvalid($info['email'])); + $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']); + $this->assertTrue(false !== strtotime($info['date'])); + } +} diff --git a/lib/plugins/styling/action.php b/lib/plugins/styling/action.php new file mode 100644 index 000000000..622c634d6 --- /dev/null +++ b/lib/plugins/styling/action.php @@ -0,0 +1,108 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +/** + * Class action_plugin_styling + * + * This handles all the save actions and loading the interface + * + * All this usually would be done within an admin plugin, but we want to have this available outside + * the admin interface using our floating dialog. + */ +class action_plugin_styling extends DokuWiki_Action_Plugin { + + /** + * Registers a callback functions + * + * @param Doku_Event_Handler $controller DokuWiki's event controller object + * @return void + */ + public function register(Doku_Event_Handler $controller) { + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); + $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); + $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_header'); + } + + /** + * Adds the preview parameter to the stylesheet loading in non-js mode + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + public function handle_header(Doku_Event &$event, $param) { + global $ACT; + global $INPUT; + if($ACT != 'admin' || $INPUT->str('page') != 'styling') return; + if(!auth_isadmin()) return; + + // set preview + $len = count($event->data['link']); + for($i = 0; $i < $len; $i++) { + if( + $event->data['link'][$i]['rel'] == 'stylesheet' && + strpos($event->data['link'][$i]['href'], 'lib/exe/css.php') !== false + ) { + $event->data['link'][$i]['href'] .= '&preview=1&tseed='.time(); + } + } + } + + /** + * Updates the style.ini settings by passing it on to handle() of the admin component + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + public function handle_action(Doku_Event &$event, $param) { + if($event->data != 'styling_plugin') return; + if(!auth_isadmin()) return; + $event->data = 'show'; + + /** @var admin_plugin_styling $hlp */ + $hlp = plugin_load('admin', 'styling'); + $hlp->handle(); + } + + /** + * Create the style form in the floating Dialog + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return void + */ + + public function handle_ajax(Doku_Event &$event, $param) { + if($event->data != 'plugin_styling') return; + if(!auth_isadmin()) return; + $event->preventDefault(); + $event->stopPropagation(); + + global $ID; + global $INPUT; + $ID = getID(); + + /** @var admin_plugin_styling $hlp */ + $hlp = plugin_load('admin', 'styling'); + if($INPUT->str('run') == 'preview') { + $hlp->run_preview(); + } else { + $hlp->form(true); + } + } + +} + +// vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php new file mode 100644 index 000000000..85d87dd71 --- /dev/null +++ b/lib/plugins/styling/admin.php @@ -0,0 +1,215 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +class admin_plugin_styling extends DokuWiki_Admin_Plugin { + + /** + * @return int sort number in admin menu + */ + public function getMenuSort() { + return 1000; + } + + /** + * @return bool true if only access for superuser, false is for superusers and moderators + */ + public function forAdminOnly() { + return true; + } + + /** + * @param string $language + * @return string + */ + public function getMenuText($language) { + $js = $this->getLang('js'); + return $js['menu']; + } + + /** + * handle the different actions (also called from ajax) + */ + public function handle() { + global $INPUT; + $run = $INPUT->extract('run')->str('run'); + if(!$run) return; + $run = "run_$run"; + $this->$run(); + } + + /** + * Render HTML output, e.g. helpful text and a form + */ + public function html() { + echo '
'; + ptln('

'.$this->getMenuText('').'

'); + $this->form(false); + echo '
'; + } + + /** + * Create the actual editing form + */ + public function form($isajax) { + global $conf; + global $ID; + define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php + require_once(DOKU_INC.'lib/exe/css.php'); + $styleini = css_styleini($conf['template'], true); + $replacements = $styleini['replacements']; + + if($isajax) { + $target = wl($ID, array('do' => 'styling_plugin')); + } else { + $target = wl($ID, array('do' => 'admin', 'page' => 'styling')); + } + + if(empty($replacements)) { + echo '

'.$this->getLang('error').'

'; + } else { + echo $this->locale_xhtml('intro'); + + echo '
'; + + echo ''; + foreach($replacements as $key => $value) { + $name = tpl_getLang($key); + if(empty($name)) $name = $this->getLang($key); + if(empty($name)) $name = $key; + + echo ''; + echo ''; + echo ''; + } + echo '
'.$name.'colorClass($key).' />'; + echo '
'; + + echo '

'; + echo ''; + echo ''; #FIXME only if preview.ini exists + echo '

'; + + echo '

'; + echo ''; + echo '

'; + + echo '

'; + echo ''; #FIXME only if local.ini exists + echo '

'; + + echo '
'; + + echo tpl_locale_xhtml('style'); + + } + } + + /** + * set the color class attribute + */ + protected function colorClass($key) { + static $colors = array( + 'text', + 'background', + 'text_alt', + 'background_alt', + 'text_neu', + 'background_neu', + 'border', + 'highlight', + 'background_site', + 'link', + 'existing', + 'missing', + ); + + if(preg_match('/colou?r/', $key) || in_array(trim($key,'_'), $colors)) { + return 'class="color"'; + } else { + return ''; + } + } + + /** + * saves the preview.ini (alos called from ajax directly) + */ + public function run_preview() { + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, $this->makeini()); + } + + /** + * deletes the preview.ini + */ + protected function run_reset() { + global $conf; + $ini = $conf['cachedir'].'/preview.ini'; + io_saveFile($ini, ''); + } + + /** + * deletes the local style.ini replacements + */ + protected function run_revert() { + $this->replaceini(''); + $this->run_reset(); + } + + /** + * save the local style.ini replacements + */ + protected function run_save() { + $this->replaceini($this->makeini()); + $this->run_reset(); + } + + /** + * create the replacement part of a style.ini from submitted data + * + * @return string + */ + protected function makeini() { + global $INPUT; + + $ini = "[replacements]\n"; + $ini .= ";These overwrites have been generated from the Template styling Admin interface\n"; + $ini .= ";Any values in this section will be overwritten by that tool again\n"; + foreach($INPUT->arr('tpl') as $key => $val) { + $ini .= $key.' = "'.addslashes($val).'"'."\n"; + } + + return $ini; + } + + /** + * replaces the replacement parts in the local ini + * + * @param string $new the new ini contents + */ + protected function replaceini($new) { + global $conf; + $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; + if(file_exists($ini)) { + $old = io_readFile($ini); + $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); + $old = trim($old); + } else { + $old = ''; + } + + io_makeFileDir($ini); + io_saveFile($ini, "$old\n\n$new"); + } + +} + +// vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/iris.js b/lib/plugins/styling/iris.js new file mode 100644 index 000000000..4eda5022e --- /dev/null +++ b/lib/plugins/styling/iris.js @@ -0,0 +1,1488 @@ +/*! Iris Color Picker - v1.0.7 - 2014-11-28 +* https://github.com/Automattic/Iris +* Copyright (c) 2014 Matt Wiebe; Licensed GPLv2 */ +(function( $, undef ){ + var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion; + + _html = '
'; + _css = '.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}'; + + // Even IE9 dosen't support gradients. Elaborate sigh. + UA = navigator.userAgent.toLowerCase(); + isIE = navigator.appName === 'Microsoft Internet Explorer'; + IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; + nonGradientIE = ( isIE && IEVersion < 10 ); + gradientType = false; + + // we don't bother with an unprefixed version, as it has a different syntax + vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ]; + + // Bail for IE <= 7 + if ( nonGradientIE && IEVersion <= 7 ) { + $.fn.iris = $.noop; + $.support.iris = false; + return; + } + + $.support.iris = true; + + function testGradientType() { + var el, base, + bgImageString = 'backgroundImage'; + + if ( nonGradientIE ) { + gradientType = 'filter'; + } + else { + el = $( '
' ); + base = 'linear-gradient(top,#fff,#000)'; + $.each( vendorPrefixes, function( i, val ){ + el.css( bgImageString, val + base ); + if ( el.css( bgImageString ).match( 'gradient' ) ) { + gradientType = i; + return false; + } + }); + // check for legacy webkit gradient syntax + if ( gradientType === false ) { + el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' ); + if ( el.css( bgImageString ).match( 'gradient' ) ) { + gradientType = 'webkit'; + } + } + el.remove(); + } + + } + + /** + * Only for CSS3 gradients. oldIE will use a separate function. + * + * Accepts as many color stops as necessary from 2nd arg on, or 2nd + * arg can be an array of color stops + * + * @param {string} origin Gradient origin - top or left, defaults to left. + * @return {string} Appropriate CSS3 gradient string for use in + */ + function createGradient( origin, stops ) { + origin = ( origin === 'top' ) ? 'top' : 'left'; + stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); + if ( gradientType === 'webkit' ) { + return legacyWebkitGradient( origin, stops ); + } else { + return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')'; + } + } + + /** + * Stupid gradients for a stupid browser. + */ + function stupidIEGradient( origin, stops ) { + var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html; + + origin = ( origin === 'top' ) ? 'top' : 'left'; + stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); + // 8 hex: AARRGGBB + // GradientType: 0 vertical, 1 horizontal + type = ( origin === 'top' ) ? 0 : 1; + self = $( this ); + lastIndex = stops.length - 1; + filter = 'filter'; + startPosProp = ( type === 1 ) ? 'left' : 'top'; + endPosProp = ( type === 1 ) ? 'right' : 'bottom'; + dimensionProp = ( type === 1 ) ? 'height' : 'width'; + template = '
'; + html = ''; + // need a positioning context + if ( self.css('position') === 'static' ) { + self.css( {position: 'relative' } ); + } + + stops = fillColorStops( stops ); + $.each(stops, function( i, startColor ) { + var endColor, endStop, filterVal; + + // we want two at a time. if we're on the last pair, bail. + if ( i === lastIndex ) { + return false; + } + + endColor = stops[ i + 1 ]; + //if our pairs are at the same color stop, moving along. + if ( startColor.stop === endColor.stop ) { + return; + } + + endStop = 100 - parseFloat( endColor.stop ) + '%'; + startColor.octoHex = new Color( startColor.color ).toIEOctoHex(); + endColor.octoHex = new Color( endColor.color ).toIEOctoHex(); + + filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')'; + html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal ); + }); + self.find( '.iris-ie-gradient-shim' ).remove(); + $( html ).prependTo( self ); + } + + function legacyWebkitGradient( origin, colorList ) { + var stops = []; + origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,'; + colorList = fillColorStops( colorList ); + $.each( colorList, function( i, val ){ + stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' ); + }); + return '-webkit-gradient(linear,' + origin + stops.join(',') + ')'; + } + + function fillColorStops( colorList ) { + var colors = [], + percs = [], + newColorList = [], + lastIndex = colorList.length - 1; + + $.each( colorList, function( index, val ) { + var color = val, + perc = false, + match = val.match( /1?[0-9]{1,2}%$/ ); + + if ( match ) { + color = val.replace( /\s?1?[0-9]{1,2}%$/, '' ); + perc = match.shift(); + } + colors.push( color ); + percs.push( perc ); + }); + + // back fill first and last + if ( percs[0] === false ) { + percs[0] = '0%'; + } + + if ( percs[lastIndex] === false ) { + percs[lastIndex] = '100%'; + } + + percs = backFillColorStops( percs ); + + $.each( percs, function( i ){ + newColorList[i] = { color: colors[i], stop: percs[i] }; + }); + return newColorList; + } + + function backFillColorStops( stops ) { + var first = 0, + last = stops.length - 1, + i = 0, + foundFirst = false, + incr, + steps, + step, + firstVal; + + if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) { + return stops; + } + while ( i < stops.length - 1 ) { + if ( ! foundFirst && stops[i] === false ) { + first = i - 1; + foundFirst = true; + } else if ( foundFirst && stops[i] !== false ) { + last = i; + i = stops.length; + } + i++; + } + steps = last - first; + firstVal = parseInt( stops[first].replace('%'), 10 ); + incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps; + i = first + 1; + step = 1; + while ( i < last ) { + stops[i] = ( firstVal + ( step * incr ) ) + '%'; + step++; + i++; + } + return backFillColorStops( stops ); + } + + $.fn.gradient = function() { + var args = arguments; + return this.each( function() { + // this'll be oldishIE + if ( nonGradientIE ) { + stupidIEGradient.apply( this, args ); + } else { + // new hotness + $( this ).css( 'backgroundImage', createGradient.apply( this, args ) ); + } + }); + }; + + $.fn.raninbowGradient = function( origin, args ) { + var opts, template, i, steps; + + origin = origin || 'top'; + opts = $.extend( {}, { s: 100, l: 50 }, args ); + template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)'; + i = 0; + steps = []; + while ( i <= 360 ) { + steps.push( template.replace('%h%', i) ); + i += 30; + } + return this.each(function() { + $(this).gradient( origin, steps ); + }); + }; + + // the colorpicker widget def. + Iris = { + options: { + color: false, + mode: 'hsl', + controls: { + horiz: 's', // horizontal defaults to saturation + vert: 'l', // vertical defaults to lightness + strip: 'h' // right strip defaults to hue + }, + hide: true, // hide the color picker by default + border: true, // draw a border around the collection of UI elements + target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. + width: 200, // the width of the collection of UI elements + palettes: false // show a palette of basic colors beneath the square. + }, + _color: '', + _palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ], + _inited: false, + _defaultHSLControls: { + horiz: 's', + vert: 'l', + strip: 'h' + }, + _defaultHSVControls: { + horiz: 'h', + vert: 'v', + strip: 's' + }, + _scale: { + h: 360, + s: 100, + l: 100, + v: 100 + }, + _create: function() { + var self = this, + el = self.element, + color = self.options.color || el.val(); + + if ( gradientType === false ) { + testGradientType(); + } + + if ( el.is( 'input' ) ) { + if ( self.options.target ) { + self.picker = $( _html ).appendTo( self.options.target ); + } else { + self.picker = $( _html ).insertAfter( el ); + } + + self._addInputListeners( el ); + } else { + el.append( _html ); + self.picker = el.find( '.iris-picker' ); + } + + // Browsers / Versions + // Feature detection doesn't work for these, and $.browser is deprecated + if ( isIE ) { + if ( IEVersion === 9 ) { + self.picker.addClass( 'iris-ie-9' ); + } else if ( IEVersion <= 8 ) { + self.picker.addClass( 'iris-ie-lt9' ); + } + } else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { + self.picker.addClass( 'iris-mozilla' ); + } + + if ( self.options.palettes ) { + self._addPalettes(); + } + + self._color = new Color( color ).setHSpace( self.options.mode ); + self.options.color = self._color.toString(); + + // prep 'em for re-use + self.controls = { + square: self.picker.find( '.iris-square' ), + squareDrag: self.picker.find( '.iris-square-value' ), + horiz: self.picker.find( '.iris-square-horiz' ), + vert: self.picker.find( '.iris-square-vert' ), + strip: self.picker.find( '.iris-strip' ), + stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' ) + }; + + // small sanity check - if we chose hsv, change default controls away from hsl + if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { + self.options.controls = self._defaultHSVControls; + } else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { + self.options.controls = self._defaultHSLControls; + } + + // store it. HSL gets squirrely + self.hue = self._color.h(); + + if ( self.options.hide ) { + self.picker.hide(); + } + + if ( self.options.border ) { + self.picker.addClass( 'iris-border' ); + } + + self._initControls(); + self.active = 'external'; + self._dimensions(); + self._change(); + }, + _has: function(needle, haystack) { + var ret = false; + $.each(haystack, function(i,v){ + if ( needle === v ) { + ret = true; + // exit the loop + return false; + } + }); + return ret; + }, + _addPalettes: function () { + var container = $( '
' ), + palette = $( '' ), + colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes; + + // do we have an existing container? Empty and reuse it. + if ( this.picker.find( '.iris-palette-container' ).length ) { + container = this.picker.find( '.iris-palette-container' ).detach().html( '' ); + } + + $.each(colors, function(index, val) { + palette.clone().data( 'color', val ) + .css( 'backgroundColor', val ).appendTo( container ) + .height( 10 ).width( 10 ); + }); + + this.picker.append(container); + }, + _paint: function() { + var self = this; + self._paintDimension( 'top', 'strip' ); + self._paintDimension( 'top', 'vert' ); + self._paintDimension( 'left', 'horiz' ); + }, + _paintDimension: function( origin, control ) { + var self = this, + c = self._color, + mode = self.options.mode, + color = self._getHSpaceColor(), + target = self.controls[ control ], + controlOpts = self.options.controls, + stops; + + // don't paint the active control + if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) { + return; + } + + switch ( controlOpts[ control ] ) { + case 'h': + if ( mode === 'hsv' ) { + color = c.clone(); + switch ( control ) { + case 'horiz': + color[controlOpts.vert](100); + break; + case 'vert': + color[controlOpts.horiz](100); + break; + case 'strip': + color.setHSpace('hsl'); + break; + } + stops = color.toHsl(); + } else { + if ( control === 'strip' ) { + stops = { s: color.s, l: color.l }; + } else { + stops = { s: 100, l: color.l }; + } + } + + target.raninbowGradient( origin, stops ); + break; + case 's': + if ( mode === 'hsv' ) { + if ( control === 'vert' ) { + stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ]; + } else if ( control === 'strip' ) { + stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ]; + } else if ( control === 'horiz' ) { + stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ]; + } + } else { // implicit mode === 'hsl' + if ( control === 'vert' && self.options.controls.horiz === 'h' ) { + stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)']; + } else { + stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)']; + } + } + + + target.gradient( origin, stops ); + break; + case 'l': + if ( control === 'strip' ) { + stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)']; + } else { + stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)']; + } + target.gradient( origin, stops ); + break; + case 'v': + if ( control === 'strip' ) { + stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ]; + } else { + stops = ['rgba(0,0,0,0)', '#000']; + } + target.gradient( origin, stops ); + break; + default: + break; + } + }, + + _getHSpaceColor: function() { + return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl(); + }, + + _dimensions: function( reset ) { + // whatever size + var self = this, + opts = self.options, + controls = self.controls, + square = controls.square, + strip = self.picker.find( '.iris-strip' ), + squareWidth = '77.5%', + stripWidth = '12%', + totalPadding = 20, + innerWidth = opts.border ? opts.width - totalPadding : opts.width, + controlsHeight, + paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length, + paletteMargin, paletteWidth, paletteContainerWidth; + + if ( reset ) { + square.css( 'width', '' ); + strip.css( 'width', '' ); + self.picker.css( {width: '', height: ''} ); + } + + squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 ); + stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 ); + controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth; + + square.width( squareWidth ).height( squareWidth ); + strip.height( squareWidth ).width( stripWidth ); + self.picker.css( { width: opts.width, height: controlsHeight } ); + + if ( ! opts.palettes ) { + return self.picker.css( 'paddingBottom', '' ); + } + + // single margin at 2% + paletteMargin = squareWidth * 2 / 100; + paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin ); + paletteWidth = paletteContainerWidth / paletteCount; + self.picker.find('.iris-palette').each( function( i ) { + var margin = i === 0 ? 0 : paletteMargin; + $( this ).css({ + width: paletteWidth, + height: paletteWidth, + marginLeft: margin + }); + }); + self.picker.css( 'paddingBottom', paletteWidth + paletteMargin ); + strip.height( paletteWidth + paletteMargin + squareWidth ); + }, + + _addInputListeners: function( input ) { + var self = this, + debounceTimeout = 100, + callback = function( event ){ + var color = new Color( input.val() ), + val = input.val().replace( /^#/, '' ); + + input.removeClass( 'iris-error' ); + // we gave a bad color + if ( color.error ) { + // don't error on an empty input - we want those allowed + if ( val !== '' ) { + input.addClass( 'iris-error' ); + } + } else { + if ( color.toString() !== self._color.toString() ) { + // let's not do this on keyup for hex shortcodes + if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { + self._setOption( 'color', color.toString() ); + } + } + } + }; + + input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); + + // If we initialized hidden, show on first focus. The rest is up to you. + if ( self.options.hide ) { + input.one( 'focus', function() { + self.show(); + }); + } + }, + + _initControls: function() { + var self = this, + controls = self.controls, + square = controls.square, + controlOpts = self.options.controls, + stripScale = self._scale[controlOpts.strip]; + + controls.stripSlider.slider({ + orientation: 'vertical', + max: stripScale, + slide: function( event, ui ) { + self.active = 'strip'; + // "reverse" for hue. + if ( controlOpts.strip === 'h' ) { + ui.value = stripScale - ui.value; + } + + self._color[controlOpts.strip]( ui.value ); + self._change.apply( self, arguments ); + } + }); + + controls.squareDrag.draggable({ + containment: controls.square.find( '.iris-square-inner' ), + zIndex: 1000, + cursor: 'move', + drag: function( event, ui ) { + self._squareDrag( event, ui ); + }, + start: function() { + square.addClass( 'iris-dragging' ); + $(this).addClass( 'ui-state-focus' ); + }, + stop: function() { + square.removeClass( 'iris-dragging' ); + $(this).removeClass( 'ui-state-focus' ); + } + }).on( 'mousedown mouseup', function( event ) { + var focusClass = 'ui-state-focus'; + event.preventDefault(); + if (event.type === 'mousedown' ) { + self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur(); + $(this).addClass( focusClass ).focus(); + } else { + $(this).removeClass( focusClass ); + } + }).on( 'keydown', function( event ) { + var container = controls.square, + draggable = controls.squareDrag, + position = draggable.position(), + distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop" + + // make alt key go "10" + if ( event.altKey ) { + distance *= 10; + } + + // Reposition if one of the directional keys is pressed + switch ( event.keyCode ) { + case 37: position.left -= distance; break; // Left + case 38: position.top -= distance; break; // Up + case 39: position.left += distance; break; // Right + case 40: position.top += distance; break; // Down + default: return true; // Exit and bubble + } + + // Keep draggable within container + position.left = Math.max( 0, Math.min( position.left, container.width() ) ); + position.top = Math.max( 0, Math.min( position.top, container.height() ) ); + + draggable.css(position); + self._squareDrag( event, { position: position }); + event.preventDefault(); + }); + + // allow clicking on the square to move there and keep dragging + square.mousedown( function( event ) { + var squareOffset, pos; + // only left click + if ( event.which !== 1 ) { + return; + } + + // prevent bubbling from the handle: no infinite loops + if ( ! $( event.target ).is( 'div' ) ) { + return; + } + + squareOffset = self.controls.square.offset(); + pos = { + top: event.pageY - squareOffset.top, + left: event.pageX - squareOffset.left + }; + event.preventDefault(); + self._squareDrag( event, { position: pos } ); + event.target = self.controls.squareDrag.get(0); + self.controls.squareDrag.css( pos ).trigger( event ); + }); + + // palettes + if ( self.options.palettes ) { + self._paletteListeners(); + } + }, + + _paletteListeners: function() { + var self = this; + self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() { + self._color.fromCSS( $(this).data('color') ); + self.active = 'external'; + self._change(); + }).on( 'keydown.palette', '.iris-palette', function( event ) { + if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) { + return true; + } + event.stopPropagation(); + $( this ).click(); + }); + }, + + _squareDrag: function( event, ui ) { + var self = this, + controlOpts = self.options.controls, + dimensions = self._squareDimensions(), + vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ), + horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] ); + + self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal ); + + self.active = 'square'; + self._change.apply( self, arguments ); + }, + + _setOption: function( key, value ) { + var self = this, + oldValue = self.options[key], + doDimensions = false, + hexLessColor, + newColor, + method; + + // ensure the new value is set. We can reset to oldValue if some check wasn't met. + self.options[key] = value; + + switch(key) { + case 'color': + // cast to string in case we have a number + value = '' + value; + hexLessColor = value.replace( /^#/, '' ); + newColor = new Color( value ).setHSpace( self.options.mode ); + if ( newColor.error ) { + self.options[key] = oldValue; + } else { + self._color = newColor; + self.options.color = self.options[key] = self._color.toString(); + self.active = 'external'; + self._change(); + } + break; + case 'palettes': + doDimensions = true; + + if ( value ) { + self._addPalettes(); + } else { + self.picker.find('.iris-palette-container').remove(); + } + + // do we need to add events? + if ( ! oldValue ) { + self._paletteListeners(); + } + break; + case 'width': + doDimensions = true; + break; + case 'border': + doDimensions = true; + method = value ? 'addClass' : 'removeClass'; + self.picker[method]('iris-border'); + break; + case 'mode': + case 'controls': + // if nothing's changed, let's bail, since this causes re-rendering the whole widget + if ( oldValue === value ) { + return; + } + + // we're using these poorly named variables because they're already scoped. + // method is the element that Iris was called on. oldValue will be the options + method = self.element; + oldValue = self.options; + oldValue.hide = ! self.picker.is( ':visible' ); + self.destroy(); + self.picker.remove(); + return $(self.element).iris(oldValue); + } + + // Do we need to recalc dimensions? + if ( doDimensions ) { + self._dimensions(true); + } + }, + + _squareDimensions: function( forceRefresh ) { + var square = this.controls.square, + dimensions, + control; + + if ( forceRefresh !== undef && square.data('dimensions') ) { + return square.data('dimensions'); + } + + control = this.controls.squareDrag; + dimensions = { + w: square.width(), + h: square.height() + }; + square.data( 'dimensions', dimensions ); + return dimensions; + }, + + _isNonHueControl: function( active, type ) { + if ( active === 'square' && this.options.controls.strip === 'h' ) { + return true; + } else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) { + return false; + } + + return true; + }, + + _change: function() { + var self = this, + controls = self.controls, + color = self._getHSpaceColor(), + actions = [ 'square', 'strip' ], + controlOpts = self.options.controls, + type = controlOpts[self.active] || 'external', + oldHue = self.hue; + + if ( self.active === 'strip' ) { + // take no action on any of the square sliders if we adjusted the strip + actions = []; + } else if ( self.active !== 'external' ) { + // for non-strip, non-external, strip should never change + actions.pop(); // conveniently the last item + } + + $.each( actions, function(index, item) { + var value, dimensions, cssObj; + if ( item !== self.active ) { + switch ( item ) { + case 'strip': + // reverse for hue + value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; + controls.stripSlider.slider( 'value', value ); + break; + case 'square': + dimensions = self._squareDimensions(); + cssObj = { + left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, + top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) + }; + + self.controls.squareDrag.css( cssObj ); + break; + } + } + }); + + // Ensure that we don't change hue if we triggered a hue reset + if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { + self._color.h(oldHue); + } + + // store hue for repeating above check next time + self.hue = self._color.h(); + + self.options.color = self._color.toString(); + + // only run after the first time + if ( self._inited ) { + self._trigger( 'change', { type: self.active }, { color: self._color } ); + } + + if ( self.element.is( ':input' ) && ! self._color.error ) { + self.element.removeClass( 'iris-error' ); + if ( self.element.val() !== self._color.toString() ) { + self.element.val( self._color.toString() ); + } + } + + self._paint(); + self._inited = true; + self.active = false; + }, + // taken from underscore.js _.debounce method + _debounce: function( func, wait, immediate ) { + var timeout, result; + return function() { + var context = this, + args = arguments, + later, + callNow; + + later = function() { + timeout = null; + if ( ! immediate) { + result = func.apply( context, args ); + } + }; + + callNow = immediate && !timeout; + clearTimeout( timeout ); + timeout = setTimeout( later, wait ); + if ( callNow ) { + result = func.apply( context, args ); + } + return result; + }; + }, + show: function() { + this.picker.show(); + }, + hide: function() { + this.picker.hide(); + }, + toggle: function() { + this.picker.toggle(); + }, + color: function(newColor) { + if ( newColor === true ) { + return this._color.clone(); + } else if ( newColor === undef ) { + return this._color.toString(); + } + this.option('color', newColor); + } + }; + // initialize the widget + $.widget( 'a8c.iris', Iris ); + // add CSS + $( '' ).appendTo( 'head' ); + +}( jQuery )); +/*! Color.js - v0.9.11 - 2013-08-09 +* https://github.com/Automattic/Color.js +* Copyright (c) 2013 Matt Wiebe; Licensed GPLv2 */ +(function(global, undef) { + + var Color = function( color, type ) { + if ( ! ( this instanceof Color ) ) + return new Color( color, type ); + + return this._init( color, type ); + }; + + Color.fn = Color.prototype = { + _color: 0, + _alpha: 1, + error: false, + // for preserving hue/sat in fromHsl().toHsl() flows + _hsl: { h: 0, s: 0, l: 0 }, + // for preserving hue/sat in fromHsv().toHsv() flows + _hsv: { h: 0, s: 0, v: 0 }, + // for setting hsl or hsv space - needed for .h() & .s() functions to function properly + _hSpace: 'hsl', + _init: function( color ) { + var func = 'noop'; + switch ( typeof color ) { + case 'object': + // alpha? + if ( color.a !== undef ) + this.a( color.a ); + func = ( color.r !== undef ) ? 'fromRgb' : + ( color.l !== undef ) ? 'fromHsl' : + ( color.v !== undef ) ? 'fromHsv' : func; + return this[func]( color ); + case 'string': + return this.fromCSS( color ); + case 'number': + return this.fromInt( parseInt( color, 10 ) ); + } + return this; + }, + + _error: function() { + this.error = true; + return this; + }, + + clone: function() { + var newColor = new Color( this.toInt() ), + copy = ['_alpha', '_hSpace', '_hsl', '_hsv', 'error']; + for ( var i = copy.length - 1; i >= 0; i-- ) { + newColor[ copy[i] ] = this[ copy[i] ]; + } + return newColor; + }, + + setHSpace: function( space ) { + this._hSpace = ( space === 'hsv' ) ? space : 'hsl'; + return this; + }, + + noop: function() { + return this; + }, + + fromCSS: function( color ) { + var list, + leadingRE = /^(rgb|hs(l|v))a?\(/; + this.error = false; + + // whitespace and semicolon trim + color = color.replace(/^\s+/, '').replace(/\s+$/, '').replace(/;$/, ''); + + if ( color.match(leadingRE) && color.match(/\)$/) ) { + list = color.replace(/(\s|%)/g, '').replace(leadingRE, '').replace(/,?\);?$/, '').split(','); + + if ( list.length < 3 ) + return this._error(); + + if ( list.length === 4 ) { + this.a( parseFloat( list.pop() ) ); + // error state has been set to true in .a() if we passed NaN + if ( this.error ) + return this; + } + + for (var i = list.length - 1; i >= 0; i--) { + list[i] = parseInt(list[i], 10); + if ( isNaN( list[i] ) ) + return this._error(); + } + + if ( color.match(/^rgb/) ) { + return this.fromRgb( { + r: list[0], + g: list[1], + b: list[2] + } ); + } else if ( color.match(/^hsv/) ) { + return this.fromHsv( { + h: list[0], + s: list[1], + v: list[2] + } ); + } else { + return this.fromHsl( { + h: list[0], + s: list[1], + l: list[2] + } ); + } + } else { + // must be hex amirite? + return this.fromHex( color ); + } + }, + + fromRgb: function( rgb, preserve ) { + if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef ) + return this._error(); + + this.error = false; + return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve ); + }, + + fromHex: function( color ) { + color = color.replace(/^#/, '').replace(/^0x/, ''); + if ( color.length === 3 ) { + color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; + } + + // rough error checking - this is where things go squirrely the most + this.error = ! /^[0-9A-F]{6}$/i.test( color ); + return this.fromInt( parseInt( color, 16 ) ); + }, + + fromHsl: function( hsl ) { + var r, g, b, q, p, h, s, l; + + if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef ) + return this._error(); + + this._hsl = hsl; // store it + this._hSpace = 'hsl'; // implicit + h = hsl.h / 360; s = hsl.s / 100; l = hsl.l / 100; + if ( s === 0 ) { + r = g = b = l; // achromatic + } + else { + q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; + p = 2 * l - q; + r = this.hue2rgb( p, q, h + 1/3 ); + g = this.hue2rgb( p, q, h ); + b = this.hue2rgb( p, q, h - 1/3 ); + } + return this.fromRgb( { + r: r * 255, + g: g * 255, + b: b * 255 + }, true ); // true preserves hue/sat + }, + + fromHsv: function( hsv ) { + var h, s, v, r, g, b, i, f, p, q, t; + if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef ) + return this._error(); + + this._hsv = hsv; // store it + this._hSpace = 'hsv'; // implicit + + h = hsv.h / 360; s = hsv.s / 100; v = hsv.v / 100; + i = Math.floor( h * 6 ); + f = h * 6 - i; + p = v * ( 1 - s ); + q = v * ( 1 - f * s ); + t = v * ( 1 - ( 1 - f ) * s ); + + switch( i % 6 ) { + case 0: + r = v; g = t; b = p; + break; + case 1: + r = q; g = v; b = p; + break; + case 2: + r = p; g = v; b = t; + break; + case 3: + r = p; g = q; b = v; + break; + case 4: + r = t; g = p; b = v; + break; + case 5: + r = v; g = p; b = q; + break; + } + + return this.fromRgb( { + r: r * 255, + g: g * 255, + b: b * 255 + }, true ); // true preserves hue/sat + + }, + // everything comes down to fromInt + fromInt: function( color, preserve ) { + this._color = parseInt( color, 10 ); + + if ( isNaN( this._color ) ) + this._color = 0; + + // let's coerce things + if ( this._color > 16777215 ) + this._color = 16777215; + else if ( this._color < 0 ) + this._color = 0; + + // let's not do weird things + if ( preserve === undef ) { + this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0; + } + // EVENT GOES HERE + return this; + }, + + hue2rgb: function( p, q, t ) { + if ( t < 0 ) { + t += 1; + } + if ( t > 1 ) { + t -= 1; + } + if ( t < 1/6 ) { + return p + ( q - p ) * 6 * t; + } + if ( t < 1/2 ) { + return q; + } + if ( t < 2/3 ) { + return p + ( q - p ) * ( 2/3 - t ) * 6; + } + return p; + }, + + toString: function() { + var hex = parseInt( this._color, 10 ).toString( 16 ); + if ( this.error ) + return ''; + // maybe left pad it + if ( hex.length < 6 ) { + for (var i = 6 - hex.length - 1; i >= 0; i--) { + hex = '0' + hex; + } + } + return '#' + hex; + }, + + toCSS: function( type, alpha ) { + type = type || 'hex'; + alpha = parseFloat( alpha || this._alpha ); + switch ( type ) { + case 'rgb': + case 'rgba': + var rgb = this.toRgb(); + if ( alpha < 1 ) { + return "rgba( " + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + alpha + " )"; + } + else { + return "rgb( " + rgb.r + ", " + rgb.g + ", " + rgb.b + " )"; + } + break; + case 'hsl': + case 'hsla': + var hsl = this.toHsl(); + if ( alpha < 1 ) { + return "hsla( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%, " + alpha + " )"; + } + else { + return "hsl( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "% )"; + } + break; + default: + return this.toString(); + } + }, + + toRgb: function() { + return { + r: 255 & ( this._color >> 16 ), + g: 255 & ( this._color >> 8 ), + b: 255 & ( this._color ) + }; + }, + + toHsl: function() { + var rgb = this.toRgb(); + var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; + var max = Math.max( r, g, b ), min = Math.min( r, g, b ); + var h, s, l = ( max + min ) / 2; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); + switch ( max ) { + case r: h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: h = ( b - r ) / d + 2; + break; + case b: h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSL space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsl.h !== h ) { + h = this._hsl.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsl.s ) { + s = this._hsl.s; + } + + return { + h: h, + s: s, + l: Math.round( l * 100 ) + }; + + }, + + toHsv: function() { + var rgb = this.toRgb(); + var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; + var max = Math.max( r, g, b ), min = Math.min( r, g, b ); + var h, s, v = max; + var d = max - min; + s = max === 0 ? 0 : d / max; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + switch( max ){ + case r: + h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: + h = ( b - r ) / d + 2; + break; + case b: + h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSV space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsv.h !== h ) { + h = this._hsv.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsv.s ) { + s = this._hsv.s; + } + + return { + h: h, + s: s, + v: Math.round( v * 100 ) + }; + }, + + toInt: function() { + return this._color; + }, + + toIEOctoHex: function() { + // AARRBBGG + var hex = this.toString(); + var AA = parseInt( 255 * this._alpha, 10 ).toString(16); + if ( AA.length === 1 ) { + AA = '0' + AA; + } + return '#' + AA + hex.replace(/^#/, '' ); + }, + + toLuminosity: function() { + var rgb = this.toRgb(); + return 0.2126 * Math.pow( rgb.r / 255, 2.2 ) + 0.7152 * Math.pow( rgb.g / 255, 2.2 ) + 0.0722 * Math.pow( rgb.b / 255, 2.2); + }, + + getDistanceLuminosityFrom: function( color ) { + if ( ! ( color instanceof Color ) ) { + throw 'getDistanceLuminosityFrom requires a Color object'; + } + var lum1 = this.toLuminosity(); + var lum2 = color.toLuminosity(); + if ( lum1 > lum2 ) { + return ( lum1 + 0.05 ) / ( lum2 + 0.05 ); + } + else { + return ( lum2 + 0.05 ) / ( lum1 + 0.05 ); + } + }, + + getMaxContrastColor: function() { + var lum = this.toLuminosity(); + var hex = ( lum >= 0.5 ) ? '000000' : 'ffffff'; + return new Color( hex ); + }, + + getReadableContrastingColor: function( bgColor, minContrast ) { + if ( ! bgColor instanceof Color ) { + return this; + } + + // you shouldn't use less than 5, but you might want to. + var targetContrast = ( minContrast === undef ) ? 5 : minContrast; + // working things + var contrast = bgColor.getDistanceLuminosityFrom( this ); + var maxContrastColor = bgColor.getMaxContrastColor(); + var maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor ); + + // if current max contrast is less than the target contrast, we had wishful thinking. + // still, go max + if ( maxContrast <= targetContrast ) { + return maxContrastColor; + } + // or, we might already have sufficient contrast + else if ( contrast >= targetContrast ) { + return this; + } + + var incr = ( 0 === maxContrastColor.toInt() ) ? -1 : 1; + while ( contrast < targetContrast ) { + this.l( incr, true ); // 2nd arg turns this into an incrementer + contrast = this.getDistanceLuminosityFrom( bgColor ); + // infininite loop prevention: you never know. + if ( this._color === 0 || this._color === 16777215 ) { + break; + } + } + + return this; + + }, + + a: function( val ) { + if ( val === undef ) + return this._alpha; + + var a = parseFloat( val ); + + if ( isNaN( a ) ) + return this._error(); + + this._alpha = a; + return this; + }, + + // TRANSFORMS + + darken: function( amount ) { + amount = amount || 5; + return this.l( - amount, true ); + }, + + lighten: function( amount ) { + amount = amount || 5; + return this.l( amount, true ); + }, + + saturate: function( amount ) { + amount = amount || 15; + return this.s( amount, true ); + }, + + desaturate: function( amount ) { + amount = amount || 15; + return this.s( - amount, true ); + }, + + toGrayscale: function() { + return this.setHSpace('hsl').s( 0 ); + }, + + getComplement: function() { + return this.h( 180, true ); + }, + + getSplitComplement: function( step ) { + step = step || 1; + var incr = 180 + ( step * 30 ); + return this.h( incr, true ); + }, + + getAnalog: function( step ) { + step = step || 1; + var incr = step * 30; + return this.h( incr, true ); + }, + + getTetrad: function( step ) { + step = step || 1; + var incr = step * 60; + return this.h( incr, true ); + }, + + getTriad: function( step ) { + step = step || 1; + var incr = step * 120; + return this.h( incr, true ); + }, + + _partial: function( key ) { + var prop = shortProps[key]; + return function( val, incr ) { + var color = this._spaceFunc('to', prop.space); + + // GETTER + if ( val === undef ) + return color[key]; + + // INCREMENT + if ( incr === true ) + val = color[key] + val; + + // MOD & RANGE + if ( prop.mod ) + val = val % prop.mod; + if ( prop.range ) + val = ( val < prop.range[0] ) ? prop.range[0] : ( val > prop.range[1] ) ? prop.range[1] : val; + + // NEW VALUE + color[key] = val; + + return this._spaceFunc('from', prop.space, color); + }; + }, + + _spaceFunc: function( dir, s, val ) { + var space = s || this._hSpace, + funcName = dir + space.charAt(0).toUpperCase() + space.substr(1); + return this[funcName](val); + } + }; + + var shortProps = { + h: { + mod: 360 + }, + s: { + range: [0,100] + }, + l: { + space: 'hsl', + range: [0,100] + }, + v: { + space: 'hsv', + range: [0,100] + }, + r: { + space: 'rgb', + range: [0,255] + }, + g: { + space: 'rgb', + range: [0,255] + }, + b: { + space: 'rgb', + range: [0,255] + } + }; + + for ( var key in shortProps ) { + if ( shortProps.hasOwnProperty( key ) ) + Color.fn[key] = Color.fn._partial(key); + } + + // play nicely with Node + browser + if ( typeof exports === 'object' ) + module.exports = Color; + else + global.Color = Color; + +}(this)); diff --git a/lib/plugins/styling/lang/en/intro.txt b/lib/plugins/styling/lang/en/intro.txt new file mode 100644 index 000000000..4ea55172f --- /dev/null +++ b/lib/plugins/styling/lang/en/intro.txt @@ -0,0 +1,2 @@ +This tool allows you to change certain style settings of your currently selected template. +All changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php new file mode 100644 index 000000000..a68464f15 --- /dev/null +++ b/lib/plugins/styling/lang/en/lang.php @@ -0,0 +1,32 @@ + + */ + +// menu entry for admin plugins +$lang['js']['menu'] = 'Template Style Settings'; + +// custom language strings for the plugin +$lang['error'] = 'Sorry, this template does not support this functionality.'; + +$lang['btn_preview'] = 'Preview your changes'; +$lang['btn_save'] = 'Save your changes'; +$lang['btn_reset'] = 'Reset your current changes'; +$lang['btn_revert'] = 'Revert all styles back to the template\'s default'; + +// default guaranteed placeholders +$lang['__text__'] = 'Main text color'; +$lang['__background__'] = 'Main text background color'; +$lang['__text_alt__'] = 'Alternative text color'; +$lang['__background_alt__'] = 'Alternative text background color'; +$lang['__text_neu__'] = 'Neutral text color'; +$lang['__background_neu__'] = 'Neutral text background color'; +$lang['__border__'] = 'Border color'; +$lang['__highlight__'] = 'Highlight color (for search results mainly)'; + + + + +//Setup VIM: ex: et ts=4 : diff --git a/lib/plugins/styling/plugin.info.txt b/lib/plugins/styling/plugin.info.txt new file mode 100644 index 000000000..cdf01ee6f --- /dev/null +++ b/lib/plugins/styling/plugin.info.txt @@ -0,0 +1,7 @@ +base styling +author Andreas Gohr +email andi@splitbrain.org +date 2015-05-16 +name styling plugin +desc Allows to edit style.ini replacements +url https://www.dokuwiki.org/plugin:styling diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js new file mode 100644 index 000000000..76cd1a847 --- /dev/null +++ b/lib/plugins/styling/script.js @@ -0,0 +1,92 @@ +/* DOKUWIKI:include_once iris.js */ + +jQuery(function () { + // user openend the admin page, set cookie and redirect + if (jQuery('#plugin__styling').length) { + DokuCookie.setValue('styling_plugin', 1); + document.location.href = document.location.href.replace(/do=admin/, ''); + } + + // continue only if the styling Dialog is currently enabled + if (DokuCookie.getValue('styling_plugin') != 1) return; + + var styling_timeout = null; + + // create dialog element + var $dialog = jQuery(document.createElement('div')); + jQuery('body').append($dialog); + + + /** + * updates the current CSS with a new preview one + */ + function styling_updateCSS() { + var now = new Date().getTime(); + var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + } + + /** + * save current values and reload preview (debounced) + */ + function styling_saveAndUpdate() { + if (styling_timeout) window.clearTimeout(styling_timeout); + styling_timeout = window.setTimeout(function () { + styling_timeout = null; + + var params = $dialog.find('input[type=text]').serializeArray(); + params[params.length] = { name: 'call', value: 'plugin_styling'}; + params[params.length] = {name: 'run', value: 'preview'}; + + jQuery.post( + DOKU_BASE + '/lib/exe/ajax.php', + params, + styling_updateCSS + ); + }, 500); + } + + // load the dialog content and apply listeners + $dialog.load( + DOKU_BASE + '/lib/exe/ajax.php', + { + 'call': 'plugin_styling', + 'run': 'html', + 'id': JSINFO.id + }, + function () { + // load the preview template + styling_updateCSS(); + + // open the dialog + $dialog.dialog({ + 'title': LANG.plugins.styling.menu, + 'width': 500, + 'height': 500, + 'top': 50, + 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, + // bring everything back to normal + 'close': function (event, ui) { + // disable the styling plugin again + DokuCookie.setValue('styling_plugin', 0); + // reload + document.location.reload() + } + }); + + // we don't need the manual preview in JS mode + $dialog.find('.btn_preview').hide(); + + // add the color picker FIXME add saveAndUpdate to correct event + $dialog.find('.color').iris({ }); + + // listen to keyup events + $dialog.find('input[type=text]').keyup(function () { + console.log('change'); + styling_saveAndUpdate(); + }); + + } + ); + +}); -- cgit v1.2.3 From 4f2e8bf8642a993a0a5edf32a3172eaf77645a25 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 23 May 2015 15:31:16 +0200 Subject: fixed ajax endpoint for styling plugin --- lib/plugins/styling/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 76cd1a847..f2b4b9ebb 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -39,7 +39,7 @@ jQuery(function () { params[params.length] = {name: 'run', value: 'preview'}; jQuery.post( - DOKU_BASE + '/lib/exe/ajax.php', + DOKU_BASE + 'lib/exe/ajax.php', params, styling_updateCSS ); @@ -48,7 +48,7 @@ jQuery(function () { // load the dialog content and apply listeners $dialog.load( - DOKU_BASE + '/lib/exe/ajax.php', + DOKU_BASE + 'lib/exe/ajax.php', { 'call': 'plugin_styling', 'run': 'html', -- cgit v1.2.3 From 49f111354ae7007893e877ceaeaed0dd3367e8f2 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 23 May 2015 15:32:35 +0200 Subject: avoid double ampersand in url --- lib/plugins/styling/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index f2b4b9ebb..caedc7a1c 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -4,7 +4,7 @@ jQuery(function () { // user openend the admin page, set cookie and redirect if (jQuery('#plugin__styling').length) { DokuCookie.setValue('styling_plugin', 1); - document.location.href = document.location.href.replace(/do=admin/, ''); + document.location.href = document.location.href.replace(/&?do=admin/, ''); } // continue only if the styling Dialog is currently enabled -- cgit v1.2.3 From e4632ba4cca90438b3d47594f217a9ed18b980be Mon Sep 17 00:00:00 2001 From: Scrutinizer Auto-Fixer Date: Sat, 23 May 2015 15:37:05 +0000 Subject: Scrutinizer Auto-Fixes This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com --- lib/plugins/styling/admin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index 85d87dd71..ab8e5a5f4 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -57,6 +57,7 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { /** * Create the actual editing form + * @param boolean $isajax */ public function form($isajax) { global $conf; -- cgit v1.2.3 From 23a5593c5cae9f85a8821c0207d9e8ad2b6f3b15 Mon Sep 17 00:00:00 2001 From: Anika Henke Date: Mon, 25 May 2015 00:54:20 +0100 Subject: improved copy for styler plugin --- lib/plugins/styling/lang/en/lang.php | 6 +++--- lib/tpl/dokuwiki/lang/en/lang.php | 4 ++-- lib/tpl/dokuwiki/lang/en/style.txt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index a68464f15..e50c6b912 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -18,11 +18,11 @@ $lang['btn_revert'] = 'Revert all styles back to the template\'s default'; // default guaranteed placeholders $lang['__text__'] = 'Main text color'; -$lang['__background__'] = 'Main text background color'; +$lang['__background__'] = 'Main background color'; $lang['__text_alt__'] = 'Alternative text color'; -$lang['__background_alt__'] = 'Alternative text background color'; +$lang['__background_alt__'] = 'Alternative background color'; $lang['__text_neu__'] = 'Neutral text color'; -$lang['__background_neu__'] = 'Neutral text background color'; +$lang['__background_neu__'] = 'Neutral background color'; $lang['__border__'] = 'Border color'; $lang['__highlight__'] = 'Highlight color (for search results mainly)'; diff --git a/lib/tpl/dokuwiki/lang/en/lang.php b/lib/tpl/dokuwiki/lang/en/lang.php index 82a720e21..b7b3e7fa1 100644 --- a/lib/tpl/dokuwiki/lang/en/lang.php +++ b/lib/tpl/dokuwiki/lang/en/lang.php @@ -6,7 +6,7 @@ $lang['__background_site__'] = 'Color for the very background (behind the conten $lang['__link__'] = 'The general link color'; $lang['__existing__'] = 'The color for links to existing pages'; $lang['__missing__'] = 'The color for links to non-existing pages'; -$lang['__site_width__'] = 'The width of the full site (can also be a percentage)'; -$lang['__sidebar_width__'] = 'The width of the sitebar if any (can also be a percentage)'; +$lang['__site_width__'] = 'The width of the full site (can be any length unit: %, px, em, ...)'; +$lang['__sidebar_width__'] = 'The width of the sidebar, if any (can be any length unit: %, px, em, ...)'; $lang['__tablet_width__'] = 'Below screensizes of this width, the site switches to tablet mode'; $lang['__phone_width__'] = 'Below screensizes of this width, the site switches to phone mode'; diff --git a/lib/tpl/dokuwiki/lang/en/style.txt b/lib/tpl/dokuwiki/lang/en/style.txt index f6bb4fc6f..7bf3e1a82 100644 --- a/lib/tpl/dokuwiki/lang/en/style.txt +++ b/lib/tpl/dokuwiki/lang/en/style.txt @@ -1,4 +1,4 @@ -If you want to adjust the logo, simply use the Media Manager to upload a ''logo.png'' in the ''wiki'' namespace and it -will be automaticaly be used. You can also upload a ''favicon.ico'' there. If you use a closed -wiki it is recommended to make the ''wiki'' namespace world readable in the ACL settings or -your logo is not shown to not logged in users. \ No newline at end of file +If you want to adjust the logo, simply use the Media Manager to upload a ''logo.png'' into the ''wiki'' or the root namespace and it +will be automatically used. You can also upload a ''favicon.ico'' there. If you use a closed +wiki it is recommended to make the ''wiki'' (or root) namespace world readable in the ACL settings or +your logo is not shown to not logged in users. -- cgit v1.2.3 From 8b3eb08dfe10f165913eb8823bc517a3c2ca1d59 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 25 May 2015 09:57:02 +0200 Subject: adjusted gitignore for adjusted plugin name --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cf1a37300..dd5c9cf18 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,7 @@ !/lib/plugins/popularity !/lib/plugins/revert !/lib/plugins/safefnrecode -!/lib/plugins/styler +!/lib/plugins/styling !/lib/plugins/testing !/lib/plugins/usermanager !/lib/plugins/action.php -- cgit v1.2.3 From 418cb6171fc2d6d04a505e28c123b22c384db8c5 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 25 May 2015 09:58:54 +0200 Subject: removed unused class --- lib/plugins/styling/admin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index ab8e5a5f4..3c2ab577c 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -93,16 +93,16 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { } echo ''; - echo '

'; + echo '

'; echo ''; echo ''; #FIXME only if preview.ini exists echo '

'; - echo '

'; + echo '

'; echo ''; echo '

'; - echo '

'; + echo '

'; echo ''; #FIXME only if local.ini exists echo '

'; -- cgit v1.2.3 From f969573b0f1e999e5252c3671da9a8b6ac0bbd38 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 25 May 2015 10:00:10 +0200 Subject: removed debuggin output --- lib/plugins/styling/script.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index caedc7a1c..7c8ffae91 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -82,7 +82,6 @@ jQuery(function () { // listen to keyup events $dialog.find('input[type=text]').keyup(function () { - console.log('change'); styling_saveAndUpdate(); }); -- cgit v1.2.3 From 5114259bf530b89afbb21bd693b1da3f96016b7f Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 25 May 2015 10:00:47 +0200 Subject: removed accidental checkin --- lib/tpl/dokuwiki/pagefooter.html | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 lib/tpl/dokuwiki/pagefooter.html diff --git a/lib/tpl/dokuwiki/pagefooter.html b/lib/tpl/dokuwiki/pagefooter.html deleted file mode 100644 index a361c4d69..000000000 --- a/lib/tpl/dokuwiki/pagefooter.html +++ /dev/null @@ -1,4 +0,0 @@ -tpl(); -- cgit v1.2.3 From 86c97e91e4aa9ed17ed75df279181e4b61353c7c Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 26 Jun 2015 12:22:43 +0200 Subject: fixes various probelms mentioned in #1163 however I think I will convert the dialog to a real popup instead. --- lib/plugins/styling/lang/en/lang.php | 1 + lib/plugins/styling/script.js | 79 ++++++++++++++---------------------- lib/plugins/styling/style.less | 8 ++++ 3 files changed, 40 insertions(+), 48 deletions(-) create mode 100644 lib/plugins/styling/style.less diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index e50c6b912..d100d4324 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -7,6 +7,7 @@ // menu entry for admin plugins $lang['js']['menu'] = 'Template Style Settings'; +$lang['js']['popup'] = 'Open as Popup'; // custom language strings for the plugin $lang['error'] = 'Sorry, this template does not support this functionality.'; diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 7c8ffae91..6dc4c84ed 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -1,10 +1,17 @@ /* DOKUWIKI:include_once iris.js */ jQuery(function () { - // user openend the admin page, set cookie and redirect - if (jQuery('#plugin__styling').length) { - DokuCookie.setValue('styling_plugin', 1); - document.location.href = document.location.href.replace(/&?do=admin/, ''); + // add popup option to admin page + var $styling_plugin = jQuery('#plugin__styling'); + if ($styling_plugin.length) { + var $hl = $styling_plugin.find('h1').first(); + var $btn = jQuery(''); + $hl.append($btn); + + $btn.click(function (e) { + DokuCookie.setValue('styling_plugin', 1); + document.location.href = document.location.href.replace(/&?do=admin/, ''); + }); } // continue only if the styling Dialog is currently enabled @@ -16,7 +23,6 @@ jQuery(function () { var $dialog = jQuery(document.createElement('div')); jQuery('body').append($dialog); - /** * updates the current CSS with a new preview one */ @@ -26,29 +32,28 @@ jQuery(function () { $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); } - /** - * save current values and reload preview (debounced) - */ - function styling_saveAndUpdate() { - if (styling_timeout) window.clearTimeout(styling_timeout); - styling_timeout = window.setTimeout(function () { - styling_timeout = null; + // prepare the dialog + $dialog.dialog({ + 'autoOpen': false, + 'title': LANG.plugins.styling.menu, + 'width': 500, + 'height': 500, + 'position': {'my': 'left bottom', 'at': 'left bottom-40', 'of': window}, + 'closeOnEscape': true, + + // bring everything back to normal on close + 'close': function (event, ui) { + // disable the styling plugin again + DokuCookie.setValue('styling_plugin', 0); + // reload + document.location.reload() + } + }); - var params = $dialog.find('input[type=text]').serializeArray(); - params[params.length] = { name: 'call', value: 'plugin_styling'}; - params[params.length] = {name: 'run', value: 'preview'}; - - jQuery.post( - DOKU_BASE + 'lib/exe/ajax.php', - params, - styling_updateCSS - ); - }, 500); - } // load the dialog content and apply listeners $dialog.load( - DOKU_BASE + 'lib/exe/ajax.php', + DOKU_BASE + 'lib/exe/ajax.php', { 'call': 'plugin_styling', 'run': 'html', @@ -59,32 +64,10 @@ jQuery(function () { styling_updateCSS(); // open the dialog - $dialog.dialog({ - 'title': LANG.plugins.styling.menu, - 'width': 500, - 'height': 500, - 'top': 50, - 'position': { 'my': 'left bottom', 'at': 'left bottom', 'of': window }, - // bring everything back to normal - 'close': function (event, ui) { - // disable the styling plugin again - DokuCookie.setValue('styling_plugin', 0); - // reload - document.location.reload() - } - }); - - // we don't need the manual preview in JS mode - $dialog.find('.btn_preview').hide(); + $dialog.dialog('open'); // add the color picker FIXME add saveAndUpdate to correct event - $dialog.find('.color').iris({ }); - - // listen to keyup events - $dialog.find('input[type=text]').keyup(function () { - styling_saveAndUpdate(); - }); - + $dialog.find('.color').iris({}); } ); diff --git a/lib/plugins/styling/style.less b/lib/plugins/styling/style.less new file mode 100644 index 000000000..8920cb183 --- /dev/null +++ b/lib/plugins/styling/style.less @@ -0,0 +1,8 @@ +#plugin__styling { + + h1 button { + font-size: 12px; + line-height: 16px; + margin-left: 1em; + } +} -- cgit v1.2.3 From 6667cd8743e57a4492cfbcbe1066ea48d444f7a2 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 26 Jun 2015 12:57:58 +0200 Subject: changed the whole thing to a real popup --- lib/plugins/styling/admin.php | 27 ++++++------- lib/plugins/styling/lang/en/lang.php | 4 +- lib/plugins/styling/popup.php | 28 ++++++++++++++ lib/plugins/styling/script.js | 73 +++++++----------------------------- 4 files changed, 56 insertions(+), 76 deletions(-) create mode 100644 lib/plugins/styling/popup.php diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index 3c2ab577c..627efbd15 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -11,6 +11,8 @@ if(!defined('DOKU_INC')) die(); class admin_plugin_styling extends DokuWiki_Admin_Plugin { + public $ispopup = false; + /** * @return int sort number in admin menu */ @@ -25,15 +27,6 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { return true; } - /** - * @param string $language - * @return string - */ - public function getMenuText($language) { - $js = $this->getLang('js'); - return $js['menu']; - } - /** * handle the different actions (also called from ajax) */ @@ -49,17 +42,19 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { * Render HTML output, e.g. helpful text and a form */ public function html() { - echo '
'; - ptln('

'.$this->getMenuText('').'

'); - $this->form(false); + $class = 'nopopup'; + if($this->ispopup) $class = 'ispopup'; + + echo '
'; + ptln('

'.$this->getLang('menu').'

'); + $this->form(); echo '
'; } /** * Create the actual editing form - * @param boolean $isajax */ - public function form($isajax) { + public function form() { global $conf; global $ID; define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php @@ -67,8 +62,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { $styleini = css_styleini($conf['template'], true); $replacements = $styleini['replacements']; - if($isajax) { - $target = wl($ID, array('do' => 'styling_plugin')); + if($this->ispopup) { + $target = DOKU_BASE.'lib/plugins/styling/popup.php'; } else { $target = wl($ID, array('do' => 'admin', 'page' => 'styling')); } diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index d100d4324..85ac0ec29 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -6,7 +6,9 @@ */ // menu entry for admin plugins -$lang['js']['menu'] = 'Template Style Settings'; +$lang['menu'] = 'Template Style Settings'; + + $lang['js']['popup'] = 'Open as Popup'; // custom language strings for the plugin diff --git a/lib/plugins/styling/popup.php b/lib/plugins/styling/popup.php new file mode 100644 index 000000000..eea4f3bfd --- /dev/null +++ b/lib/plugins/styling/popup.php @@ -0,0 +1,28 @@ +ispopup = true; + +// handle posts +$plugin->handle(); + +// output plugin in a very minimal template: +?> + + + <?php echo $plugin->getLang('menu') ?> + + + +
+ html() ?> +
+ + diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 6dc4c84ed..8b4563986 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -1,74 +1,29 @@ /* DOKUWIKI:include_once iris.js */ jQuery(function () { - // add popup option to admin page + var $styling_plugin = jQuery('#plugin__styling'); - if ($styling_plugin.length) { + if(!$styling_plugin.length) return; + + + if (!$styling_plugin.hasClass('ispopup')) { var $hl = $styling_plugin.find('h1').first(); var $btn = jQuery(''); $hl.append($btn); $btn.click(function (e) { - DokuCookie.setValue('styling_plugin', 1); - document.location.href = document.location.href.replace(/&?do=admin/, ''); + var windowFeatures = "menubar=no,location=no,resizable=yes,scrollbars=yes,status=false,width=500,height=500"; + window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling', windowFeatures) }); + return; } - // continue only if the styling Dialog is currently enabled - if (DokuCookie.getValue('styling_plugin') != 1) return; - - var styling_timeout = null; - - // create dialog element - var $dialog = jQuery(document.createElement('div')); - jQuery('body').append($dialog); - - /** - * updates the current CSS with a new preview one - */ - function styling_updateCSS() { - var now = new Date().getTime(); - var $style = jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); - } - - // prepare the dialog - $dialog.dialog({ - 'autoOpen': false, - 'title': LANG.plugins.styling.menu, - 'width': 500, - 'height': 500, - 'position': {'my': 'left bottom', 'at': 'left bottom-40', 'of': window}, - 'closeOnEscape': true, - - // bring everything back to normal on close - 'close': function (event, ui) { - // disable the styling plugin again - DokuCookie.setValue('styling_plugin', 0); - // reload - document.location.reload() - } - }); - - - // load the dialog content and apply listeners - $dialog.load( - DOKU_BASE + 'lib/exe/ajax.php', - { - 'call': 'plugin_styling', - 'run': 'html', - 'id': JSINFO.id - }, - function () { - // load the preview template - styling_updateCSS(); - - // open the dialog - $dialog.dialog('open'); + // add the color picker + $styling_plugin.find('.color').iris({}); - // add the color picker FIXME add saveAndUpdate to correct event - $dialog.find('.color').iris({}); - } - ); + // load preview in main window + var now = new Date().getTime(); + var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); }); -- cgit v1.2.3 From 3e2beab52631135f77d0ad5b6ed297458bfff2f2 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 26 Jun 2015 13:31:46 +0200 Subject: added a simple loading screen for the JavaScript preview --- lib/plugins/styling/lang/en/lang.php | 2 +- lib/plugins/styling/script.js | 25 ++++++++++++++++++++++++- lib/plugins/styling/style.less | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index 85ac0ec29..a076065cb 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -8,7 +8,7 @@ // menu entry for admin plugins $lang['menu'] = 'Template Style Settings'; - +$lang['js']['loader'] = 'Preview is loading...
if this does not goes away, your values may be faulty'; $lang['js']['popup'] = 'Open as Popup'; // custom language strings for the plugin diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 8b4563986..f732c0524 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -3,7 +3,7 @@ jQuery(function () { var $styling_plugin = jQuery('#plugin__styling'); - if(!$styling_plugin.length) return; + if (!$styling_plugin.length) return; if (!$styling_plugin.hasClass('ispopup')) { @@ -21,9 +21,32 @@ jQuery(function () { // add the color picker $styling_plugin.find('.color').iris({}); + // append the loader screen + $loader = window.opener.jQuery('#plugin__styling_loader'); + if (!$loader.length) { + $loader = jQuery('
' + LANG.plugins.styling.loader + '
'); + $loader.css({ + 'position': 'absolute', + 'width': '100%', + 'height': '100%', + 'top': 0, + 'left': 0, + 'z-index': 5000, + 'background-color': '#fff', + 'opacity': '0.7', + 'color': '#000', + 'font-size': '40px', + 'text-align': 'center', + 'line-height': '90px' + }); + window.opener.jQuery('body').append($loader); + } + // load preview in main window var now = new Date().getTime(); var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', ''); $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + }); diff --git a/lib/plugins/styling/style.less b/lib/plugins/styling/style.less index 8920cb183..3cb563fdb 100644 --- a/lib/plugins/styling/style.less +++ b/lib/plugins/styling/style.less @@ -6,3 +6,7 @@ margin-left: 1em; } } + +#plugin__styling_loader { + display: none; +} -- cgit v1.2.3 From bcea9d64ebf4b0cc2c80209b7669e3e7e954c4c8 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 26 Jun 2015 13:35:15 +0200 Subject: removed no longer needed ajax action components --- lib/plugins/styling/action.php | 48 ------------------------------------------ lib/plugins/styling/script.js | 8 ++----- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/lib/plugins/styling/action.php b/lib/plugins/styling/action.php index 622c634d6..896e14bef 100644 --- a/lib/plugins/styling/action.php +++ b/lib/plugins/styling/action.php @@ -26,8 +26,6 @@ class action_plugin_styling extends DokuWiki_Action_Plugin { * @return void */ public function register(Doku_Event_Handler $controller) { - $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); - $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action'); $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_header'); } @@ -57,52 +55,6 @@ class action_plugin_styling extends DokuWiki_Action_Plugin { } } - /** - * Updates the style.ini settings by passing it on to handle() of the admin component - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - public function handle_action(Doku_Event &$event, $param) { - if($event->data != 'styling_plugin') return; - if(!auth_isadmin()) return; - $event->data = 'show'; - - /** @var admin_plugin_styling $hlp */ - $hlp = plugin_load('admin', 'styling'); - $hlp->handle(); - } - - /** - * Create the style form in the floating Dialog - * - * @param Doku_Event $event event object by reference - * @param mixed $param [the parameters passed as fifth argument to register_hook() when this - * handler was registered] - * @return void - */ - - public function handle_ajax(Doku_Event &$event, $param) { - if($event->data != 'plugin_styling') return; - if(!auth_isadmin()) return; - $event->preventDefault(); - $event->stopPropagation(); - - global $ID; - global $INPUT; - $ID = getID(); - - /** @var admin_plugin_styling $hlp */ - $hlp = plugin_load('admin', 'styling'); - if($INPUT->str('run') == 'preview') { - $hlp->run_preview(); - } else { - $hlp->form(true); - } - } - } // vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index f732c0524..409148471 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -5,6 +5,8 @@ jQuery(function () { var $styling_plugin = jQuery('#plugin__styling'); if (!$styling_plugin.length) return; + // add the color picker + $styling_plugin.find('.color').iris({}); if (!$styling_plugin.hasClass('ispopup')) { var $hl = $styling_plugin.find('h1').first(); @@ -18,9 +20,6 @@ jQuery(function () { return; } - // add the color picker - $styling_plugin.find('.color').iris({}); - // append the loader screen $loader = window.opener.jQuery('#plugin__styling_loader'); if (!$loader.length) { @@ -45,8 +44,5 @@ jQuery(function () { // load preview in main window var now = new Date().getTime(); var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', ''); $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); - - }); -- cgit v1.2.3 From c2f2dedb56cd3872aa50d58de0cb3083b36b7872 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 26 Jun 2015 14:04:20 +0200 Subject: reset page back to normal on closing dialog --- lib/plugins/styling/script.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 409148471..6fc2b9043 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -2,12 +2,15 @@ jQuery(function () { + var doreload = 1; + var $styling_plugin = jQuery('#plugin__styling'); if (!$styling_plugin.length) return; // add the color picker $styling_plugin.find('.color').iris({}); + // add button on main page if (!$styling_plugin.hasClass('ispopup')) { var $hl = $styling_plugin.find('h1').first(); var $btn = jQuery(''); @@ -20,6 +23,23 @@ jQuery(function () { return; } + // reload the main page on close + window.onunload = function(e) { + if(doreload) { + window.opener.document.location.reload(); + } + return null; + }; + + // don't reload on our own buttons + jQuery('input[type=submit]').click(function(e){ + doreload = false; + }); + + // remove style + var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', ''); + // append the loader screen $loader = window.opener.jQuery('#plugin__styling_loader'); if (!$loader.length) { @@ -41,8 +61,11 @@ jQuery(function () { window.opener.jQuery('body').append($loader); } - // load preview in main window - var now = new Date().getTime(); - var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + // load preview in main window (timeout works around chrome updating CSS weirdness) + window.setTimeout(function() { + var now = new Date().getTime(); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + }, 500); + + }); -- cgit v1.2.3 From d634152e42ef1c75d899f021132ce0c2632257ac Mon Sep 17 00:00:00 2001 From: Anika Henke Date: Tue, 7 Jul 2015 12:39:55 +0100 Subject: improved various minor frontend issues in styling plugin * improved spacing of popup * made primary buttons clearer * xhtml and validity fixes * improved some lang strings * moved 'open as popup' after intro * fixed page reload after clicking 'open as popup' button --- lib/plugins/styling/admin.php | 16 ++++++++-------- lib/plugins/styling/lang/en/lang.php | 10 +++++----- lib/plugins/styling/popup.php | 14 ++++++++------ lib/plugins/styling/script.js | 14 ++++++++------ lib/plugins/styling/style.less | 7 ++----- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index 627efbd15..f241e3fed 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -43,7 +43,7 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { */ public function html() { $class = 'nopopup'; - if($this->ispopup) $class = 'ispopup'; + if($this->ispopup) $class = 'ispopup page'; echo '
'; ptln('

'.$this->getLang('menu').'

'); @@ -75,7 +75,7 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { echo '
'; - echo ''; + echo '
'; foreach($replacements as $key => $value) { $name = tpl_getLang($key); if(empty($name)) $name = $this->getLang($key); @@ -83,22 +83,22 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { echo ''; echo ''; - echo ''; echo ''; } - echo '
'.$name.'colorClass($key).' />'; + echo 'colorClass($key).' />
'; + echo ''; echo '

'; - echo ''; - echo ''; #FIXME only if preview.ini exists + echo ' '; + echo ''; #FIXME only if preview.ini exists echo '

'; echo '

'; - echo ''; + echo ''; echo '

'; echo '

'; - echo ''; #FIXME only if local.ini exists + echo ''; #FIXME only if local.ini exists echo '

'; echo '
'; diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index a076065cb..010743956 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -8,15 +8,15 @@ // menu entry for admin plugins $lang['menu'] = 'Template Style Settings'; -$lang['js']['loader'] = 'Preview is loading...
if this does not goes away, your values may be faulty'; -$lang['js']['popup'] = 'Open as Popup'; +$lang['js']['loader'] = 'Preview is loading...
if this does not goes away, your values may be faulty'; +$lang['js']['popup'] = 'Open as a popup'; // custom language strings for the plugin $lang['error'] = 'Sorry, this template does not support this functionality.'; -$lang['btn_preview'] = 'Preview your changes'; -$lang['btn_save'] = 'Save your changes'; -$lang['btn_reset'] = 'Reset your current changes'; +$lang['btn_preview'] = 'Preview changes'; +$lang['btn_save'] = 'Save changes'; +$lang['btn_reset'] = 'Reset current changes'; $lang['btn_revert'] = 'Revert all styles back to the template\'s default'; // default guaranteed placeholders diff --git a/lib/plugins/styling/popup.php b/lib/plugins/styling/popup.php index eea4f3bfd..964b19e29 100644 --- a/lib/plugins/styling/popup.php +++ b/lib/plugins/styling/popup.php @@ -4,6 +4,7 @@ require_once(DOKU_INC . 'inc/init.php'); //close session session_write_close(); header('Content-Type: text/html; charset=utf-8'); +header('X-UA-Compatible: IE=edge,chrome=1'); /** @var admin_plugin_styling $plugin */ $plugin = plugin_load('admin', 'styling'); @@ -14,15 +15,16 @@ $plugin->ispopup = true; $plugin->handle(); // output plugin in a very minimal template: -?> - +?> + + <?php echo $plugin->getLang('menu') ?> + + - -
- html() ?> -
+ + html() ?> diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 6fc2b9043..84b251eab 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -12,14 +12,15 @@ jQuery(function () { // add button on main page if (!$styling_plugin.hasClass('ispopup')) { - var $hl = $styling_plugin.find('h1').first(); - var $btn = jQuery(''); - $hl.append($btn); + var $form = $styling_plugin.find('form.styling').first(); + var $btn = jQuery(''); + $form.prepend($btn); $btn.click(function (e) { var windowFeatures = "menubar=no,location=no,resizable=yes,scrollbars=yes,status=false,width=500,height=500"; window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling', windowFeatures) - }); + e.preventDefault(); + }).wrap('

'); return; } @@ -54,9 +55,10 @@ jQuery(function () { 'background-color': '#fff', 'opacity': '0.7', 'color': '#000', - 'font-size': '40px', + 'font-size': '2.5em', 'text-align': 'center', - 'line-height': '90px' + 'line-height': 1.5, + 'padding-top': '2em' }); window.opener.jQuery('body').append($loader); } diff --git a/lib/plugins/styling/style.less b/lib/plugins/styling/style.less index 3cb563fdb..120768289 100644 --- a/lib/plugins/styling/style.less +++ b/lib/plugins/styling/style.less @@ -1,9 +1,6 @@ #plugin__styling { - - h1 button { - font-size: 12px; - line-height: 16px; - margin-left: 1em; + input.primary { + font-weight: bold; } } -- cgit v1.2.3 From 147d8f481419c685b5408b7b793fd32a8923f35e Mon Sep 17 00:00:00 2001 From: Anika Henke Date: Tue, 7 Jul 2015 13:04:52 +0100 Subject: added labels to form entries --- lib/plugins/styling/admin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index f241e3fed..c6c04bb52 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -82,8 +82,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { if(empty($name)) $name = $key; echo ''; - echo ''.$name.''; - echo 'colorClass($key).' />'; + echo ''; + echo 'colorClass($key).' />'; echo ''; } echo ''; -- cgit v1.2.3 From 75c8c6faaeefed18f12a4ce652c4161567219910 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 18 Jul 2015 12:23:26 +0200 Subject: readded cookie to styling plugin --- lib/plugins/styling/script.js | 95 +++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 84b251eab..f2d550b21 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -2,10 +2,58 @@ jQuery(function () { - var doreload = 1; + /** + * Function to reload the preview styles in the main window + * + * @param {window} target the main window + */ + function applyPreview(target) { + // remove style + var $style = target.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); + $style.attr('href', ''); + + // append the loader screen + $loader = target.jQuery('#plugin__styling_loader'); + if (!$loader.length) { + $loader = jQuery('
' + LANG.plugins.styling.loader + '
'); + $loader.css({ + 'position': 'absolute', + 'width': '100%', + 'height': '100%', + 'top': 0, + 'left': 0, + 'z-index': 5000, + 'background-color': '#fff', + 'opacity': '0.7', + 'color': '#000', + 'font-size': '2.5em', + 'text-align': 'center', + 'line-height': 1.5, + 'padding-top': '2em' + }); + target.jQuery('body').append($loader); + } + + // load preview in main window (timeout works around chrome updating CSS weirdness) + target.setTimeout(function () { + var now = new Date().getTime(); + $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); + }, 500); + } + var doreload = 1; var $styling_plugin = jQuery('#plugin__styling'); - if (!$styling_plugin.length) return; + + // if we are not on the plugin page (either main or popup) + if (!$styling_plugin.length) { + // handle the preview cookie + if(DokuCookie.getValue('styling_plugin') == 1) { + applyPreview(window); + } + return; // nothing more to do here + } + + /* ---- from here on we're in the popup or admin page ---- */ // add the color picker $styling_plugin.find('.color').iris({}); @@ -18,15 +66,18 @@ jQuery(function () { $btn.click(function (e) { var windowFeatures = "menubar=no,location=no,resizable=yes,scrollbars=yes,status=false,width=500,height=500"; - window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling', windowFeatures) + window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling_popup', windowFeatures); e.preventDefault(); }).wrap('

'); - return; + return; // we exit here if this is not the popup } + /* ---- from here on we're in the popup only ---- */ + // reload the main page on close window.onunload = function(e) { if(doreload) { + window.opener.DokuCookie.setValue('styling_plugin', 0); window.opener.document.location.reload(); } return null; @@ -37,37 +88,9 @@ jQuery(function () { doreload = false; }); - // remove style - var $style = window.opener.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]'); - $style.attr('href', ''); - - // append the loader screen - $loader = window.opener.jQuery('#plugin__styling_loader'); - if (!$loader.length) { - $loader = jQuery('
' + LANG.plugins.styling.loader + '
'); - $loader.css({ - 'position': 'absolute', - 'width': '100%', - 'height': '100%', - 'top': 0, - 'left': 0, - 'z-index': 5000, - 'background-color': '#fff', - 'opacity': '0.7', - 'color': '#000', - 'font-size': '2.5em', - 'text-align': 'center', - 'line-height': 1.5, - 'padding-top': '2em' - }); - window.opener.jQuery('body').append($loader); - } - - // load preview in main window (timeout works around chrome updating CSS weirdness) - window.setTimeout(function() { - var now = new Date().getTime(); - $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); - }, 500); - + // on first load apply preview + applyPreview(window.opener); + // enable the preview cookie + window.opener.DokuCookie.setValue('styling_plugin', 1); }); -- cgit v1.2.3 From 8d4151ded3d683f2ef8524b9bb48460a767e334f Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 18 Jul 2015 12:46:50 +0200 Subject: added styler into default plugin section The icon does not match the others because the NuvolaX icon theme seems to have vanished from the Internet. Ideally all those admin icons could use a replacement. But that should be a different PR I guess. --- inc/html.php | 7 +++++++ lib/images/admin/README | 2 ++ lib/images/admin/styling.png | Bin 0 -> 970 bytes lib/tpl/dokuwiki/css/_admin.css | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 lib/images/admin/styling.png diff --git a/inc/html.php b/inc/html.php index 2f10e3c08..36083a57e 100644 --- a/inc/html.php +++ b/inc/html.php @@ -2076,6 +2076,13 @@ function html_admin(){ $menu['config']['prompt'].'
'); } unset($menu['config']); + + if($menu['styling']){ + ptln('
  • '); + } + unset($menu['styling']); } ptln(''); diff --git a/lib/images/admin/README b/lib/images/admin/README index 90bab9578..53e7d839a 100644 --- a/lib/images/admin/README +++ b/lib/images/admin/README @@ -1,2 +1,4 @@ These icons were taken from the nuvoX KDE icon theme and are GPL licensed See http://www.kde-look.org/content/show.php/nuvoX?content=38467 + +styling.png from https://openclipart.org/detail/25595/brush Public Domain diff --git a/lib/images/admin/styling.png b/lib/images/admin/styling.png new file mode 100644 index 000000000..859c8c9ef Binary files /dev/null and b/lib/images/admin/styling.png differ diff --git a/lib/tpl/dokuwiki/css/_admin.css b/lib/tpl/dokuwiki/css/_admin.css index a9518d0ed..bdde006e0 100644 --- a/lib/tpl/dokuwiki/css/_admin.css +++ b/lib/tpl/dokuwiki/css/_admin.css @@ -39,6 +39,9 @@ .dokuwiki ul.admin_tasks li.admin_config { background-image: url(../../images/admin/config.png); } +.dokuwiki ul.admin_tasks li.admin_styling { + background-image: url(../../images/admin/styling.png); +} .dokuwiki ul.admin_tasks li.admin_revert { background-image: url(../../images/admin/revert.png); } -- cgit v1.2.3 From b28801bc7f36b024f1051c1e7afd9be8a4c5171e Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 18 Jul 2015 12:51:25 +0200 Subject: added plugin to list of bundled extensions --- lib/plugins/extension/helper/extension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index 719249fbe..c23840805 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -108,7 +108,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { return in_array($this->id, array( 'authad', 'authldap', 'authmysql', 'authpgsql', 'authplain', 'acl', 'info', 'extension', - 'revert', 'popularity', 'config', 'safefnrecode', 'testing', 'template:dokuwiki' + 'revert', 'popularity', 'config', 'safefnrecode', 'styling', 'testing', 'template:dokuwiki' ) ); } -- cgit v1.2.3 From 1dd04f9774f268918c142128043ff8696ea41b42 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 24 Jul 2015 09:46:42 +0200 Subject: fixed styling preview in IE IE doesn't like it when you create a DOM element in one window and try to insert it in another window. --- lib/plugins/styling/script.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index f2d550b21..2f4285269 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -5,7 +5,7 @@ jQuery(function () { /** * Function to reload the preview styles in the main window * - * @param {window} target the main window + * @param {Window} target the main window */ function applyPreview(target) { // remove style @@ -13,9 +13,9 @@ jQuery(function () { $style.attr('href', ''); // append the loader screen - $loader = target.jQuery('#plugin__styling_loader'); + var $loader = target.jQuery('#plugin__styling_loader'); if (!$loader.length) { - $loader = jQuery('
    ' + LANG.plugins.styling.loader + '
    '); + $loader = target.jQuery('
    ' + LANG.plugins.styling.loader + '
    '); $loader.css({ 'position': 'absolute', 'width': '100%', @@ -68,6 +68,7 @@ jQuery(function () { var windowFeatures = "menubar=no,location=no,resizable=yes,scrollbars=yes,status=false,width=500,height=500"; window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling_popup', windowFeatures); e.preventDefault(); + e.stopPropagation(); }).wrap('

    '); return; // we exit here if this is not the popup } -- cgit v1.2.3 From 84e76a7e9cfdd3dd7beb341445af056cdd642606 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 24 Jul 2015 09:53:17 +0200 Subject: make personal style ini to cause cache reloads --- inc/template.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/template.php b/inc/template.php index 95dc52deb..7a25e12c8 100644 --- a/inc/template.php +++ b/inc/template.php @@ -297,6 +297,7 @@ function tpl_metaheaders($alt = true) { // prepare seed for js and css $tseed = $updateVersion; $depends = getConfigFiles('main'); + $depends[] = DOKU_CONF."tpl/".$conf['template']."/style.ini"; foreach($depends as $f) $tseed .= @filemtime($f); $tseed = md5($tseed); -- cgit v1.2.3 From cf2c8e759bf06596f9492d090f4dd8dbb76a178c Mon Sep 17 00:00:00 2001 From: Anika Henke Date: Sat, 25 Jul 2015 22:58:51 +0100 Subject: changed input submits to buttons, fixed small RTL issue --- lib/plugins/styling/admin.php | 10 +++++----- lib/plugins/styling/lang/en/lang.php | 2 +- lib/plugins/styling/script.js | 2 +- lib/plugins/styling/style.less | 6 +++++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index c6c04bb52..c747c3130 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -83,22 +83,22 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { echo ''; echo ''; - echo 'colorClass($key).' />'; + echo 'colorClass($key).' dir="ltr" />'; echo ''; } echo ''; echo '

    '; - echo ' '; - echo ''; #FIXME only if preview.ini exists + echo ' '; + echo ''; #FIXME only if preview.ini exists echo '

    '; echo '

    '; - echo ''; + echo ''; echo '

    '; echo '

    '; - echo ''; #FIXME only if local.ini exists + echo ''; #FIXME only if local.ini exists echo '

    '; echo ''; diff --git a/lib/plugins/styling/lang/en/lang.php b/lib/plugins/styling/lang/en/lang.php index 010743956..e0011eb83 100644 --- a/lib/plugins/styling/lang/en/lang.php +++ b/lib/plugins/styling/lang/en/lang.php @@ -17,7 +17,7 @@ $lang['error'] = 'Sorry, this template does not support this functionality.'; $lang['btn_preview'] = 'Preview changes'; $lang['btn_save'] = 'Save changes'; $lang['btn_reset'] = 'Reset current changes'; -$lang['btn_revert'] = 'Revert all styles back to the template\'s default'; +$lang['btn_revert'] = 'Revert styles back to template\'s default'; // default guaranteed placeholders $lang['__text__'] = 'Main text color'; diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index 2f4285269..aa343fd71 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -85,7 +85,7 @@ jQuery(function () { }; // don't reload on our own buttons - jQuery('input[type=submit]').click(function(e){ + jQuery(':button').click(function(e){ doreload = false; }); diff --git a/lib/plugins/styling/style.less b/lib/plugins/styling/style.less index 120768289..be0e16a5b 100644 --- a/lib/plugins/styling/style.less +++ b/lib/plugins/styling/style.less @@ -1,7 +1,11 @@ #plugin__styling { - input.primary { + button.primary { font-weight: bold; } + + [dir=rtl] & table input { + text-align: right; + } } #plugin__styling_loader { -- cgit v1.2.3 From ec2b4bab83a5eb6ddcdb41853d311b99b71c6f86 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 26 Jul 2015 11:43:02 +0200 Subject: fix problem with IE11 hopefully doesn't break in the other browsers --- lib/plugins/styling/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/styling/script.js b/lib/plugins/styling/script.js index aa343fd71..074c8dc40 100644 --- a/lib/plugins/styling/script.js +++ b/lib/plugins/styling/script.js @@ -35,7 +35,7 @@ jQuery(function () { } // load preview in main window (timeout works around chrome updating CSS weirdness) - target.setTimeout(function () { + setTimeout(function () { var now = new Date().getTime(); $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now); }, 500); -- cgit v1.2.3