├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── resources └── icon.drawit │ ├── Data │ ├── Info.plist │ └── QuickLook │ ├── Preview.jpg │ └── Thumbnail.jpg └── source ├── css ├── main.css └── setting.css ├── custom.css ├── i18n.js ├── icon.png ├── index.html ├── js ├── classes │ ├── fancy-settings.js │ ├── search.js │ ├── setting.js │ └── tab.js └── i18n.js ├── lib ├── default.css ├── mootools-core.js └── store.js ├── manifest.js └── settings.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2 [ API CHANGE! ] 4 | * **Feature**: Added version number to README file 5 | * **Feature**: Added CHANGELOG 6 | * **Feature**: Added alignment support 7 | * **Change**: Removed the possibility to use objects to define options for elements other than popupButton 8 | * **Change**: Added support for option groups in popupButtons, changing the format when defining options as objects 9 | * **Change**: Changed the default value of "display" in the slider params from true to false 10 | * **Change**: Search doesn't change the DOM structure of settings any more 11 | * **Change**: i18n now returns the string you entered instead of undefined if no translation can be found 12 | * **Bug**: Fixed an issue were the search field had the wrong placeholder when "search" had no translation 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /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* 115 | -------------------------------------------------------------------------------- /resources/icon.drawit/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/fancy-settings/82e8dd73d3e816ae13990d0ac22b0baf13a4bac1/resources/icon.drawit/Data -------------------------------------------------------------------------------- /resources/icon.drawit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileVersion 6 | 2 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/icon.drawit/QuickLook/Preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/fancy-settings/82e8dd73d3e816ae13990d0ac22b0baf13a4bac1/resources/icon.drawit/QuickLook/Preview.jpg -------------------------------------------------------------------------------- /resources/icon.drawit/QuickLook/Thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/fancy-settings/82e8dd73d3e816ae13990d0ac22b0baf13a4bac1/resources/icon.drawit/QuickLook/Thumbnail.jpg -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/fancy-settings/82e8dd73d3e816ae13990d0ac22b0baf13a4bac1/source/icon.png -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | -------------------------------------------------------------------------------- /source/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 | --------------------------------------------------------------------------------