├── icon512.png ├── src ├── .DS_Store ├── inject │ ├── icon.png │ ├── icon.png.bak │ ├── icon.png.bak3 │ ├── icon.pngbak2 │ ├── inject.css │ └── inject.js ├── options_custom │ ├── icon.png │ ├── custom.css │ ├── js │ │ ├── i18n.js │ │ └── classes │ │ │ ├── tab.js │ │ │ ├── search.js │ │ │ ├── fancy-settings.js │ │ │ └── setting.js │ ├── css │ │ ├── setting.css │ │ └── main.css │ ├── settings.js │ ├── index.html │ ├── i18n.js │ ├── lib │ │ ├── store.js │ │ └── default.css │ ├── README.md │ └── manifest.js ├── bg │ ├── background.html │ └── background.js └── browser_action │ └── browser_action.html ├── icons ├── icon128.png ├── icon16.png ├── icon19.png ├── icon48.png ├── icon16.png.bak ├── icon19.png.bak ├── icon48.png.bak └── icon128.png.bak ├── screenshots ├── meeting_costifier.png ├── meeting_costifier2.png └── meeting_costifier3.png ├── readme.md ├── LICENSE ├── manifest.json └── _locales └── en └── messages.json /icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icon512.png -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/.DS_Store -------------------------------------------------------------------------------- /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon128.png -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon19.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon48.png -------------------------------------------------------------------------------- /icons/icon16.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon16.png.bak -------------------------------------------------------------------------------- /icons/icon19.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon19.png.bak -------------------------------------------------------------------------------- /icons/icon48.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon48.png.bak -------------------------------------------------------------------------------- /src/inject/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/inject/icon.png -------------------------------------------------------------------------------- /icons/icon128.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/icons/icon128.png.bak -------------------------------------------------------------------------------- /src/inject/icon.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/inject/icon.png.bak -------------------------------------------------------------------------------- /src/inject/icon.png.bak3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/inject/icon.png.bak3 -------------------------------------------------------------------------------- /src/inject/icon.pngbak2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/inject/icon.pngbak2 -------------------------------------------------------------------------------- /src/options_custom/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/src/options_custom/icon.png -------------------------------------------------------------------------------- /screenshots/meeting_costifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/screenshots/meeting_costifier.png -------------------------------------------------------------------------------- /screenshots/meeting_costifier2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/screenshots/meeting_costifier2.png -------------------------------------------------------------------------------- /screenshots/meeting_costifier3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/meeting-costifier/master/screenshots/meeting_costifier3.png -------------------------------------------------------------------------------- /src/bg/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/options_custom/custom.css: -------------------------------------------------------------------------------- 1 | /* 2 | // Add your own style rules here, not in css/main.css 3 | // or css/setting.css for easy updating reasons. 4 | */ 5 | -------------------------------------------------------------------------------- /src/bg/background.js: -------------------------------------------------------------------------------- 1 | chrome.extension.onMessage.addListener( 2 | function(request, sender, sendResponse) { 3 | chrome.pageAction.show(sender.tab.id); 4 | sendResponse(); 5 | }); 6 | 7 | chrome.tabs.onUpdated.addListener( 8 | function(tabId, changeInfo, tab) { 9 | // read changeInfo data and do something with it 10 | // like send the new url to contentscripts.js 11 | if (changeInfo.url) { 12 | chrome.tabs.sendMessage( tabId, { 13 | message: 'AddingCalendarEvent', 14 | url: changeInfo.url 15 | }) 16 | } 17 | } 18 | ); -------------------------------------------------------------------------------- /src/browser_action/browser_action.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 |
15 |

Meeting Costifier

16 |

When navigating to a Google Calendar page in full view, you will see a new button at the bottom of the page that will add the cost of the meeting to the title.

17 |

Values used: $100 per hour per person.

18 | Create Calendar Event 19 |
-------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Meeting Costifier 2 | 3 | Quickly add a cost tag to your Google meeting signifying the value of meeting time. Encourage productive meetings and sanctify time. 4 | 5 | ## Download 6 | 7 | You can download this extension in the Google Chrome store: https://chrome.google.com/webstore/detail/meeting-costifier/binhlcibajcpjcmnkagcommjdmealmno 8 | 9 | ## Parameters 10 | 11 | Currently, this extension uses the following parameters: 12 | - Duration of the meeting (start/end date parsed from the DOM) 13 | - Number of participants (parsed from the children divs in participants list or the guest count if available) 14 | - $100 an hour 15 | 16 | The cost calculation is currently #people * hours * $100. 17 | 18 | ## Screenshots 19 | 20 | ![Image](screenshots/meeting_costifier.png) -------------------------------------------------------------------------------- /src/options_custom/js/i18n.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | // 6 | (function () { 7 | var lang = navigator.language; 8 | if (this.i18n === undefined) { this.i18n = {}; } 9 | this.i18n.get = function (value) { 10 | if (value === "lang") { 11 | return lang; 12 | } 13 | 14 | if (this.hasOwnProperty(value)) { 15 | value = this[value]; 16 | if (value.hasOwnProperty(lang)) { 17 | return value[lang]; 18 | } else if (value.hasOwnProperty("en")) { 19 | return value["en"]; 20 | } else { 21 | return Object.values(value)[0]; 22 | } 23 | } else { 24 | return value; 25 | } 26 | }; 27 | }()); 28 | -------------------------------------------------------------------------------- /src/inject/inject.css: -------------------------------------------------------------------------------- 1 | /* These values were inspected from an existing google calendar button. */ 2 | #meetingCost { 3 | display: inline-block; 4 | background-color: rgb(14, 114, 237); 5 | color: rgb(255, 255, 255); 6 | line-height: 36px; 7 | font-family: "Google Sans", Roboto, Helvetica, Arial, sans-serif; 8 | font-weight: 500; 9 | font-size: 14px; 10 | border-width: 0px; 11 | border-style: initial; 12 | border-color: initial; 13 | border-image: initial; 14 | padding: 0px 8px; 15 | border-radius: 4px; 16 | font-family: 'Google Sans',Roboto,Arial,sans-serif; 17 | border-radius: 4px; 18 | line-height: 38px; 19 | font-weight: 500; 20 | letter-spacing: .25px; 21 | line-height: 36px; 22 | text-decoration: none; 23 | text-transform: none; 24 | font-size: 14px; 25 | cursor: pointer; 26 | text-align: center; 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Time by Ping 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Meeting Costifier", 3 | "version": "0.0.2", 4 | "manifest_version": 2, 5 | "description": "Quickly add a cost tag to your Google meeting signifying the value of meeting time. Encourage productive meetings", 6 | "icons": { 7 | "16": "icons/icon16.png", 8 | "48": "icons/icon48.png", 9 | "128": "icons/icon128.png" 10 | }, 11 | "default_locale": "en", 12 | "background": { 13 | "page": "src/bg/background.html", 14 | "persistent": true 15 | }, 16 | "options_page": "src/options_custom/index.html", 17 | "browser_action": { 18 | "default_icon": "icons/icon19.png", 19 | "default_title": "Meeting Costifier", 20 | "default_popup": "src/browser_action/browser_action.html" 21 | }, 22 | "permissions": [ 23 | "https://calendar.google.com/*", 24 | "tabs" 25 | ], 26 | "content_scripts": [ 27 | { 28 | "matches": [ 29 | "https://calendar.google.com/calendar/*" 30 | ], 31 | "css": [ 32 | "src/inject/inject.css" 33 | ] 34 | }, 35 | { 36 | "matches": [ 37 | "https://calendar.google.com/calendar/*" 38 | ], 39 | "js": [ 40 | "src/inject/inject.js" 41 | ] 42 | } 43 | ], 44 | "web_accessible_resources": ["src/inject/icon.png"] 45 | } -------------------------------------------------------------------------------- /src/options_custom/css/setting.css: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | */ 6 | .tab-content h2 { 7 | margin: 0; 8 | padding-bottom: 5px; 9 | font-size: 26px; 10 | color: #707070; 11 | line-height: 1; 12 | } 13 | 14 | .setting.group { 15 | border-top: 1px solid #EEEEEE; 16 | margin-top: 10px; 17 | padding-top: 5px; 18 | padding-left: 2px; 19 | } 20 | 21 | .setting.group-name { 22 | width: 140px; 23 | padding: 0; 24 | font-size: 14px; 25 | font-weight: bold; 26 | vertical-align: top; 27 | } 28 | 29 | .setting.bundle { 30 | max-width: 600px; 31 | margin-bottom: 5px; 32 | } 33 | 34 | .setting.bundle.list-box { 35 | margin-bottom: 10px; 36 | } 37 | 38 | .setting.label.radio-buttons + .setting.container.radio-buttons { 39 | margin-top: 3px; 40 | } 41 | 42 | .setting.label, .setting.element-label { 43 | margin-right: 15px; 44 | font-size: 13px; 45 | font-weight: normal; 46 | } 47 | 48 | .setting.label.checkbox, .setting.element-label { 49 | margin-left: 5px; 50 | margin-right: 0; 51 | } 52 | 53 | .setting.label.checkbox { 54 | position: relative; 55 | top: 1px; 56 | } 57 | 58 | .setting.element.slider { 59 | position: relative; 60 | width: 150px; 61 | top: 4px; 62 | } 63 | 64 | .setting.element.list-box { 65 | display: block; 66 | height: 100px; 67 | width: 100%; 68 | } 69 | 70 | .setting.display.slider { 71 | margin-left: 5px; 72 | color: #666666; 73 | } 74 | 75 | #nothing-found { 76 | display: none; 77 | margin-top: 10px; 78 | font-size: 18px; 79 | font-weight: lighter; 80 | color: #999999; 81 | } 82 | -------------------------------------------------------------------------------- /src/options_custom/settings.js: -------------------------------------------------------------------------------- 1 | window.addEvent("domready", function () { 2 | // Option 1: Use the manifest: 3 | new FancySettings.initWithManifest(function (settings) { 4 | settings.manifest.myButton.addEvent("action", function () { 5 | alert("You clicked me!"); 6 | }); 7 | }); 8 | 9 | // Option 2: Do everything manually: 10 | /* 11 | var settings = new FancySettings("My Extension", "icon.png"); 12 | 13 | var username = settings.create({ 14 | "tab": i18n.get("information"), 15 | "group": i18n.get("login"), 16 | "name": "username", 17 | "type": "text", 18 | "label": i18n.get("username"), 19 | "text": i18n.get("x-characters") 20 | }); 21 | 22 | var password = settings.create({ 23 | "tab": i18n.get("information"), 24 | "group": i18n.get("login"), 25 | "name": "password", 26 | "type": "text", 27 | "label": i18n.get("password"), 28 | "text": i18n.get("x-characters-pw"), 29 | "masked": true 30 | }); 31 | 32 | var myDescription = settings.create({ 33 | "tab": i18n.get("information"), 34 | "group": i18n.get("login"), 35 | "name": "myDescription", 36 | "type": "description", 37 | "text": i18n.get("description") 38 | }); 39 | 40 | var myButton = settings.create({ 41 | "tab": "Information", 42 | "group": "Logout", 43 | "name": "myButton", 44 | "type": "button", 45 | "label": "Disconnect:", 46 | "text": "Logout" 47 | }); 48 | 49 | // ... 50 | 51 | myButton.addEvent("action", function () { 52 | alert("You clicked me!"); 53 | }); 54 | 55 | settings.align([ 56 | username, 57 | password 58 | ]); 59 | */ 60 | }); 61 | -------------------------------------------------------------------------------- /src/options_custom/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 40 |
41 |
42 |

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /src/options_custom/js/classes/tab.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | // 6 | (function () { 7 | var Bundle = new Class({ 8 | "initialize": function (creator) { 9 | this.creator = creator; 10 | 11 | // Create DOM elements 12 | this.tab = new Element("div", {"class": "tab"}); 13 | this.content = new Element("div", {"class": "tab-content"}); 14 | 15 | // Create event handlers 16 | this.tab.addEvent("click", this.activate.bind(this)); 17 | }, 18 | 19 | "activate": function () { 20 | if (this.creator.activeBundle && this.creator.activeBundle !== this) { 21 | this.creator.activeBundle.deactivate(); 22 | } 23 | this.tab.addClass("active"); 24 | this.content.addClass("show"); 25 | this.creator.activeBundle = this; 26 | }, 27 | 28 | "deactivate": function () { 29 | this.tab.removeClass("active"); 30 | this.content.removeClass("show"); 31 | this.creator.activeBundle = null; 32 | } 33 | }); 34 | 35 | this.Tab = new Class({ 36 | "activeBundle": null, 37 | 38 | "initialize": function (tabContainer, tabContentContainer) { 39 | this.tabContainer = tabContainer; 40 | this.tabContentContainer = tabContentContainer; 41 | }, 42 | 43 | "create": function () { 44 | var bundle = new Bundle(this); 45 | bundle.tab.inject(this.tabContainer); 46 | bundle.content.inject(this.tabContentContainer); 47 | if (!this.activeBundle) { bundle.activate(); } 48 | return bundle; 49 | } 50 | }); 51 | }()); 52 | -------------------------------------------------------------------------------- /src/options_custom/i18n.js: -------------------------------------------------------------------------------- 1 | // SAMPLE 2 | this.i18n = { 3 | "settings": { 4 | "en": "Settings", 5 | "de": "Optionen" 6 | }, 7 | "search": { 8 | "en": "Search", 9 | "de": "Suche" 10 | }, 11 | "nothing-found": { 12 | "en": "No matches were found.", 13 | "de": "Keine Übereinstimmungen gefunden." 14 | }, 15 | 16 | 17 | 18 | "information": { 19 | "en": "Information", 20 | "de": "Information" 21 | }, 22 | "login": { 23 | "en": "Login", 24 | "de": "Anmeldung" 25 | }, 26 | "username": { 27 | "en": "Username:", 28 | "de": "Benutzername:" 29 | }, 30 | "password": { 31 | "en": "Password:", 32 | "de": "Passwort:" 33 | }, 34 | "x-characters": { 35 | "en": "6 - 12 characters", 36 | "de": "6 - 12 Zeichen" 37 | }, 38 | "x-characters-pw": { 39 | "en": "10 - 18 characters", 40 | "de": "10 - 18 Zeichen" 41 | }, 42 | "description": { 43 | "en": "This is a description. You can write any text inside of this.
\ 44 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\ 45 | labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\ 46 | et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\ 47 | ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\ 48 | dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\ 49 | Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", 50 | 51 | "de": "Das ist eine Beschreibung. Du kannst hier beliebigen Text einfügen.
\ 52 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\ 53 | labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\ 54 | et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\ 55 | ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\ 56 | dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\ 57 | Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." 58 | }, 59 | "logout": { 60 | "en": "Logout", 61 | "de": "Abmeldung" 62 | }, 63 | "enable": { 64 | "en": "Enable", 65 | "de": "Aktivieren" 66 | }, 67 | "disconnect": { 68 | "en": "Disconnect:", 69 | "de": "Trennen:" 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /src/options_custom/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | */ 6 | .fancy { 7 | text-shadow: #F5F5F5 0 1px 0; 8 | } 9 | 10 | #sidebar { 11 | position: absolute; 12 | background-color: #EDEDED; 13 | background-image: linear-gradient(top, #EDEDED, #F5F5F5); 14 | background-image: -webkit-gradient( 15 | linear, 16 | left top, 17 | left 500, 18 | color-stop(0, #EDEDED), 19 | color-stop(1, #F5F5F5) 20 | ); 21 | background-image: -moz-linear-gradient( 22 | center top, 23 | #EDEDED 0%, 24 | #F5F5F5 100% 25 | ); 26 | background-image: -o-linear-gradient(top, #EDEDED, #F5F5F5); 27 | width: 219px; 28 | top: 0; 29 | left: 0; 30 | bottom: 0; 31 | border-right: 1px solid #C2C2C2; 32 | box-shadow: inset -8px 0 30px -30px black; 33 | } 34 | 35 | #icon { 36 | position: absolute; 37 | width: 30px; 38 | height: 30px; 39 | top: 12px; 40 | left: 12px; 41 | } 42 | 43 | #sidebar h1 { 44 | position: absolute; 45 | top: 13px; 46 | right: 25px; 47 | font-size: 26px; 48 | color: #707070; 49 | } 50 | 51 | #tab-container { 52 | position: absolute; 53 | top: 50px; 54 | left: 0; 55 | right: 0; 56 | bottom: 0; 57 | overflow-y: auto; 58 | overflow-x: hidden; 59 | text-align: right; 60 | } 61 | 62 | #tab-container .tab { 63 | height: 28px; 64 | padding-right: 25px; 65 | border-top: 1px solid transparent; 66 | border-bottom: 1px solid transparent; 67 | font-size: 12px; 68 | line-height: 28px; 69 | color: #808080; 70 | cursor: pointer; 71 | } 72 | 73 | #search-container { 74 | margin-top: 5px; 75 | margin-bottom: 5px; 76 | padding-right: 23px !important; 77 | cursor: default !important; 78 | } 79 | 80 | #search-container input { 81 | width: 130px; 82 | } 83 | 84 | #tab-container .tab.active, body.searching #search-container { 85 | background-color: #D4D4D4; 86 | border-color: #BFBFBF; 87 | color: black; 88 | text-shadow: #DBDBDB 0 1px 0; 89 | box-shadow: inset -12px 0 30px -30px black; 90 | } 91 | 92 | body.searching #tab-container .tab.active { 93 | background-color: transparent; 94 | border-color: transparent; 95 | color: #808080; 96 | text-shadow: inherit; 97 | box-shadow: none; 98 | } 99 | 100 | #content { 101 | position: absolute; 102 | top: 0; 103 | left: 220px; 104 | right: 0; 105 | bottom: 0; 106 | overflow: auto; 107 | } 108 | 109 | .tab-content { 110 | display: none; 111 | position: absolute; 112 | width: 840px; 113 | top: 0; 114 | left: 0; 115 | bottom: 0; 116 | padding: 20px; 117 | padding-top: 15px; 118 | } 119 | 120 | body.searching .tab-content { 121 | display: none !important; 122 | } 123 | 124 | body.searching #search-result-container { 125 | display: block !important; 126 | } 127 | 128 | body.measuring .tab-content, body.measuring #search-result-container { 129 | display: block !important; 130 | opacity: 0; 131 | overflow: hidden; 132 | } 133 | -------------------------------------------------------------------------------- /src/options_custom/lib/store.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/store-js 4 | // License: MIT-license 5 | // 6 | (function () { 7 | var Store = this.Store = function (name, defaults) { 8 | var key; 9 | this.name = name; 10 | 11 | if (defaults !== undefined) { 12 | for (key in defaults) { 13 | if (defaults.hasOwnProperty(key) && this.get(key) === undefined) { 14 | this.set(key, defaults[key]); 15 | } 16 | } 17 | } 18 | }; 19 | 20 | Store.prototype.get = function (name) { 21 | name = "store." + this.name + "." + name; 22 | if (localStorage.getItem(name) === null) { return undefined; } 23 | try { 24 | return JSON.parse(localStorage.getItem(name)); 25 | } catch (e) { 26 | return null; 27 | } 28 | }; 29 | 30 | Store.prototype.set = function (name, value) { 31 | if (value === undefined) { 32 | this.remove(name); 33 | } else { 34 | if (typeof value === "function") { 35 | value = null; 36 | } else { 37 | try { 38 | value = JSON.stringify(value); 39 | } catch (e) { 40 | value = null; 41 | } 42 | } 43 | 44 | localStorage.setItem("store." + this.name + "." + name, value); 45 | } 46 | 47 | return this; 48 | }; 49 | 50 | Store.prototype.remove = function (name) { 51 | localStorage.removeItem("store." + this.name + "." + name); 52 | return this; 53 | }; 54 | 55 | Store.prototype.removeAll = function () { 56 | var name, 57 | i; 58 | 59 | name = "store." + this.name + "."; 60 | for (i = (localStorage.length - 1); i >= 0; i--) { 61 | if (localStorage.key(i).substring(0, name.length) === name) { 62 | localStorage.removeItem(localStorage.key(i)); 63 | } 64 | } 65 | 66 | return this; 67 | }; 68 | 69 | Store.prototype.toObject = function () { 70 | var values, 71 | name, 72 | i, 73 | key, 74 | value; 75 | 76 | values = {}; 77 | name = "store." + this.name + "."; 78 | for (i = (localStorage.length - 1); i >= 0; i--) { 79 | if (localStorage.key(i).substring(0, name.length) === name) { 80 | key = localStorage.key(i).substring(name.length); 81 | value = this.get(key); 82 | if (value !== undefined) { values[key] = value; } 83 | } 84 | } 85 | 86 | return values; 87 | }; 88 | 89 | Store.prototype.fromObject = function (values, merge) { 90 | if (merge !== true) { this.removeAll(); } 91 | for (var key in values) { 92 | if (values.hasOwnProperty(key)) { 93 | this.set(key, values[key]); 94 | } 95 | } 96 | 97 | return this; 98 | }; 99 | }()); 100 | -------------------------------------------------------------------------------- /src/options_custom/README.md: -------------------------------------------------------------------------------- 1 | # [Fancy Settings 1.2](https://github.com/frankkohlhepp/fancy-settings) 2 | *Create fancy, chrome-look-alike settings for your Chrome or Safari extension in minutes!* 3 | 4 | ### Howto 5 | Welcome to Fancy Settings! Are you ready for tabs, groups, search, good style? 6 | Let's get started, it only takes a few minutes... 7 | 8 | Settings can be of different types: text input, checkbox, slider, etc. Some "settings" are not actual settings but provide functionality that is relevant to the options page: description (which is simply a block of text), button. 9 | 10 | Settings are defined in the manifest.js file as JavaScript objects. Each setting is defined by specifying a number of parameters. All types of settings are configured with the string parameters tab, group, name and type. 11 | 12 | ###Basic example: 13 | ```javascript 14 | { 15 | "tab": "Tab 1", 16 | "group": "Group 1", 17 | "name": "checkbox1", 18 | "type": "checkbox" 19 | } 20 | ``` 21 | 22 | "name" is used as a part of the key when storing the setting's value in localStorage. 23 | If it's missing, nothing will be saved. 24 | 25 | ###Additionally, all types of settings are configured with their own custom parameters: 26 | 27 | ###Description ("type": "description") 28 | 29 | text (string) the block of text, which can include HTML tags. You can continue multiple lines of text by putting a \ at the end of a line, just as with any JavaScript file. 30 | 31 | #### 32 | Button ("type": "button") 33 | ``` 34 | Label (string) text shown in front of the button 35 | 36 | Text (string) text shown on the button 37 | ``` 38 | 39 | ####Text ("type": "text") 40 | ``` 41 | label (string) text shown in front of the text field 42 | 43 | text (string) text shown in the text field when empty 44 | 45 | masked (boolean) indicates a password field 46 | ``` 47 | 48 | ####Checkbox ("type": "checkbox") 49 | ``` 50 | label (string) text shown behind the checkbox 51 | ``` 52 | 53 | ####Slider ("type": "slider") 54 | ``` 55 | label (string) text shown in front of the slider 56 | 57 | max (number) maximal value of the slider 58 | 59 | min (number) minimal value of the slider 60 | 61 | step (number) steps between two values 62 | 63 | display (boolean) indicates whether to show the slider display 64 | 65 | displayModifier (function) a function to modify the value shown in the display 66 | ``` 67 | 68 | ####PopupButton ("type": "popupButton"), ListBox ("type": "listBox") & RadioButtons ("type": "radioButtons") 69 | ``` 70 | label (string) text shown in front of the options 71 | 72 | options (array of options) 73 | 74 | where an option can be one of the following formats: 75 | ``` 76 | 77 | ####"value" 78 | ``` 79 | ["value", "displayed text"] 80 | 81 | {value: "value", text: "displayed text"} 82 | ``` 83 | The "displayed text" field is optional and is displayed to the user when you don't want to display the internal value directly to the user. 84 | 85 | #### You can also group options so that the user can easily choose among them (groups may only be applied to popupButtons): 86 | 87 | ```javascript 88 | "options": { 89 | "groups": [ 90 | "Hot", "Cold", 91 | ], 92 | "values": [ 93 | { 94 | "value": "hot", 95 | "text": "Very hot", 96 | "group": "Hot", 97 | }, 98 | { 99 | "value": "Medium", 100 | "group": 1, 101 | }, 102 | { 103 | "value": "Cold", 104 | "group": 2, 105 | }, 106 | ["Non-existing"] 107 | ], 108 | }, 109 | 110 | ``` 111 | 112 | ### License 113 | Fancy Settings is licensed under the **LGPL 2.1**. 114 | For details see *LICENSE.txt* -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "l10nTabName": { 3 | "message":"Localization" 4 | ,"description":"name of the localization tab" 5 | } 6 | ,"l10nHeader": { 7 | "message":"It does localization too! (this whole tab is, actually)" 8 | ,"description":"Header text for the localization section" 9 | } 10 | ,"l10nIntro": { 11 | "message":"'L10n' refers to 'Localization' - 'L' an 'n' are obvious, and 10 comes from the number of letters between those two. It is the process/whatever of displaying something in the language of choice. It uses 'I18n', 'Internationalization', which refers to the tools / framework supporting L10n. I.e., something is internationalized if it has I18n support, and can be localized. Something is localized for you if it is in your language / dialect." 12 | ,"description":"introduce the basic idea." 13 | } 14 | ,"l10nProd": { 15 | "message":"You are planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on." 16 | ,"description":"drive the point home. It's good for you." 17 | } 18 | ,"l10nFirstParagraph": { 19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!" 20 | ,"description":"inform that elements will be localized on load" 21 | } 22 | ,"l10nSecondParagraph": { 23 | "message":"If you need more complex localization, you can also define data-l10n-args. This should contain $containerType$ filled with $dataType$, which will be passed into Chrome's i18n API as $functionArgs$. In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!" 24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note." 25 | ,"placeholders": { 26 | "containerType": { 27 | "content":"$1" 28 | ,"example":"'array', 'list', or something similar" 29 | ,"description":"type of the args container" 30 | } 31 | ,"dataType": { 32 | "content":"$2" 33 | ,"example":"string" 34 | ,"description":"type of data in each array index" 35 | } 36 | ,"functionArgs": { 37 | "content":"$3" 38 | ,"example":"arguments" 39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc." 40 | } 41 | } 42 | } 43 | ,"l10nThirdParagraph": { 44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set." 45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input" 46 | } 47 | ,"l10nButtonsBefore": { 48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:" 49 | } 50 | ,"l10nButton": { 51 | "message":"in a button" 52 | } 53 | ,"l10nButtonsBetween": { 54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):" 55 | } 56 | ,"l10nSubmit": { 57 | "message":"a submit value" 58 | } 59 | ,"l10nButtonsAfter": { 60 | "message":"Awesome, no?" 61 | } 62 | ,"l10nExtras": { 63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body." 64 | ,"description":"inform about places which may not be obvious, like , etc" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/options_custom/manifest.js: -------------------------------------------------------------------------------- 1 | // SAMPLE 2 | this.manifest = { 3 | "name": "My Extension", 4 | "icon": "icon.png", 5 | "settings": [ 6 | { 7 | "tab": i18n.get("information"), 8 | "group": i18n.get("login"), 9 | "name": "username", 10 | "type": "text", 11 | "label": i18n.get("username"), 12 | "text": i18n.get("x-characters") 13 | }, 14 | { 15 | "tab": i18n.get("information"), 16 | "group": i18n.get("login"), 17 | "name": "password", 18 | "type": "text", 19 | "label": i18n.get("password"), 20 | "text": i18n.get("x-characters-pw"), 21 | "masked": true 22 | }, 23 | { 24 | "tab": i18n.get("information"), 25 | "group": i18n.get("login"), 26 | "name": "myDescription", 27 | "type": "description", 28 | "text": i18n.get("description") 29 | }, 30 | { 31 | "tab": i18n.get("information"), 32 | "group": i18n.get("logout"), 33 | "name": "myCheckbox", 34 | "type": "checkbox", 35 | "label": i18n.get("enable") 36 | }, 37 | { 38 | "tab": i18n.get("information"), 39 | "group": i18n.get("logout"), 40 | "name": "myButton", 41 | "type": "button", 42 | "label": i18n.get("disconnect"), 43 | "text": i18n.get("logout") 44 | }, 45 | { 46 | "tab": "Details", 47 | "group": "Sound", 48 | "name": "noti_volume", 49 | "type": "slider", 50 | "label": "Notification volume:", 51 | "max": 1, 52 | "min": 0, 53 | "step": 0.01, 54 | "display": true, 55 | "displayModifier": function (value) { 56 | return (value * 100).floor() + "%"; 57 | } 58 | }, 59 | { 60 | "tab": "Details", 61 | "group": "Sound", 62 | "name": "sound_volume", 63 | "type": "slider", 64 | "label": "Sound volume:", 65 | "max": 100, 66 | "min": 0, 67 | "step": 1, 68 | "display": true, 69 | "displayModifier": function (value) { 70 | return value + "%"; 71 | } 72 | }, 73 | { 74 | "tab": "Details", 75 | "group": "Food", 76 | "name": "myPopupButton", 77 | "type": "popupButton", 78 | "label": "Soup 1 should be:", 79 | "options": { 80 | "groups": [ 81 | "Hot", "Cold", 82 | ], 83 | "values": [ 84 | { 85 | "value": "hot", 86 | "text": "Very hot", 87 | "group": "Hot", 88 | }, 89 | { 90 | "value": "Medium", 91 | "group": 1, 92 | }, 93 | { 94 | "value": "Cold", 95 | "group": 2, 96 | }, 97 | ["Non-existing"] 98 | ], 99 | }, 100 | }, 101 | { 102 | "tab": "Details", 103 | "group": "Food", 104 | "name": "myListBox", 105 | "type": "listBox", 106 | "label": "Soup 2 should be:", 107 | "options": [ 108 | ["hot", "Hot and yummy"], 109 | ["cold"] 110 | ] 111 | }, 112 | { 113 | "tab": "Details", 114 | "group": "Food", 115 | "name": "myRadioButtons", 116 | "type": "radioButtons", 117 | "label": "Soup 3 should be:", 118 | "options": [ 119 | ["hot", "Hot and yummy"], 120 | ["cold"] 121 | ] 122 | } 123 | ], 124 | "alignment": [ 125 | [ 126 | "username", 127 | "password" 128 | ], 129 | [ 130 | "noti_volume", 131 | "sound_volume" 132 | ] 133 | ] 134 | }; 135 | -------------------------------------------------------------------------------- /src/inject/inject.js: -------------------------------------------------------------------------------- 1 | const googleCalendarTabEventDetailsId = 'tabEventDetails'; 2 | const addingCalendarEventMessage = 'AddingCalendarEvent'; 3 | 4 | chrome.runtime.onMessage.addListener( 5 | function(request, sender, sendResponse) { 6 | // listen for messages sent from background.js 7 | if (request.message === addingCalendarEventMessage) { 8 | if (request.url.toString().indexOf('https://calendar.google.com/calendar/r/eventedit/' > 0)) { 9 | waitForElement(googleCalendarTabEventDetailsId, function(){ 10 | addButtonDivWithIcon(); 11 | }); 12 | } 13 | } 14 | }); 15 | 16 | function waitForElement(id, callback){ 17 | const poops = setInterval(function(){ 18 | if(document.getElementById(id)){ 19 | clearInterval(poops); 20 | callback(); 21 | } 22 | }, 100); 23 | } 24 | 25 | waitForElement(googleCalendarTabEventDetailsId, function(){ 26 | addButtonDivWithIcon(); 27 | }); 28 | 29 | function addButtonDivWithIcon() { 30 | const button = createCostButton(); 31 | const icon = createCostIcon(); 32 | const div = createCostDiv(icon, button); 33 | if (!document.getElementById('meetingCostDiv')) { 34 | const googleCalendarTabEventElement = document.getElementById(googleCalendarTabEventDetailsId); 35 | googleCalendarTabEventElement.prepend(div); 36 | } 37 | } 38 | 39 | function createCostDiv(icon, button) { 40 | const div = document.createElement('div'); 41 | div.id = 'meetingCostDiv'; 42 | div.appendChild(icon); 43 | div.appendChild(button); 44 | return div; 45 | } 46 | 47 | function createCostIcon() { 48 | const icon = document.createElement('img'); 49 | icon.src = chrome.extension.getURL('src/inject/icon.png'); 50 | icon.style.margin = '0px 0px 0px 26px'; 51 | return icon; 52 | } 53 | 54 | function createCostButton() { 55 | const button = document.createElement('button'); 56 | button.style.width = '164px'; 57 | button.style.margin = '4px 0px 4px 22px'; 58 | button.style.height = '36px'; 59 | button.innerHTML = 'Add Meeting Costs'; 60 | button.id = 'meetingCost'; 61 | button.addEventListener('click', buttonClickEvent); 62 | return button; 63 | } 64 | 65 | function buttonClickEvent() { 66 | const descriptionInputElement = document.querySelector('[aria-label="Description"]'); 67 | const startDate = parseDomStartDate(); 68 | const endDate = parseDomEndDate(); 69 | const numberOfParticipants = parseNumberOfParticipants(); 70 | const durationInHours = calculateDurationInHours(endDate, startDate); 71 | const costPerHour = 100; 72 | removePlaceholderDescriptionText(); 73 | prependDescriptionTextWithMeetingCosts(descriptionInputElement, numberOfParticipants, costPerHour, durationInHours); 74 | } 75 | 76 | function prependDescriptionTextWithMeetingCosts(descriptionInputElement, numberOfParticipants, costPerHour, durationInHours) { 77 | const unroundedMeetingCost = numberOfParticipants * costPerHour * durationInHours; 78 | const roundedMeetingCost = Math.ceil(unroundedMeetingCost/100)*100 79 | descriptionInputElement.innerHTML = `[Meeting cost: $${roundedMeetingCost}] - ${descriptionInputElement.innerHTML}`; 80 | } 81 | 82 | function removePlaceholderDescriptionText() { 83 | const defaultTexts = document.querySelectorAll('[jsname="V67aGc"]'); 84 | for (const defaultText of defaultTexts) { 85 | if (defaultText && defaultText.innerHTML === 'Add description') { 86 | defaultText.innerHTML = ''; 87 | } 88 | }; 89 | } 90 | 91 | function calculateDurationInHours(endDate, startDate) { 92 | return (endDate - startDate) / (1000 * 3600); 93 | } 94 | 95 | function parseNumberOfParticipants() { 96 | const participants = document.getElementById('xDetDlgAtt'); 97 | const participantsElements = document.querySelector('[aria-label="Guests invited to this event."]');; 98 | let numberOfParticipants; 99 | if (participants) { 100 | numberOfParticipants = participants.innerHTML.match(/([\d.]+) *guests/)[1]; 101 | } 102 | else if (participantsElements) { 103 | numberOfParticipants = participantsElements.childElementCount; 104 | } 105 | if (numberOfParticipants <= 0) { 106 | numberOfParticipants = 1; 107 | } 108 | return numberOfParticipants; 109 | } 110 | 111 | function parseDomEndDate() { 112 | const endTimeElement = document.querySelector('[aria-label="End time"]'); 113 | const endDateElement = document.querySelector('[aria-label="End date"]'); 114 | const endTimeText = `${endDateElement.value} ${endTimeElement.value.slice(0, -2)} ${endTimeElement.value.slice(-2, endTimeElement.value.length)}`; 115 | const endDate = Date.parse(endTimeText); 116 | return endDate; 117 | } 118 | 119 | function parseDomStartDate() { 120 | const startTimeElement = document.querySelector('[aria-label="Start time"]'); 121 | const startDateElement = document.querySelector('[aria-label="Start date"]'); 122 | const startTimeText = `${startDateElement.value} ${startTimeElement.value.slice(0, -2)} ${startTimeElement.value.slice(-2, startTimeElement.value.length)}`; 123 | const startDate = Date.parse(startTimeText); 124 | return startDate; 125 | } 126 | -------------------------------------------------------------------------------- /src/options_custom/js/classes/search.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | // 6 | (function () { 7 | this.Search = new Class({ 8 | "index": [], 9 | "groups": {}, 10 | 11 | "initialize": function (search, searchResultContainer) { 12 | var setting, 13 | find; 14 | 15 | this.search = search; 16 | this.searchResultContainer = searchResultContainer; 17 | this.setting = new Setting(new Element("div")); 18 | 19 | // Create setting for message "nothing found" 20 | setting = new Setting(this.searchResultContainer); 21 | this.nothingFound = setting.create({ 22 | "type": "description", 23 | "text": (i18n.get("nothing-found") || "No matches were found.") 24 | }); 25 | this.nothingFound.bundle.set("id", "nothing-found"); 26 | 27 | // Create event handlers 28 | find = (function (event) { 29 | this.find(event.target.get("value")); 30 | }).bind(this); 31 | 32 | this.search.addEvent("keyup", (function (event) { 33 | if (event.key === "esc") { 34 | this.reset(); 35 | } else { 36 | find(event); 37 | } 38 | }).bind(this)); 39 | this.search.addEventListener("search", find, false); 40 | }, 41 | 42 | "bind": function (tab) { 43 | tab.addEvent("click", this.reset.bind(this)); 44 | }, 45 | 46 | "add": function (setting) { 47 | var searchSetting = this.setting.create(setting.params); 48 | setting.search = searchSetting; 49 | searchSetting.original = setting; 50 | this.index.push(searchSetting); 51 | 52 | setting.addEvent("action", function (value, stopPropagation) { 53 | if (searchSetting.set !== undefined && stopPropagation !== true) { 54 | searchSetting.set(value, true); 55 | } 56 | }); 57 | searchSetting.addEvent("action", function (value) { 58 | if (setting.set !== undefined) { 59 | setting.set(value, true); 60 | } 61 | setting.fireEvent("action", [value, true]); 62 | }); 63 | }, 64 | 65 | "find": function (searchString) { 66 | // Exit search mode 67 | if (searchString.trim() === "") { 68 | document.body.removeClass("searching"); 69 | return; 70 | } 71 | 72 | // Or enter search mode 73 | this.index.each(function (setting) { setting.bundle.dispose(); }); 74 | Object.each(this.groups, function (group) { group.dispose(); }); 75 | document.body.addClass("searching"); 76 | 77 | // Filter settings 78 | var result = this.index.filter(function (setting) { 79 | if (setting.params.searchString.contains(searchString.trim().toLowerCase())) { 80 | return true; 81 | } 82 | }); 83 | 84 | // Display settings 85 | result.each((function (setting) { 86 | var group, 87 | row; 88 | 89 | // Create group if it doesn't exist already 90 | if (this.groups[setting.params.group] === undefined) { 91 | this.groups[setting.params.group] = (new Element("table", { 92 | "class": "setting group" 93 | })).inject(this.searchResultContainer); 94 | 95 | group = this.groups[setting.params.group]; 96 | row = (new Element("tr")).inject(group); 97 | 98 | (new Element("td", { 99 | "class": "setting group-name", 100 | "text": setting.params.group 101 | })).inject(row); 102 | 103 | group.content = (new Element("td", { 104 | "class": "setting group-content" 105 | })).inject(row); 106 | } else { 107 | group = this.groups[setting.params.group].inject(this.searchResultContainer); 108 | } 109 | 110 | setting.bundle.inject(group.content); 111 | }).bind(this)); 112 | 113 | if (result.length === 0) { 114 | this.nothingFound.bundle.addClass("show"); 115 | } else { 116 | this.nothingFound.bundle.removeClass("show"); 117 | } 118 | }, 119 | 120 | "reset": function () { 121 | this.search.set("value", ""); 122 | this.search.blur(); 123 | this.find(""); 124 | } 125 | }); 126 | }()); 127 | -------------------------------------------------------------------------------- /src/options_custom/js/classes/fancy-settings.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | // 6 | (function () { 7 | var FancySettings = this.FancySettings = new Class({ 8 | "tabs": {}, 9 | 10 | "initialize": function (name, icon) { 11 | // Set title and icon 12 | $("title").set("text", name); 13 | $("favicon").set("href", icon); 14 | $("icon").set("src", icon); 15 | $("settings-label").set("text", (i18n.get("settings") || "Settings")); 16 | $("search-label").set("text", (i18n.get("search") || "Search")); 17 | $("search").set("placeholder", (i18n.get("search") || "Search") + "..."); 18 | 19 | this.tab = new Tab($("tab-container"), $("content")); 20 | this.search = new Search($("search"), $("search-result-container")); 21 | }, 22 | 23 | "create": function (params) { 24 | var tab, 25 | group, 26 | row, 27 | content, 28 | bundle; 29 | 30 | // Create tab if it doesn't exist already 31 | if (this.tabs[params.tab] === undefined) { 32 | this.tabs[params.tab] = {"groups":{}}; 33 | tab = this.tabs[params.tab]; 34 | 35 | tab.content = this.tab.create(); 36 | tab.content.tab.set("text", params.tab); 37 | this.search.bind(tab.content.tab); 38 | 39 | tab.content = tab.content.content; 40 | (new Element("h2", { 41 | "text": params.tab 42 | })).inject(tab.content); 43 | } else { 44 | tab = this.tabs[params.tab]; 45 | } 46 | 47 | // Create group if it doesn't exist already 48 | if (tab.groups[params.group] === undefined) { 49 | tab.groups[params.group] = {}; 50 | group = tab.groups[params.group]; 51 | 52 | group.content = (new Element("table", { 53 | "class": "setting group" 54 | })).inject(tab.content); 55 | 56 | row = (new Element("tr")).inject(group.content); 57 | 58 | (new Element("td", { 59 | "class": "setting group-name", 60 | "text": params.group 61 | })).inject(row); 62 | 63 | content = (new Element("td", { 64 | "class": "setting group-content" 65 | })).inject(row); 66 | 67 | group.setting = new Setting(content); 68 | } else { 69 | group = tab.groups[params.group]; 70 | } 71 | 72 | // Create and index the setting 73 | bundle = group.setting.create(params); 74 | this.search.add(bundle); 75 | 76 | return bundle; 77 | }, 78 | 79 | "align": function (settings) { 80 | var types, 81 | type, 82 | maxWidth; 83 | 84 | types = [ 85 | "text", 86 | "button", 87 | "slider", 88 | "popupButton" 89 | ]; 90 | type = settings[0].params.type; 91 | maxWidth = 0; 92 | 93 | if (!types.contains(type)) { 94 | throw "invalidType"; 95 | } 96 | 97 | settings.each(function (setting) { 98 | if (setting.params.type !== type) { 99 | throw "multipleTypes"; 100 | } 101 | 102 | var width = setting.label.offsetWidth; 103 | if (width > maxWidth) { 104 | maxWidth = width; 105 | } 106 | }); 107 | 108 | settings.each(function (setting) { 109 | var width = setting.label.offsetWidth; 110 | if (width < maxWidth) { 111 | if (type === "button" || type === "slider") { 112 | setting.element.setStyle("margin-left", (maxWidth - width + 2) + "px"); 113 | setting.search.element.setStyle("margin-left", (maxWidth - width + 2) + "px"); 114 | } else { 115 | setting.element.setStyle("margin-left", (maxWidth - width) + "px"); 116 | setting.search.element.setStyle("margin-left", (maxWidth - width) + "px"); 117 | } 118 | } 119 | }); 120 | } 121 | }); 122 | 123 | FancySettings.__proto__.initWithManifest = function (callback) { 124 | var settings, 125 | output; 126 | 127 | settings = new FancySettings(manifest.name, manifest.icon); 128 | settings.manifest = {}; 129 | 130 | manifest.settings.each(function (params) { 131 | output = settings.create(params); 132 | if (params.name !== undefined) { 133 | settings.manifest[params.name] = output; 134 | } 135 | }); 136 | 137 | if (manifest.alignment !== undefined) { 138 | document.body.addClass("measuring"); 139 | manifest.alignment.each(function (group) { 140 | group = group.map(function (name) { 141 | return settings.manifest[name]; 142 | }); 143 | settings.align(group); 144 | }); 145 | document.body.removeClass("measuring"); 146 | } 147 | 148 | if (callback !== undefined) { 149 | callback(settings); 150 | } 151 | }; 152 | }()); 153 | -------------------------------------------------------------------------------- /src/options_custom/lib/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright (c) 2007 - 2010 blueprintcss.org 3 | // Modified and extended by Frank Kohlhepp in 2011 4 | // https://github.com/frankkohlhepp/default-css 5 | // License: MIT-license 6 | */ 7 | 8 | /* 9 | // Reset the default browser CSS 10 | */ 11 | html { 12 | margin: 0; 13 | padding: 0; 14 | border: 0; 15 | } 16 | 17 | body, div, span, object, iframe, 18 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 19 | a, abbr, acronym, address, code, 20 | del, dfn, em, img, q, dl, dt, dd, ol, ul, li, 21 | fieldset, form, label, legend, 22 | table, caption, tbody, tfoot, thead, tr, th, td, 23 | article, aside, dialog, figure, footer, header, 24 | hgroup, nav, section { 25 | margin: 0; 26 | padding: 0; 27 | border: 0; 28 | font-family: inherit; 29 | font-size: 100%; 30 | font-weight: inherit; 31 | font-style: inherit; 32 | vertical-align: baseline; 33 | } 34 | 35 | article, aside, dialog, figure, footer, header, 36 | hgroup, nav, section { 37 | display: block; 38 | } 39 | 40 | body { 41 | background-color: white; 42 | line-height: 1.5; 43 | } 44 | 45 | table { 46 | border-collapse: separate; 47 | border-spacing: 0; 48 | } 49 | 50 | caption, th, td { 51 | text-align: left; 52 | font-weight: normal; 53 | } 54 | 55 | table, th, td { 56 | vertical-align: middle; 57 | } 58 | 59 | blockquote:before, blockquote:after, q:before, q:after { 60 | content: ""; 61 | } 62 | 63 | blockquote, q { 64 | quotes: "" ""; 65 | } 66 | 67 | a img { 68 | border: none; 69 | } 70 | 71 | /* 72 | // Default typography 73 | */ 74 | html { 75 | font-size: 100.01%; 76 | } 77 | 78 | body { 79 | background-color: white; 80 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 81 | font-size: 75%; 82 | color: #222222; 83 | } 84 | 85 | /* Headings */ 86 | h1, h2, h3, h4, h5, h6 { 87 | font-weight: normal; 88 | color: #111111; 89 | } 90 | 91 | h1 { 92 | margin-bottom: 0.5em; 93 | font-size: 3em; 94 | line-height: 1; 95 | } 96 | 97 | h2 { 98 | margin-bottom: 0.75em; 99 | font-size: 2em; 100 | } 101 | 102 | h3 { 103 | margin-bottom: 1em; 104 | font-size: 1.5em; 105 | line-height: 1; 106 | } 107 | 108 | h4 { 109 | margin-bottom: 1.25em; 110 | font-size: 1.2em; 111 | line-height: 1.25; 112 | } 113 | 114 | h5 { 115 | margin-bottom: 1.5em; 116 | font-size: 1em; 117 | font-weight: bold; 118 | } 119 | 120 | h6 { 121 | font-size: 1em; 122 | font-weight: bold; 123 | } 124 | 125 | h1 img, h2 img, h3 img, 126 | h4 img, h5 img, h6 img { 127 | margin: 0; 128 | } 129 | 130 | /* Text elements */ 131 | p { 132 | margin: 0 0 1.5em; 133 | } 134 | 135 | .left { 136 | float: left !important; 137 | } 138 | 139 | p .left { 140 | margin: 1.5em 1.5em 1.5em 0; 141 | padding: 0; 142 | } 143 | 144 | .right { 145 | float: right !important; 146 | } 147 | 148 | p .right { 149 | margin: 1.5em 0 1.5em 1.5em; 150 | padding: 0; 151 | } 152 | 153 | a:focus, a:hover { 154 | color: #0099FF; 155 | } 156 | 157 | a { 158 | color: #0066CC; 159 | text-decoration: underline; 160 | } 161 | 162 | blockquote { 163 | margin: 1.5em; 164 | font-style: italic; 165 | color: #666666; 166 | } 167 | 168 | strong, dfn { 169 | font-weight: bold; 170 | } 171 | 172 | em, dfn { 173 | font-style: italic; 174 | } 175 | 176 | sup, sub { 177 | line-height: 0; 178 | } 179 | 180 | abbr, acronym { 181 | border-bottom: 1px dotted #666666; 182 | } 183 | 184 | address { 185 | margin: 0 0 1.5em; 186 | font-style: italic; 187 | } 188 | 189 | del { 190 | color: #666666; 191 | } 192 | 193 | pre { 194 | margin: 1.5em 0; 195 | white-space: pre; 196 | } 197 | 198 | pre, code, tt { 199 | font: 1em "andale mono", "lucida console", monospace; 200 | line-height: 1.5; 201 | } 202 | 203 | /* Lists */ 204 | li ul, li ol { 205 | margin: 0; 206 | } 207 | 208 | ul, ol { 209 | margin: 0 1.5em 1.5em 0; 210 | padding-left: 1.5em; 211 | } 212 | 213 | ul { 214 | list-style-type: disc; 215 | } 216 | 217 | ol { 218 | list-style-type: decimal; 219 | } 220 | 221 | dl { 222 | margin: 0 0 1.5em 0; 223 | } 224 | 225 | dl dt { 226 | font-weight: bold; 227 | } 228 | 229 | dd { 230 | margin-left: 1.5em; 231 | } 232 | 233 | /* Tables */ 234 | table { 235 | width: 100%; 236 | margin-bottom: 1.4em; 237 | } 238 | 239 | th { 240 | font-weight: bold; 241 | } 242 | 243 | table.zebra thead th, table.zebra tfoot th { 244 | background-color: #BFBFBF; 245 | } 246 | 247 | th, td, caption { 248 | padding: 4px 10px 4px 5px; 249 | } 250 | 251 | table.zebra tbody tr:nth-child(even) td, table.zebra tbody tr.even td { 252 | background-color: #E6E6E6; 253 | } 254 | 255 | caption { 256 | background-color: #EEEEEE; 257 | } 258 | 259 | /* Misc classes */ 260 | .fancy { 261 | text-shadow: white 0 1px 0; 262 | } 263 | 264 | .bfancy { 265 | text-shadow: black 0 1px 0; 266 | } 267 | 268 | .fancyt { 269 | text-shadow: white 0 -1px 0; 270 | } 271 | 272 | .bfancyt { 273 | text-shadow: black 0 -1px 0; 274 | } 275 | 276 | .no-fancy { 277 | text-shadow: none; 278 | } 279 | 280 | .select { 281 | cursor: auto; 282 | user-select: auto; 283 | -webkit-user-select: auto; 284 | -moz-user-select: auto; 285 | -o-user-select: auto; 286 | } 287 | 288 | img.select, .select img { 289 | user-drag: auto; 290 | -webkit-user-drag: auto; 291 | -moz-user-drag: auto; 292 | -o-user-drag: auto; 293 | } 294 | 295 | .no-select { 296 | cursor: default; 297 | user-select: none; 298 | -webkit-user-select: none; 299 | -moz-user-select: none; 300 | -o-user-select: none; 301 | } 302 | 303 | img.no-select, .no-select img { 304 | user-drag: none; 305 | -webkit-user-drag: none; 306 | -moz-user-drag: none; 307 | -o-user-drag: none; 308 | } 309 | 310 | .focus:focus, .focus :focus { 311 | outline: auto; 312 | } 313 | 314 | .no-focus:focus, .no-focus :focus { 315 | outline: 0; 316 | } 317 | 318 | .small { 319 | margin-bottom: 1.875em; 320 | font-size: .8em; 321 | line-height: 1.875em; 322 | } 323 | 324 | .large { 325 | margin-bottom: 1.25em; 326 | font-size: 1.2em; 327 | line-height: 2.5em; 328 | } 329 | 330 | .show { 331 | display: block !important; 332 | } 333 | 334 | .show-inline { 335 | display: inline-block !important; 336 | } 337 | 338 | .hide { 339 | display: none; 340 | } 341 | 342 | .quiet { 343 | color: #666666; 344 | } 345 | 346 | .loud { 347 | color: black; 348 | } 349 | 350 | .highlight { 351 | background-color: yellow; 352 | } 353 | 354 | .added { 355 | background-color: #006600; 356 | color: white; 357 | } 358 | 359 | .removed { 360 | background-color: #990000; 361 | color: white; 362 | } 363 | 364 | .first { 365 | margin-left: 0; 366 | padding-left: 0; 367 | } 368 | 369 | .last { 370 | margin-right: 0; 371 | padding-right: 0; 372 | } 373 | 374 | .top { 375 | margin-top: 0; 376 | padding-top: 0; 377 | } 378 | 379 | .bottom { 380 | margin-bottom: 0; 381 | padding-bottom: 0; 382 | } 383 | 384 | /* 385 | // Default styling for forms 386 | */ 387 | fieldset { 388 | margin: 0 0 1.5em 0; 389 | padding: 0 1.4em 1.4em 1.4em; 390 | border: 1px solid #CCCCCC; 391 | } 392 | 393 | legend { 394 | margin-top: -0.2em; 395 | margin-bottom: 1em; 396 | font-weight: bold; 397 | font-size: 1.2em; 398 | } 399 | 400 | /* Form fields */ 401 | input[type=text], input[type=password], textarea { 402 | background-color: white; 403 | border: 1px solid #BBBBBB; 404 | } 405 | 406 | input[type=text], input[type=password], 407 | textarea, select { 408 | margin: 0.5em 0; 409 | } 410 | 411 | input[type=text], input[type=password] { 412 | width: 300px; 413 | padding: 4px; 414 | } 415 | 416 | textarea { 417 | width: 450px; 418 | height: 170px; 419 | padding: 5px; 420 | } 421 | 422 | /* success, info, notice and error boxes */ 423 | .success, .info, .notice, .error { 424 | margin-bottom: 1em; 425 | padding: 0.8em; 426 | border: 2px solid #DDDDDD; 427 | } 428 | 429 | .success { 430 | background-color: #E6EFC2; 431 | border-color: #C6D880; 432 | color: #264409; 433 | } 434 | 435 | .info { 436 | background-color: #D5EDF8; 437 | border-color: #92CAE4; 438 | color: #205791; 439 | } 440 | 441 | .notice { 442 | background-color: #FFF6BF; 443 | border-color: #FFD324; 444 | color: #514721; 445 | } 446 | 447 | .error { 448 | background-color: #FBE3E4; 449 | border-color: #FBC2C4; 450 | color: #8A1F11; 451 | } 452 | 453 | .success a { 454 | color: #264409; 455 | } 456 | 457 | .info a { 458 | color: #205791; 459 | } 460 | 461 | .notice a { 462 | color: #514721; 463 | } 464 | 465 | .error a { 466 | color: #8A1F11; 467 | } 468 | -------------------------------------------------------------------------------- /src/options_custom/js/classes/setting.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011 Frank Kohlhepp 3 | // https://github.com/frankkohlhepp/fancy-settings 4 | // License: LGPL v2.1 5 | // 6 | (function () { 7 | var settings, 8 | Bundle; 9 | 10 | settings = new Store("settings"); 11 | Bundle = new Class({ 12 | // Attributes: 13 | // - tab 14 | // - group 15 | // - name 16 | // - type 17 | // 18 | // Methods: 19 | // - initialize 20 | // - createDOM 21 | // - setupDOM 22 | // - addEvents 23 | // - get 24 | // - set 25 | "Implements": Events, 26 | 27 | "initialize": function (params) { 28 | this.params = params; 29 | this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•"; 30 | 31 | this.createDOM(); 32 | this.setupDOM(); 33 | this.addEvents(); 34 | 35 | if (this.params.id !== undefined) { 36 | this.element.set("id", this.params.id); 37 | } 38 | 39 | if (this.params.name !== undefined) { 40 | this.set(settings.get(this.params.name), true); 41 | } 42 | 43 | this.params.searchString = this.params.searchString.toLowerCase(); 44 | }, 45 | 46 | "addEvents": function () { 47 | this.element.addEvent("change", (function (event) { 48 | if (this.params.name !== undefined) { 49 | settings.set(this.params.name, this.get()); 50 | } 51 | 52 | this.fireEvent("action", this.get()); 53 | }).bind(this)); 54 | }, 55 | 56 | "get": function () { 57 | return this.element.get("value"); 58 | }, 59 | 60 | "set": function (value, noChangeEvent) { 61 | this.element.set("value", value); 62 | 63 | if (noChangeEvent !== true) { 64 | this.element.fireEvent("change"); 65 | } 66 | 67 | return this; 68 | } 69 | }); 70 | 71 | Bundle.Description = new Class({ 72 | // text 73 | "Extends": Bundle, 74 | "addEvents": undefined, 75 | "get": undefined, 76 | "set": undefined, 77 | 78 | "initialize": function (params) { 79 | this.params = params; 80 | this.params.searchString = ""; 81 | 82 | this.createDOM(); 83 | this.setupDOM(); 84 | }, 85 | 86 | "createDOM": function () { 87 | this.bundle = new Element("div", { 88 | "class": "setting bundle description" 89 | }); 90 | 91 | this.container = new Element("div", { 92 | "class": "setting container description" 93 | }); 94 | 95 | this.element = new Element("p", { 96 | "class": "setting element description" 97 | }); 98 | }, 99 | 100 | "setupDOM": function () { 101 | if (this.params.text !== undefined) { 102 | this.element.set("html", this.params.text); 103 | } 104 | 105 | this.element.inject(this.container); 106 | this.container.inject(this.bundle); 107 | } 108 | }); 109 | 110 | Bundle.Button = new Class({ 111 | // label, text 112 | // action -> click 113 | "Extends": Bundle, 114 | "get": undefined, 115 | "set": undefined, 116 | 117 | "initialize": function (params) { 118 | this.params = params; 119 | this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•"; 120 | 121 | this.createDOM(); 122 | this.setupDOM(); 123 | this.addEvents(); 124 | 125 | if (this.params.id !== undefined) { 126 | this.element.set("id", this.params.id); 127 | } 128 | 129 | this.params.searchString = this.params.searchString.toLowerCase(); 130 | }, 131 | 132 | "createDOM": function () { 133 | this.bundle = new Element("div", { 134 | "class": "setting bundle button" 135 | }); 136 | 137 | this.container = new Element("div", { 138 | "class": "setting container button" 139 | }); 140 | 141 | this.element = new Element("input", { 142 | "class": "setting element button", 143 | "type": "button" 144 | }); 145 | 146 | this.label = new Element("label", { 147 | "class": "setting label button" 148 | }); 149 | }, 150 | 151 | "setupDOM": function () { 152 | if (this.params.label !== undefined) { 153 | this.label.set("html", this.params.label); 154 | this.label.inject(this.container); 155 | this.params.searchString += this.params.label + "•"; 156 | } 157 | 158 | if (this.params.text !== undefined) { 159 | this.element.set("value", this.params.text); 160 | this.params.searchString += this.params.text + "•"; 161 | } 162 | 163 | this.element.inject(this.container); 164 | this.container.inject(this.bundle); 165 | }, 166 | 167 | "addEvents": function () { 168 | this.element.addEvent("click", (function () { 169 | this.fireEvent("action"); 170 | }).bind(this)); 171 | } 172 | }); 173 | 174 | Bundle.Text = new Class({ 175 | // label, text, masked 176 | // action -> change & keyup 177 | "Extends": Bundle, 178 | 179 | "createDOM": function () { 180 | this.bundle = new Element("div", { 181 | "class": "setting bundle text" 182 | }); 183 | 184 | this.container = new Element("div", { 185 | "class": "setting container text" 186 | }); 187 | 188 | this.element = new Element("input", { 189 | "class": "setting element text", 190 | "type": "text" 191 | }); 192 | 193 | this.label = new Element("label", { 194 | "class": "setting label text" 195 | }); 196 | }, 197 | 198 | "setupDOM": function () { 199 | if (this.params.label !== undefined) { 200 | this.label.set("html", this.params.label); 201 | this.label.inject(this.container); 202 | this.params.searchString += this.params.label + "•"; 203 | } 204 | 205 | if (this.params.text !== undefined) { 206 | this.element.set("placeholder", this.params.text); 207 | this.params.searchString += this.params.text + "•"; 208 | } 209 | 210 | if (this.params.masked === true) { 211 | this.element.set("type", "password"); 212 | this.params.searchString += "password" + "•"; 213 | } 214 | 215 | this.element.inject(this.container); 216 | this.container.inject(this.bundle); 217 | }, 218 | 219 | "addEvents": function () { 220 | var change = (function (event) { 221 | if (this.params.name !== undefined) { 222 | settings.set(this.params.name, this.get()); 223 | } 224 | 225 | this.fireEvent("action", this.get()); 226 | }).bind(this); 227 | 228 | this.element.addEvent("change", change); 229 | this.element.addEvent("keyup", change); 230 | } 231 | }); 232 | 233 | Bundle.Checkbox = new Class({ 234 | // label 235 | // action -> change 236 | "Extends": Bundle, 237 | 238 | "createDOM": function () { 239 | this.bundle = new Element("div", { 240 | "class": "setting bundle checkbox" 241 | }); 242 | 243 | this.container = new Element("div", { 244 | "class": "setting container checkbox" 245 | }); 246 | 247 | this.element = new Element("input", { 248 | "id": String.uniqueID(), 249 | "class": "setting element checkbox", 250 | "type": "checkbox", 251 | "value": "true" 252 | }); 253 | 254 | this.label = new Element("label", { 255 | "class": "setting label checkbox", 256 | "for": this.element.get("id") 257 | }); 258 | }, 259 | 260 | "setupDOM": function () { 261 | this.element.inject(this.container); 262 | this.container.inject(this.bundle); 263 | 264 | if (this.params.label !== undefined) { 265 | this.label.set("html", this.params.label); 266 | this.label.inject(this.container); 267 | this.params.searchString += this.params.label + "•"; 268 | } 269 | }, 270 | 271 | "get": function () { 272 | return this.element.get("checked"); 273 | }, 274 | 275 | "set": function (value, noChangeEvent) { 276 | this.element.set("checked", value); 277 | 278 | if (noChangeEvent !== true) { 279 | this.element.fireEvent("change"); 280 | } 281 | 282 | return this; 283 | } 284 | }); 285 | 286 | Bundle.Slider = new Class({ 287 | // label, max, min, step, display, displayModifier 288 | // action -> change 289 | "Extends": Bundle, 290 | 291 | "initialize": function (params) { 292 | this.params = params; 293 | this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•"; 294 | 295 | this.createDOM(); 296 | this.setupDOM(); 297 | this.addEvents(); 298 | 299 | if (this.params.name !== undefined) { 300 | this.set((settings.get(this.params.name) || 0), true); 301 | } else { 302 | this.set(0, true); 303 | } 304 | 305 | this.params.searchString = this.params.searchString.toLowerCase(); 306 | }, 307 | 308 | "createDOM": function () { 309 | this.bundle = new Element("div", { 310 | "class": "setting bundle slider" 311 | }); 312 | 313 | this.container = new Element("div", { 314 | "class": "setting container slider" 315 | }); 316 | 317 | this.element = new Element("input", { 318 | "class": "setting element slider", 319 | "type": "range" 320 | }); 321 | 322 | this.label = new Element("label", { 323 | "class": "setting label slider" 324 | }); 325 | 326 | this.display = new Element("span", { 327 | "class": "setting display slider" 328 | }); 329 | }, 330 | 331 | "setupDOM": function () { 332 | if (this.params.label !== undefined) { 333 | this.label.set("html", this.params.label); 334 | this.label.inject(this.container); 335 | this.params.searchString += this.params.label + "•"; 336 | } 337 | 338 | if (this.params.max !== undefined) { 339 | this.element.set("max", this.params.max); 340 | } 341 | 342 | if (this.params.min !== undefined) { 343 | this.element.set("min", this.params.min); 344 | } 345 | 346 | if (this.params.step !== undefined) { 347 | this.element.set("step", this.params.step); 348 | } 349 | 350 | this.element.inject(this.container); 351 | if (this.params.display !== false) { 352 | if (this.params.displayModifier !== undefined) { 353 | this.display.set("text", this.params.displayModifier(0)); 354 | } else { 355 | this.display.set("text", 0); 356 | } 357 | this.display.inject(this.container); 358 | } 359 | this.container.inject(this.bundle); 360 | }, 361 | 362 | "addEvents": function () { 363 | this.element.addEvent("change", (function (event) { 364 | if (this.params.name !== undefined) { 365 | settings.set(this.params.name, this.get()); 366 | } 367 | 368 | if (this.params.displayModifier !== undefined) { 369 | this.display.set("text", this.params.displayModifier(this.get())); 370 | } else { 371 | this.display.set("text", this.get()); 372 | } 373 | this.fireEvent("action", this.get()); 374 | }).bind(this)); 375 | }, 376 | 377 | "get": function () { 378 | return Number.from(this.element.get("value")); 379 | }, 380 | 381 | "set": function (value, noChangeEvent) { 382 | this.element.set("value", value); 383 | 384 | if (noChangeEvent !== true) { 385 | this.element.fireEvent("change"); 386 | } else { 387 | if (this.params.displayModifier !== undefined) { 388 | this.display.set("text", this.params.displayModifier(Number.from(value))); 389 | } else { 390 | this.display.set("text", Number.from(value)); 391 | } 392 | } 393 | 394 | return this; 395 | } 396 | }); 397 | 398 | Bundle.PopupButton = new Class({ 399 | // label, options[{value, text}] 400 | // action -> change 401 | "Extends": Bundle, 402 | 403 | "createDOM": function () { 404 | this.bundle = new Element("div", { 405 | "class": "setting bundle popup-button" 406 | }); 407 | 408 | this.container = new Element("div", { 409 | "class": "setting container popup-button" 410 | }); 411 | 412 | this.element = new Element("select", { 413 | "class": "setting element popup-button" 414 | }); 415 | 416 | this.label = new Element("label", { 417 | "class": "setting label popup-button" 418 | }); 419 | 420 | if (this.params.options === undefined) { return; } 421 | 422 | // convert array syntax into object syntax for options 423 | function arrayToObject(option) { 424 | if (typeOf(option) == "array") { 425 | option = { 426 | "value": option[0], 427 | "text": option[1] || option[0], 428 | }; 429 | } 430 | return option; 431 | } 432 | 433 | // convert arrays 434 | if (typeOf(this.params.options) == "array") { 435 | var values = []; 436 | this.params.options.each((function(values, option) { 437 | values.push(arrayToObject(option)); 438 | }).bind(this, values)); 439 | this.params.options = { "values": values }; 440 | } 441 | 442 | var groups; 443 | if (this.params.options.groups !== undefined) { 444 | groups = {}; 445 | this.params.options.groups.each((function (groups, group) { 446 | this.params.searchString += (group) + "•"; 447 | groups[group] = (new Element("optgroup", { 448 | "label": group, 449 | }).inject(this.element)); 450 | }).bind(this, groups)); 451 | } 452 | 453 | if (this.params.options.values !== undefined) { 454 | this.params.options.values.each((function(groups, option) { 455 | option = arrayToObject(option); 456 | this.params.searchString += (option.text || option.value) + "•"; 457 | 458 | // find the parent of this option - either a group or the main element 459 | var parent; 460 | if (option.group && this.params.options.groups) { 461 | if ((option.group - 1) in this.params.options.groups) { 462 | option.group = this.params.options.groups[option.group-1]; 463 | } 464 | if (option.group in groups) { 465 | parent = groups[option.group]; 466 | } 467 | else { 468 | parent = this.element; 469 | } 470 | } 471 | else { 472 | parent = this.element; 473 | } 474 | 475 | (new Element("option", { 476 | "value": option.value, 477 | "text": option.text || option.value, 478 | })).inject(parent); 479 | }).bind(this, groups)); 480 | } 481 | }, 482 | 483 | "setupDOM": function () { 484 | if (this.params.label !== undefined) { 485 | this.label.set("html", this.params.label); 486 | this.label.inject(this.container); 487 | this.params.searchString += this.params.label + "•"; 488 | } 489 | 490 | this.element.inject(this.container); 491 | this.container.inject(this.bundle); 492 | } 493 | }); 494 | 495 | Bundle.ListBox = new Class({ 496 | // label, options[{value, text}] 497 | // action -> change 498 | "Extends": Bundle.PopupButton, 499 | 500 | "createDOM": function () { 501 | this.bundle = new Element("div", { 502 | "class": "setting bundle list-box" 503 | }); 504 | 505 | this.container = new Element("div", { 506 | "class": "setting container list-box" 507 | }); 508 | 509 | this.element = new Element("select", { 510 | "class": "setting element list-box", 511 | "size": "2" 512 | }); 513 | 514 | this.label = new Element("label", { 515 | "class": "setting label list-box" 516 | }); 517 | 518 | if (this.params.options === undefined) { return; } 519 | this.params.options.each((function (option) { 520 | this.params.searchString += (option.text || option.value) + "•"; 521 | 522 | (new Element("option", { 523 | "value": option.value, 524 | "text": option.text || option.value 525 | })).inject(this.element); 526 | }).bind(this)); 527 | }, 528 | 529 | "get": function () { 530 | return (this.element.get("value") || undefined); 531 | } 532 | }); 533 | 534 | Bundle.Textarea = new Class({ 535 | // label, text, value 536 | // action -> change & keyup 537 | "Extends": Bundle, 538 | 539 | "createDOM": function () { 540 | this.bundle = new Element("div", { 541 | "class": "setting bundle textarea" 542 | }); 543 | 544 | this.container = new Element("div", { 545 | "class": "setting container textarea" 546 | }); 547 | 548 | this.element = new Element("textarea", { 549 | "class": "setting element textarea" 550 | }); 551 | 552 | this.label = new Element("label", { 553 | "class": "setting label textarea" 554 | }); 555 | }, 556 | 557 | "setupDOM": function () { 558 | if (this.params.label !== undefined) { 559 | this.label.set("html", this.params.label); 560 | this.label.inject(this.container); 561 | this.params.searchString += this.params.label + "•"; 562 | } 563 | 564 | if (this.params.text !== undefined) { 565 | this.element.set("placeholder", this.params.text); 566 | this.params.searchString += this.params.text + "•"; 567 | } 568 | 569 | if (this.params.value !== undefined) { 570 | this.element.appendText(this.params.text); 571 | } 572 | 573 | this.element.inject(this.container); 574 | this.container.inject(this.bundle); 575 | }, 576 | 577 | "addEvents": function () { 578 | var change = (function (event) { 579 | if (this.params.name !== undefined) { 580 | settings.set(this.params.name, this.get()); 581 | } 582 | 583 | this.fireEvent("action", this.get()); 584 | }).bind(this); 585 | 586 | this.element.addEvent("change", change); 587 | this.element.addEvent("keyup", change); 588 | } 589 | }); 590 | 591 | Bundle.RadioButtons = new Class({ 592 | // label, options[{value, text}] 593 | // action -> change 594 | "Extends": Bundle, 595 | 596 | "createDOM": function () { 597 | var settingID = String.uniqueID(); 598 | 599 | this.bundle = new Element("div", { 600 | "class": "setting bundle radio-buttons" 601 | }); 602 | 603 | this.label = new Element("label", { 604 | "class": "setting label radio-buttons" 605 | }); 606 | 607 | this.containers = []; 608 | this.elements = []; 609 | this.labels = []; 610 | 611 | if (this.params.options === undefined) { return; } 612 | this.params.options.each((function (option) { 613 | var optionID, 614 | container; 615 | 616 | this.params.searchString += (option.text || option.value) + "•"; 617 | 618 | optionID = String.uniqueID(); 619 | container = (new Element("div", { 620 | "class": "setting container radio-buttons" 621 | })).inject(this.bundle); 622 | this.containers.push(container); 623 | 624 | this.elements.push((new Element("input", { 625 | "id": optionID, 626 | "name": settingID, 627 | "class": "setting element radio-buttons", 628 | "type": "radio", 629 | "value": option.value 630 | })).inject(container)); 631 | 632 | this.labels.push((new Element("label", { 633 | "class": "setting element-label radio-buttons", 634 | "for": optionID, 635 | "text": option.text || option.value 636 | })).inject(container)); 637 | }).bind(this)); 638 | }, 639 | 640 | "setupDOM": function () { 641 | if (this.params.label !== undefined) { 642 | this.label.set("html", this.params.label); 643 | this.label.inject(this.bundle, "top"); 644 | this.params.searchString += this.params.label + "•"; 645 | } 646 | }, 647 | 648 | "addEvents": function () { 649 | this.bundle.addEvent("change", (function (event) { 650 | if (this.params.name !== undefined) { 651 | settings.set(this.params.name, this.get()); 652 | } 653 | 654 | this.fireEvent("action", this.get()); 655 | }).bind(this)); 656 | }, 657 | 658 | "get": function () { 659 | var checkedEl = this.elements.filter((function (el) { 660 | return el.get("checked"); 661 | }).bind(this)); 662 | return (checkedEl[0] && checkedEl[0].get("value")); 663 | }, 664 | 665 | "set": function (value, noChangeEvent) { 666 | var desiredEl = this.elements.filter((function (el) { 667 | return (el.get("value") === value); 668 | }).bind(this)); 669 | desiredEl[0] && desiredEl[0].set("checked", true); 670 | 671 | if (noChangeEvent !== true) { 672 | this.bundle.fireEvent("change"); 673 | } 674 | 675 | return this; 676 | } 677 | }); 678 | 679 | this.Setting = new Class({ 680 | "initialize": function (container) { 681 | this.container = container; 682 | }, 683 | 684 | "create": function (params) { 685 | var types, 686 | bundle; 687 | 688 | // Available types 689 | types = { 690 | "description": "Description", 691 | "button": "Button", 692 | "text": "Text", 693 | "textarea": "Textarea", 694 | "checkbox": "Checkbox", 695 | "slider": "Slider", 696 | "popupButton": "PopupButton", 697 | "listBox": "ListBox", 698 | "radioButtons": "RadioButtons" 699 | }; 700 | 701 | if (types.hasOwnProperty(params.type)) { 702 | bundle = new Bundle[types[params.type]](params); 703 | bundle.bundleContainer = this.container; 704 | bundle.bundle.inject(this.container); 705 | return bundle; 706 | } else { 707 | throw "invalidType"; 708 | } 709 | } 710 | }); 711 | }()); 712 | --------------------------------------------------------------------------------